summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Weipert <code@drogueronin.de>2023-08-21 01:26:53 +0200
committerDaniel Weipert <code@drogueronin.de>2023-08-21 01:26:53 +0200
commitca093f0fb457a7037eb8a3acf3304e0646fa4278 (patch)
tree26e0e20b38a14ba4f7c3c9f33748fa68168863e9
parent43a28ad77190f2e55e2e6ba65a9a7b5b1f5dea6c (diff)
opponent counter attacks
-rw-r--r--index.html12
-rw-r--r--resources/css/menu.css2
-rw-r--r--resources/js/classes/Monster.js3
-rw-r--r--resources/js/classes/utility/TechniqueEffect.js12
-rw-r--r--resources/js/game.js253
-rw-r--r--resources/js/ui.js1
6 files changed, 220 insertions, 63 deletions
diff --git a/index.html b/index.html
index 4ae9189..7564564 100644
--- a/index.html
+++ b/index.html
@@ -24,13 +24,11 @@
<div id="techniques"></div>
<div id="status">
- <div>
- Money: <span data-template-slot="money"></span>
- Monster Progress: <span data-template-slot="monsterProgress"></span>
- Trainer Progress: <span data-template-slot="trainerProgress"></span>
- <button data-template-slot="nextTrainer">&rArr; Next Trainer</button>
- <button data-template-slot="nextArea">&rArr; Next Area</button>
- </div>
+ <span>Money: <span data-template-slot="money"></span></span>
+ <span>Monster Progress: <span data-template-slot="monsterProgress"></span></span>
+ <span>Trainer Progress: <span data-template-slot="trainerProgress"></span></span>
+ <button data-template-slot="nextTrainer">&rArr; Next Trainer</button>
+ <button data-template-slot="nextArea">&rArr; Next Area</button>
</div>
<div id="menu">
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
@@ -25,6 +25,11 @@ class TechniqueEffect {
statusEffect = null;
/**
+ * @type {ElementType}
+ */
+ switchType = '';
+
+ /**
* @type {Monster}
*/
user = null;
@@ -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') {