summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Weipert <code@drogueronin.de>2023-08-15 22:48:11 +0200
committerDaniel Weipert <code@drogueronin.de>2023-08-15 22:48:11 +0200
commit0a93c96bb7ea429f5cbe3b436c0fc4efa594500c (patch)
treed5e98d915d46dc82b28fffd86fe950e4b8a84bf7
parentd2b78b32ad41dd7d2e7f86cda8bada33238fd286 (diff)
status effects
-rw-r--r--script.js245
-rw-r--r--style.css23
2 files changed, 237 insertions, 31 deletions
diff --git a/script.js b/script.js
index 9eecc44..d66f2bb 100644
--- a/script.js
+++ b/script.js
@@ -5,6 +5,7 @@ const DB = {
shapes: {},
elements: {},
techniques: {},
+ statusEffects: {},
};
const ElementType = {
@@ -81,6 +82,40 @@ const StatusType = {
dodge: 'dodge',
speed: 'speed',
};
+const StatusEffectType = {
+ blinded: 'blinded',
+ burn: 'burn',
+ chargedup: 'chargedup',
+ charging: 'charging',
+ confused: 'confused',
+ diehard: 'diehard',
+ dozing: 'dozing',
+ elementalshield: 'elementalshield',
+ eliminated: 'eliminated',
+ enraged: 'enraged',
+ exhausted: 'exhausted',
+ faint: 'faint',
+ feedback: 'feedback',
+ festering: 'festering',
+ flinching: 'flinching',
+ focused: 'focused',
+ grabbed: 'grabbed',
+ hardshell: 'hardshell',
+ harpooned: 'harpooned',
+ lifeleech: 'lifeleech',
+ lockdown: 'lockdown',
+ noddingoff: 'noddingoff',
+ poison: 'poison',
+ prickly: 'prickly',
+ recover: 'recover',
+ slow: 'slow',
+ sniping: 'sniping',
+ softened: 'softened',
+ stuck: 'stuck',
+ tired: 'tired',
+ wasting: 'wasting',
+ wild: 'wild',
+};
class State {
money = 0;
@@ -100,8 +135,10 @@ class State {
class Monster {
#level = 1;
+ #status = null;
+ #hp = 0;
+
exp = 1;
- hp = 0;
tasteWarm = TasteWarm.tasteless;
tasteCold = TasteCold.tasteless;
@@ -111,7 +148,7 @@ class Monster {
heldItem = null;
statusEffect = '';
- statusModifiers = {
+ statusModifier = {
hp: 0,
melee: 0,
armour: 0,
@@ -176,6 +213,14 @@ class Monster {
}
}
+ get hp () {
+ return this.#hp;
+ }
+
+ set hp (hp) {
+ this.#hp = Math.max(0, Math.min(hp, this.status.hp));
+ }
+
get name () {
return slugToName(this.slug);
}
@@ -219,6 +264,33 @@ class Monster {
this.hp = statusPostEvolve.hp - (statusPreEvolve.hp - hpPreEvolve);
}
+ async useTechnique(technique, enemy) {
+ const additionalEffects = technique.effects
+ .filter((effect) => !['damage', 'splash', 'area'].includes(effect))
+ .map((effect) => new TechniqueEffect(effect));
+
+ for (const effect of additionalEffects) {
+ let recipient;
+
+ if (effect.recipient === 'user') {
+ recipient = this;
+ } else {
+ recipient = enemy;
+ }
+
+ if (effect.type === 'status') {
+ if (effect.application === 'give') {
+ recipient.statusEffect = await fetchStatusEffect(effect.effect);
+ }
+ else if (effect.application === 'remove') {
+ if (recipient.statusEffect.slug === effect.effect) {
+ recipient.statusEffect = '';
+ }
+ }
+ }
+ }
+ }
+
getTasteStatusModifier (statusName, baseStatus) {
let positive = 0;
let negative = 0;
@@ -257,13 +329,17 @@ class Monster {
}
get status () {
+ if (this.#status) {
+ return this.#status;
+ }
+
const multiplier = this.level + 7;
- let hp = (this.shape.hp * multiplier) + this.statusModifiers.hp;
- let melee = (this.shape.melee * multiplier) + this.statusModifiers.melee;
- let armour = (this.shape.armour * multiplier) + this.statusModifiers.armour;
- let ranged = (this.shape.ranged * multiplier) + this.statusModifiers.ranged;
- let dodge = (this.shape.dodge * multiplier) + this.statusModifiers.dodge;
- let speed = (this.shape.speed * multiplier) + this.statusModifiers.speed;
+ let hp = (this.shape.hp * multiplier) + this.statusModifier.hp;
+ let melee = (this.shape.melee * multiplier) + this.statusModifier.melee;
+ let armour = (this.shape.armour * multiplier) + this.statusModifier.armour;
+ let ranged = (this.shape.ranged * multiplier) + this.statusModifier.ranged;
+ let dodge = (this.shape.dodge * multiplier) + this.statusModifier.dodge;
+ let speed = (this.shape.speed * multiplier) + this.statusModifier.speed;
// Tastes
melee += this.getTasteStatusModifier('melee', melee);
@@ -272,7 +348,7 @@ class Monster {
dodge += this.getTasteStatusModifier('dodge', melee);
speed += this.getTasteStatusModifier('speed', melee);
- return {
+ this.#status = {
hp,
melee,
armour,
@@ -280,6 +356,17 @@ class Monster {
dodge,
speed,
};
+
+ return this.#status;
+ }
+
+ set status (status) {
+ this.#status = status;
+ }
+
+ resetStatus () {
+ this.#status = null;
+ this.status;
}
};
@@ -307,10 +394,48 @@ class Technique {
get sfx () {
return DB.techniques[this.slug].sfx;
}
+
+ get effects () {
+ return DB.techniques[this.slug].effects;
+ }
}
class Item {}
+class TechniqueEffect {
+ constructor (str) {
+ this.recipient = str.split(',')[1];
+ this.application = str.split(' ')[0];
+ this.type = str.split(',')[0].split(' ')[1].split('_')[0];
+ this.effect = str.split(',')[0].split(' ')[1].split('_')[1];
+ }
+}
+
+class StatusEffect {
+ constructor (slug) {
+ this.slug = slug;
+ }
+
+ get effects () {
+ return DB.statusEffects[this.slug].effects;
+ }
+
+ get name () {
+ return slugToName(this.slug);
+ }
+
+ get status () {
+ const status = {};
+
+ const statusChangeKeys = Object.keys(DB.statusEffects[this.slug]).filter((key) => key.startsWith('stat'));
+ for (const key of statusChangeKeys) {
+ status[key.replace('stat', '')] = DB.statusEffects[this.slug][key];
+ }
+
+ return status;
+ }
+}
+
function simpleDamageMultiplier (techniqueTypes, targetTypes) {
let multiplier = 1;
@@ -360,7 +485,7 @@ function simpleDamageCalculation (technique, user, target) {
const moveStrength = technique.power * multiplier;
const damage = Math.floor((userStrength * moveStrength) / targetResist);
- return damage;
+ return Math.max(damage, 1);
}
function calculateAwardedExperience (playerMonster, enemyMonster) {
@@ -386,6 +511,13 @@ async function fetchTechnique (slug) {
return new Technique(slug);
}
+async function fetchStatusEffect (slug) {
+ if (! DB.statusEffects[slug]) {
+ DB.statusEffects[slug] = await fetch(`/modules/tuxemon/mods/tuxemon/db/technique/status_${slug}.json`).then((response) => response.json());
+ }
+
+ return new StatusEffect(slug);
+}
function standardizeColor (color) {
var ctx = document.createElement('canvas').getContext('2d');
@@ -433,6 +565,7 @@ function slugToName (slug) {
await fetchMonster('lunight'),
await fetchMonster('prophetoise'),
await fetchMonster('drashimi'),
+ await fetchMonster('glombroc'),
];
state.activeMonster = state.partyMonsters[0];
state.activeTechnique = await fetchTechnique(state.activeMonster.moveset[0].technique);
@@ -453,6 +586,12 @@ function slugToName (slug) {
const UI = {
activeMonster: null,
+ enemyImg: null,
+ damageHighlightClickDuration: 0.1,
+ damageHighlightClickTimeout: null,
+ imgClickDuration: 0.1,
+ damageClickTimeout: null,
+
damageAnimationIsRunning: false,
damageAnimationNumber: 0,
@@ -509,15 +648,12 @@ function slugToName (slug) {
battleEnemy.appendChild(damageNode);
setTimeout(() => damageNode.remove(), (damageNodeDuration * 1000) - 500);
- const enemyImg = battleEnemy.querySelector('.battle__monster-img');
- const imgClickDuration = 0.1;
- enemyImg.style.transitionDuration = imgClickDuration + 's';
- enemyImg.classList.add('damaged');
- clearTimeout(clickTimeout);
- clickTimeout = setTimeout(() => enemyImg.classList.remove('damaged'), imgClickDuration * 1000);
+ this.enemyImg.classList.add('damaged');
+ clearTimeout(this.damageHighlightClickTimeout);
+ this.damageHighlightClickTimeout = setTimeout(() => this.enemyImg.classList.remove('damaged'), this.damageHighlightClickDuration * 1000);
var enemyAnimation = battleEnemy.querySelector('.battle__monster-sprite__animation');
- if (!this.damageAnimationIsRunning && state.activeTechnique.animation) {
+ if (!this.damageAnimationIsRunning && state.activeTechnique.animation && DB.allAnimations[state.activeTechnique.animation]) {
this.damageAnimationIsRunning = true;
const damageAnimationLoop = () => {
@@ -528,7 +664,7 @@ function slugToName (slug) {
this.damageAnimationNumber++;
- if (this.damageAnimationNumber === DB.allAnimations[state.activeTechnique.animation].length) {
+ if (this.damageAnimationNumber >= DB.allAnimations[state.activeTechnique.animation].length) {
this.damageAnimationIsRunning = false;
this.damageAnimationNumber = 0;
enemyAnimation.src = '';
@@ -555,7 +691,7 @@ function slugToName (slug) {
battleMonster.querySelector('.battle__monster-info__name').textContent = monster.name;
battleMonster.querySelector('.battle__monster-info__gender').textContent = monster.gender === 'male' ? '♂' : monster.gender === 'female' ? '♀' : '⚲';
battleMonster.querySelector('.battle__monster-info__level').textContent = monster.level;
- battleMonster.querySelector('.battle__monster-info__status').innerHTML = UI.createStatusIcon(monster.statusEffect);
+ battleMonster.querySelector('.battle__monster-info__status').innerHTML = UI.createStatusEffectIcon(monster.statusEffect);
battleMonster.querySelector('.battle__monster-img').src = `/modules/tuxemon/mods/tuxemon/gfx/sprites/battle/${monster.slug}-front.png`;
UI.setHp(monster, battleMonster.querySelector('.hp'));
@@ -572,7 +708,17 @@ function slugToName (slug) {
battlePlayer.appendChild(battleMonster);
} else {
battleMonster.classList.add('battle__monster--enemy');
- battleEnemy.querySelector('.battle__monster') && battleEnemy.removeChild(battleEnemy.querySelector('.battle__monster'));
+
+ this.enemyImg = battleMonster.querySelector('.battle__monster-img');
+ this.enemyImg.style.transitionDuration = this.damageHighlightClickDuration + 's';
+
+ const previousBattleMonster = battleEnemy.querySelector('.battle__monster');
+ if (previousBattleMonster) {
+ this.enemyImg.classList = previousBattleMonster.querySelector('.battle__monster-img').classList;
+
+ battleEnemy.removeChild(previousBattleMonster);
+ }
+
battleEnemy.appendChild(battleMonster);
}
},
@@ -581,7 +727,11 @@ function slugToName (slug) {
UI.setBattleMonster(state.enemy.monster, 'enemy');
},
- async setActiveMonster () {
+ setActiveMonster () {
+ UI.setBattleMonster(state.activeMonster, 'player');
+ },
+
+ async setActiveTechnique () {
let activeMoveIndex = 0;
while ((await fetchTechnique(state.activeMonster.moveset[activeMoveIndex].technique)).power === 0) {
activeMoveIndex++;
@@ -647,12 +797,12 @@ function slugToName (slug) {
return img.outerHTML;
},
- createStatusIcon (status) {
- if (!status) return '';
+ createStatusEffectIcon (statusEffect) {
+ if (!statusEffect) return '';
var img = document.createElement('img');
- img.src = `/modules/tuxemon/mods/tuxemon/gfx/ui/icons/status/icon_${status}.png`;
- img.title = slugToName(status);
+ img.src = `/modules/tuxemon/mods/tuxemon/gfx/ui/icons/status/icon_${statusEffect.slug}.png`;
+ img.title = statusEffect.name;
return img.outerHTML;
},
@@ -682,6 +832,7 @@ function slugToName (slug) {
}
state.activeMonster = state.partyMonsters[Array.prototype.indexOf.call(document.querySelector('#party').children, target)];
+ UI.setActiveTechnique();
UI.setActiveMonster();
popup.remove();
@@ -707,14 +858,50 @@ function slugToName (slug) {
},
};
+ const Game = {
+ applyEffect (monster) {
+ if (!monster.statusEffect) {
+ return;
+ }
+
+ if (monster.statusEffect.slug === 'poison' || monster.statusEffect.slug === 'burn') {
+ const statusEffectDamage = Math.floor(monster.status.hp / 8);
+ UI.createDamage(clickEvent, statusEffectDamage);
+ monster.hp -= statusEffectDamage;
+ }
+
+ if (monster.statusEffect.slug === 'recover') {
+ const statusEffectHeal = Math.floor(monster.status.hp / 16);
+ monster.hp += statusEffectHeal;
+ }
+
+ if (monster.statusEffect.effects.includes('statchange')) {
+ monster.resetStatus();
+
+ for (const statusType in monster.statusEffect.status) {
+ const statusChange = monster.statusEffect.status[statusType];
+
+ monster.status[statusType] = Math.floor(eval(`${monster.status[statusType]} ${statusChange.operation} ${statusChange.value}`));
+ }
+ }
+ },
+ };
+
UI.setActiveMonster();
UI.setEnemyMonster();
- var clickTimeout;
+ let clickEvent;
document.querySelector('#battle__enemy').addEventListener('click', async (event) => {
+ clickEvent = event;
+
+ Game.applyEffect(state.activeMonster);
+ Game.applyEffect(state.enemy.monster);
+
const damage = simpleDamageCalculation(state.activeTechnique, state.activeMonster, state.enemy.monster);
UI.createDamage(event, damage);
+ await state.activeMonster.useTechnique(state.activeTechnique, state.enemy.monster);
+
state.enemy.monster.hp -= damage;
if (state.enemy.monster.hp <= 0) {
const faintedMonster = state.enemy.monster;
@@ -728,10 +915,14 @@ function slugToName (slug) {
if (state.activeMonster.canEvolve()) {
await fetchMonster(state.activeMonster.evolutions[0].monster_slug);
state.activeMonster.evolve();
- UI.setActiveMonster();
}
}
+
+ UI.setActiveMonster();
+ UI.setEnemyMonster();
+
UI.setHp(state.enemy.monster, battleEnemy);
+
money.textContent = state.money;
});
diff --git a/style.css b/style.css
index c0bf5f4..5c4b2f6 100644
--- a/style.css
+++ b/style.css
@@ -86,8 +86,13 @@ img {
/* align-items: center; */
}
.battle__monster-info__status {
- flex-basis: 5%;
- text-align: center;
+ /* flex-basis: 5%; */
+ flex-basis: calc(1rem + 0.25rem);
+ /* text-align: center; */
+}
+.battle__monster-info__status img {
+ width: 1rem;
+ height: 1rem;
}
.battle__monster-info-exp {
@@ -111,10 +116,10 @@ img {
.battle__monster-sprite {
margin-bottom: 0.25rem;
}
-.battle__monster-sprite img {
+.battle__monster-img {
transition-property: filter;
}
-.battle__monster-sprite img.damaged {
+.battle__monster-img.damaged {
filter: brightness(2);
}
.battle__monster-sprite__animation {
@@ -129,6 +134,8 @@ img {
display: inline-flex;
align-items: center;
+
+ cursor: pointer;
}
.battle__monster-technique__name {}
.battle__monster-technique__types {}
@@ -190,10 +197,18 @@ img {
}
+#status {
+ color: #fff;
+ background-color: #000;
+ padding: 0.25rem;
+}
+
+
#menu {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
text-align: center;
+ background-color: beige;
}
#menu > div {
cursor: pointer;