summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Weipert <code@drogueronin.de>2023-08-20 20:48:29 +0200
committerDaniel Weipert <code@drogueronin.de>2023-08-20 20:48:29 +0200
commit43a28ad77190f2e55e2e6ba65a9a7b5b1f5dea6c (patch)
tree759b4f27a68e729df313b9f8370117d4a6fbae37
parentb227fc9c2ef361f8aa5ee2f24aa1ade6bc972a73 (diff)
area progression, item usage
-rw-r--r--db/_generated/areas/route1.json2
-rw-r--r--db/areas/route1.json1
-rw-r--r--index.html19
-rw-r--r--resources/css/battle.css34
-rw-r--r--resources/css/menu.css139
-rw-r--r--resources/js/classes/InventoryItem.js3
-rw-r--r--resources/js/classes/Monster.js4
-rw-r--r--resources/js/classes/State.js5
-rw-r--r--resources/js/classes/Trainer.js36
-rw-r--r--resources/js/game.js131
-rw-r--r--resources/js/main.js15
-rw-r--r--resources/js/ui.js329
12 files changed, 552 insertions, 166 deletions
diff --git a/db/_generated/areas/route1.json b/db/_generated/areas/route1.json
index 11eaf46..3956a3c 100644
--- a/db/_generated/areas/route1.json
+++ b/db/_generated/areas/route1.json
@@ -1 +1 @@
-{"encounter_slug":"route1","encounters":[{"monster":"pairagrin","encounter_rate":3.5,"daytime":true,"exp_req_mod":1,"level_range":[2,4]},{"monster":"aardorn","encounter_rate":3.5,"daytime":true,"exp_req_mod":1,"level_range":[2,4]},{"monster":"cataspike","encounter_rate":3.5,"daytime":true,"exp_req_mod":1,"level_range":[2,4]},{"monster":"pairagrin","encounter_rate":3.5,"daytime":false,"exp_req_mod":1,"level_range":[3,5]},{"monster":"aardorn","encounter_rate":3.5,"daytime":false,"exp_req_mod":1,"level_range":[3,5]},{"monster":"cataspike","encounter_rate":3.5,"daytime":false,"exp_req_mod":1,"level_range":[3,5]}],"requiredEncounters":10,"trainers":[{"name":"Bruder Mikki","monsters":[{"slug":"memnomnom","level":5},{"slug":"jelillow","level":5}],"inventory":[{"slug":"potion","quantity":2}]}],"environment_slug":"forest","previousArea":"paper-town","nextArea":"route2","environment":{"slug":"forest","battle_graphics":{"island_back":"woodland_island.png","island_front":"woodland_island.png","background":"forest_background.png"},"battle_music":"music_battle_loop"}} \ No newline at end of file
+{"encounter_slug":"route1","encounters":[{"monster":"pairagrin","encounter_rate":3.5,"daytime":true,"exp_req_mod":1,"level_range":[2,4]},{"monster":"aardorn","encounter_rate":3.5,"daytime":true,"exp_req_mod":1,"level_range":[2,4]},{"monster":"cataspike","encounter_rate":3.5,"daytime":true,"exp_req_mod":1,"level_range":[2,4]},{"monster":"pairagrin","encounter_rate":3.5,"daytime":false,"exp_req_mod":1,"level_range":[3,5]},{"monster":"aardorn","encounter_rate":3.5,"daytime":false,"exp_req_mod":1,"level_range":[3,5]},{"monster":"cataspike","encounter_rate":3.5,"daytime":false,"exp_req_mod":1,"level_range":[3,5]}],"requiredEncounters":10,"trainers":[{"name":"Bruder Mikki","sprite":"dragonrider.png","monsters":[{"slug":"memnomnom","level":5},{"slug":"jelillow","level":5}],"inventory":[{"slug":"potion","quantity":2}]}],"environment_slug":"forest","previousArea":"paper-town","nextArea":"route2","environment":{"slug":"forest","battle_graphics":{"island_back":"woodland_island.png","island_front":"woodland_island.png","background":"forest_background.png"},"battle_music":"music_battle_loop"}} \ No newline at end of file
diff --git a/db/areas/route1.json b/db/areas/route1.json
index 54cc345..3d1cf23 100644
--- a/db/areas/route1.json
+++ b/db/areas/route1.json
@@ -8,6 +8,7 @@
"trainers": [
{
"name": "Bruder Mikki",
+ "sprite": "dragonrider.png",
"monsters": [
{
"slug": "memnomnom",
diff --git a/index.html b/index.html
index ae14db0..4ae9189 100644
--- a/index.html
+++ b/index.html
@@ -14,7 +14,8 @@
<div class="wrap">
<div id="battle">
<div id="battle__opponent">
- <img class="battle__monster-sprite__animation" src="" draggable="false" />
+ <img class="battle__technique-animation" src="" draggable="false" />
+ <img class="battle__opponent__trainer-sprite" src="" draggable="false" />
</div>
<div id="battle__player"></div>
@@ -25,10 +26,10 @@
<div id="status">
<div>
Money: <span data-template-slot="money"></span>
- Monster Progress: <span data-template-slot="monster-progress"></span>
- Trainer Progress: <span data-template-slot="trainer-progress"></span>
- <button data-template-slot="next-trainer">&rArr; Next Trainer</button>
- <button data-template-slot="next-area">&rArr; Next Area</button>
+ 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>
</div>
@@ -226,7 +227,13 @@
</template>
<template id="tpl___inventory">
- <div class="inventory"></div>
+ <div class="inventory">
+ <div data-template-slot="items"></div>
+ <div data-template-slot="modes" class="inventory__selection-modes">
+ <button data-template-slot="modeUse" data-selection-mode="use" class="menu-button">Use</button>
+ <button data-template-slot="modeInfo" data-selection-mode="info" class="menu-button">Info</button>
+ </div>
+ </div>
</template>
<template id="tpl___inventory__item">
diff --git a/resources/css/battle.css b/resources/css/battle.css
index 2c09e5a..0052072 100644
--- a/resources/css/battle.css
+++ b/resources/css/battle.css
@@ -78,15 +78,25 @@
.battle__monster-img {
cursor: pointer;
transition-property: filter;
+
+ width: 90px;
}
.battle__monster-img.damaged {
filter: brightness(2);
}
-.battle__monster-sprite__animation {
+
+.battle__technique-animation {
position: fixed;
z-index: 10;
}
+.battle__opponent__trainer-sprite {
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 120px;
+}
+
.battle__monster-technique {
background-color: beige;
border: 2px solid #000;
@@ -97,18 +107,27 @@
cursor: pointer;
}
-.battle__monster-technique__name {}
-.battle__monster-technique__types {}
-.battle__monster-technique__power {}
-.battle__monster--player {
- flex-direction: row-reverse;
+
+
+
+.battle__monster--opponent {
+ align-items: flex-start;
}
+
.battle__monster--opponent .battle__monster-info-exp,
.battle__monster--opponent .battle__monster-technique {
display: none;
}
+.battle__monster--player {
+ flex-direction: row-reverse;
+ align-items: flex-end;
+}
+
+
+
+
.exp-bar {
width: 100%;
}
@@ -124,6 +143,9 @@
width: 0%;
}
+
+
+
.hp-bar {
flex-grow: 1;
}
diff --git a/resources/css/menu.css b/resources/css/menu.css
index 38f4189..16a9d02 100644
--- a/resources/css/menu.css
+++ b/resources/css/menu.css
@@ -25,6 +25,8 @@
}
+
+
.menu-button {
font-size: 1.5rem;
border: 1px solid #000;
@@ -44,6 +46,8 @@
}
+
+
.tabs {
display: grid;
}
@@ -81,13 +85,6 @@
-.gender-icon {
- line-height: 1em;
-}
-
-
-
-
#status {
color: #fff;
background-color: #000;
@@ -96,6 +93,7 @@
+
#menu {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -117,7 +115,64 @@
-.inventory {}
+
+.party__monsters {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+}
+
+.party__monster {
+ cursor: pointer;
+ padding: 0.75rem;
+ text-align: center;
+ font-size: 0.75rem;
+}
+.party__monster:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.party__selection-modes {
+ display: flex;
+ justify-content: space-between;
+ padding: 1rem;
+}
+
+.party__selection-modes button[selected] {
+ border-color: var(--color-success);
+ color: var(--color-success);
+}
+
+
+
+
+.monster-stats {
+ padding: 0.5rem;
+}
+
+.monster-stats__identity {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.monster-stats table {
+ width: 100%;
+}
+
+.monster-stats table th {
+ text-align: left;
+}
+
+.monster-stats table td {
+ text-align: right;
+}
+
+.gender-icon {
+ line-height: 1em;
+}
+
+
+
.inventory__popup {
align-items: start;
@@ -140,6 +195,22 @@
text-align: center;
}
+.inventory__selection-modes {
+ display: flex;
+ padding: 1rem;
+}
+
+.inventory__selection-modes button[selected] {
+ border-color: var(--color-success);
+ color: var(--color-success);
+}
+
+.inventory__monster-selection {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+}
+
+
.menu__journal {
@@ -192,55 +263,3 @@
font-size: 2rem;
width: 80vw;
}
-
-
-
-.party__monsters {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
-}
-
-.party__monster {
- cursor: pointer;
- padding: 0.75rem;
- text-align: center;
- font-size: 0.75rem;
-}
-.party__monster:hover {
- background-color: rgba(0, 0, 0, 0.1);
-}
-
-.party__selection-modes {
- display: flex;
- justify-content: space-between;
- padding: 1rem;
-}
-
-.party__selection-modes button[selected] {
- border-color: var(--color-success);
- color: var(--color-success);
-}
-
-
-
-.monster-stats {
- padding: 0.5rem;
-}
-
-.monster-stats__identity {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.monster-stats table {
- width: 100%;
-}
-
-.monster-stats table th {
- text-align: left;
-}
-
-.monster-stats table td {
- text-align: right;
-}
diff --git a/resources/js/classes/InventoryItem.js b/resources/js/classes/InventoryItem.js
index d6eb311..9db42ff 100644
--- a/resources/js/classes/InventoryItem.js
+++ b/resources/js/classes/InventoryItem.js
@@ -12,8 +12,9 @@ class InventoryItem {
/**
* @param {Item} item
*/
- constructor (item) {
+ constructor (item, quantity = 1) {
this.item = item;
+ this.quantity = quantity;
}
/* Item */
diff --git a/resources/js/classes/Monster.js b/resources/js/classes/Monster.js
index 5c930f8..69bb654 100644
--- a/resources/js/classes/Monster.js
+++ b/resources/js/classes/Monster.js
@@ -66,6 +66,10 @@ class Monster {
return DB.monsters[this.slug].types;
}
+ get category () {
+ return DB.monsters[this.slug].category;
+ }
+
get moveset () {
return DB.monsters[this.slug].moveset;
}
diff --git a/resources/js/classes/State.js b/resources/js/classes/State.js
index 5d3cfff..6180c52 100644
--- a/resources/js/classes/State.js
+++ b/resources/js/classes/State.js
@@ -48,4 +48,9 @@ class State {
* @type {Technique}
*/
activeTechnique = null;
+
+ /**
+ * @type {InventoryItem}
+ */
+ activeBall = null;
};
diff --git a/resources/js/classes/Trainer.js b/resources/js/classes/Trainer.js
index f2c2df9..d63e52d 100644
--- a/resources/js/classes/Trainer.js
+++ b/resources/js/classes/Trainer.js
@@ -7,6 +7,9 @@ class Trainer {
*/
type = 'trainer';
+ name = '';
+ sprite = '';
+
/**
* @type {Monster[]}
*/
@@ -22,28 +25,41 @@ class Trainer {
*/
activeMonster = [];
- constructor (monsters, inventory = []) {
- this.#monsters = monsters;
- this.#inventory = inventory;
+ /**
+ * @param {(Monster[]|Object[])} monsters
+ * @param {(InventoryItem[]|Object[])} inventory
+ */
+ constructor (trainerData) {
+ this.#monsters = trainerData.monsters;
+ this.#inventory = trainerData.inventory || [];
+ this.name = trainerData.name;
+ this.sprite = trainerData.sprite;
}
async initialize () {
for (const monsterData of this.#monsters) {
- const monster = await fetchMonster(monsterData.slug);
+ if (monsterData instanceof Monster) {
+ this.monsters.push(monsterData);
+ } else {
+ const monster = await fetchMonster(monsterData.slug);
- monster.level = monsterData.level || monster.level;
+ monster.level = monsterData.level || monster.level;
- this.monsters.push(monster);
+ this.monsters.push(monster);
+ }
}
this.activeMonster = this.monsters[0];
for (const itemData of this.#inventory) {
- const item = new InventoryItem(await fetchItem(itemData.slug));
-
- item.amount = itemData.amount || 1;
+ if (itemData instanceof InventoryItem) {
+ this.inventory.push(itemData);
+ } else {
+ const item = new InventoryItem(await fetchItem(itemData.slug));
- this.inventory.push(item);
+ item.quantity = itemData.quantity || 1;
+ this.inventory.push(item);
+ }
}
}
}
diff --git a/resources/js/game.js b/resources/js/game.js
index 1a91f06..b233ef2 100644
--- a/resources/js/game.js
+++ b/resources/js/game.js
@@ -5,6 +5,7 @@ const Game = {
postAction: [],
},
+ isLoadingArea: false,
isInBattle: false,
didTechniqueHit: false,
@@ -49,7 +50,12 @@ const Game = {
await Game.encounterWildMonster();
} else if (Memory.state.opponent.type === 'trainer') {
if (Memory.state.opponent.activeMonster === Memory.state.opponent.monsters[Memory.state.opponent.monsters.length - 1]) {
- await Game.progressToArea(await fetchArea(Memory.state.currentArea.nextArea));
+ Memory.state.currentArea.trainerProgress++;
+ if (Memory.state.currentArea.encounters.length > 0) {
+ await Game.encounterWildMonster();
+ } else {
+ await Game.progressToNextArea();
+ }
} else {
await Game.encounterNextTrainerMonster();
}
@@ -312,12 +318,16 @@ const Game = {
* @param {MouseEvent} event
*/
async battleClick (event) {
+ if (Game.isLoadingArea) {
+ return;
+ }
+
Game.isInBattle = true;
UI.battleClickEvent = event;
await Game.tryUseTechnique(Memory.state.activeTechnique, Memory.state.player.activeMonster, Memory.state.opponent.activeMonster);
- Game.progressTurn();
+ await Game.progressTurn();
},
/**
@@ -349,24 +359,94 @@ const Game = {
},
/**
+ * @param {InventoryItem} item
+ * @param {Monster} monster
+ *
+ * @returns {boolean}
+ */
+ canUseItem (item, monster = null) {
+ let isApplicable = true;
+
+ for (const itemConditionCode of item.conditions) {
+ const itemCondition = new ItemCondition(itemConditionCode);
+ let conditionIsApplicable = true;
+
+ if (itemCondition.what === 'current_hp') {
+ const value = parseInt(itemCondition.value) * monster.stats.hp;
+ conditionIsApplicable = eval(`${monster.hp} ${itemCondition.comparator} ${value}`);
+ }
+
+ else if (itemCondition.what === 'wild_monster') {
+ conditionIsApplicable = Game.isBattleType('monster');
+ }
+
+ else if (itemCondition.what === 'threat') {
+ conditionIsApplicable = Memory.state.opponent.activeMonster.category === 'threat';
+ }
+
+ else {
+ conditionIsApplicable = false;
+ }
+
+ if (itemCondition.is === 'not') {
+ conditionIsApplicable = !conditionIsApplicable;
+ }
+
+ isApplicable = isApplicable && conditionIsApplicable;
+ }
+
+ return isApplicable;
+ },
+
+ /**
* @param {InventoryItem}
+ * @param {Monster}
*/
- async useItem (item) {
+ async useItem (item, monster) {
for (const itemEffectCode of item.effects) {
const itemEffect = new ItemEffect(itemEffectCode);
- if (itemEffect.type === 'evolve') {
+ if (itemEffect.type === 'heal') {
+ monster.hp += itemEffect.amount;
+ item.quantity--;
+ }
+
+ else if (itemEffect.type === 'capture') {
+ Memory.state.activeBall = item;
+ UI.drawActiveBall();
+ }
+
+ else if (itemEffect.type === 'evolve') {
const evolution = Memory.state.player.activeMonster.getPossibleEvolutions('item')[0];
if (evolution) {
await fetchMonster(evolution.monster_slug);
Memory.state.player.activeMonster.evolve(evolution);
UI.drawActiveMonster();
+
+ item.quantity--;
}
}
}
},
/**
+ * @param {Array} inventory
+ * @param {InventoryItem} item
+ */
+ removeItemFromInventory (inventory, item) {
+ inventory.splice(inventory.indexOf(item), 1);
+ },
+
+ /**
+ * @param {string} type
+ *
+ * @returns {boolean}
+ */
+ isBattleType (type) {
+ return Memory.state.opponent.type === type;
+ },
+
+ /**
* @param {Monster} monster
*/
async evolveMonster (monster) {
@@ -374,14 +454,29 @@ const Game = {
monster.evolve(monster.evolutions[0]);
},
+ /**
+ * @returns {boolean}
+ */
+ canCatchMonster () {
+ return Game.isBattleType('monster') && Memory.state.activeBall;
+ },
+
async catchMonster () {
- if (Memory.state.opponent.type === 'trainer') {
+ if (!Game.canCatchMonster()) {
return;
}
+ Memory.state.activeBall.quantity--;
+ if (Memory.state.activeBall.quantity === 0) {
+ Game.removeItemFromInventory(Memory.state.player.inventory, Memory.state.activeBall);
+ Memory.state.activeBall = null;
+ UI.drawActiveBall();
+ }
+
const caughtMonster = new Monster(Memory.state.opponent.activeMonster.slug);
caughtMonster.initialize();
caughtMonster.level = Memory.state.opponent.activeMonster.level;
+ caughtMonster.hp = Memory.state.opponent.activeMonster.hp;
Memory.state.player.monsters.push(caughtMonster);
@@ -401,17 +496,29 @@ const Game = {
/**
* @param {Area} area
*/
- async progressToArea (area) {
+ async jumpToArea (area) {
Memory.state.currentArea = area;
UI.drawArea(area);
- UI.drawStatus();
+ },
+
+ async progressToNextArea () {
+ Game.isLoadingArea = true;
- if (area.encounters.length > 0) {
+ const currentArea = Memory.state.currentArea;
+ const nextArea = await fetchArea(currentArea.nextArea);
+
+ await Game.jumpToArea(nextArea);
+
+ if (nextArea.encounters.length > 0) {
await Game.encounterWildMonster();
} else {
await Game.encounterTrainer();
}
+
+ UI.drawStatus();
+
+ Game.isLoadingArea = false;
},
async encounterWildMonster () {
@@ -421,7 +528,7 @@ const Game = {
const monster = await fetchMonster(randomMonster.monster);
monster.level = randomLevel;
- const wildMonster = new Trainer([monster]);
+ const wildMonster = new Trainer({ monsters: [monster] });
wildMonster.type = 'monster';
await wildMonster.initialize();
Memory.state.opponent = wildMonster;
@@ -432,7 +539,7 @@ const Game = {
async encounterTrainer () {
const nextTrainer = Memory.state.currentArea.trainers[Memory.state.currentArea.trainerProgress];
- const trainer = new Trainer(nextTrainer.monsters, nextTrainer.inventory);
+ const trainer = new Trainer(nextTrainer);
if (nextTrainer.name === 'Rival') {
trainer.monsters.push(Memory.state.rivalMonster);
}
@@ -440,6 +547,7 @@ const Game = {
Memory.state.opponent = trainer;
UI.drawOpponentMonster();
+ UI.drawStatus();
},
async encounterNextTrainerMonster () {
@@ -451,7 +559,8 @@ const Game = {
};
// Game click bindings
-UI.elements.status.querySelector('[data-template-slot="next-trainer"]').addEventListener('click', Game.encounterTrainer);
+UI.elements.nextTrainer.addEventListener('click', Game.encounterTrainer);
+UI.elements.nextArea.addEventListener('click', Game.progressToNextArea);
UI.elements.battleOpponent.addEventListener('click', Game.battleClick);
UI.elements.techniques.addEventListener('click', Game.techniqueClick);
UI.elements.menuCatch.addEventListener('click', Game.catchMonster);
diff --git a/resources/js/main.js b/resources/js/main.js
index 815d734..8622731 100644
--- a/resources/js/main.js
+++ b/resources/js/main.js
@@ -4,23 +4,26 @@
// Start Game
const possibleStarterMonsters = ['budaye', 'dollfin', 'grintot', 'ignibus', 'memnomnom'];
- Memory.state.player = new Trainer(
- [
+ Memory.state.player = new Trainer({
+ monsters: [
await fetchMonster(possibleStarterMonsters[Math.round(Math.random() * (possibleStarterMonsters.length - 1))]),
],
- [
- new InventoryItem(await fetchItem('tuxeball')),
+ inventory: [
+ new InventoryItem(await fetchItem('tuxeball'), 5),
new InventoryItem(await fetchItem('potion')),
]
- );
+ });
await Memory.state.player.initialize();
Memory.state.activeTechnique = Memory.state.player.activeMonster.activeTechniques[0];
+ Memory.state.activeBall = Memory.state.player.inventory[0]; // tuxeball
Memory.state.rivalMonster = await fetchMonster(possibleStarterMonsters[Math.round(Math.random() * (possibleStarterMonsters.length - 1))]);
const area = await fetchArea('paper-town');
- await Game.progressToArea(area);
+ await Game.jumpToArea(area);
+ await Game.encounterTrainer();
+ UI.drawStatus();
UI.drawActiveMonster();
UI.drawActiveTechniques();
diff --git a/resources/js/ui.js b/resources/js/ui.js
index 0d6a8f9..dbec9b2 100644
--- a/resources/js/ui.js
+++ b/resources/js/ui.js
@@ -37,12 +37,15 @@ const UI = {
battle: document.querySelector('#battle'),
battleOpponent: document.querySelector('#battle__opponent'),
battleOpponentSprite: null,
- battleOpponentAnimation: document.querySelector('.battle__monster-sprite__animation'),
+ battleOpponentTrainerSprite: document.querySelector('.battle__opponent__trainer-sprite'),
+ battleOpponentAnimation: document.querySelector('.battle__technique-animation'),
battlePlayer: document.querySelector('#battle__player'),
techniques: document.querySelector('#techniques'),
status: document.querySelector('#status'),
+ nextTrainer: document.querySelector('#status [data-template-slot="nextTrainer"]'),
+ nextArea: document.querySelector('#status [data-template-slot="nextArea"]'),
menuParty: document.querySelector('#menu__party'),
menuInventory: document.querySelector('#menu__inventory'),
@@ -355,13 +358,15 @@ const UI = {
UI.elements.battleOpponentSprite = battleMonsterNode.querySelector('[data-template-slot="sprite"]');
UI.elements.battleOpponentSprite.style.transitionDuration = `${UI.damageHighlightClickDuration}s`;
- // en/disable catch
- if (Memory.state.opponent.type === 'trainer') {
- UI.elements.menuCatch.setAttribute('disabled', true);
+ if (Game.isBattleType('trainer') && Memory.state.opponent.sprite) {
+ UI.elements.battleOpponentTrainerSprite.src = `/modules/tuxemon/mods/tuxemon/gfx/sprites/player/${Memory.state.opponent.sprite}`;
} else {
- UI.elements.menuCatch.removeAttribute('disabled');
+ UI.elements.battleOpponentTrainerSprite.src = '';
}
+ // en/disable catch
+ UI.drawActiveBall();
+
const previousBattleMonsterNode = UI.elements.battleOpponent.querySelector('.battle__monster');
if (previousBattleMonsterNode) {
UI.elements.battleOpponentSprite.classList = previousBattleMonsterNode.querySelector('[data-template-slot="sprite"]').classList;
@@ -564,19 +569,36 @@ const UI = {
/* Menu */
partySelectionMode: 'select',
+ inventorySelectionMode: 'use',
drawStatus () {
- UI.elements.status.querySelector('[data-template-slot="money"]').textContent = `${Memory.state.money} €`;
- UI.elements.status.querySelector('[data-template-slot="monster-progress"]').textContent = `${Memory.state.currentArea.monsterProgress} / ${'10'}`;
- UI.elements.status.querySelector('[data-template-slot="trainer-progress"]').textContent = `${Memory.state.currentArea.trainerProgress} / ${Memory.state.currentArea.trainers.length}`;
+ const currentArea = Memory.state.currentArea;
- const nextTrainerButton = UI.elements.status.querySelector('[data-template-slot="next-trainer"]');
- if (Memory.state.currentArea.monsterProgress >= Memory.state.currentArea.requiredEncounters) {
+ UI.elements.status.querySelector('[data-template-slot="money"]').textContent = `${Memory.state.money} €`;
+ UI.elements.status.querySelector('[data-template-slot="monsterProgress"]').textContent = `${currentArea.monsterProgress} / ${currentArea.requiredEncounters}`;
+ UI.elements.status.querySelector('[data-template-slot="trainerProgress"]').textContent = `${currentArea.trainerProgress} / ${currentArea.trainers.length}`;
+
+ const nextTrainerButton = UI.elements.nextTrainer;
+ if (
+ Memory.state.opponent.type === 'monster' &&
+ currentArea.monsterProgress >= currentArea.requiredEncounters &&
+ currentArea.trainerProgress < currentArea.trainers.length
+ ) {
nextTrainerButton.disabled = false;
} else {
nextTrainerButton.disabled = true;
}
+
+ const nextAreaButton = UI.elements.nextArea;
+ if (
+ currentArea.monsterProgress >= currentArea.requiredEncounters &&
+ currentArea.trainerProgress === currentArea.trainers.length
+ ) {
+ nextAreaButton.disabled = false;
+ } else {
+ nextAreaButton.disabled = true;
+ }
},
openPartyMenu () {
@@ -586,13 +608,7 @@ const UI = {
party.id = 'party';
for (const monsterIdx in Memory.state.player.monsters) {
const monster = Memory.state.player.monsters[monsterIdx];
- 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}`;
+ const partyMonster = UI.createPartyMonster(monster);
partyMonster.addEventListener('click', async (event) => {
// bubble up to partyNode
@@ -648,9 +664,24 @@ const UI = {
UI.drawPopup(popup);
},
+ drawActiveBall () {
+ if (Game.canCatchMonster()) {
+ UI.elements.menuCatch.removeAttribute('disabled');
+ } else {
+ UI.elements.menuCatch.setAttribute('disabled', true);
+ }
+
+ if (Memory.state.activeBall) {
+ UI.elements.menuCatch.querySelector('img').src = `/modules/tuxemon/mods/tuxemon/gfx/items/${Memory.state.activeBall.slug}.png`;
+ } else {
+ UI.elements.menuCatch.querySelector('img').src = `/modules/tuxemon/mods/tuxemon/gfx/items/tuxeball.png`;
+ }
+ },
+
openInventoryMenu () {
const popup = UI.createPopup();
const inventory = UI.createTemplate(Template.inventory);
+ inventory.id = 'inventory';
const tabs = {
heal: {
@@ -680,17 +711,7 @@ const UI = {
};
for (const item of Memory.state.player.inventory) {
- const inventoryItemNode = UI.createTemplate(Template.inventoryItem);
-
- inventoryItemNode.title = item.description;
-
- inventoryItemNode.querySelector('[data-template-slot="sprite"]').src = `/modules/tuxemon/mods/tuxemon/${item.sprite}`;
- inventoryItemNode.querySelector('[data-template-slot="name"]').textContent = item.name;
- inventoryItemNode.querySelector('[data-template-slot="quantity"]').textContent = item.quantity;
-
- inventoryItemNode.addEventListener('click', async () => {
- Game.useItem(item);
- });
+ const inventoryItemNode = UI.createInventoryItem(item);
if (['potion', 'revive'].includes(item.category)) {
tabs['heal'].items.push(inventoryItemNode);
@@ -726,7 +747,23 @@ const UI = {
}));
tabsNode.style.gridTemplateColumns = '1fr 1fr 1fr';
- inventory.appendChild(tabsNode);
+ inventory.querySelector('[data-template-slot="items"]').appendChild(tabsNode);
+
+ const selectionModesNode = inventory.querySelector('[data-template-slot="modes"]');
+ const selectionModeNodes = selectionModesNode.querySelectorAll('[data-selection-mode]');
+ selectionModeNodes.forEach((node) => {
+ if (node.dataset.selectionMode === UI.inventorySelectionMode) {
+ node.setAttribute('selected', true);
+ }
+
+ node.addEventListener('click', () => {
+ selectionModesNode.querySelector(`[data-selection-mode="${UI.inventorySelectionMode}"]`).removeAttribute('selected');
+
+ UI.inventorySelectionMode = node.dataset.selectionMode;
+
+ node.setAttribute('selected', true);
+ });
+ });
popup.querySelector('.popup').appendChild(inventory);
popup.classList.add('inventory__popup');
@@ -750,42 +787,6 @@ const UI = {
UI.drawPopup(popup);
},
- openSaveDialog () {
- const popup = UI.createPopup();
- const dialog = UI.createTemplate(Template.dialogSave);
-
- const saveData = Memory.save();
-
- dialog.querySelector('[data-template-slot="saveData"]').value = saveData;
- dialog.querySelector('[data-template-slot="saveClipboard"]').addEventListener('click', async () => {
- if (navigator.clipboard) {
- await navigator.clipboard.writeText(saveData);
- alert('Saved to clipboard!');
- } else {
- alert('ERROR: Browser can\'t copy to clipboard! You have to do it manually.');
- }
- });
-
- popup.querySelector('.popup').appendChild(dialog);
- UI.drawPopup(popup);
- },
-
- openLoadDialog () {
- const popup = UI.createPopup();
- const dialog = UI.createTemplate(Template.dialogLoad);
-
- dialog.querySelector('[data-template-slot="load"]').addEventListener('click', () => {
- Memory.load(
- dialog.querySelector('[data-template-slot="saveData"]').value.trim()
- );
-
- document.querySelectorAll('.popup__overlay').forEach((element) => element.remove())
- });
-
- popup.querySelector('.popup').appendChild(dialog);
- UI.drawPopup(popup);
- },
-
openSettingsMenu () {
const popup = UI.createPopup();
const template = UI.createTemplate(Template.menuSettings);
@@ -842,6 +843,26 @@ const UI = {
},
+ /* Menu - Party */
+
+ /**
+ * @param {Monster}
+ *
+ * @returns {HTMLElement}
+ */
+ createPartyMonster (monster) {
+ 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}`;
+
+ return partyMonster;
+ },
+
+
/* Menu - Monster */
/**
@@ -940,6 +961,9 @@ const UI = {
return movesetListNode;
},
+ /**
+ * @param {Monster} monster
+ */
openStatsMenu (monster) {
const popup = UI.createPopup();
const statusMenu = UI.createStatsMenu(monster);
@@ -960,6 +984,181 @@ const UI = {
UI.drawPopup(popup);
},
+
+
+ /* Menu - Inventory */
+
+ /**
+ * @param {InventoryItem} item
+ *
+ * @returns {HTMLElement}
+ */
+ createInventoryItem (item) {
+ const inventoryItemNode = UI.createTemplate(Template.inventoryItem);
+
+ inventoryItemNode.title = item.description;
+ inventoryItemNode.dataset.templateItem = item.slug;
+
+ inventoryItemNode.querySelector('[data-template-slot="sprite"]').src = `/modules/tuxemon/mods/tuxemon/${item.sprite}`;
+ inventoryItemNode.querySelector('[data-template-slot="name"]').textContent = item.name;
+ inventoryItemNode.querySelector('[data-template-slot="quantity"]').textContent = item.quantity;
+
+ inventoryItemNode.addEventListener('click', async () => {
+ if (UI.inventorySelectionMode === 'use') {
+ if (item.category === 'potion') {
+ UI.openItemMonsterSelection(item);
+ }
+
+ else if (item.category === 'capture') {
+ Game.useItem(item);
+ }
+ }
+ else if (UI.inventorySelectionMode === 'info') {
+ UI.openItemInfo(item);
+ }
+ });
+
+ return inventoryItemNode;
+ },
+
+ /**
+ * @param {InventoryItem} item
+ */
+ redrawInventoryItem (item) {
+ const itemNode = document.querySelector(`#inventory *[data-template-item="${item.slug}"]`);
+ if (item.quantity === 0) {
+ itemNode.remove();
+ }
+
+ const newNode = UI.createInventoryItem(item);
+ itemNode.replaceWith(newNode);
+ },
+
+ /**
+ * @param {InventoryItem} item
+ * @param {Monster[]} monsters
+ *
+ * @returns {HTMLElement}
+ */
+ createItemMonsterSelection (item, monsters) {
+ const template = document.createElement('div');
+
+ /**
+ * @param {Monster} monster
+ *
+ * @returns {(HTMLElement|null)}
+ */
+ const createMonsterNode = (monster) => {
+ if (!Game.canUseItem(item, monster)) {
+ return null;
+ }
+
+ const monsterNode = UI.createPartyMonster(monster);
+
+ monsterNode.addEventListener('click', async () => {
+ await Game.useItem(item, monster);
+
+ if (item.quantity === 0) {
+ Game.removeItemFromInventory(Memory.state.player.inventory, item);
+ template.dispatchEvent(new Event('item:isExhausted'));
+ } else {
+ const replacingMonsterNode = createMonsterNode(monster);
+ if (replacingMonsterNode) {
+ monsterNode.replaceWith(replacingMonsterNode);
+ } else {
+ monsterNode.remove();
+ template.dispatchEvent(new Event('monster:lostCondition'));
+ }
+ }
+
+ UI.redrawInventoryItem(item);
+ });
+
+ return monsterNode;
+ };
+
+ for (const monster of monsters) {
+ const monsterNode = createMonsterNode(monster);
+
+ monsterNode && template.appendChild(monsterNode);
+ }
+
+ return template;
+ },
+
+ /**
+ * @param {InventoryItem} item
+ */
+ openItemMonsterSelection (item) {
+ const popup = UI.createPopup();
+ const template = UI.createItemMonsterSelection(item, Memory.state.player.monsters);
+ template.classList.add('inventory__monster-selection');
+
+ if (template.children.length === 0) {
+ alert('No applicable monsters.');
+ return;
+ }
+
+ template.addEventListener('item:isExhausted', () => popup.remove());
+ template.addEventListener('monster:lostCondition', () => popup.remove());
+
+ popup.querySelector('.popup').appendChild(template);
+
+ UI.drawPopup(popup);
+ },
+
+ /**
+ * @param {InventoryItem} item
+ */
+ openItemInfo (item) {
+ const popup = UI.createPopup();
+ const template = document.createElement('div');
+
+ template.textContent = item.conditions + ' -- ' + item.effects + ' -- ' + item.description;
+
+ popup.querySelector('.popup').appendChild(template);
+
+ UI.drawPopup(popup);
+ },
+
+
+ /* Menu - Journal */
+
+ openSaveDialog () {
+ const popup = UI.createPopup();
+ const dialog = UI.createTemplate(Template.dialogSave);
+
+ const saveData = Memory.save();
+
+ dialog.querySelector('[data-template-slot="saveData"]').value = saveData;
+ dialog.querySelector('[data-template-slot="saveClipboard"]').addEventListener('click', async () => {
+ if (navigator.clipboard) {
+ await navigator.clipboard.writeText(saveData);
+ alert('Saved to clipboard!');
+ } else {
+ alert('ERROR: Browser can\'t copy to clipboard! You have to do it manually.');
+ }
+ });
+
+ popup.querySelector('.popup').appendChild(dialog);
+ UI.drawPopup(popup);
+ },
+
+ openLoadDialog () {
+ const popup = UI.createPopup();
+ const dialog = UI.createTemplate(Template.dialogLoad);
+
+ dialog.querySelector('[data-template-slot="load"]').addEventListener('click', () => {
+ Memory.load(
+ dialog.querySelector('[data-template-slot="saveData"]').value.trim()
+ );
+
+ document.querySelectorAll('.popup__overlay').forEach((element) => element.remove())
+ });
+
+ popup.querySelector('.popup').appendChild(dialog);
+ UI.drawPopup(popup);
+ },
};
// UI element click bindings