diff options
Diffstat (limited to 'resources/js/ui.js')
-rw-r--r-- | resources/js/ui.js | 399 |
1 files changed, 325 insertions, 74 deletions
diff --git a/resources/js/ui.js b/resources/js/ui.js index 3940574..6f38867 100644 --- a/resources/js/ui.js +++ b/resources/js/ui.js @@ -14,6 +14,10 @@ const Template = { techniques: document.querySelector('#tpl___techniques'), technique: document.querySelector('#tpl___technique'), + healingCenter: document.querySelector('#tpl___healing-center'), + shop: document.querySelector('#tpl___shop'), + shopItem: document.querySelector('#tpl___shop__item'), + party: document.querySelector('#tpl___party'), partyMonster: document.querySelector('#tpl___party__monster'), @@ -51,6 +55,7 @@ const UI = { log: document.querySelector('#log'), status: document.querySelector('#status'), + showMap: document.querySelector('#status [data-template-slot="showMap"]'), nextTrainer: document.querySelector('#status [data-template-slot="nextTrainer"]'), changeArea: document.querySelector('#status [data-template-slot="changeArea"]'), @@ -274,6 +279,14 @@ const UI = { return document.createElement('i'); } + if (statusEffect.slug === 'faint') { + const node = document.createElement('b'); + node.innerHTML = 'X'; + node.title = statusEffect.name; + + return node; + } + const img = document.createElement('img'); img.src = `/modules/tuxemon/mods/tuxemon/gfx/ui/icons/status/icon_${statusEffect.slug}.png`; img.title = statusEffect.name; @@ -420,6 +433,10 @@ const UI = { * @returns {void} */ drawActiveTechniques () { + if (!Memory.state.player) { // on starter selection screen only + return; + } + const activeTechniques = UI.createActiveTechniques(Memory.state.player.activeMonster); activeTechniques.id = 'techniques'; @@ -495,8 +512,35 @@ const UI = { UI.elements.log.classList.toggle('log--is-hidden'); }, + openLog () { + UI.elements.log.classList.remove('log--is-hidden'); + }, + + closeLog () { + UI.elements.log.classList.add('log--is-hidden'); + }, + drawArea () { - UI.elements.battle.style.backgroundImage = `url(/modules/tuxemon/mods/tuxemon/gfx/ui/combat/${Memory.state.currentArea.environment.battle_graphics.background})`; + if (Game.isTown(Memory.state.currentArea)) { + UI.elements.sceneTown.querySelector('[data-template-slot="map"]').replaceChildren(UI.createMap()); + + UI.closeLog(); + + UI.elements.sceneBattle.classList.add('hidden'); + UI.elements.sceneTown.classList.remove('hidden'); + } else { + UI.elements.battle.style.backgroundImage = `url(/modules/tuxemon/mods/tuxemon/gfx/ui/combat/${Memory.state.currentArea.environment.battle_graphics.background})`; + + UI.elements.sceneTown.classList.add('hidden'); + UI.elements.sceneBattle.classList.remove('hidden'); + + UI.drawOpponentMonster(); + UI.drawActiveMonster(); + UI.drawActiveTechniques(); + } + + UI.drawStatus(); + UI.drawActiveBall(); }, progressTurn () { @@ -633,39 +677,120 @@ const UI = { /* Town */ - async drawTown () { + drawTown () {}, + + + /* Map */ + + /** + * @returns {HTMLElement} + */ + createMap () { + const template = document.createElement('div'); const currentArea = Memory.state.currentArea; - UI.elements.sceneTown.innerHTML = Memory.state.currentArea.map; + template.innerHTML = currentArea.map; + template.style.width = '100vw'; + template.style.maxWidth = '750px'; + + if (currentArea.locations) { + for (const locationId of Object.keys(currentArea.locations)) { + const location = currentArea.locations[locationId]; + + template.querySelector(`[data-location="${locationId}"]`).addEventListener('click', () => { + if (location.type === 'healingCenter') { + UI.openHealingCenter(location); + } + + else if (location.type === 'shop') { + UI.openShop(location); + } + }); + } + } + + return template; + }, - for (const locationId of Object.keys(currentArea.locations)) { - const location = currentArea.locations[locationId]; + /** + * @param {Object} healingCenter + */ + openHealingCenter (healingCenter) { + const popup = UI.createPopup(); + const template = UI.createTemplate(Template.healingCenter); + + const price = convertToCurrencyBase(healingCenter.price); + template.querySelector('[data-template-slot="price"]').innerHTML = formatPrice(price); - UI.elements.sceneTown.querySelector(`[data-location="${locationId}"]`).addEventListener('click', () => { - if (location.type === 'healingCenter') { - UI.openHealingCenter(location); + template.querySelector('[data-template-slot="heal"]').addEventListener('click', () => { + const applicableMonsters = Memory.state.player.monsters.filter((monster) => monster.hp < monster.stats.hp || monster.statusEffect); + if (applicableMonsters.length === 0) { + alert('No applicable monsters.'); + return; + } + + const monsterSelectionPopup = UI.createPopup(); + const monsterSelection = UI.createMonsterSelection(applicableMonsters); + + monsterSelection.addEventListener('monster:selected', (event) => { + if (Memory.state.money < price) { + alert(`Not enough ${DB.currencies.map[Memory.state.Settings.currency].symbol}.`); + return; } - else if (location.type === 'shop') { - UI.openShop(location); + Memory.state.money -= price; + event.detail.monster.hp = event.detail.monster.stats.hp; + event.detail.monster.statusEffect = null; + event.detail.node.remove(); + + if (monsterSelection.children.length === 0) { + monsterSelectionPopup.remove(); } + + UI.drawStatus(); }); - } - }, - openHealingCenter (healingCenter) {}, + monsterSelectionPopup.querySelector('.popup').appendChild(monsterSelection); + UI.drawPopup(monsterSelectionPopup); + }); + + popup.querySelector('.popup').appendChild(template); + UI.drawPopup(popup); + }, + /** + * @param {Object} shop + */ async openShop (shop) { const popup = UI.createPopup(); - const template = document.createElement('div'); + const template = UI.createTemplate(Template.shop); for (const itemData of shop.items) { + const price = convertToCurrencyBase(itemData.price); const item = await fetchItem(itemData.item_name); - const itemNode = document.createElement('div'); + const itemNode = UI.createTemplate(Template.shopItem); + + itemNode.querySelector('[data-template-slot="sprite"]').src = item.sprite; + itemNode.querySelector('[data-template-slot="name"]').innerHTML = item.name; + itemNode.querySelector('[data-template-slot="price"]').innerHTML = formatPrice(price); + + itemNode.addEventListener('click', () => { + if (Memory.state.money < price) { + alert(`Not enough ${DB.currencies.map[Memory.state.Settings.currency].symbol}.`); + return; + } + + Memory.state.money -= price; - itemNode.innerHTML = `<img src="/modules/tuxemon/mods/tuxemon/${item.sprite}" />`; - itemNode.innerHTML += `${item.name}`; - itemNode.innerHTML += `${itemData.price} ${DB.currencies.map[Memory.state.Settings.currency].symbol}`; + const itemInInventory = Memory.state.player.inventory.find((inventoryItem) => inventoryItem.slug === item.slug); + if (itemInInventory) { + itemInInventory.quantity++; + } else { + Memory.state.player.inventory.push(new InventoryItem(item, 1)); + } + + UI.drawStatus(); + }); template.appendChild(itemNode); } @@ -679,6 +804,7 @@ const UI = { partySelectionMode: 'select', inventorySelectionMode: 'use', + isHighlighting: false, /** @@ -727,6 +853,56 @@ const UI = { return template; }, + /** + * @param {Monster[]} monsters + * + * @returns {HTMLElement} + */ + createPartySelection (monsters) { + const party = UI.createTemplate(Template.party); + party.id = 'party'; + for (const monsterIdx in monsters) { + const monster = monsters[monsterIdx]; + const partyMonster = UI.createPartyMonster(monster); + + partyMonster.addEventListener('click', async (event) => { + // bubble up to partyNode + let target = event.target; + while (target.parentNode.id !== party.id) { + target = target.parentNode; + } + + party.dispatchEvent(new CustomEvent('party:monster:selected', { + detail: { + monster: monster, + mode: UI.partySelectionMode, + node: partyMonster, + }, + })); + }); + + party.querySelector('[data-template-slot="monsters"]').appendChild(partyMonster); + } + + const selectionModesNode = party.querySelector('[data-template-slot="modes"]'); + const selectionModeNodes = selectionModesNode.querySelectorAll('[data-party-selection-mode]'); + selectionModeNodes.forEach((node) => { + if (node.dataset.partySelectionMode === UI.partySelectionMode) { + node.setAttribute('selected', true); + } + + node.addEventListener('click', () => { + selectionModesNode.querySelector(`[data-party-selection-mode="${UI.partySelectionMode}"]`).removeAttribute('selected'); + + UI.partySelectionMode = node.dataset.partySelectionMode; + + node.setAttribute('selected', true); + }); + }); + + return party; + }, + drawStatus () { const currentArea = Memory.state.currentArea; @@ -739,15 +915,84 @@ const UI = { 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; + if (!Game.isTown(currentArea)) { + if ( + Memory.state.opponent.type === 'monster' && + currentArea.monsterProgress >= currentArea.requiredEncounters && + currentArea.trainerProgress < currentArea.trainers.length && + !Game.isInBattle + ) { + nextTrainerButton.disabled = false; + } else { + nextTrainerButton.disabled = true; + } } else { nextTrainerButton.disabled = true; } + + const changeAreaButton = UI.elements.changeArea; + if (!Game.isTown(currentArea)) { + if ( + Game.isInBattle || + (Memory.state.opponent && Memory.state.opponent.type === 'trainer') + ) { + changeAreaButton.disabled = true; + } else { + changeAreaButton.disabled = false; + } + } else { + changeAreaButton.disabled = false; + } + }, + + /** + * @param {Monster[]} monsters + */ + openStarterMonsterSelection (monsters) { + const popup = UI.createPopup().cloneNode(true); // remove close event + const template = UI.createPartySelection(monsters); + + const title = document.createElement('h1'); + title.textContent = 'Select your Tuxemon!'; + title.style.textAlign = 'center'; + template.prepend(title); + + template.addEventListener('party:monster:selected', (event) => { + const monster = event.detail.monster; + + if (UI.partySelectionMode === 'select') { + template.dispatchEvent(new CustomEvent('starter:monster:selected', { + detail: { + monster: monster, + node: event.detail.node, + popup: popup, + }, + })); + } + else if (UI.partySelectionMode === 'stats') { + UI.openStatsMenu(monster); + } + else if (UI.partySelectionMode === 'techniques') { + UI.openMovesetSelection(monster); + } + }); + + popup.querySelector('.popup').appendChild(template); + UI.drawPopup(popup); + + return template; + }, + + openMap () { + if (Game.isInBattle || Game.isTown(Memory.state.currentArea)) { + return; + } + + const popup = UI.createPopup(); + const template = UI.createMap(); + + popup.querySelector('.popup').appendChild(template); + UI.drawPopup(popup); }, openAreaSelection () { @@ -771,6 +1016,10 @@ const UI = { canGo = canGo && currentArea.trainerProgress >= currentArea.trainers.length; } + else if (condition.startsWith('area.')) { + canGo = Memory.state.areaProgress.hasOwnProperty(condition.replace('area.', '')); + } + else if (condition.startsWith('event.')) { canGo = false; } @@ -798,60 +1047,25 @@ const UI = { openPartyMenu () { const popup = UI.createPopup(); + const template = UI.createPartySelection(Memory.state.player.monsters); - const party = UI.createTemplate(Template.party); - party.id = 'party'; - for (const monsterIdx in Memory.state.player.monsters) { - const monster = Memory.state.player.monsters[monsterIdx]; - const partyMonster = UI.createPartyMonster(monster); - - partyMonster.addEventListener('click', async (event) => { - // bubble up to partyNode - let target = event.target; - while (target.parentNode.id !== party.id) { - target = target.parentNode; - } - - if (UI.partySelectionMode === 'select') { - Game.setActivePlayerMonster(monster); - - popup.remove(); - } - else if (UI.partySelectionMode === 'stats') { - UI.openStatsMenu(monster); - } - else if (UI.partySelectionMode === 'techniques') { - UI.openMovesetSelection(monster); - } - - UI.events.dispatchEvent(new CustomEvent('party:monsterSelected', { - detail: { - monster: monster, - mode: UI.partySelectionMode, - }, - })); - }); + template.addEventListener('party:monster:selected', (event) => { + const monster = event.detail.monster; - party.querySelector('[data-template-slot="monsters"]').appendChild(partyMonster); - } + if (UI.partySelectionMode === 'select') { + Game.setActivePlayerMonster(monster); - const selectionModesNode = party.querySelector('[data-template-slot="modes"]'); - const selectionModeNodes = selectionModesNode.querySelectorAll('[data-party-selection-mode]'); - selectionModeNodes.forEach((node) => { - if (node.dataset.partySelectionMode === UI.partySelectionMode) { - node.setAttribute('selected', true); + popup.remove(); + } + else if (UI.partySelectionMode === 'stats') { + UI.openStatsMenu(monster); + } + else if (UI.partySelectionMode === 'techniques') { + UI.openMovesetSelection(monster); } - - node.addEventListener('click', () => { - selectionModesNode.querySelector(`[data-party-selection-mode="${UI.partySelectionMode}"]`).removeAttribute('selected'); - - UI.partySelectionMode = node.dataset.partySelectionMode; - - node.setAttribute('selected', true); - }); }); - popup.querySelector('.popup').appendChild(party); + popup.querySelector('.popup').appendChild(template); UI.drawPopup(popup); }, @@ -1067,13 +1281,44 @@ const UI = { const exchangedMoney = baseRateMoney * newCurrency.rate; Memory.state.money = Number(exchangedMoney.toFixed(newCurrency.decimals)); - UI.drawTown(); + UI.drawArea(); UI.drawStatus(); }); template.querySelector('[data-template-slot="currency.lastUpdated"]').textContent = DB.currencies.last_updated; + // Highlight + + template.querySelector('[data-template-slot="highlight"]').addEventListener('click', () => { + UI.isHighlighting = !UI.isHighlighting; + + const elements = [ + UI.elements.battleOpponent, + UI.elements.battlePlayer.querySelector('[data-template-slot="sprite"]'), + UI.elements.techniques, + ...UI.elements.sceneTown.querySelectorAll('[data-location]'), + UI.elements.showMap, + UI.elements.nextTrainer, + UI.elements.changeArea, + UI.elements.menuParty, + UI.elements.menuCatch, + UI.elements.menuInventory, + UI.elements.menuLog, + UI.elements.menuJournal, + UI.elements.menuSettings, + ]; + + for (const element of elements) { + if (UI.isHighlighting) { + element.classList.add('setting-highlight'); + } else { + element.classList.remove('setting-highlight'); + } + } + }); + + popup.querySelector('.popup').appendChild(template); UI.drawPopup(popup); }, @@ -1093,6 +1338,7 @@ const UI = { 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="statusEffect"]').innerHTML = UI.createStatusEffectIcon(monster.statusEffect).outerHTML; partyMonster.querySelector('[data-template-slot="hpText"]').textContent = `${monster.hp} / ${monster.stats.hp}`; return partyMonster; @@ -1235,7 +1481,7 @@ const UI = { inventoryItemNode.title = item.description; inventoryItemNode.dataset.inventoryItem = item.slug; - inventoryItemNode.querySelector('[data-template-slot="sprite"]').src = `/modules/tuxemon/mods/tuxemon/${item.sprite}`; + inventoryItemNode.querySelector('[data-template-slot="sprite"]').src = item.sprite; inventoryItemNode.querySelector('[data-template-slot="name"]').textContent = item.name; inventoryItemNode.querySelector('[data-template-slot="quantity"]').textContent = item.quantity; @@ -1245,6 +1491,10 @@ const UI = { UI.openItemMonsterSelection(item); } + else if (item.category === 'revive') { + UI.openItemMonsterSelection(item); + } + else if (item.category === 'capture') { Game.useItem(item); } @@ -1389,6 +1639,7 @@ const UI = { }; // UI element click bindings +UI.elements.showMap.addEventListener('click', UI.openMap); UI.elements.changeArea.addEventListener('click', UI.openAreaSelection); UI.elements.menuParty.addEventListener('click', UI.openPartyMenu); UI.elements.menuInventory.addEventListener('click', UI.openInventoryMenu); |