const state = new State(); const Game = { phases: { preAction: [], action: [], postAction: [], }, didTechniqueHit: false, turn: 0, async progressTurn () { Game.turn++; await Game.applyStatusEffect(state.enemy.monster); await Game.applyStatusEffect(state.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 = []; // enemy defeated if (state.enemy.monster.hp <= 0) { // money state.money += state.enemy.monster.level * state.enemy.monster.moneyModifier; // exp state.activeMonster.exp += calculateAwardedExperience(state.enemy.monster, [state.activeMonster])[0]; if (state.activeMonster.canLevelUp()) { state.activeMonster.levelUp(); } if (state.activeMonster.canEvolve()) { await fetchMonster(state.activeMonster.evolutions[0].monster_slug); state.activeMonster.evolve(); } await Game.spawnEnemyMonster(); } UI.drawEnemyMonster(); UI.drawActiveMonster(); UI.drawActiveTechniques(); UI.elements.money.textContent = `${state.money} €`; }, /** * @param {Technique} technique * @param {Monster} user * @param {Monster} target */ async useTechnique (technique, user, target) { technique.use(); if (!Game.didTechniqueHit) { UI.drawDamageMiss(UI.createDamageMiss()); return; } for (const techniqueEffect of technique.effects) { // damage if (['damage', 'splash', 'area'].includes(techniqueEffect)) { Game.phases.action.push(() => { const damage = simpleDamageCalculation(state.activeTechnique, state.activeMonster, state.enemy.monster); state.enemy.monster.hp -= damage; const damageNode = UI.createDamage(damage); UI.applyMultiplierToDamage(damageNode, simpleDamageMultiplier(state.activeTechnique.types, state.enemy.monster.types)); UI.applyTechniqueToDamage(damageNode, state.activeTechnique); UI.drawDamage(damageNode); UI.drawTechniqueAnimation(); }); } else if (techniqueEffect === 'money') { Game.phases.action.push(() => { const money = Math.max(1, Math.floor(Math.random() * target.level)); state.money += money; const damageNode = UI.createDamage(`${money} €`); UI.applyTechniqueToDamage(damageNode, state.activeTechnique); UI.drawDamage(damageNode); UI.drawTechniqueAnimation(); }); } else if (techniqueEffect === 'enhance') { UI.drawTechniqueAnimation(); } // status effect else if (techniqueEffect.includes('status_')) { const statusEffect_recipient = techniqueEffect.split(',')[1]; const statusEffect_application = techniqueEffect.split(' ')[0]; const statusEffect_type = techniqueEffect.split(',')[0].split(' ')[1].split('_')[0]; const statusEffect_effect = techniqueEffect.split(',')[0].split(' ')[1].split('_')[1]; const statusEffect = await fetchStatusEffect(statusEffect_effect); if (statusEffect.slug === 'lifeleech') { statusEffect.issuer = user; } let recipient; if (statusEffect_recipient === 'user') { recipient = user; } else { recipient = target; } Game.phases.postAction.push(() => { // add status effect const potency = Math.random(); const success = technique.potency >= potency; if (success) { // TODO: check replace if (recipient.statusEffect) return; recipient.statusEffect = statusEffect; } }); UI.drawTechniqueAnimation(); } } }, rechargeTechnique () { const feedbackNode = UI.createActionFeedback('recharge'); UI.drawActionFeedback(feedbackNode); }, /** * @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--; }); }, async spawnEnemyMonster () { state.enemy.monster = await fetchMonster(DB.allMonsters[Math.floor(Math.random() * DB.allMonsters.length)]); state.enemy.monster.level = Math.ceil(Math.random() * state.activeMonster.level); // state.enemy.monster.experienceModifier = state.enemy.monster.level; state.enemy.monster.moneyModifier = state.enemy.monster.level; UI.drawEnemyMonster(); }, /** * @param {MouseEvent} event */ async battleClick (event) { UI.battleClickEvent = event; // hit? const accuracy = Math.random(); Game.didTechniqueHit = state.activeTechnique.accuracy >= accuracy; if (state.activeTechnique.isUsable()) { await Game.useTechnique(state.activeTechnique, state.activeMonster, state.enemy.monster); } else { Game.rechargeTechnique(); } 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); state.activeTechnique = state.activeMonster.activeTechniques[idx]; // trigger battle click const rect = UI.elements.battleEnemy.getBoundingClientRect(); const xMin = rect.left + 64; const xMax = rect.right - 64; const yMin = rect.top + 32; const yMax = rect.bottom - 32; UI.elements.battleEnemy.dispatchEvent(new MouseEvent('click', { clientX: Math.random() * (xMax - xMin) + xMin, clientY: Math.random() * (yMax - yMin) + yMin, })); }, catchMonster () { const caughtMonster = new Monster(state.enemy.monster.slug); caughtMonster.initialize(); caughtMonster.level = state.enemy.monster.level; state.partyMonsters.push(caughtMonster); Game.spawnEnemyMonster(); }, /** * @returns {string} */ save () { const saveMonster = (monsterData, monsterState) => { monsterData.level = monsterState.level; monsterData.hp = monsterState.hp; if (monsterData.statusEffect && monsterData.statusEffect.slug === 'lifeleech') { monsterData.statusEffect = null; } return monsterData; }; const saveData = JSON.parse(JSON.stringify(state)); for (const idx in saveData.monsters) { saveData.monsters[idx] = saveMonster(saveData.monsters[idx], state.monsters[idx]); } for (const idx in saveData.partyMonsters) { saveData.partyMonsters[idx] = saveMonster(saveData.partyMonsters[idx], state.partyMonsters[idx]); } saveData.activeMonsterIdx = state.partyMonsters.indexOf(state.activeMonster); saveData.enemy.monster = saveMonster(saveData.enemy.monster, state.enemy.monster); return btoa(JSON.stringify(saveData)); }, /** * @param {string} saveData */ async load (saveData) { /** * @param {Monster} monsterData */ const loadMonster = async (monsterData) => { const monster = await fetchMonster(monsterData.slug); monster.level = monsterData.level; monster.hp = monsterData.hp; monster.exp = monsterData.exp; monster.tasteWarm = monsterData.tasteWarm; monster.tasteCold = monsterData.tasteCold; monster.gender = monsterData.gender; monster.heldItem = await loadItem(monsterData.heldItem); monster.statusEffect = await loadStatusEffect(monsterData.statusEffect); monster.statModifiers = monsterData.statModifiers; monster.experienceModifier = monsterData.experienceModifier; monster.moneyModifier = monsterData.moneyModifier; monster.activeTechniques = await Promise.all(monsterData.activeTechniques.map(async (technique) => { return loadTechnique(technique); })); return monster; }; /** * @param {Item} itemData */ const loadItem = async (itemData) => {}; /** * @param {StatusEffect} statusEffectData */ const loadStatusEffect = async (statusEffectData) => { if (!statusEffectData) { return null; } const statusEffect = await fetchStatusEffect(statusEffectData.slug); statusEffect.turnsLeft = statusEffectData.turnsLeft; return statusEffect; }; /** * @param {Technique} techniqueData */ const loadTechnique = async (techniqueData) => { const technique = await fetchTechnique(techniqueData.slug); return technique; }; /** * @type {State} */ const loadedState = JSON.parse(atob(saveData)); state.money = loadedState.money; state.monsters = await Promise.all(loadedState.monsters.map(async (monsterData) => await loadMonster(monsterData))); state.inventory = await Promise.all(loadedState.inventory.map(async (itemData) => await loadItem(itemData))); state.partyMonsters = await Promise.all(loadedState.partyMonsters.map(async (monsterData) => await loadMonster(monsterData))); state.activeMonster = state.partyMonsters[loadedState.activeMonsterIdx]; state.activeTechnique = await loadTechnique(loadedState.activeTechnique); state.enemy.monster = await loadMonster(loadedState.enemy.monster); UI.drawEnemyMonster(); UI.drawActiveMonster(); UI.drawActiveTechniques(); }, }; // Game click bindings UI.elements.battleEnemy.addEventListener('click', Game.battleClick); UI.elements.techniques.addEventListener('click', Game.techniqueClick); UI.elements.menuCatch.addEventListener('click', Game.catchMonster); (async function () { await initializeDB(); const possibleStarterMonsters = ['budaye', 'dollfin', 'grintot', 'ignibus', 'memnomnom']; // state.enemy.monster = await fetchMonster('grintot'); state.enemy.monster = await fetchMonster(possibleStarterMonsters[Math.round(Math.random() * (possibleStarterMonsters.length - 1))]); state.partyMonsters = [ await fetchMonster('dollfin'), await fetchMonster(possibleStarterMonsters[Math.round(Math.random() * (possibleStarterMonsters.length - 1))]), await fetchMonster('corvix'), await fetchMonster('lunight'), await fetchMonster('prophetoise'), await fetchMonster('drashimi'), await fetchMonster('glombroc'), await fetchMonster('uneye'), await fetchMonster('nostray'), await fetchMonster('dragarbor'), await fetchMonster('mk01_omega'), ]; state.activeMonster = state.partyMonsters[0]; state.activeTechnique = state.activeMonster.activeTechniques[0]; state.inventory = [ new InventoryItem(await fetchItem('tuxeball')), new InventoryItem(await fetchItem('ancient_egg')), new InventoryItem(await fetchItem('sweet_sand')), new InventoryItem(await fetchItem('tectonic_drill')), new InventoryItem(await fetchItem('surfboard')), new InventoryItem(await fetchItem('sledgehammer')), new InventoryItem(await fetchItem('raise_melee')), new InventoryItem(await fetchItem('raise_speed')), new InventoryItem(await fetchItem('mm_fire')), new InventoryItem(await fetchItem('mm_water')), new InventoryItem(await fetchItem('cureall')), new InventoryItem(await fetchItem('potion')), new InventoryItem(await fetchItem('super_potion')), ]; UI.drawEnemyMonster(); UI.drawActiveMonster(); UI.drawActiveTechniques(); })();