From 118bdcc49ea317785c8e67e7e62ee94fcbf7807a Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 1 Mar 2025 01:15:11 +0100 Subject: [PATCH 01/18] =?UTF-8?q?[Bug][UI/UX]=20Never=20select=20invisible?= =?UTF-8?q?=20containers=20in=20Pok=C3=A9dex=20(#5449)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/pokedex-ui-handler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index a18f138e4f7..02c19f11813 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -986,7 +986,7 @@ export default class PokedexUiHandler extends MessageUiHandler { this.updateScroll(); const proportion = this.filterBarCursor / Math.max(1, this.filterBar.numFilters - 1); const targetCol = Math.min(8, proportion < 0.5 ? Math.floor(proportion * 8) : Math.ceil(proportion * 8)); - this.setCursor(Math.min(targetCol, numberOfStarters)); + this.setCursor(Math.min(targetCol, numberOfStarters - 1)); success = true; } break; @@ -1108,7 +1108,7 @@ export default class PokedexUiHandler extends MessageUiHandler { } break; case Button.DOWN: - if (currentRow < numOfRows - 1) { // not last row + if ((currentRow < numOfRows - 1) && (this.cursor + 9 < this.filteredPokemonData.length)) { // not last row if (currentRow - this.scrollCursor === 8) { // last row of visible pokemon this.scrollCursor++; this.updateScroll(); From 7cc5f40af8f0b37fc2e790f8d23c9310b0f72907 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 1 Mar 2025 01:55:22 +0100 Subject: [PATCH 02/18] =?UTF-8?q?[Bug][UI/UX]=20Restore=20egg/tm=20move=20?= =?UTF-8?q?and=20passive=20icons=20when=20using=20filters=20in=20Pok=C3=A9?= =?UTF-8?q?dex=20(#5450)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/pokedex-ui-handler.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index 02c19f11813..83739ba26a8 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -1577,6 +1577,37 @@ export default class PokedexUiHandler extends MessageUiHandler { container.icon.setTint(0); } + if (data.eggMove1) { + container.eggMove1Icon.setVisible(true); + } else { + container.eggMove1Icon.setVisible(false); + } + if (data.eggMove2) { + container.eggMove2Icon.setVisible(true); + } else { + container.eggMove2Icon.setVisible(false); + } + if (data.tmMove1) { + container.tmMove1Icon.setVisible(true); + } else { + container.tmMove1Icon.setVisible(false); + } + if (data.tmMove2) { + container.tmMove2Icon.setVisible(true); + } else { + container.tmMove2Icon.setVisible(false); + } + if (data.passive1) { + container.passive1Icon.setVisible(true); + } else { + container.passive1Icon.setVisible(false); + } + if (data.passive2) { + container.passive2Icon.setVisible(true); + } else { + container.passive2Icon.setVisible(false); + } + if (this.showDecorations) { if (this.pokerusSpecies.includes(data.species)) { From b348626ec2257352dc758e5aecaf4dcdd153fff7 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 1 Mar 2025 02:00:56 +0100 Subject: [PATCH 03/18] [Bug][UI/UX] Make candy icons show up again in starter select screen (#5451) --- src/ui/abstact-option-select-ui-handler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index 07e43a344dd..a462ed158cb 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -147,7 +147,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { itemIcon.setScale(3 * this.scale); this.optionSelectIcons.push(itemIcon); - this.optionSelectContainer.add(itemIcon); + this.optionSelectTextContainer.add(itemIcon); itemIcon.setPositionRelative(this.optionSelectText, 36 * this.scale, 7 + i * (114 * this.scale - 3)); @@ -156,7 +156,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { itemOverlayIcon.setScale(3 * this.scale); this.optionSelectIcons.push(itemOverlayIcon); - this.optionSelectContainer.add(itemOverlayIcon); + this.optionSelectTextContainer.add(itemOverlayIcon); itemOverlayIcon.setPositionRelative(this.optionSelectText, 36 * this.scale, 7 + i * (114 * this.scale - 3)); From d16197e950bbe59bf86976a5245a10a34c4c6af4 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 1 Mar 2025 04:34:49 +0100 Subject: [PATCH 04/18] =?UTF-8?q?[Bug]=20Gendered=20forms=20display=20corr?= =?UTF-8?q?ect=20information=20in=20Pok=C3=A9dex=20(#5452)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/pokedex-page-ui-handler.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index 49bb52dddbb..e9060fb43b6 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -602,6 +602,14 @@ export default class PokedexPageUiHandler extends MessageUiHandler { this.battleForms = []; const species = this.species; + + let formKey = this.species?.forms.length > 0 ? this.species.forms[this.formIndex].formKey : ""; + this.isFormGender = formKey === "male" || formKey === "female"; + if (this.isFormGender && ((this.savedStarterAttributes.female === true && formKey === "male") || (this.savedStarterAttributes.female === false && formKey === "female"))) { + this.formIndex = (this.formIndex + 1) % 2; + formKey = this.species.forms[this.formIndex].formKey; + } + const formIndex = this.formIndex ?? 0; this.starterId = this.getStarterSpeciesId(this.species.speciesId); @@ -635,12 +643,9 @@ export default class PokedexPageUiHandler extends MessageUiHandler { this.eggMoves = speciesEggMoves[this.starterId] ?? []; this.hasEggMoves = Array.from({ length: 4 }, (_, em) => (globalScene.gameData.starterData[this.starterId].eggMoves & (1 << em)) !== 0); - const formKey = this.species?.forms.length > 0 ? this.species.forms[this.formIndex].formKey : ""; this.tmMoves = speciesTmMoves[species.speciesId]?.filter(m => Array.isArray(m) ? (m[0] === formKey ? true : false ) : true) .map(m => Array.isArray(m) ? m[1] : m).sort((a, b) => allMoves[a].name > allMoves[b].name ? 1 : -1) ?? []; - this.isFormGender = formKey === "male" || formKey === "female"; - const passiveId = starterPassiveAbilities.hasOwnProperty(species.speciesId) ? species.speciesId : starterPassiveAbilities.hasOwnProperty(this.starterId) ? this.starterId : pokemonPrevolutions[this.starterId]; const passives = starterPassiveAbilities[passiveId]; From 89e8085c39c4f193124b731b5ea26b12734a367d Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 1 Mar 2025 05:22:51 +0100 Subject: [PATCH 05/18] =?UTF-8?q?[Bug]=20Display=20Pok=C3=A9mon=20name=20i?= =?UTF-8?q?n=20Focus=20Punch=20lost=20focus=20message=20(#5448)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/data/move.ts | 2 +- test/moves/focus_punch.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 18f4b220911..11ce7069664 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -9384,7 +9384,7 @@ export function initMoves() { .attr(BypassBurnDamageReductionAttr), new AttackMove(Moves.FOCUS_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, -1, -3, 3) .attr(MessageHeaderAttr, (user, move) => i18next.t("moveTriggers:isTighteningFocus", { pokemonName: getPokemonNameWithAffix(user) })) - .attr(PreUseInterruptAttr, i18next.t("moveTriggers:lostFocus"), user => !!user.turnData.attacksReceived.find(r => r.damage)) + .attr(PreUseInterruptAttr, (user, target, move) => i18next.t("moveTriggers:lostFocus", { pokemonName: getPokemonNameWithAffix(user) }), user => !!user.turnData.attacksReceived.find(r => r.damage)) .punchingMove(), new AttackMove(Moves.SMELLING_SALTS, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 3) .attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1) diff --git a/test/moves/focus_punch.test.ts b/test/moves/focus_punch.test.ts index 9bf858dfda5..1f14a19fbd7 100644 --- a/test/moves/focus_punch.test.ts +++ b/test/moves/focus_punch.test.ts @@ -140,6 +140,6 @@ describe("Moves - Focus Punch", () => { await game.phaseInterceptor.to("MessagePhase", false); const consoleSpy = vi.spyOn(console, "log"); await game.phaseInterceptor.to("MoveEndPhase", true); - expect(consoleSpy).nthCalledWith(1, i18next.t("moveTriggers:lostFocus")); + expect(consoleSpy).nthCalledWith(1, i18next.t("moveTriggers:lostFocus", { pokemonName: "Charizard" })); }); }); From c7df847e661727c02bc0980337cd767f99dcb048 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 1 Mar 2025 20:33:31 +0100 Subject: [PATCH 06/18] [Bug] Unlocking correct base form of Zygarde when captured (#5453) --- src/system/game-data.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 57e25325ba4..aefc583a98a 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1645,11 +1645,19 @@ export class GameData { } else if (formIndex === 3) { dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(1); } - } - const allFormChanges = pokemonFormChanges.hasOwnProperty(species.speciesId) ? pokemonFormChanges[species.speciesId] : []; - const toCurrentFormChanges = allFormChanges.filter(f => (f.formKey === formKey)); - if (toCurrentFormChanges.length > 0) { - dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(0); + } else if (pokemon.species.speciesId === Species.ZYGARDE) { + if (formIndex === 4) { + dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(2); + } else if (formIndex === 5) { + dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(3); + } + } else { + const allFormChanges = pokemonFormChanges.hasOwnProperty(species.speciesId) ? pokemonFormChanges[species.speciesId] : []; + const toCurrentFormChanges = allFormChanges.filter(f => (f.formKey === formKey)); + if (toCurrentFormChanges.length > 0) { + // Needs to do this or Castform can unlock the wrong form, etc. + dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(0); + } } } From 5c73a1ea46033d011ef2f1884a2b41e9cd88d31b Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 1 Mar 2025 21:43:18 +0100 Subject: [PATCH 07/18] [Bug][UI/UX] Starter select screen now looks for and displays form-specific abilities (#5454) --- src/ui/starter-select-ui-handler.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 771554f18de..12dd668af39 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -8,6 +8,7 @@ import i18next from "i18next"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { starterColors } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; +import type { Ability } from "#app/data/ability"; import { allAbilities } from "#app/data/ability"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate, getGrowthRateColor } from "#app/data/exp"; @@ -3364,7 +3365,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } if (dexEntry.caughtAttr) { - const ability = allAbilities[this.lastSpecies.getAbility(abilityIndex!)]; // TODO: is this bang correct? + let ability: Ability; + if (this.lastSpecies.forms?.length > 1) { + ability = allAbilities[this.lastSpecies.forms[formIndex ?? 0].getAbility(abilityIndex!)]; + } else { + ability = allAbilities[this.lastSpecies.getAbility(abilityIndex!)]; // TODO: is this bang correct? + } this.pokemonAbilityText.setText(ability.name); const isHidden = abilityIndex === (this.lastSpecies.ability2 ? 2 : 1); From 04ac5c376241b7026a244fff3faec429b1ade729 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 1 Mar 2025 21:54:29 +0100 Subject: [PATCH 08/18] [Bug][UI/UX] Shiny button can cycle variants even if non-shiny form is uncaught (#5457) --- src/ui/pokedex-page-ui-handler.ts | 12 ++++++------ src/ui/starter-select-ui-handler.ts | 19 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index e9060fb43b6..68df02c191d 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -1580,15 +1580,14 @@ export default class PokedexPageUiHandler extends MessageUiHandler { starterAttributes.variant = newVariant; // store the selected variant this.savedStarterAttributes.variant = starterAttributes.variant; - if (newVariant > props.variant) { - this.setSpeciesDetails(this.species, { variant: newVariant as Variant }); - success = true; - } else { + if ((this.isCaught() & DexAttr.NON_SHINY) && (newVariant <= props.variant)) { this.setSpeciesDetails(this.species, { shiny: false, variant: 0 }); success = true; - starterAttributes.shiny = false; this.savedStarterAttributes.shiny = starterAttributes.shiny; + } else { + this.setSpeciesDetails(this.species, { variant: newVariant as Variant }); + success = true; } } } @@ -2201,7 +2200,8 @@ export default class PokedexPageUiHandler extends MessageUiHandler { const isNonShinyCaught = !!(caughtAttr & DexAttr.NON_SHINY); const isShinyCaught = !!(caughtAttr & DexAttr.SHINY); - this.canCycleShiny = isNonShinyCaught && isShinyCaught; + const caughtVariants = [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => caughtAttr & v); + this.canCycleShiny = (isNonShinyCaught && isShinyCaught) || (isShinyCaught && caughtVariants.length > 1); const isMaleCaught = !!(caughtAttr & DexAttr.MALE); const isFemaleCaught = !!(caughtAttr & DexAttr.FEMALE); diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 12dd668af39..543e24d3b97 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2068,20 +2068,20 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } while (newVariant !== props.variant); starterAttributes.variant = newVariant; // store the selected variant - // If going to a higher variant, display that - if (newVariant > props.variant) { + if ((this.speciesStarterDexEntry!.caughtAttr & DexAttr.NON_SHINY) && (newVariant <= props.variant)) { + // If we have run out of variants, go back to non shiny + this.setSpeciesDetails(this.lastSpecies, { shiny: false, variant: 0 }); + this.pokemonShinyIcon.setVisible(false); + success = true; + starterAttributes.shiny = false; + } else { + // If going to a higher variant, or only shiny forms are caught, go to next variant this.setSpeciesDetails(this.lastSpecies, { variant: newVariant as Variant }); // Cycle tint based on current sprite tint const tint = getVariantTint(newVariant as Variant); this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant as Variant)); this.pokemonShinyIcon.setTint(tint); success = true; - // If we have run out of variants, go back to non shiny - } else { - this.setSpeciesDetails(this.lastSpecies, { shiny: false, variant: 0 }); - this.pokemonShinyIcon.setVisible(false); - success = true; - starterAttributes.shiny = false; } } } @@ -3328,7 +3328,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const isNonShinyCaught = !!(caughtAttr & DexAttr.NON_SHINY); const isShinyCaught = !!(caughtAttr & DexAttr.SHINY); - this.canCycleShiny = isNonShinyCaught && isShinyCaught; + const caughtVariants = [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => caughtAttr & v); + this.canCycleShiny = (isNonShinyCaught && isShinyCaught) || (isShinyCaught && caughtVariants.length > 1); const isMaleCaught = !!(caughtAttr & DexAttr.MALE); const isFemaleCaught = !!(caughtAttr & DexAttr.FEMALE); From 8f280bec498568396662240231d487e64e61a9d4 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 1 Mar 2025 22:11:05 +0100 Subject: [PATCH 09/18] [Bug][UI/UX] Restore V button in battle (#5459) --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 91367cf73ec..111464b5e5c 100644 --- a/index.html +++ b/index.html @@ -133,7 +133,7 @@ V -
+
V
From b50d2c07834c0cab0b39ab90ca489fc3636f6677 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 1 Mar 2025 22:24:15 +0100 Subject: [PATCH 10/18] =?UTF-8?q?[Bug][UI/UX]=20Fix=20bug=20in=20Pok=C3=A9?= =?UTF-8?q?dex=20for=20Mothim=20and=20other=20niche=20cases=20(#5458)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/pokedex-page-ui-handler.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index 68df02c191d..eee900d411e 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -789,6 +789,10 @@ export default class PokedexPageUiHandler extends MessageUiHandler { const formIndex = otherFormIndex !== undefined ? otherFormIndex : this.formIndex; const caughtAttr = this.isCaught(species); + if (caughtAttr && (!species.forms.length || species.forms.length === 1)) { + return true; + } + const isFormCaught = (caughtAttr & globalScene.gameData.getFormAttr(formIndex ?? 0)) > 0n; return isFormCaught; } From 882b965da101c992e18752b22e77f203f8e9b9be Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 2 Mar 2025 02:46:38 +0100 Subject: [PATCH 11/18] [Bug] Pledge moves in summary do not break the game (#5461) --- src/data/move.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/move.ts b/src/data/move.ts index 11ce7069664..677ad9f0ebc 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -5236,7 +5236,7 @@ export class CombinedPledgeTypeAttr extends VariableMoveTypeAttr { return false; } - const combinedPledgeMove = user.turnData.combiningPledge; + const combinedPledgeMove = user?.turnData?.combiningPledge; if (!combinedPledgeMove) { return false; } From 659bc82676ec23dd0193ce54ba459cff5c422084 Mon Sep 17 00:00:00 2001 From: damocleas Date: Sun, 2 Mar 2025 12:19:17 -0500 Subject: [PATCH 12/18] [i18n] Up version to 1.7.6, Update locales submodule --- package-lock.json | 4 ++-- package.json | 2 +- public/locales | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d31469f4a45..453a525581b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pokemon-rogue-battle", - "version": "1.7.5", + "version": "1.7.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pokemon-rogue-battle", - "version": "1.7.5", + "version": "1.7.6", "hasInstallScript": true, "dependencies": { "@material/material-color-utilities": "^0.2.7", diff --git a/package.json b/package.json index 1953b886c80..4c9204f60f9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.7.5", + "version": "1.7.6", "type": "module", "scripts": { "start": "vite", diff --git a/public/locales b/public/locales index b4534f03ba8..0e5c6096ba2 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit b4534f03ba8eb8709486ee967257b6f3725702dd +Subproject commit 0e5c6096ba26f6b87aed1aab3fe9b0b23f6cbb7b From 73afb35c4d745d136287199225a04a6b8ea3db26 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sun, 2 Mar 2025 12:40:40 -0600 Subject: [PATCH 13/18] [GitHub] Update CODEOWNERS to fix team ownership (#5465) --- .github/CODEOWNERS | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8e7606f0a48..79ab1bdc38a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,4 +4,19 @@ * @pagefaultgames/junior-dev-team # github actions/templates etc. - Dev Leads -/.github @pagefaultgames/dev-leads +/.github @pagefaultgames/senior-dev-team + +# Art Team +/public/**/*.png @pagefaultgames/art-team +/public/**/*.json @pagefaultgames/art-team +/public/images @pagefaultgames/art-team +/public/battle-anims @pagefaultgames/art-team + +# Audio files +*.mp3 @pagefaultgames/composer-team +*.wav @pagefaultgames/composer-team +*.ogg @pagefaultgames/composer-team +/public/audio @pagefaultgames/composer-team + +# Balance Files; contain actual code logic and must also be owned by dev team +/src/data/balance @pagefaultgames/balance-team @pagefaultgames/junior-dev-team \ No newline at end of file From 6f686a05e39cb75c2ee110ab996b2b4ed0735cb0 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 2 Mar 2025 20:35:59 +0100 Subject: [PATCH 14/18] [UI/UX] [Bug] Hiding tera button when toggling on stats mode (#5466) --- src/ui/starter-select-ui-handler.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 543e24d3b97..34a29411de4 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -3353,7 +3353,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.canCycleForm = species.forms.filter(f => f.isStarterSelectable || !pokemonFormChanges[species.speciesId]?.find(fc => fc.formKey)) .map((_, f) => dexEntry.caughtAttr & globalScene.gameData.getFormAttr(f)).filter(f => f).length > 1; this.canCycleNature = globalScene.gameData.getNaturesForAttr(dexEntry.natureAttr).length > 1; - this.canCycleTera = globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && !Utils.isNullOrUndefined(getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2); + this.canCycleTera = !this.statsMode && globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && !Utils.isNullOrUndefined(getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2); } if (dexEntry.caughtAttr && species.malePercent !== null) { @@ -3858,12 +3858,20 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.showStats(); this.statsMode = true; this.pokemonSprite.setVisible(false); + this.teraIcon.setVisible(false); + this.canCycleTera = false; + this.updateInstructions(); } else { this.statsMode = false; this.statsContainer.setVisible(false); this.pokemonSprite.setVisible(!!this.speciesStarterDexEntry?.caughtAttr); //@ts-ignore this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. !?!? + this.teraIcon.setVisible(globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id)); + const props = globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); + const formIndex = props.formIndex; + this.canCycleTera = !this.statsMode && globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && !Utils.isNullOrUndefined(getPokemonSpeciesForm(this.lastSpecies.speciesId, formIndex ?? 0).type2); + this.updateInstructions(); } } From 5e469620ef8ed098d22c79d279ecdfdbaed0ba22 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sun, 2 Mar 2025 19:15:51 -0600 Subject: [PATCH 15/18] [Bug] Fix battle-anims crashing in certain cases (#5337) --- src/data/battle-anims.ts | 132 +++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 62 deletions(-) diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index a179f3a3e9b..a42779563f2 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -1,7 +1,20 @@ import { globalScene } from "#app/global-scene"; -import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move"; +import { + AttackMove, + BeakBlastHeaderAttr, + DelayedAttackAttr, + MoveFlags, + SelfStatusMove, + allMoves, +} from "./move"; import type Pokemon from "../field/pokemon"; -import * as Utils from "../utils"; +import { + type nil, + getFrameMs, + getEnumKeys, + getEnumValues, + animationFileName, +} from "../utils"; import type { BattlerIndex } from "../battle"; import type { Element } from "json-stable-stringify"; import { Moves } from "#enums/moves"; @@ -401,7 +414,7 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent { if (Object.keys(tweenProps).length) { globalScene.tweens.add(Object.assign({ targets: moveAnim.bgSprite, - duration: Utils.getFrameMs(this.duration * 3) + duration: getFrameMs(this.duration * 3) }, tweenProps)); } return this.duration * 2; @@ -437,7 +450,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent { globalScene.tweens.add({ targets: moveAnim.bgSprite, - duration: Utils.getFrameMs(this.duration * 3) + duration: getFrameMs(this.duration * 3) }); return this.duration * 2; @@ -455,8 +468,8 @@ export const encounterAnims = new Map(); export function initCommonAnims(): Promise { return new Promise(resolve => { - const commonAnimNames = Utils.getEnumKeys(CommonAnim); - const commonAnimIds = Utils.getEnumValues(CommonAnim); + const commonAnimNames = getEnumKeys(CommonAnim); + const commonAnimIds = getEnumValues(CommonAnim); const commonAnimFetches: Promise>[] = []; for (let ca = 0; ca < commonAnimIds.length; ca++) { const commonAnimId = commonAnimIds[ca]; @@ -493,7 +506,7 @@ export function initMoveAnim(move: Moves): Promise { const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP; const fetchAnimAndResolve = (move: Moves) => { - globalScene.cachedFetch(`./battle-anims/${Utils.animationFileName(move)}.json`) + globalScene.cachedFetch(`./battle-anims/${animationFileName(move)}.json`) .then(response => { const contentType = response.headers.get("content-type"); if (!response.ok || contentType?.indexOf("application/json") === -1) { @@ -550,7 +563,7 @@ function useDefaultAnim(move: Moves, defaultMoveAnim: Moves) { * @remarks use {@linkcode useDefaultAnim} to use a default animation */ function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) { - const moveName = Utils.animationFileName(move); + const moveName = animationFileName(move); console.warn(`Could not load animation file for move '${moveName}'`, ...optionalParams); } @@ -560,7 +573,7 @@ function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) { */ export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise { const anims = Array.isArray(encounterAnim) ? encounterAnim : [ encounterAnim ]; - const encounterAnimNames = Utils.getEnumKeys(EncounterAnim); + const encounterAnimNames = getEnumKeys(EncounterAnim); const encounterAnimFetches: Promise>[] = []; for (const anim of anims) { if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) { @@ -922,7 +935,7 @@ export abstract class BattleAnim { let f = 0; globalScene.tweens.addCounter({ - duration: Utils.getFrameMs(3), + duration: getFrameMs(3), repeat: anim?.frames.length ?? 0, onRepeat: () => { if (!f) { @@ -994,47 +1007,39 @@ export abstract class BattleAnim { const moveSprite = sprites[graphicIndex]; if (spritePriorities[graphicIndex] !== frame.priority) { spritePriorities[graphicIndex] = frame.priority; + /** Move the position that the moveSprite is rendered in based on the priority. + * @param priority The priority level to draw the sprite. + * - 0: Draw the sprite in front of the pokemon on the field. + * - 1: Draw the sprite in front of the user pokemon. + * - 2: Draw the sprite in front of its `bgSprite` (if it has one), or its + * `AnimFocus` (if that is user/target), otherwise behind everything. + * - 3: Draw the sprite behind its `AnimFocus` (if that is user/target), otherwise in front of everything. + */ const setSpritePriority = (priority: number) => { - switch (priority) { - case 0: - globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, globalScene.getEnemyPokemon(false) ?? globalScene.getPlayerPokemon(false)!); // TODO: is this bang correct? - break; - case 1: - globalScene.field.moveTo(moveSprite, globalScene.field.getAll().length - 1); - break; - case 2: - switch (frame.focus) { - case AnimFocus.USER: - if (this.bgSprite) { - globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite); - } else { - globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? - } - break; - case AnimFocus.TARGET: - globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? - break; - default: - setSpritePriority(1); - break; - } - break; - case 3: - switch (frame.focus) { - case AnimFocus.USER: - globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? - break; - case AnimFocus.TARGET: - globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? - break; - default: - setSpritePriority(1); - break; - } - break; - default: - setSpritePriority(1); + /** The sprite we are moving the moveSprite in relation to */ + let targetSprite: Phaser.GameObjects.GameObject | nil; + /** The method that is being used to move the sprite.*/ + let moveFunc: ((sprite: Phaser.GameObjects.GameObject, target: Phaser.GameObjects.GameObject) => void) | + ((sprite: Phaser.GameObjects.GameObject) => void) = globalScene.field.bringToTop; + + if (priority === 0) { // Place the sprite in front of the pokemon on the field. + targetSprite = globalScene.getEnemyField().find(p => p) ?? globalScene.getPlayerField().find(p => p); + console.log(typeof targetSprite); + moveFunc = globalScene.field.moveBelow; + } else if (priority === 2 && this.bgSprite) { + moveFunc = globalScene.field.moveAbove; + targetSprite = this.bgSprite; + } else if (priority === 2 || priority === 3) { + moveFunc = priority === 2 ? globalScene.field.moveBelow : globalScene.field.moveAbove; + if (frame.focus === AnimFocus.USER) { + targetSprite = this.user; + } else if (frame.focus === AnimFocus.TARGET) { + targetSprite = this.target; + } } + // If target sprite is not undefined and exists in the field container, then move the sprite using the moveFunc. + // Otherwise, default to just bringing it to the top. + targetSprite && globalScene.field.exists(targetSprite) ? moveFunc.bind(globalScene.field)(moveSprite as Phaser.GameObjects.GameObject, targetSprite) : globalScene.field.bringToTop(moveSprite as Phaser.GameObjects.GameObject); }; setSpritePriority(frame.priority); } @@ -1052,11 +1057,13 @@ export abstract class BattleAnim { } } if (anim?.frameTimedEvents.has(f)) { - for (const event of anim.frameTimedEvents.get(f)!) { // TODO: is this bang correct? - r = Math.max((anim.frames.length - f) + event.execute(this), r); + const base = anim.frames.length - f; + // Bang is correct due to `has` check above, which cannot return true for an undefined / null `f` + for (const event of anim.frameTimedEvents.get(f)!) { + r = Math.max(base + event.execute(this), r); } } - const targets = Utils.getEnumValues(AnimFrameTarget); + const targets = getEnumValues(AnimFrameTarget); for (const i of targets) { const count = i === AnimFrameTarget.GRAPHIC ? g : i === AnimFrameTarget.USER ? u : t; if (count < spriteCache[i].length) { @@ -1084,7 +1091,7 @@ export abstract class BattleAnim { } if (r) { globalScene.tweens.addCounter({ - duration: Utils.getFrameMs(r), + duration: getFrameMs(r), onComplete: () => cleanUpAndComplete() }); } else { @@ -1166,7 +1173,7 @@ export abstract class BattleAnim { let existingFieldSprites = globalScene.field.getAll().slice(0); globalScene.tweens.addCounter({ - duration: Utils.getFrameMs(3) * frameTimeMult, + duration: getFrameMs(3) * frameTimeMult, repeat: anim!.frames.length, onRepeat: () => { existingFieldSprites = globalScene.field.getAll().slice(0); @@ -1215,11 +1222,12 @@ export abstract class BattleAnim { } } if (anim?.frameTimedEvents.get(frameCount)) { + const base = anim.frames.length - frameCount; for (const event of anim.frameTimedEvents.get(frameCount)!) { - totalFrames = Math.max((anim.frames.length - frameCount) + event.execute(this, frameTimedEventPriority), totalFrames); + totalFrames = Math.max(base + event.execute(this, frameTimedEventPriority), totalFrames); } } - const targets = Utils.getEnumValues(AnimFrameTarget); + const targets = getEnumValues(AnimFrameTarget); for (const i of targets) { const count = graphicFrameCount; if (count < spriteCache[i].length) { @@ -1244,7 +1252,7 @@ export abstract class BattleAnim { } if (totalFrames) { globalScene.tweens.addCounter({ - duration: Utils.getFrameMs(totalFrames), + duration: getFrameMs(totalFrames), onComplete: () => cleanUpAndComplete() }); } else { @@ -1342,15 +1350,15 @@ export class EncounterBattleAnim extends BattleAnim { } export async function populateAnims() { - const commonAnimNames = Utils.getEnumKeys(CommonAnim).map(k => k.toLowerCase()); + const commonAnimNames = getEnumKeys(CommonAnim).map(k => k.toLowerCase()); const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/\_/g, "")); - const commonAnimIds = Utils.getEnumValues(CommonAnim) as CommonAnim[]; - const chargeAnimNames = Utils.getEnumKeys(ChargeAnim).map(k => k.toLowerCase()); + const commonAnimIds = getEnumValues(CommonAnim) as CommonAnim[]; + const chargeAnimNames = getEnumKeys(ChargeAnim).map(k => k.toLowerCase()); const chargeAnimMatchNames = chargeAnimNames.map(k => k.replace(/\_/g, " ")); - const chargeAnimIds = Utils.getEnumValues(ChargeAnim) as ChargeAnim[]; + const chargeAnimIds = getEnumValues(ChargeAnim) as ChargeAnim[]; const commonNamePattern = /name: (?:Common:)?(Opp )?(.*)/; const moveNameToId = {}; - for (const move of Utils.getEnumValues(Moves).slice(1)) { + for (const move of getEnumValues(Moves).slice(1)) { const moveName = Moves[move].toUpperCase().replace(/\_/g, ""); moveNameToId[moveName] = move; } From cabcfcbd39968c01cb03e4e76288a716926687b1 Mon Sep 17 00:00:00 2001 From: Xavion3 Date: Mon, 3 Mar 2025 13:22:37 +1100 Subject: [PATCH 16/18] [Balance] Make megas/max player pokemon unable to tera (#5469) Co-authored-by: damocleas --- src/field/pokemon.ts | 14 ++++++++++++-- src/ui/command-ui-handler.ts | 5 ++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index f0486a8f111..53d4b6c54d2 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1226,10 +1226,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * Checks if the {@linkcode Pokemon} has is the specified {@linkcode Species} or is fused with it. * @param species the pokemon {@linkcode Species} to check + * @param formKey If provided, requires the species to be in that form * @returns `true` if the pokemon is the species or is fused with it, `false` otherwise */ - hasSpecies(species: Species): boolean { - return this.species.speciesId === species || this.fusionSpecies?.speciesId === species; + hasSpecies(species: Species, formKey?: string): boolean { + if (Utils.isNullOrUndefined(formKey)) { + return this.species.speciesId === species || this.fusionSpecies?.speciesId === species; + } + + return (this.species.speciesId === species && this.getFormKey() === formKey) || (this.fusionSpecies?.speciesId === species && this.getFusionFormKey() === formKey); } abstract isBoss(): boolean; @@ -3204,6 +3209,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return maxForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && maxForms.includes(this.getFusionFormKey()!)); } + isMega(): boolean { + const megaForms = [ SpeciesFormKey.MEGA, SpeciesFormKey.MEGA_X, SpeciesFormKey.MEGA_Y, SpeciesFormKey.PRIMAL ] as string[]; + return megaForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && megaForms.includes(this.getFusionFormKey()!)); + } + canAddTag(tagType: BattlerTagType): boolean { if (this.getTag(tagType)) { return false; diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index f23cc78c9f7..20cffbbe30a 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -10,6 +10,7 @@ import { globalScene } from "#app/global-scene"; import { TerastallizeAccessModifier } from "#app/modifier/modifier"; import { Type } from "#app/enums/type"; import { getTypeRgb } from "#app/data/type"; +import { Species } from "#enums/species"; export enum Command { FIGHT = 0, @@ -180,9 +181,11 @@ export default class CommandUiHandler extends UiHandler { canTera(): boolean { const hasTeraMod = !!globalScene.getModifiers(TerastallizeAccessModifier).length; + const activePokemon = globalScene.getField()[this.fieldIndex]; + const isBlockedForm = activePokemon.isMega() || activePokemon.isMax() || activePokemon.hasSpecies(Species.NECROZMA, "ultra"); const currentTeras = globalScene.arena.playerTerasUsed; const plannedTera = globalScene.currentBattle.preTurnCommands[0]?.command === Command.TERA && this.fieldIndex > 0 ? 1 : 0; - return hasTeraMod && (currentTeras + plannedTera) < 1; + return hasTeraMod && !isBlockedForm && (currentTeras + plannedTera) < 1; } toggleTeraButton() { From ff4184e23f99cd918ce867ea1577a040ea481e28 Mon Sep 17 00:00:00 2001 From: damocleas Date: Mon, 3 Mar 2025 19:33:53 -0500 Subject: [PATCH 17/18] [Balance] Make Pickup and Honey Gather Unsuppressable (#5476) --- src/data/ability.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 2b73fea60bc..37b97ffb5e6 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -6186,7 +6186,8 @@ export function initAbilities() { .attr(ProtectStatAbAttr, Stat.ATK) .ignorable(), new Ability(Abilities.PICKUP, 3) - .attr(PostBattleLootAbAttr), + .attr(PostBattleLootAbAttr) + .attr(UnsuppressableAbilityAbAttr), new Ability(Abilities.TRUANT, 3) .attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.TRUANT, 1, false), new Ability(Abilities.HUSTLE, 3) @@ -6378,7 +6379,8 @@ export function initAbilities() { .attr(PostSummonWeatherChangeAbAttr, WeatherType.SNOW) .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SNOW), new Ability(Abilities.HONEY_GATHER, 4) - .attr(MoneyAbAttr), + .attr(MoneyAbAttr) + .attr(UnsuppressableAbilityAbAttr), new Ability(Abilities.FRISK, 4) .attr(FriskAbAttr), new Ability(Abilities.RECKLESS, 4) From 6181afc6d2c311c569882306ed7c6478b69ebc85 Mon Sep 17 00:00:00 2001 From: David Yang Date: Tue, 4 Mar 2025 08:40:04 +0800 Subject: [PATCH 18/18] [UI/UX] Make left button also go back to run history from run info (#5474) --- src/ui/run-info-ui-handler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index bf07374e21a..f1fe9ac8194 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -889,7 +889,7 @@ export default class RunInfoUiHandler extends UiHandler { /** * Takes input from the user to perform a desired action. * @param button - Button object to be processed - * Button.CANCEL - removes all containers related to RunInfo and returns the user to Run History + * Button.CANCEL, Button.LEFT - removes all containers related to RunInfo and returns the user to Run History * Button.CYCLE_FORM, Button.CYCLE_SHINY, Button.CYCLE_ABILITY - runs the function buttonCycleOption() */ override processInput(button: Button): boolean { @@ -900,6 +900,7 @@ export default class RunInfoUiHandler extends UiHandler { switch (button) { case Button.CANCEL: + case Button.LEFT: success = true; if (this.pageMode === RunInfoUiMode.MAIN) { this.runInfoContainer.removeAll(true);