diff options
Diffstat (limited to 'resources/js/game.js')
-rw-r--r-- | resources/js/game.js | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/resources/js/game.js b/resources/js/game.js new file mode 100644 index 0000000..1f1f92b --- /dev/null +++ b/resources/js/game.js @@ -0,0 +1,459 @@ +const state = new State(); + + +const Game = { + phases: { + preAction: [], + action: [], + postAction: [], + }, + + didTechniqueHit: false, + + + async progressTurn () { + 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 = []; + + 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) { + if (!Game.didTechniqueHit) { + UI.drawDamageMiss(UI.createDamageMiss()); + return; + } + + if (state.activeMonster.hp === state.activeMonster.stats.hp) { + state.activeMonster.hp = 1; + } + + 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); + }); + } + + else if (techniqueEffect === 'money') { + state.money += Math.floor(Math.random() * target.level); + } + + else if (techniqueEffect === 'enhance') { + UI.drawDamage(UI.createDamage('!!ENHANCE!!')); + } + + // 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); + 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(); + } + } + }, + + /** + * @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 damageNode = UI.createDamage(statusEffectHeal); + UI.applyStatusEffectToDamage(damageNode, monster.statusEffect); + UI.drawDamage(damageNode); + }); + } + + // 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; + + await Game.useTechnique(state.activeTechnique, state.activeMonster, state.enemy.monster); + + Game.phases.postAction.push(async () => { + // 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(); + } + }); + + 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; + + 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(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]; + + UI.drawEnemyMonster(); + UI.drawActiveMonster(); + UI.drawActiveTechniques(); +})(); |