// Need to require DB again. Apparently this is ok, as it'll // still only have one require cross project (?) const database = require('./database'); const util = require('util') // Get the decks requested function getDecks(deckIds = false){ // Await promise, once it's done get the data, if errors send err const dPromise = new Promise((resolve, reject) => { database.dbGetDecks(deckIds).then(data => { let decks = []; data.forEach((deck) => { //let itemId = item.length; //item.push(itemId); // Add the next available item // Add the deck info to deckItem // deckItem[itemId] = {}; decks.push({ 'deckId': deck.deckId, 'playerId': deck.playerId, 'deckName': deck.deckName, }); }); // Resolve the decks pulled resolve(decks); //console.log(item); //console.log(deckItem); //console.log(decks); }) .catch(err => { throw err; }) }); return dPromise; } //getDecks(); function getDeckList(deckIds = false){ const dPromise = new Promise((resolve, reject) => { database.dbGetDeckList(deckIds).then(data => { let deckList = []; data.forEach((listItem) => { // Add the deck info to deckItem deckList.push({ 'deckId': listItem.deckId, 'playerId': listItem.playerId, 'cardId': listItem.cardId, 'cardCount': listItem.cardCount, }); resolve(deckList); }); }) .catch(err => { throw err; }) }); return dPromise; } function getCards(cardIds = false){ const dPromise = new Promise((resolve, reject) => { database.dbGetCards(cardIds).then(data => { let cards = []; data.forEach((card) => { cards.push({ 'id': card.id, 'cardName': card.cardName, 'cardCost': card.cardCost, 'cardType': card.cardType, 'cardAttack': card.cardAttack, 'cardRarity': card.cardRarity, 'cardClass': [], 'cardColourRequirement': [], }); }); resolve(cards); }) .catch(err => { throw err; }) }); return dPromise; } function getCardClasses(cardIds = false){ const dPromise = new Promise((resolve, reject) => { database.dbGetCardClasses(cardIds).then(data => { let cardClasses = []; data.forEach((cardClass) => { cardClasses.push({ 'cardId': cardClass.cardId, 'classId': cardClass.classId, }); }); resolve(cardClasses); }) .catch(err => { throw err; }) }); return dPromise; } function getCardColourRequirement(cardIds = false){ const dPromise = new Promise((resolve, reject) => { database.dbGetCardColourRequirement(cardIds).then(data => { let colourRequirements = []; data.forEach((cardColourReq) => { colourRequirements.push({ 'cardId': cardColourReq.cardId, 'colourId': cardColourReq.colourId, 'cost': cardColourReq.cost, }); }); resolve(colourRequirements); }) .catch(err => { throw err; }) }); return dPromise; } function getCardManaColour(cardIds = false){ const cPromise = new Promise((resolve, reject) => { database.dbGetCardManaColour(cardIds).then(data => { let manaColours = []; data.forEach((cardManaColour) => { manaColours.push({ 'cardId': cardManaColour.cardId, 'colourId': cardManaColour.colourId, }); }); resolve(manaColours); }) .catch(err => { throw err; }) }); return cPromise; } // Build card effects // TODO: Accept card Ids (array) function buildCardEffects(effects, effectSteps, effectTriggers){ const cPromise = new Promise((resolve, reject) => { let cardEffects = {}; let effectData = {}; effects.forEach((effect) => { // If card doesn't currently have an effect if(cardEffects[effect.cardId] === undefined){ cardEffects[effect.cardId] = []; } // Add the effectId to the temp cardId cardEffects[effect.cardId].push(effect.effectId); // Now actually start the effect effectData[effect.effectId]={}; // Add the description to new effect effectData[effect.effectId]['description'] = effect.description; effectData[effect.effectId]['step'] = {}; effectData[effect.effectId]['trigger'] = {}; }); //console.log(cardEffects); //console.log(effectData); effectSteps.forEach((step) => { // If step doesn't exist, add step (id of DB item) if(effectData[step.effectId]['step'][step.stepId] === undefined){ effectData[step.effectId]['step'][step.stepId] = {}; // Add the step effect details effectData[step.effectId]['step'][step.stepId] = { // Order steps occur in, two same = 'simultaneous' 'stepOrder': step.stepOrder, // The hardcoded effect that will occur at step 'basicEffectId': step.basicEffectId, // How much/many the effect will do/trigger 'amount': step.amount, 'target': [], }; } // At this point there should be data in effectStep // Add a new 'target' (each step can have many) effectData[step.effectId]['step'][step.stepId]['target'] .push({ 'colourId': step.colourId, 'typeId': step.typeId, 'classId': step.classId, 'passiveId': step.passiveId, 'itemFromStep': step.itemFromStep, }); }); //console.log(util.inspect(effectData, true, 4, true)) effectTriggers.forEach((trigger) => { // If trigger doesn't exist, add trigger (id of DB item) if(effectData[trigger.effectId]['trigger'][trigger.triggerId] === undefined){ effectData[trigger.effectId]['trigger'][trigger.triggerId] = {}; // Add the trigger effect details effectData[trigger.effectId]['trigger'][trigger.triggerId] = { 'triggerTypeId': trigger.triggerTypeId, 'amount': trigger.amount, 'target': [], }; } // Add a new 'target' (each trigger can have many) effectData[trigger.effectId]['trigger'][trigger.triggerId]['target'] .push({ 'colourId': trigger.colourId, 'typeId': trigger.typeId, 'classId': trigger.classId, 'passiveId': trigger.passiveId, }); }); //console.log(util.inspect(effectData, true, 5, true)) resolve([cardEffects, effectData]); }); return cPromise; } function getCardPassive(){ const cPromise = new Promise((resolve, reject) => { database.dbGetPassive().then(data => { let passives = []; data.forEach((passive) => { passives.push({ 'cardId': passive.cardId, 'passiveId': passive.passiveId, }); }); console.log(passives); resolve(passives); }) .catch(err => { throw err; }) }); return cPromise; } // https://www.geeksforgeeks.org/how-to-wait-for-multiple-promises-in-javascript/ // https://medium.com/@nikolozz/using-socket-io-with-async-await-13fa8c2dc9d9 function requestDeck(itemData = null){ return new Promise((resolve, reject) => { (async () => { // Get the deck(s) requested. // Not 100% on how to do two differening atm // Besides all of playerId, and all of deckId. But 1,2/2,3 for instance // Build array of decks to get let deckIds = []; for(let i = 0; i < itemData.players.length; i++){ let deckStuff = itemData.players[i][1].deck; deckIds.push([deckStuff.playerId, deckStuff.deckId]); // So should be array of [playerId, deckId] which is primary key in DB } // Get said decks, and their deckLists const [decks, deckList] = await Promise.all([ getDecks(deckIds), getDeckList(deckIds) ]); /* console.log('--- decks ---'); console.log(decks); console.log('= deckLists =') console.log(deckList); console.log('------'); */ // Now loop the deckList for the cardIds let deckCardIds = []; deckList.forEach((deckItem) => { deckCardIds.push(deckItem.cardId); }); // Next, get the cards in the deck by their ID // TODO: https://stackoverflow.com/a/65510676 // Get each cards data, colourReqs, and classes const [cards, cardClasses, cardColourRequirements, cardManaColours, cardPassives] = await Promise.all([ getCards(deckCardIds), getCardClasses(deckCardIds), getCardColourRequirement(deckCardIds), getCardManaColour(deckCardIds), getCardPassive(deckCardIds), ]); // Return all effect data from DB const [effects] = await Promise.all([ database.dbGetEffect(deckCardIds), // Get effects ]); // Loop the effects for their effectIds to then get the steps/triggers from DB let effectIds = []; await effects.forEach((effect) => { effectIds.push(effect.effectId); }); // Then pass the effectIds to get their steps/triggers const [effectSteps, effectTriggers] = await Promise.all([ database.dbGetEffectStep(effectIds), database.dbGetEffectTrigger(effectIds), ]); /* console.log('--- Effects ---'); console.log(effects); */ // Build Effects const [cardEffects] = await Promise.all([ buildCardEffects(effects, effectSteps, effectTriggers), ]); // cardEffects[0][cardId] == [array of effect IDs] // cardEffects[1][effectId] == {object of the effect data} //console.log(cardEffects); // Build the cardData (maybe do all the components here too) const [builtCards] = await Promise.all([ buildCards(cards, cardClasses, cardColourRequirements, cardManaColours, cardEffects, cardPassives), ]); //console.log(builtCards); // TODO: addEffectsToCards // TODO: Finally do the effects for each card. This will be the complicater // since it's not 100% thought out yet... ( // Add the cards (x of how many cardObjects with cardId are in count in decklist) // to a deck array to pass through. Or decks array with deckId specified? //console.log(deckList); // These are the four basic fellas for this // from public/board.js where current game testing/logic is applied // So creating as a test for a 'room' to see if it's simple enough to // send and play with // BUILD THE DATA TO SEND TO CLIENTS! (Kinda) // To continue from previous item/itemCount from other funcs, something like this let item = []; let itemCount = 0; let player = {}; let players = []; // Jank check to allow current client call, and new roomMod.roomGeneration call to // both work (will replace client call with roomGeneration when closer) if(itemData !== null){ if(itemData['item'] !== undefined){ item = itemData['item']; } if(itemData['itemCount'] !== undefined){ itemCount = itemData['itemCount']; } if(itemData['player'] !== undefined){ player = itemData['player']; } if(itemData['players'] !== undefined){ players = itemData['players']; } }else{ itemData = {}; } // TODO: Deck data/deckIn should be in deckGen, other stuff probably in a cardGen // function let deckData = {}; // New but may be useful let deckIn = {}; // Which deck the item is in? Kinda the player/boardelement thing? let cardData = {}; let boardElement = {}; // New(in here) let cardStatus = {}; // Tapped, etc // player let listPosition = {}; let cardFace = {}; // REALLY NEW let cardColours = {}; // Array of req. let cardAttack = {}; // Base, Current let cardSprite = {}; // Maybe from DB? let cardManaColour = {}; // The card colour value when played in mana zone let cardEffect = {}; // Passive let flight = {}; let reach = {}; // TODO: Set the player. For now will do this in front-end as testing currently let forPlayer = 0; // TODO: Change to actually have each player select a deck // Loop and create the deck first // DECKS TODO: REDO FOR itemData.component instead of existing itemData decks.forEach((deck) => { item.push(itemCount); // Add new item to add stuff for itemData.component.deck[itemCount] = {'deckId':deck.deckId, 'playerId':deck.playerId, 'deckName':deck.deckName, 'deckSize': 0}; // Probably remove maxlength or change how I've just done it? player[itemCount] = forPlayer; // TODO: Actually set to the correct player somehow deckData[itemCount] = {'deckId':deck.deckId, 'playerId':deck.playerId, 'deckName':deck.deckName, 'maxLength': 0}; // Max length for listPositioning later in cardBuild itemData.component.cardCount.deck[forPlayer] = 0; boardElement[itemCount] = 'realDeck'; cardFace[itemCount] = 0; // Face down for deck itemCount++; // Needed/good forPlayer++; // Jank/bad }) // CARDS IN DECKS // TODO: REDO FOR itemData.component instead of existing itemData // Loop each item in the deckList // Loop inside it X times where X is cardCount // Add the builtCard with same cardId as deckList item X times // and load that deck for them. This just sets first deck to player0 to players.length deckList.forEach((deckListItem) => { let deckItem = null; // Loop each deck, if the deck/playerIds match, add association for(key in deckData){ //Object.keys(deckData).forEach(function(key) { // Less efficient than for // Needs to check deck AND player id, as that's the primary key (together) // TODO: change deckData[key] to itemData.component.deck[key] allover then remove deckData if(deckData[key].deckId == deckListItem.deckId && deckData[key].playerId == deckListItem.playerId){ deckItem = key; // Key is the `item` key // Now add cards to the player that this deck belongs to forPlayer = player[key]; } }; // For each new card, loop to the cardCount (how many cards in deck) // and add to the deck // TODO: Associate player to deck differently (both could be using same deck // via some kind of 'try a deck' or 'use friends deck' option) for(let i = 0; i < deckListItem.cardCount; i++){ item.push(itemCount); // Add new item to add stuff for // ^ not using item.length incase anything in future gets deleted // from item for instance cardData[itemCount] = builtCards[deckListItem.cardId]; // builtCards id set to cardId from DB, so adding the builtCard object is based on the deckList's cardId (from DB) // The data to be passed (for basic stat/inspect basically) // atk, mana, cost, etc. will be their own components that alter game by game // TODO: Make this the cardData that's important/will be passed to the clients itemData.component.cardData[itemCount] = { 'id': builtCards[deckListItem.cardId].id, 'name': builtCards[deckListItem.cardId].name, 'cost': builtCards[deckListItem.cardId].cost, 'attack': builtCards[deckListItem.cardId].atk, 'type': builtCards[deckListItem.cardId].type, // Unit, Spell, Token, etc. 'passive': builtCards[deckListItem.cardId].passive, 'class': builtCards[deckListItem.cardId].cardClass, 'manaColour': builtCards[deckListItem.cardId].cardManaColour, 'colour': builtCards[deckListItem.cardId].colourRequirements, 'rarity': builtCards[deckListItem.cardId].rarity, 'effect': [] }; // Effects should just be the description to draw onto the card(s) for (const [key, effect] of Object.entries(builtCards[deckListItem.cardId].effect)) { itemData.component.cardData[itemCount]['effect'].push(effect.description); } // TODO: Above cardData should be built correctly first off probably. Then clean-up all the // dupe code created for the tidy up // For above 'builtCards' should be changed I think // boardElement[itemCount] = 'deck'; // Add all cards to deck at match start // Add the entity into 'inDeck' component // also add a listPosition based on current deckLength itemData.component.inDeck[itemCount] = true; // Not sure what to store here itemData.component.listPosition[itemCount] = itemData.component.cardCount.deck[forPlayer] + 1; // Types in new component switch(builtCards[deckListItem.cardId].type){ case(1): // Unit itemData.component.type.unit[itemCount] = itemCount; break; case(2): // Spell itemData.component.type.spell[itemCount] = itemCount; break; case(3): // Token itemData.component.type.token[itemCount] = itemCount; break; } // Associate the card with the deck // TODO: Change deckIn to something more sensical deckIn[itemCount] = deckItem; // Attempt to set the listPosition of each card in related deck // Increase the length of the deck in deckItem listPosition[itemCount] = deckData[deckItem].maxLength; // TODO: better deckData[deckItem].maxLength++; // TODO: everything like below, using the new component.XYZ //itemData.component.deck[deckItem].deckSize++; itemData.component.cardCount.deck[forPlayer]++; // // Adding to get everything sorted in one! cardStatus[itemCount] = null; cardFace[itemCount] = 0; // Face down by default (in deck) cardColours[itemCount] = cardData[itemCount].colourRequirements; // TODO; cardManaColour[itemCount] = cardData[itemCount].cardManaColour; // From cardData set the base attack, and current (same when deckbuild) let atk = cardData[itemCount].atk; cardAttack[itemCount] = [atk, atk]; // Base, Current player[itemCount] = forPlayer; // Jank TODO: actually set to correct player // Add the cardEffect(s) to the cardEffect component // only if there is an effect, starting to think about // not wasting RAM/storage, y'know if(cardData[itemCount].effect.length > 0){ cardEffect[itemCount] = cardData[itemCount].effect; } // Add each passive if(cardData[itemCount].passive.length > 0){ //console.log(cardData[itemCount].passive); for(let i = 0; i < cardData[itemCount].passive.length; i++){ switch (cardData[itemCount].passive[i]){ // Flight case 1: flight[itemCount] = itemCount; break; // Reach case 2: reach[itemCount] = itemCount; break; } } } // Set the handSize to 0, will need moving somewhere else //itemData.component.deck[deckItem].handSize = 0; itemData.component.cardCount.hand[forPlayer] = 0; itemData.component.cardCount.board[forPlayer] = 0; itemData.component.cardCount.shield[forPlayer] = 0; itemData.component.cardCount.mana[forPlayer] = 0; itemData.component.cardCount.grave[forPlayer] = 0; itemData.component.cardCount.void[forPlayer] = 0; itemCount++; // Increment item to not overwrite } // Loop adding cards is done, so set the cardCount of deck (so plays know deck length without the cards) // for each player //itemData.component.cardCount.deck[forPlayer] = deckData[deckItem].maxLength; //itemData.component.deck[itemCount].deckSize++; // Handsize here too, why not. It's always 0 to start (on generation) // When it comes to reconnecting the actual data will be needed //itemData.component.cardCount.hand[forPlayer] = 0; }); // ADD all new elements, and updated data into itemData itemData.item = item; itemData.component.item = item; itemData.itemCount = itemCount; itemData.component.itemCount = itemCount; itemData.player = player; itemData.players = players; itemData.deckData = deckData; itemData.deckIn = deckIn; itemData.cardData = cardData; itemData.boardElement = boardElement; itemData.cardStatus = cardStatus; itemData.listPosition = listPosition; itemData.cardFace = cardFace; itemData.cardColours = cardColours; itemData.cardAttack = cardAttack; itemData.cardSprite = cardSprite; itemData.cardManaColour = cardManaColour; itemData.cardEffect = cardEffect; itemData.flight = flight; itemData.reach = reach; // item, itemCount, deckData, cardData, boardElement //console.log(cardData); // Returning everything to be looped in the front-end // This won't be looped as it will at final, instead just for deck generation // Returned as object over array, as easier to disect when gets to front-end let dataReturn = itemData; return resolve(dataReturn); //return resolve(cardData); // Promise stuff testing })() }); // TODO: In future the item inc. all the decks, cards, locations, and other attributes // will come from here // TODO: unrelated, but thought. If lots of players on, generating cards each time // will be a big hit, as well as all the DB hits, so may need to load all cardObjects // into memory/array when server starts. This then prevents any DB changes to alter // things, but outside of testing that won't occur(?), may need to test this at some // point to see. For now DB, and generating is ok, as still working on it } function buildCards(cards, cardClasses, cardColourRequirements, cardManaColours, cardEffects, cardPassives){ console.log(cardColourRequirements); const dPromise = new Promise((resolve, reject) => { builtCards = {}; //console.log(cards); // Loop the cards and build the base cards.forEach((card) => { let builtCard = { id: card.id, name: card.cardName, colour: [], cost: card.cardCost, colourRequirements: [], cardManaColour: [], // Should probably be an object type: card.cardType, atk: card.cardAttack, rarity: card.cardRarity, effect: [], cardClass: [], passive: [], }; // Give the card an easily accessible Id for compares // and to add to the cardItem being built builtCards[card.id] = builtCard; }); //console.log(builtCards); // Next loop each class, and add to the new builtCard (if cardIds match) cardClasses.forEach((cardClass) => { // Check the card exists (it should always, but don't want jank) if(cardClass.cardId in builtCards){ // It's the Id for now // TODO: Send the text too (from join) but don't use for compares //console.log(cardClass.cardId); //console.log(cardClass.classId); //console.log('---'); //console.log(builtCards[cardClass.cardId]); // Add the class to the class array (cards can have multiple) builtCards[cardClass.cardId].cardClass.push(cardClass.classId); // TODO: As an array [classId, className] then // card.classes[x][0] can be compared as numeric in code } }); // Do the same for cardColourRequirements (if cardIds match) cardColourRequirements.forEach((colourReq) => { // Check the card exists (it should always, but don't want jank) if(colourReq.cardId in builtCards){ // Add the colours to the class array (cards can have multiple) builtCards[colourReq.cardId].colourRequirements.push([colourReq.colourId, colourReq.cost]); // TODO: As an array [classId, className] then // card.classes[x][0] can be compared as numeric in code } }); // Add card mana colours (colour value when played in mana zone) cardManaColours.forEach((manaCol) => { // Check the card exists (it should always, but don't want jank) if(manaCol.cardId in builtCards){ // Add the colours to the class array (cards can have multiple) builtCards[manaCol.cardId].cardManaColour.push(manaCol.colourId); } }); // Add the card effects that have been pre-built // Loop the cards (each with an array of effect IDs) for (const [key] of Object.entries(cardEffects[0])) { // If the cardId is not in builtCards, skip // should always be, but eh let cardId = cardEffects[0][key]; if(builtCards[cardId] === undefined){ continue; } // Loop the effects in each of the card's effect arrays for (const [key, value] of Object.entries(cardEffects[1])) { // Add each effect that belongs to the card builtCards[cardId].effect.push(value); } } // Add card mana colours (colour value when played in mana zone) cardPassives.forEach((passive) => { // Check the card exists (it should always, but don't want jank) if(passive.cardId in builtCards){ // Add the colours to the class array (cards can have multiple) builtCards[passive.cardId]['passive'].push(passive.passiveId); } }); resolve(builtCards); }); return dPromise; } // TODO: Add the effects onto the cards passed through // Takes cards array (of obj), and array of effects (from DB or built, probabaly pre-built?) function addCardsEffects(){} module.exports = { requestDeck };