summaryrefslogtreecommitdiff
path: root/resources/js
diff options
context:
space:
mode:
Diffstat (limited to 'resources/js')
-rw-r--r--resources/js/classes/StatusEffect.js2
-rw-r--r--resources/js/classes/Technique.js20
-rw-r--r--resources/js/game.js90
-rw-r--r--resources/js/ui.js154
4 files changed, 186 insertions, 80 deletions
diff --git a/resources/js/classes/StatusEffect.js b/resources/js/classes/StatusEffect.js
index ac6ae54..e1ae9c9 100644
--- a/resources/js/classes/StatusEffect.js
+++ b/resources/js/classes/StatusEffect.js
@@ -4,6 +4,8 @@ class StatusEffect {
/**
* @type {Monster}
+ *
+ * currently only used for lifeleech
*/
issuer = null;
diff --git a/resources/js/classes/Technique.js b/resources/js/classes/Technique.js
index a24e094..7558748 100644
--- a/resources/js/classes/Technique.js
+++ b/resources/js/classes/Technique.js
@@ -3,10 +3,14 @@ class Technique {
#potency = 0;
#power = 0;
+ turnLastUse = 0;
+
constructor (slug) {
this.slug = slug;
this.resetStats();
+
+ this.turnLastUse = -this.rechargeLength;
}
get name () {
@@ -36,6 +40,22 @@ class Technique {
return DB.techniques[this.slug].effects;
}
+ get rechargeLength () {
+ return DB.techniques[this.slug].recharge;
+ }
+
+ isUsable () {
+ if (this.turnLastUse >= Game.turn) {
+ return true;
+ }
+
+ return Game.turn - this.turnLastUse >= this.rechargeLength;
+ }
+
+ use () {
+ this.turnLastUse = Game.turn;
+ }
+
get accuracy () {
return this.#accuracy;
}
diff --git a/resources/js/game.js b/resources/js/game.js
index 1f1f92b..140f9d5 100644
--- a/resources/js/game.js
+++ b/resources/js/game.js
@@ -9,9 +9,12 @@ const Game = {
},
didTechniqueHit: false,
+ turn: 0,
async progressTurn () {
+ Game.turn++;
+
await Game.applyStatusEffect(state.enemy.monster);
await Game.applyStatusEffect(state.activeMonster);
@@ -28,11 +31,30 @@ const Game = {
}
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;
+ UI.elements.money.textContent = `${state.money} €`;
},
/**
@@ -41,15 +63,13 @@ const Game = {
* @param {Monster} target
*/
async useTechnique (technique, user, target) {
+ technique.use();
+
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
@@ -62,15 +82,24 @@ const Game = {
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') {
- state.money += Math.floor(Math.random() * target.level);
+ 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.drawDamage(UI.createDamage('!!ENHANCE!!'));
+ UI.drawTechniqueAnimation();
}
// status effect
@@ -81,7 +110,10 @@ const Game = {
const statusEffect_effect = techniqueEffect.split(',')[0].split(' ')[1].split('_')[1];
const statusEffect = await fetchStatusEffect(statusEffect_effect);
- statusEffect.issuer = user;
+
+ if (statusEffect.slug === 'lifeleech') {
+ statusEffect.issuer = user;
+ }
let recipient;
if (statusEffect_recipient === 'user') {
@@ -108,6 +140,11 @@ const Game = {
}
},
+ rechargeTechnique () {
+ const feedbackNode = UI.createActionFeedback('recharge');
+ UI.drawActionFeedback(feedbackNode);
+ },
+
/**
* @param {Monster} monster
*/
@@ -165,9 +202,9 @@ const Game = {
Game.phases.postAction.push(() => {
monster.hp += statusEffectHeal;
- const damageNode = UI.createDamage(statusEffectHeal);
- UI.applyStatusEffectToDamage(damageNode, monster.statusEffect);
- UI.drawDamage(damageNode);
+ const feedbackNode = UI.createActionFeedback(statusEffectHeal);
+ UI.applyStatusEffectToDamage(feedbackNode, monster.statusEffect);
+ UI.drawActionFeedback(feedbackNode);
});
}
@@ -255,28 +292,11 @@ const Game = {
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();
- }
- });
+ if (state.activeTechnique.isUsable()) {
+ await Game.useTechnique(state.activeTechnique, state.activeMonster, state.enemy.monster);
+ } else {
+ Game.rechargeTechnique();
+ }
Game.progressTurn();
},
@@ -327,6 +347,10 @@ const Game = {
monsterData.level = monsterState.level;
monsterData.hp = monsterState.hp;
+ if (monsterData.statusEffect && monsterData.statusEffect.slug === 'lifeleech') {
+ monsterData.statusEffect = null;
+ }
+
return monsterData;
};
diff --git a/resources/js/ui.js b/resources/js/ui.js
index f6a36b8..3739ce6 100644
--- a/resources/js/ui.js
+++ b/resources/js/ui.js
@@ -6,16 +6,20 @@ const Template = {
battleExpBar: document.querySelector('#tpl___battle__exp-bar'),
battleDamage: document.querySelector('#tpl___battle__damage'),
- movesetList: document.querySelector('#tpl___moveset__list'),
- movesetItem: document.querySelector('#tpl___moveset__item'),
-
techniques: document.querySelector('#tpl___techniques'),
technique: document.querySelector('#tpl___technique'),
party: document.querySelector('#tpl___party'),
partyMonster: document.querySelector('#tpl___party__monster'),
+ monsterStats: document.querySelector('#tpl___monster-stats'),
+
+ movesetList: document.querySelector('#tpl___moveset__list'),
+ movesetItem: document.querySelector('#tpl___moveset__item'),
+
menuJournal: document.querySelector('#tpl___menu__journal'),
+ dialogSave: document.querySelector('#tpl___dialog__save'),
+ dialogLoad: document.querySelector('#tpl___dialog__load'),
};
const UI = {
@@ -92,11 +96,6 @@ const UI = {
/* Battle */
- /**
- * @type {MouseEvent}
- */
- battleClickEvent: null,
-
techniqueAnimationIsRunning: false,
techniqueAnimationNumber: 0,
techniqueAnimationFps: 20,
@@ -150,6 +149,21 @@ const UI = {
},
/**
+ * @param {string} gender
+ *
+ * @returns {HTMLElement}
+ */
+ createGenderIcon (gender) {
+ const icon = document.createElement('span');
+ icon.textContent = gender === 'male' ? '♂' : gender === 'female' ? '♀' : '⚲';
+ icon.title = slugToName(gender);
+
+ icon.classList.add('gender-icon');
+
+ return icon;
+ },
+
+ /**
* @param {string} type
*
* @returns {HTMLElement}
@@ -186,7 +200,7 @@ const UI = {
const template = UI.createTemplate(Template.battleMonster);
template.querySelector('[data-template-slot="name"]').textContent = monster.name;
- template.querySelector('[data-template-slot="gender"]').textContent = monster.gender === 'male' ? '♂' : monster.gender === 'female' ? '♀' : '⚲';
+ template.querySelector('[data-template-slot="gender"]').innerHTML = UI.createGenderIcon(monster.gender).outerHTML;
template.querySelector('[data-template-slot="level"]').textContent = monster.level;
template.querySelector('[data-template-slot="statusEffect"]').innerHTML = UI.createStatusEffectIcon(monster.statusEffect).outerHTML;
template.querySelector('[data-template-slot="sprite"]').src = `/modules/tuxemon/mods/tuxemon/gfx/sprites/battle/${monster.slug}-front.png`;
@@ -239,10 +253,15 @@ const UI = {
const techniqueNode = UI.createTemplate(Template.technique);
techniqueNode.querySelector('[data-template-slot="name"]').textContent = technique.name;
+ techniqueNode.querySelector('[data-template-slot="recharge"]').textContent = technique.rechargeLength;
techniqueNode.querySelector('[data-template-slot="types"]').innerHTML = technique.types.map((type) => UI.createElementTypeIcon(type).outerHTML).join('');
+ techniqueNode.querySelector('[data-template-slot="range"]').textContent = technique.range;
techniqueNode.querySelector('[data-template-slot="power"]').textContent = technique.power;
techniqueNode.querySelector('[data-template-slot="accuracy"]').textContent = technique.accuracy;
- techniqueNode.querySelector('[data-template-slot="range"]').textContent = technique.range;
+
+ if (!technique.isUsable()) {
+ techniqueNode.setAttribute('disabled', true);
+ }
template.appendChild(techniqueNode);
}
@@ -295,13 +314,16 @@ const UI = {
* @returns {void}
*/
drawTechniqueAnimation () {
+ const x = UI.battleClickEvent.clientX;
+ const y = UI.battleClickEvent.clientY;
+
if (!UI.techniqueAnimationIsRunning && state.activeTechnique.animation && DB.allAnimations[state.activeTechnique.animation]) {
UI.techniqueAnimationIsRunning = true;
const techniqueAnimationLoop = () => {
UI.elements.battleEnemyAnimation.src = `/modules/tuxemon/mods/tuxemon/animations/technique/${state.activeTechnique.animation}_${("00" + UI.techniqueAnimationNumber).slice(-2)}.png`;
- UI.elements.battleEnemyAnimation.style.top = UI.battleClickEvent.clientY - (UI.elements.battleEnemyAnimation.clientHeight / 2);
- UI.elements.battleEnemyAnimation.style.left = UI.battleClickEvent.clientX - (UI.elements.battleEnemyAnimation.clientWidth / 2);
+ UI.elements.battleEnemyAnimation.style.top = y - (UI.elements.battleEnemyAnimation.clientHeight / 2) + 'px';
+ UI.elements.battleEnemyAnimation.style.left = x - (UI.elements.battleEnemyAnimation.clientWidth / 2) + 'px';
// console.log(UI.elements.battleEnemyAnimation.src);
UI.techniqueAnimationNumber++;
@@ -323,32 +345,46 @@ const UI = {
/* Battle - Damage */
+ /**
+ * @type {MouseEvent}
+ */
+ battleClickEvent: null,
+
damageHighlightClickDuration: 0.1,
damageHighlightClickTimeout: null,
/**
- * @param {number|string} damage
+ * @param {any} feedback
*
* @returns {HTMLElement}
*/
- createDamage (damage) {
- const damageNode = UI.createTemplate(Template.battleDamage);
- damageNode.innerHTML = damage;
+ createActionFeedback (feedback) {
+ const feedbackNode = UI.createTemplate(Template.battleDamage);
+ feedbackNode.innerHTML = feedback;
- damageNode.style.top = UI.battleClickEvent.pageY - UI.elements.battleEnemy.offsetTop + (Math.random() * 40 - 20);
- damageNode.style.left = UI.battleClickEvent.pageX - UI.elements.battleEnemy.offsetLeft + (Math.random() * 40 - 20);
+ feedbackNode.style.top = UI.battleClickEvent.pageY - UI.elements.battleEnemy.offsetTop + (Math.random() * 40 - 20) + 'px';
+ feedbackNode.style.left = UI.battleClickEvent.pageX - UI.elements.battleEnemy.offsetLeft + (Math.random() * 40 - 20) + 'px';
- damageNode.dataset.duration = 2;
- damageNode.style.animationDuration = `${damageNode.dataset.duration}s`;
+ feedbackNode.dataset.duration = 2;
+ feedbackNode.style.animationDuration = `${feedbackNode.dataset.duration}s`;
- return damageNode;
+ return feedbackNode;
+ },
+
+ /**
+ * @param {number|string} damage
+ *
+ * @returns {HTMLElement}
+ */
+ createDamage (damage) {
+ return UI.createActionFeedback(damage);
},
/**
* @returns {HTMLElement}
*/
createDamageMiss () {
- return UI.createDamage('MISS!');
+ return UI.createActionFeedback('MISS!');
},
/**
@@ -392,23 +428,27 @@ const UI = {
/**
* @param {HTMLElement} damageNode
*/
+ drawActionFeedback (node) {
+ UI.elements.battleEnemy.appendChild(node);
+ setTimeout(() => node.remove(), (node.dataset.duration * 1000) - 500);
+ },
+
+ /**
+ * @param {HTMLElement} damageNode
+ */
drawDamage (damageNode) {
- UI.elements.battleEnemy.appendChild(damageNode);
- setTimeout(() => damageNode.remove(), (damageNode.dataset.duration * 1000) - 500);
+ UI.drawActionFeedback(damageNode);
UI.elements.battleEnemySprite.classList.add('damaged');
clearTimeout(UI.damageHighlightClickTimeout);
UI.damageHighlightClickTimeout = setTimeout(() => UI.elements.battleEnemySprite.classList.remove('damaged'), UI.damageHighlightClickDuration * 1000);
-
- UI.drawTechniqueAnimation();
},
/**
* @param {HTMLElement} damageNode
*/
drawDamageMiss (damageNode) {
- UI.elements.battleEnemy.appendChild(damageNode);
- setTimeout(() => damageNode.remove(), (damageNode.dataset.duration * 1000) - 500);
+ UI.drawActionFeedback(damageNode);
},
@@ -428,6 +468,10 @@ const UI = {
const partyMonster = UI.createTemplate(Template.partyMonster);
partyMonster.querySelector('[data-template-slot="sprite"]').src = `/modules/tuxemon/mods/tuxemon/gfx/sprites/battle/${monster.slug}-front.png`;
+ partyMonster.querySelector('[data-template-slot="name"]').textContent = monster.name;
+ partyMonster.querySelector('[data-template-slot="gender"]').innerHTML = UI.createGenderIcon(monster.gender).outerHTML;
+ partyMonster.querySelector('[data-template-slot="level"]').textContent = monster.level;
+ partyMonster.querySelector('[data-template-slot="hpText"]').textContent = `${monster.hp} / ${monster.stats.hp}`;
partyMonster.addEventListener('click', async (event) => {
// bubble up to partyNode
@@ -495,7 +539,7 @@ const UI = {
UI.drawPopup(popup);
},
- openJournalMenu () { // TODO
+ openJournalMenu () {
const popup = UI.createPopup();
const journal = UI.createTemplate(Template.menuJournal);
@@ -511,28 +555,32 @@ const UI = {
UI.drawPopup(popup);
},
- openSaveDialog () { // TODO
+ openSaveDialog () {
const popup = UI.createPopup();
+ const dialog = UI.createTemplate(Template.dialogSave);
- const textarea = document.createElement('textarea');
- textarea.value = Game.save();
+ dialog.querySelector('[data-template-slot="saveData"]').value = Game.save();
+ dialog.querySelector('[data-template-slot="saveClipboard"]').addEventListener('click', () => {
+ alert('Saved to clipboard!');
+ });
- popup.querySelector('.popup').appendChild(textarea);
+ popup.querySelector('.popup').appendChild(dialog);
UI.drawPopup(popup);
},
- openLoadDialog () { // TODO
+ openLoadDialog () {
const popup = UI.createPopup();
+ const dialog = UI.createTemplate(Template.dialogLoad);
- const textarea = document.createElement('textarea');
-
- const loadButton = document.createElement('button');
- loadButton.textContent = "Load";
- loadButton.addEventListener('click', () => Game.load(textarea.value.trim()));
+ dialog.querySelector('[data-template-slot="load"]').addEventListener('click', () => {
+ Game.load(
+ dialog.querySelector('[data-template-slot="saveData"]').value.trim()
+ );
- popup.querySelector('.popup').appendChild(textarea);
- popup.querySelector('.popup').appendChild(loadButton);
+ document.querySelectorAll('.popup__overlay').forEach((element) => element.remove())
+ });
+ popup.querySelector('.popup').appendChild(dialog);
UI.drawPopup(popup);
},
@@ -545,12 +593,20 @@ const UI = {
* @returns {HTMLElement}
*/
createStatsMenu (monster) { // TODO
- const template = document.createElement('div');
- template.textContent = "select moves for " + monster.name;
- template.style.width = '90vw';
- template.style.height = '90vh';
+ const template = UI.createTemplate(Template.monsterStats);
- template.addEventListener('click', () => UI.openMovesetSelection(monster));
+ template.querySelector('[data-template-slot="name"]').textContent = monster.name;
+ template.querySelector('[data-template-slot="gender"]').innerHTML = UI.createGenderIcon(monster.gender).outerHTML;
+ template.querySelector('[data-template-slot="level"]').textContent = monster.level;
+ template.querySelector('[data-template-slot="types"]').innerHTML = monster.types.map((type) => UI.createElementTypeIcon(type).outerHTML).join('');
+
+ template.querySelector('[data-template-slot="stats.melee"]').textContent = monster.stats.melee;
+ template.querySelector('[data-template-slot="stats.armour"]').textContent = monster.stats.armour;
+ template.querySelector('[data-template-slot="stats.ranged"]').textContent = monster.stats.ranged;
+ template.querySelector('[data-template-slot="stats.dodge"]').textContent = monster.stats.dodge;
+ template.querySelector('[data-template-slot="stats.speed"]').textContent = monster.stats.speed;
+
+ template.querySelector('[data-template-slot="techniques"]').addEventListener('click', () => UI.openMovesetSelection(monster));
return template;
},
@@ -566,7 +622,7 @@ const UI = {
const technique = await fetchTechnique(move.technique);
const movesetItemNode = UI.createTemplate(Template.movesetItem);
- movesetItemNode.querySelector('[data-template-slot="name"]').textContent = slugToName(technique.slug);
+ movesetItemNode.querySelector('[data-template-slot="name"]').textContent = technique.name;
movesetItemNode.querySelector('[data-template-slot="types"]').innerHTML = technique.types.map((type) => UI.createElementTypeIcon(type).outerHTML).join('');
movesetItemNode.querySelector('[data-template-slot="power"]').textContent = technique.power;
movesetItemNode.querySelector('[data-template-slot="level"]').textContent = move.level_learned;
@@ -587,6 +643,10 @@ const UI = {
return false;
}
+ if (monster.activeTechniques.length === 4 && !movesetItemNode.hasAttribute('selected')) {
+ return;
+ }
+
// un/select
movesetItemNode.toggleAttribute('selected');