From c657c77d0cf49afba627b93848e1915e2ce7d3ff Mon Sep 17 00:00:00 2001 From: Daniel Weipert Date: Thu, 31 Aug 2023 20:23:06 +0200 Subject: story and npcs --- resources/css/menu.css | 4 +++- resources/js/classes/Npc.js | 15 +++++++++++++++ resources/js/db.js | 4 ++-- resources/js/game.js | 22 +++++++++++++--------- resources/js/helpers.js | 2 +- resources/js/memory.js | 5 ++--- resources/js/story.js | 38 +++++++++++++++++++++++++++++++------- resources/js/ui.js | 7 ++++--- 8 files changed, 71 insertions(+), 26 deletions(-) create mode 100644 resources/js/classes/Npc.js (limited to 'resources') diff --git a/resources/css/menu.css b/resources/css/menu.css index 56e2525..70fc0b8 100644 --- a/resources/css/menu.css +++ b/resources/css/menu.css @@ -386,6 +386,7 @@ border: 2px solid yellow !important; } +.setting-highlight--map svg [data-interactable], .setting-highlight--map svg [data-location], .setting-highlight--map svg [data-area], .setting-highlight--map svg [data-encounter] { @@ -425,7 +426,8 @@ .story__popup { padding: 1rem; - max-width: 300px; + max-width: 100vw; + width: 360px; display: grid; grid-gap: 1rem; diff --git a/resources/js/classes/Npc.js b/resources/js/classes/Npc.js new file mode 100644 index 0000000..6121bce --- /dev/null +++ b/resources/js/classes/Npc.js @@ -0,0 +1,15 @@ +class Npc { + name = ''; + spriteName = ''; + + constructor (slug) { + this.slug = slug; + + this.name = translate(slug) || slugToName(slug.replace('spyder_', '')); + this.spriteName = DB.npcs[slug]?.template[0].sprite_name; + } + + get sprite () { + return `/modules/tuxemon/mods/tuxemon/gfx/sprites/player/${this.spriteName}.png`; + } +} diff --git a/resources/js/db.js b/resources/js/db.js index 3c8be05..35ed7bd 100644 --- a/resources/js/db.js +++ b/resources/js/db.js @@ -171,10 +171,10 @@ async function fetchItem (slug) { */ async function fetchNpc (slug) { if (! DB.npcs[slug]) { - DB.npcs[slug] = await fetchDBData(`/modules/tuxemon/mods/tuxemon/db/npc/${slug}.json`).then((response) => response.json()); + DB.npcs[slug] = await fetchDBData(`/db/_generated/npc/${slug}.json`).then((response) => response.json()); } - return DB.npcs[slug]; + return new Npc(slug); } /** diff --git a/resources/js/game.js b/resources/js/game.js index fb25ea4..2c6ecc0 100644 --- a/resources/js/game.js +++ b/resources/js/game.js @@ -26,6 +26,7 @@ const Game = { isProgressingTurn: false, playerIsChoosingNextMonster: false, isStoryBattle: false, + didWinStoryBattle: true, doBattleAnimation: true, opponentActionTimeout: null, didTechniqueHit: false, @@ -127,6 +128,7 @@ const Game = { UI.showMap(); } + Game.didWinStoryBattle = true; Game.isStoryBattle = false; } else { await Game.encounterNextTrainerMonster(); @@ -151,6 +153,7 @@ const Game = { // whole party defeated if (!Memory.state.player.monsters.some((monster) => monster.hp > 0)) { Memory.state.Game.isInBattle = false; + Game.didWinStoryBattle = false; Game.isStoryBattle = false; if (Memory.state.currentArea.monsterProgress < Memory.state.currentArea.requiredEncounters) { @@ -161,16 +164,10 @@ const Game = { await Game.goToArea(Memory.state.lastVisitedTown); // heal all monsters full - let totalHealingCenterPrice = 0; - for (const monster of Memory.state.player.monsters) { - monster.hp = monster.stats.hp; - monster.statusEffect = null; - - // pay healing center - const healingCenterPrice = Object.values(Memory.state.currentArea.locations).find((location) => location.type === 'healingCenter').price; - totalHealingCenterPrice += healingCenterPrice; - } + Game.healParty(); + const healingCenterPrice = Object.values(Memory.state.currentArea.locations).find((location) => location.type === 'healingCenter').price; + const totalHealingCenterPrice = healingCenterPrice * Memory.state.player.monsters.length; Memory.state.money -= totalHealingCenterPrice; Game.addPhaseEvent('postTurnEnd', () => { @@ -1006,6 +1003,13 @@ const Game = { monster.evolve(monster.evolutions[0]); }, + healParty () { + for (const monster of Memory.state.player.monsters) { + monster.hp = monster.stats.hp; + monster.statusEffect = null; + } + }, + /** * @param {string} type * diff --git a/resources/js/helpers.js b/resources/js/helpers.js index bceaf5e..aa149b1 100644 --- a/resources/js/helpers.js +++ b/resources/js/helpers.js @@ -19,7 +19,7 @@ function slugToName (slug) { * @returns {string} */ function nl2br (text) { - return text.replace(new RegExp(/\\n/g), '
'); + return text.replace(new RegExp(/\\n/g), '

'); } /** diff --git a/resources/js/memory.js b/resources/js/memory.js index f8d8a82..47608da 100644 --- a/resources/js/memory.js +++ b/resources/js/memory.js @@ -251,12 +251,11 @@ const Memory = { UI.drawArea(); UI.drawStatus(); }, - - - async () => Story.progress(Memory.state.currentStory), ]; await UI.showLoading(loadActions); + + await Story.progress(Memory.state.currentStory) }, /** diff --git a/resources/js/story.js b/resources/js/story.js index bdec2e1..1901829 100644 --- a/resources/js/story.js +++ b/resources/js/story.js @@ -10,10 +10,14 @@ const Story = { })); }); - await Story.progress('introduction'); + await Story.immediateProgress('start', 'introduction'); }, async introduction () { + await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('omnichannel-ceo'), text: translate('spyder_intro00', true) }); + await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('omnichannel-ceo'), text: translate('spyder_intro01', true) }); + await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('tuxemart-keeper'), text: translate('spyder_intro_shopkeeper1', true) }); + const possibleStarterMonsters = await Promise.all( [ 'budaye', @@ -37,7 +41,7 @@ const Story = { event.detail.popup.remove(); - await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('spyder_dante'), text: translate('spyder_intro_shopkeeper4', true) }); + await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('tuxemart-keeper'), text: translate('spyder_intro_shopkeeper4', true) }); // set rival monster Memory.state.rivalMonster = event.detail.monster.slug; @@ -55,6 +59,7 @@ const Story = { async selectStarterMonster () { await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('spyder_dante'), text: translate('spyder_papertown_myfirstmon_notmet', true) }); + await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('spyder_dante'), text: translate('spyder_papertown_myfirstmon1', true) }); await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('spyder_dante'), text: translate('spyder_papertown_myfirstmon2', true) }); const possibleStarterMonsters = await Promise.all( @@ -93,7 +98,7 @@ const Story = { })); }); - await Story.progress('battleRivalOne'); + await Story.immediateProgress('selectStarterMonster', 'battleRivalOne'); }, async battleRivalOne () { @@ -102,9 +107,18 @@ const Story = { Memory.state.opponent = new Trainer({ monsters: [ rivalMonster ] }); await Memory.state.opponent.initialize(); - await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('spyder_rivalbillie'), text: translate('spyder_papertown_firstfight', true) }); + await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('spyder_billie'), text: translate('spyder_papertown_firstfight', true) }); await Story.battle(); + + if (Game.didWinStoryBattle) { + await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('spyder_billie'), text: translate('spyder_papertown_firstfight_win', true) }); + } else { + await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('spyder_billie'), text: translate('spyder_papertown_firstfight_lose', true) }); + } + + await UI.buildAndShowStoryPopup({ speaker: await fetchNpc('spyder_billie'), text: translate('spyder_papertown_firstfight_after', true) }); + Game.healParty(); }, @@ -112,8 +126,6 @@ const Story = { /** * @param {string} slug - * - * @returns {Promise} */ async progress (slug) { if (!Story[slug]) { @@ -131,8 +143,16 @@ const Story = { }, /** - * @returns {Promise} + * @param {string} fromSlug + * @param {string} toSlug */ + async immediateProgress (fromSlug, toSlug) { + Memory.state.storyProgress[fromSlug] = true; + Memory.saveToLocalStorage(); + + await Story.progress(toSlug); + }, + async battle () { const previousArea = Object.assign({}, Memory.state.currentArea); @@ -152,6 +172,10 @@ const Story = { }, 100); }); + // reset incremented trainer progress + if (Memory.state.areaProgress[previousArea.slug]) { + Memory.state.areaProgress[previousArea.slug].trainerProgress = previousArea.trainerProgress; + } if (previousArea.slug === Memory.state.currentArea.slug) { Memory.state.currentArea.trainerProgress = previousArea.trainerProgress; diff --git a/resources/js/ui.js b/resources/js/ui.js index 6884e14..e5afb53 100644 --- a/resources/js/ui.js +++ b/resources/js/ui.js @@ -556,7 +556,8 @@ const UI = { }, drawBattle () { - UI.elements.battle.style.backgroundImage = `url(/modules/tuxemon/mods/tuxemon/gfx/ui/combat/${Memory.state.currentArea.environment.battle_graphics.background})`; + UI.elements.battle.style.backgroundImage = `url(${Memory.state.currentArea.environment.battle_graphics.background})`; + UI.elements.battle.style.backgroundPosition = `${Memory.state.currentArea.environment.battle_graphics.background_position || 'top'}`; UI.showBattle(); UI.drawOpponentMonster(); @@ -1945,8 +1946,8 @@ const UI = { applyStoryPopupContent (popup, { speaker, text }) { const template = UI.createTemplate(Template.storyPopup); - template.querySelector('[data-template-slot="speaker.sprite"]').src = `/modules/tuxemon/mods/tuxemon/gfx/sprites/player/${speaker.template[0].sprite_name}.png`; - template.querySelector('[data-template-slot="speaker.name"]').textContent = slugToName(speaker.slug.replace('spyder_', '')); + template.querySelector('[data-template-slot="speaker.sprite"]').src = speaker.sprite; + template.querySelector('[data-template-slot="speaker.name"]').textContent = speaker.name; template.querySelector('[data-template-slot="text"]').innerHTML = nl2br(text); template.querySelector('[data-template-slot="next"]').addEventListener('click', UI.wrapCallback(() => { popup.dispatchEvent(new Event('next')); -- cgit v1.2.3