const Game = { phases: { preAction: [], action: [], postAction: [], }, didTechniqueHit: false, async progressTurn () { Memory.state.turn++; await Game.applyStatusEffect(Memory.state.opponent.activeMonster); await Game.applyStatusEffect(Memory.state.player.activeMonster); for (const event of Game.phases.preAction) { event(); } Game.phases.preAction = []; for (const event of Game.phases.action) { event(); } Game.phases.action = []; for (const event of Game.phases.postAction) { event(); } Game.phases.postAction = []; // opponent defeated if (Memory.state.opponent.activeMonster.hp <= 0) { // money Memory.state.money += Memory.state.opponent.activeMonster.level * Memory.state.opponent.activeMonster.moneyModifier; // exp Memory.state.player.activeMonster.exp += calculateAwardedExperience(Memory.state.opponent.activeMonster, [Memory.state.player.activeMonster])[0]; if (Memory.state.player.activeMonster.canLevelUp()) { Memory.state.player.activeMonster.levelUp(); } if (Memory.state.player.activeMonster.canEvolve()) { await Game.evolveMonster(Memory.state.player.activeMonster); } await Game.spawnOpponentMonster(); } UI.drawOpponentMonster(); UI.drawActiveMonster(); UI.drawActiveTechniques(); UI.elements.money.textContent = `${Memory.state.money} €`; }, /** * @param {Technique} technique * @param {Monster} user * @param {Monster} target */ async tryUseTechnique (technique, user, target) { let canUse = true; // recharge if (technique.isRecharging()) { const feedbackNode = UI.createActionFeedback('recharge'); feedbackNode.classList.add('recharge'); UI.drawActionFeedback(feedbackNode); canUse = false; } // noddingoff if (user.statusEffect && user.statusEffect.slug === StatusEffectType.noddingoff) { const feedbackNode = UI.createActionFeedback('noddingoff'); UI.drawActionFeedback(feedbackNode); canUse = false; } if (canUse) { // hit? const accuracy = Math.random(); Game.didTechniqueHit = technique.accuracy >= accuracy; if (!Game.didTechniqueHit) { technique.use(); UI.drawDamageMiss(UI.createDamageMiss()); return; } await Game.useTechnique(technique, user, target); } }, /** * @param {Technique} technique * @param {Monster} user * @param {Monster} target */ async useTechnique (technique, user, target) { technique.use(); for (const techniqueEffectCode of technique.effects) { const techniqueEffect = new TechniqueEffect(techniqueEffectCode); techniqueEffect.setUser(user); techniqueEffect.setTarget(target); // damage if (['damage', 'splash', 'area'].includes(techniqueEffect.type)) { Game.phases.action.push(() => { const damage = simpleDamageCalculation(technique, user, target); target.hp -= damage; const damageNode = UI.createDamage(damage); UI.applyMultiplierToDamage(damageNode, simpleDamageMultiplier(technique.types, target.types)); UI.applyTechniqueToDamage(damageNode, technique); UI.drawDamage(damageNode); UI.drawTechniqueAnimation(technique); }); } // money else if (techniqueEffect.type === 'money') { Game.phases.action.push(() => { const money = Math.max(1, Math.floor(Math.random() * target.level)); Memory.state.money += money; const damageNode = UI.createDamage(`${money} €`); UI.applyTechniqueToDamage(damageNode, technique); UI.drawDamage(damageNode); UI.drawTechniqueAnimation(technique); }); } // healing else if (techniqueEffect.type === 'healing') { for (const recipient of techniqueEffect.recipients) { console.log((user.level + 7) * technique.healingPower); recipient.hp += (user.level + 7) * technique.healingPower; } } // enhance else if (techniqueEffect.type === 'enhance') { UI.drawTechniqueAnimation(technique); } // status effect else if (techniqueEffect.type === 'status') { const statusEffect = await fetchStatusEffect(techniqueEffect.statusEffect); if (statusEffect.slug === 'lifeleech') { statusEffect.issuer = user; } Game.phases.postAction.push(() => { // add status effect const potency = Math.random(); const success = technique.potency >= potency; if (success) { for (const recipient of techniqueEffect.recipients) { // TODO: check replace if (recipient.statusEffect) continue; recipient.statusEffect = statusEffect; } } }); UI.drawTechniqueAnimation(technique); } } }, /** * @param {Monster} monster */ async applyStatusEffect (monster) { if (!monster.statusEffect) { return; } if (monster.statusEffect.turnsLeft === 0) { Game.phases.preAction.push(() => { monster.statusEffect.onRemove && monster.statusEffect.onRemove(); // if still 0 turns left after remove action if (monster.statusEffect.turnsLeft === 0) { monster.statusEffect = null; } else { Game.applyStatusEffect(monster); } }); return; } // poison / burn if (monster.statusEffect.slug === 'poison' || monster.statusEffect.slug === 'burn') { const statusEffectDamage = Math.floor(monster.stats.hp / 8); Game.phases.postAction.push(() => { monster.hp -= statusEffectDamage; const damageNode = UI.createDamage(statusEffectDamage); UI.applyStatusEffectToDamage(damageNode, monster.statusEffect); UI.drawDamage(damageNode); }); } // lifeleech else if (monster.statusEffect.slug === 'lifeleech') { const statusEffectLeech = Math.floor(monster.stats.hp / 16); Game.phases.postAction.push(() => { monster.hp -= statusEffectLeech; monster.statusEffect.issuer.hp += statusEffectLeech; const damageNode = UI.createDamage(statusEffectLeech); UI.applyStatusEffectToDamage(damageNode, monster.statusEffect); UI.drawDamage(damageNode); }); } // recover else if (monster.statusEffect.slug === 'recover') { const statusEffectHeal = Math.floor(monster.stats.hp / 16); Game.phases.postAction.push(() => { monster.hp += statusEffectHeal; const feedbackNode = UI.createActionFeedback(statusEffectHeal); UI.applyStatusEffectToDamage(feedbackNode, monster.statusEffect); UI.drawActionFeedback(feedbackNode); }); } // stuck else if (monster.statusEffect.slug === 'stuck') { for (const technique of monster.activeTechniques) { if ([TechniqueRange.melee, TechniqueRange.touch].includes(technique.range)) { Game.phases.preAction.push(() => { technique.potency = technique.stats.potency * 0.5; technique.power = technique.stats.power * 0.5; }); monster.statusEffect.onRemove = () => { technique.resetStats(); }; } } } // grabbed else if (monster.statusEffect.slug === 'grabbed') { for (const technique of monster.activeTechniques) { if ([TechniqueRange.ranged, TechniqueRange.reach].includes(technique.range)) { Game.phases.preAction.push(() => { technique.potency = technique.stats.potency * 0.5; technique.power = technique.stats.power * 0.5; }); monster.statusEffect.onRemove = () => { technique.resetStats(); }; } } } // charging else if (monster.statusEffect.slug === 'charging') { const nextStatusEffect = await fetchStatusEffect('chargedup'); monster.statusEffect.onRemove = () => { monster.statusEffect = nextStatusEffect; }; } // statchange else if (monster.statusEffect.effects.includes('statchange')) { monster.resetStatModifiers(); for (const statType in monster.statusEffect.stats) { const statChange = monster.statusEffect.stats[statType]; const modifiedValue = Math.floor(eval(`${monster.stats[statType]} ${statChange.operation} ${statChange.value}`)); Game.phases.preAction.push(() => { monster.setStatModifier(statType, modifiedValue); }); } monster.statusEffect.onRemove = () => { monster.resetStatModifiers(); }; } Game.phases.postAction.push(() => { monster.statusEffect.turnsLeft--; }); }, /** * @param {Monster} monster */ async evolveMonster (monster) { await fetchMonster(monster.evolutions[0].monster_slug); monster.evolve(monster.evolutions[0]); }, async spawnOpponentMonster () { Memory.state.opponent.activeMonster = await fetchMonster(DB.allMonsters[Math.floor(Math.random() * DB.allMonsters.length)]); Memory.state.opponent.activeMonster.level = Math.ceil(Math.random() * Memory.state.player.activeMonster.level); // Memory.state.opponent.activeMonster.experienceModifier = Memory.state.opponent.activeMonster.level; Memory.state.opponent.activeMonster.moneyModifier = Memory.state.opponent.activeMonster.level; UI.drawOpponentMonster(); }, /** * @param {MouseEvent} event */ async battleClick (event) { UI.battleClickEvent = event; await Game.tryUseTechnique(Memory.state.activeTechnique, Memory.state.player.activeMonster, Memory.state.opponent.activeMonster); Game.progressTurn(); }, /** * @param {MouseEvent} event */ techniqueClick (event) { if (event.target === UI.elements.techniques) { return; } let target = event.target; while (!target.classList.contains('techniques__technique')) { target = target.parentNode; } const idx = [...UI.elements.techniques.children].indexOf(target); Memory.state.activeTechnique = Memory.state.player.activeMonster.activeTechniques[idx]; // trigger battle click const rect = UI.elements.battleOpponent.getBoundingClientRect(); const xMin = rect.left + 64; const xMax = rect.right - 64; const yMin = rect.top + 32; const yMax = rect.bottom - 32; UI.elements.battleOpponent.dispatchEvent(new MouseEvent('click', { clientX: Math.random() * (xMax - xMin) + xMin, clientY: Math.random() * (yMax - yMin) + yMin, })); }, catchMonster () { const caughtMonster = new Monster(Memory.state.opponent.activeMonster.slug); caughtMonster.initialize(); caughtMonster.level = Memory.state.opponent.activeMonster.level; Memory.state.player.monsters.push(caughtMonster); Game.spawnOpponentMonster(); }, }; // Game click bindings UI.elements.battleOpponent.addEventListener('click', Game.battleClick); UI.elements.techniques.addEventListener('click', Game.techniqueClick); UI.elements.menuCatch.addEventListener('click', Game.catchMonster);