From ca093f0fb457a7037eb8a3acf3304e0646fa4278 Mon Sep 17 00:00:00 2001 From: Daniel Weipert Date: Mon, 21 Aug 2023 01:26:53 +0200 Subject: opponent counter attacks --- resources/css/menu.css | 2 + resources/js/classes/Monster.js | 3 + resources/js/classes/utility/TechniqueEffect.js | 12 ++ resources/js/game.js | 253 ++++++++++++++++++------ resources/js/ui.js | 1 + 5 files changed, 215 insertions(+), 56 deletions(-) (limited to 'resources') diff --git a/resources/css/menu.css b/resources/css/menu.css index 16a9d02..1c5fb76 100644 --- a/resources/css/menu.css +++ b/resources/css/menu.css @@ -89,6 +89,8 @@ color: #fff; background-color: #000; padding: 0.25rem; + display: flex; + justify-content: space-between; } diff --git a/resources/js/classes/Monster.js b/resources/js/classes/Monster.js index 69bb654..84ea5da 100644 --- a/resources/js/classes/Monster.js +++ b/resources/js/classes/Monster.js @@ -112,6 +112,9 @@ class Monster { return translate(this.slug) || slugToName(this.slug); } + /** + * @returns {Object[]} + */ getLearnableTechniques () { return this.moveset.filter((move) => this.level >= move.level_learned); } diff --git a/resources/js/classes/utility/TechniqueEffect.js b/resources/js/classes/utility/TechniqueEffect.js index 852efad..fe38888 100644 --- a/resources/js/classes/utility/TechniqueEffect.js +++ b/resources/js/classes/utility/TechniqueEffect.js @@ -24,6 +24,11 @@ class TechniqueEffect { */ statusEffect = null; + /** + * @type {ElementType} + */ + switchType = ''; + /** * @type {Monster} */ @@ -52,6 +57,13 @@ class TechniqueEffect { this.recipient = effectCode.split(' ')[1]; } + else if (effectCode.startsWith('switch')) { + this.type = 'switch'; + + this.recipient = effectCode.split(' ')[1].split(',')[0]; + this.switchType = effectCode.split(' ')[1].split(',')[1]; + } + else { this.type = effectCode; } diff --git a/resources/js/game.js b/resources/js/game.js index b233ef2..d31d6e5 100644 --- a/resources/js/game.js +++ b/resources/js/game.js @@ -1,35 +1,69 @@ const Game = { phases: { - preAction: [], - action: [], - postAction: [], + preAction: { + opponent: [], + player: [], + }, + action: { + opponent: [], + player: [], + }, + postAction: { + opponent: [], + player: [], + }, }, isLoadingArea: false, + isProgressingTurn: false, + playerIsChoosingNextMonster: false, + doBattleAnimation: true, + opponentActionTimeout: null, isInBattle: false, didTechniqueHit: false, async progressTurn () { + Game.isProgressingTurn = true; Memory.state.turn++; + // status effects 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(); + // Phases + for (const phaseKey of Object.keys(Game.phases)) { + for (const event of Game.phases[phaseKey].player) { + event(); + + await Game.handleDefeatOpponent(); + if (!Game.playerIsChoosingNextMonster) await Game.handleDefeatPlayer(); + } + Game.phases[phaseKey].player = []; + + Game.doBattleAnimation = false; + for (const event of Game.phases[phaseKey].opponent) { + event(); + + await Game.handleDefeatOpponent(); + if (!Game.playerIsChoosingNextMonster) await Game.handleDefeatPlayer(); + } + Game.doBattleAnimation = true; + Game.phases[phaseKey].opponent = []; } - Game.phases.postAction = []; - // opponent defeated + UI.progressTurn(); + Game.isProgressingTurn = false; + }, + + async handleDefeatOpponent () { if (Memory.state.opponent.activeMonster.hp <= 0) { + clearTimeout(Game.opponentActionTimeout); + Game.opponentActionTimeout = null; + for (const phase of Object.keys(Game.phases)) { + Game.removePhaseEvents(phase, 'opponent'); + } + Game.removePhaseEvents('action', 'player'); + // money Memory.state.money += Memory.state.opponent.activeMonster.level * Memory.state.opponent.activeMonster.moneyModifier; @@ -39,7 +73,7 @@ const Game = { if (Memory.state.player.activeMonster.canLevelUp()) { Memory.state.player.activeMonster.levelUp(); } - if (Memory.state.player.activeMonster.canEvolve()) { + if (Memory.state.player.activeMonster.canEvolve('standard')) { await Game.evolveMonster(Memory.state.player.activeMonster); } @@ -61,8 +95,56 @@ const Game = { } } } + }, - UI.progressTurn(); + async handleDefeatPlayer () { + if (Memory.state.player.activeMonster.hp <= 0) { + clearTimeout(Game.opponentActionTimeout); + Game.opponentActionTimeout = null; + for (const phase of Object.keys(Game.phases)) { + Game.removePhaseEvents(phase, 'player'); + } + Game.removePhaseEvents('action', 'opponent'); + + Game.playerIsChoosingNextMonster = true; + UI.openPartyMenu(); + } + }, + + /** + * @param {Object} phase + * @param {Monster} monster + * @param {Function} event + */ + addPhaseEvent (phase, monster, event) { + if (monster === Memory.state.player.activeMonster) { + phase.player.push(event); + } else { + phase.opponent.push(event); + } + }, + + /** + * @param {('preAction' | 'action' | 'postAction')} phase + * @param {('player' | 'opponent')} type + */ + removePhaseEvents (phase, type) { + Game.phases[phase][type] = []; + }, + + clearAllPhaseEvents () { + Game.removePhaseEvents('preAction', 'player'); + Game.removePhaseEvents('preAction', 'opponent'); + Game.removePhaseEvents('action', 'player'); + Game.removePhaseEvents('action', 'opponent'); + Game.removePhaseEvents('postAction', 'player'); + Game.removePhaseEvents('postAction', 'opponent'); + }, + + clearCurrentTurn () { + Game.clearAllPhaseEvents(); + clearTimeout(Game.opponentActionTimeout); + Game.opponentActionTimeout = null; }, /** @@ -75,17 +157,21 @@ const Game = { // recharge if (technique.isRecharging()) { - const feedbackNode = UI.createActionFeedback('recharge'); - feedbackNode.classList.add('recharge'); - UI.drawActionFeedback(feedbackNode); + if (Game.doBattleAnimation) { + 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); + if (Game.doBattleAnimation) { + const feedbackNode = UI.createActionFeedback('noddingoff'); + UI.drawActionFeedback(feedbackNode); + } canUse = false; } @@ -97,7 +183,7 @@ const Game = { Game.didTechniqueHit = technique.accuracy >= accuracy; if (!Game.didTechniqueHit) { technique.use(); - UI.drawDamageMiss(UI.createDamageMiss()); + Game.doBattleAnimation && UI.drawDamageMiss(UI.createDamageMiss()); return; } @@ -121,42 +207,51 @@ const Game = { // damage if (['damage', 'splash', 'area'].includes(techniqueEffect.type)) { - Game.phases.action.push(() => { + Game.addPhaseEvent(Game.phases.action, user, () => { 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); + + if (Game.doBattleAnimation) { + 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(() => { + Game.addPhaseEvent(Game.phases.action, user, () => { 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); + if (Game.doBattleAnimation) { + 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; } } + // switch + else if (techniqueEffect.type === 'switch') { + techniqueEffect.recipient.types = [techniqueEffect.switchType]; + } + // enhance else if (techniqueEffect.type === 'enhance') { - UI.drawTechniqueAnimation(technique); + Game.doBattleAnimation && UI.drawTechniqueAnimation(technique); } // status effect @@ -167,7 +262,7 @@ const Game = { statusEffect.issuer = user; } - Game.phases.postAction.push(() => { + Game.addPhaseEvent(Game.phases.postAction, user, () => { // add status effect const potency = Math.random(); const success = technique.potency >= potency; @@ -182,7 +277,7 @@ const Game = { } }); - UI.drawTechniqueAnimation(technique); + Game.doBattleAnimation && UI.drawTechniqueAnimation(technique); } } }, @@ -196,7 +291,7 @@ const Game = { } if (monster.statusEffect.turnsLeft === 0) { - Game.phases.preAction.push(() => { + Game.addPhaseEvent(Game.phases.preAction, monster, () => { monster.statusEffect.onRemove && monster.statusEffect.onRemove(); // if still 0 turns left after remove action @@ -214,12 +309,14 @@ const Game = { if (monster.statusEffect.slug === 'poison' || monster.statusEffect.slug === 'burn') { const statusEffectDamage = Math.floor(monster.stats.hp / 8); - Game.phases.postAction.push(() => { + Game.addPhaseEvent(Game.phases.postAction, monster, () => { monster.hp -= statusEffectDamage; - const damageNode = UI.createDamage(statusEffectDamage); - UI.applyStatusEffectToDamage(damageNode, monster.statusEffect); - UI.drawDamage(damageNode); + if (Game.doBattleAnimation) { + const damageNode = UI.createDamage(statusEffectDamage); + UI.applyStatusEffectToDamage(damageNode, monster.statusEffect); + UI.drawDamage(damageNode); + } }); } @@ -227,13 +324,15 @@ const Game = { else if (monster.statusEffect.slug === 'lifeleech') { const statusEffectLeech = Math.floor(monster.stats.hp / 16); - Game.phases.postAction.push(() => { + Game.addPhaseEvent(Game.phases.postAction, monster, () => { monster.hp -= statusEffectLeech; monster.statusEffect.issuer.hp += statusEffectLeech; - const damageNode = UI.createDamage(statusEffectLeech); - UI.applyStatusEffectToDamage(damageNode, monster.statusEffect); - UI.drawDamage(damageNode); + if (Game.doBattleAnimation) { + const damageNode = UI.createDamage(statusEffectLeech); + UI.applyStatusEffectToDamage(damageNode, monster.statusEffect); + UI.drawDamage(damageNode); + } }); } @@ -241,12 +340,14 @@ const Game = { else if (monster.statusEffect.slug === 'recover') { const statusEffectHeal = Math.floor(monster.stats.hp / 16); - Game.phases.postAction.push(() => { + Game.addPhaseEvent(Game.phases.postAction, monster, () => { monster.hp += statusEffectHeal; - const feedbackNode = UI.createActionFeedback(statusEffectHeal); - UI.applyStatusEffectToDamage(feedbackNode, monster.statusEffect); - UI.drawActionFeedback(feedbackNode); + if (Game.doBattleAnimation) { + const feedbackNode = UI.createActionFeedback(statusEffectHeal); + UI.applyStatusEffectToDamage(feedbackNode, monster.statusEffect); + UI.drawActionFeedback(feedbackNode); + } }); } @@ -254,7 +355,7 @@ const Game = { else if (monster.statusEffect.slug === 'stuck') { for (const technique of monster.activeTechniques) { if ([TechniqueRange.melee, TechniqueRange.touch].includes(technique.range)) { - Game.phases.preAction.push(() => { + Game.addPhaseEvent(Game.phases.preAction, monster, () => { technique.potency = technique.stats.potency * 0.5; technique.power = technique.stats.power * 0.5; }); @@ -270,7 +371,7 @@ const Game = { else if (monster.statusEffect.slug === 'grabbed') { for (const technique of monster.activeTechniques) { if ([TechniqueRange.ranged, TechniqueRange.reach].includes(technique.range)) { - Game.phases.preAction.push(() => { + Game.addPhaseEvent(Game.phases.preAction, monster, () => { technique.potency = technique.stats.potency * 0.5; technique.power = technique.stats.power * 0.5; }); @@ -299,7 +400,7 @@ const Game = { const statChange = monster.statusEffect.stats[statType]; const modifiedValue = Math.floor(eval(`${monster.stats[statType]} ${statChange.operation} ${statChange.value}`)); - Game.phases.preAction.push(() => { + Game.addPhaseEvent(Game.phases.preAction, monster, () => { monster.setStatModifier(statType, modifiedValue); }); } @@ -309,7 +410,7 @@ const Game = { }; } - Game.phases.postAction.push(() => { + Game.addPhaseEvent(Game.phases.postAction, monster, () => { monster.statusEffect.turnsLeft--; }); }, @@ -318,15 +419,48 @@ const Game = { * @param {MouseEvent} event */ async battleClick (event) { - if (Game.isLoadingArea) { + if (Game.isLoadingArea || Game.isProgressingTurn) { return; } Game.isInBattle = true; UI.battleClickEvent = event; + // player await Game.tryUseTechnique(Memory.state.activeTechnique, Memory.state.player.activeMonster, Memory.state.opponent.activeMonster); + // opponent + if (!Game.opponentActionTimeout) { + let speedDifference = Memory.state.opponent.activeMonster.stats.speed - Memory.state.player.activeMonster.stats.speed; + if (speedDifference > 0) speedDifference = speedDifference / 2; + else if (speedDifference < 0) speedDifference = speedDifference * 2; + + const opponentActiveMonster = Memory.state.opponent.activeMonster; + Game.opponentActionTimeout = setTimeout(async () => { + if (opponentActiveMonster.hp <= 0) { + Game.opponentActionTimeout = null; + return; + } + + Game.doBattleAnimation = false; + await Game.tryUseTechnique( + await fetchTechnique(Memory.state.opponent.activeMonster.getLearnableTechniques()[Math.floor(Math.random() * Memory.state.opponent.activeMonster.getLearnableTechniques().length)].technique), + Memory.state.opponent.activeMonster, + Memory.state.player.activeMonster + ); + Game.doBattleAnimation = true; + + await Game.progressTurn(); + + Game.opponentActionTimeout = null; + }, Math.max(500, 2000 - (speedDifference * 10))); + console.log( + 'Opponent Attack Timeout', + Memory.state.opponent.activeMonster.stats.speed, Memory.state.player.activeMonster.stats.speed, + 2000 - (speedDifference * 10) + ); + } + await Game.progressTurn(); }, @@ -466,6 +600,8 @@ const Game = { return; } + Game.clearCurrentTurn(); + Memory.state.activeBall.quantity--; if (Memory.state.activeBall.quantity === 0) { Game.removeItemFromInventory(Memory.state.player.inventory, Memory.state.activeBall); @@ -497,6 +633,8 @@ const Game = { * @param {Area} area */ async jumpToArea (area) { + Game.clearCurrentTurn(); + Memory.state.currentArea = area; UI.drawArea(area); @@ -504,6 +642,7 @@ const Game = { async progressToNextArea () { Game.isLoadingArea = true; + Game.clearCurrentTurn(); const currentArea = Memory.state.currentArea; const nextArea = await fetchArea(currentArea.nextArea); @@ -537,6 +676,8 @@ const Game = { }, async encounterTrainer () { + Game.clearCurrentTurn(); + const nextTrainer = Memory.state.currentArea.trainers[Memory.state.currentArea.trainerProgress]; const trainer = new Trainer(nextTrainer); diff --git a/resources/js/ui.js b/resources/js/ui.js index dbec9b2..c67e2f0 100644 --- a/resources/js/ui.js +++ b/resources/js/ui.js @@ -624,6 +624,7 @@ const UI = { UI.drawActiveMonster(); UI.drawActiveTechniques(); + Game.playerIsChoosingNextMonster = false; popup.remove(); } else if (UI.partySelectionMode === 'stats') { -- cgit v1.2.3