From 58bf18af88a96c8f011dc9b42fbfbd6f9af3cadd Mon Sep 17 00:00:00 2001 From: Opaque02 <66582645+Opaque02@users.noreply.github.com> Date: Sun, 18 Aug 2024 15:39:08 +1000 Subject: [PATCH 01/13] Fixed issue with falsy issue within condition to get a stat for IV scanner --- src/ui/battle-message-ui-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/battle-message-ui-handler.ts b/src/ui/battle-message-ui-handler.ts index 1c7dfb27630..7a30e2787df 100644 --- a/src/ui/battle-message-ui-handler.ts +++ b/src/ui/battle-message-ui-handler.ts @@ -226,7 +226,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler { highestIv = ivs[s]; } }); - if (shownStat) { + if (shownStat !== null && shownStat !== undefined) { shownStats.push(shownStat); statsPool.splice(statsPool.indexOf(shownStat), 1); } From 69c1389ec4ceda8e9389129118a4456edbffe15c Mon Sep 17 00:00:00 2001 From: KimJeongSun Date: Sun, 18 Aug 2024 15:12:20 +0900 Subject: [PATCH 02/13] add fix setting code to prevent form/variant bug when default form/variant setting is wrong. in addition, that fix code include gender fix, so i revert old gender fix. update wrong log message. --- src/ui/starter-select-ui-handler.ts | 96 ++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 5e942f3e75a..9f2df1f2329 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2916,14 +2916,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const isCaught = this.scene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0); const isVariant3Caught = !!(isCaught & DexAttr.VARIANT_3); const isVariant2Caught = !!(isCaught & DexAttr.VARIANT_2); + const isDefaultVariantCaught = !!(isCaught & DexAttr.DEFAULT_VARIANT); const isVariantCaught = !!(isCaught & DexAttr.SHINY); const isMaleCaught = !!(isCaught & DexAttr.MALE); const isFemaleCaught = !!(isCaught & DexAttr.FEMALE); + const starterAttributes = this.starterPreferences[species.speciesId]; + + const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); + const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); + const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); + if (!dexEntry.caughtAttr) { - const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); - const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); - const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); if (shiny === undefined || shiny !== props.shiny) { shiny = props.shiny; } @@ -2942,6 +2946,83 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (natureIndex === undefined || natureIndex !== defaultNature) { natureIndex = defaultNature; } + } else { + // compare current shiny, formIndex, female, variant, abilityIndex, natureIndex with the caught ones + // if the current ones are not caught, we need to find the next caught ones + if (shiny) { + if (!(isVariantCaught || isVariant2Caught || isVariant3Caught)) { + shiny = false; + starterAttributes.shiny = false; + variant = 0; + starterAttributes.variant = 0; + } else { + shiny = true; + starterAttributes.shiny = true; + if (variant === 0 && !isDefaultVariantCaught) { + if (isVariant2Caught) { + variant = 1; + starterAttributes.variant = 1; + } else if (isVariant3Caught) { + variant = 2; + starterAttributes.variant = 2; + } else { + variant = 0; + starterAttributes.variant = 0; + } + } else if (variant === 1 && !isVariant2Caught) { + if (isVariantCaught) { + variant = 0; + starterAttributes.variant = 0; + } else if (isVariant3Caught) { + variant = 2; + starterAttributes.variant = 2; + } else { + variant = 0; + starterAttributes.variant = 0; + } + } else if (variant === 2 && !isVariant3Caught) { + if (isVariantCaught) { + variant = 0; + starterAttributes.variant = 0; + } else if (isVariant2Caught) { + variant = 1; + starterAttributes.variant = 1; + } else { + variant = 0; + starterAttributes.variant = 0; + } + } + } + } + if (female) { + if (!isFemaleCaught) { + female = false; + starterAttributes.female = false; + } + } else { + if (!isMaleCaught) { + female = true; + starterAttributes.female = true; + } + } + + if (species.forms) { + const formCount = species.forms.length; + let newFormIndex = formIndex??0; + if (species.forms[newFormIndex]) { + const isValidForm = species.forms[newFormIndex].isStarterSelectable && dexEntry.caughtAttr & this.scene.gameData.getFormAttr(newFormIndex); + if (!isValidForm) { + do { + newFormIndex = (newFormIndex + 1) % formCount; + if (species.forms[newFormIndex].isStarterSelectable && dexEntry.caughtAttr & this.scene.gameData.getFormAttr(newFormIndex)) { + break; + } + } while (newFormIndex !== props.formIndex); + formIndex = newFormIndex; + starterAttributes.form = formIndex; + } + } + } } this.shinyOverlay.setVisible(shiny ?? false); // TODO: is false the correct default? @@ -2993,12 +3074,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } if (dexEntry.caughtAttr && species.malePercent !== null) { - let gender: Gender; - if ((female && isFemaleCaught) || (!female && !isMaleCaught)) { - gender = Gender.FEMALE; - } else { - gender = Gender.MALE; - } + const gender = !female ? Gender.MALE : Gender.FEMALE; this.pokemonGenderText.setText(getGenderSymbol(gender)); this.pokemonGenderText.setColor(getGenderColor(gender)); this.pokemonGenderText.setShadowColor(getGenderColor(gender, true)); @@ -3479,7 +3555,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { checkIconId(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, female: boolean, formIndex: number, shiny: boolean, variant: number) { if (icon.frame.name !== species.getIconId(female, formIndex, shiny, variant)) { - console.log(`${species.name}'s variant icon does not exist. Replacing with default.`); + console.log(`${species.name}'s icon ${icon.frame.name} does not match getIconId with female: ${female}, formIndex: ${formIndex}, shiny: ${shiny}, variant: ${variant}`); icon.setTexture(species.getIconAtlasKey(formIndex, false, variant)); icon.setFrame(species.getIconId(female, formIndex, false, variant)); } From 1b7a161934930c1de4493182f82386ff2b223073 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 18 Aug 2024 09:52:32 -0700 Subject: [PATCH 03/13] [Hotfix] Fix Memory Mushroom not showing relearner moves (#3619) * Fix Memory Mushroom not showing relearner moves * Fix rollout test --- src/field/pokemon.ts | 2 +- src/test/moves/rollout.test.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index f1721299ad0..10851451a1a 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -921,7 +921,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * by how many learnable moves there are for the {@linkcode Pokemon}. */ getLearnableLevelMoves(): Moves[] { - let levelMoves = this.getLevelMoves(1, true).map(lm => lm[1]); + let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]); if (this.metBiome === -1 && !this.scene.gameMode.isFreshStartChallenge() && !this.scene.gameMode.isDaily) { levelMoves = this.getUnlockedEggMoves().concat(levelMoves); } diff --git a/src/test/moves/rollout.test.ts b/src/test/moves/rollout.test.ts index ad323c447f5..728fe1ecd45 100644 --- a/src/test/moves/rollout.test.ts +++ b/src/test/moves/rollout.test.ts @@ -12,6 +12,7 @@ import { SPLASH_ONLY } from "#test/utils/testUtils"; describe("Moves - Rollout", () => { let phaserGame: Phaser.Game; let game: GameManager; + const TIMEOUT = 20 * 1000; beforeAll(() => { phaserGame = new Phaser.Game({ @@ -77,5 +78,5 @@ describe("Moves - Rollout", () => { // reset expect(turn6Dmg).toBeGreaterThanOrEqual(turn1Dmg - variance); expect(turn6Dmg).toBeLessThanOrEqual(turn1Dmg + variance); - }); + }, TIMEOUT); }); From 67da7956119afe32f7ec178a32c14d1488fec6cc Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Sun, 18 Aug 2024 10:29:11 -0700 Subject: [PATCH 04/13] Rewrite player faint logic in FaintPhase (#3614) --- src/phases.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/phases.ts b/src/phases.ts index 88acfc825ef..565914879e4 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -4033,13 +4033,24 @@ export class FaintPhase extends PokemonPhase { } if (this.player) { - const nonFaintedLegalPartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()); - const nonFaintedPartyMemberCount = nonFaintedLegalPartyMembers.length; - if (!nonFaintedPartyMemberCount) { + /** The total number of Pokemon in the player's party that can legally fight */ + const legalPlayerPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); + /** The total number of legal player Pokemon that aren't currently on the field */ + const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); + if (!legalPlayerPokemon.length) { + /** If the player doesn't have any legal Pokemon, end the game */ this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } else if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) { + } else if (this.scene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { + /** + * If the player has exactly one Pokemon in total at this point in a double battle, and that Pokemon + * is already on the field, unshift a phase that moves that Pokemon to center position. + */ this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); - } else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount()) { + } else if (legalPlayerPartyPokemon.length > 0) { + /** + * If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field, + * push a phase that prompts the player to summon a Pokemon from their party. + */ this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); } } else { From c8ed89e186dfeb06f521caaee643ef30a2bfe653 Mon Sep 17 00:00:00 2001 From: cam Date: Sun, 18 Aug 2024 14:44:50 -0400 Subject: [PATCH 05/13] 867 runerigus sprite (#3629) cropped static frames, fixed cropped sprite set runerigus exp to use the shiny exp's animation verified all hex colors are unchanged - fixed ultra necrozma exp front variant swapped arrays. - xatu female eye color fix --- public/images/pokemon/867.png | Bin 2128 -> 1047 bytes public/images/pokemon/back/867.png | Bin 1617 -> 800 bytes public/images/pokemon/back/shiny/867.png | Bin 743 -> 793 bytes public/images/pokemon/exp/867.json | 1590 +++++++++-------- public/images/pokemon/exp/867.png | Bin 23716 -> 13409 bytes public/images/pokemon/exp/back/867.png | Bin 7341 -> 3724 bytes public/images/pokemon/exp/back/shiny/867.png | Bin 3270 -> 3715 bytes public/images/pokemon/shiny/867.png | Bin 976 -> 1043 bytes .../pokemon/variant/back/female/178_2.png | Bin 6304 -> 6305 bytes .../pokemon/variant/back/female/178_3.png | Bin 6304 -> 6305 bytes .../images/pokemon/variant/exp/800-ultra.json | 32 +- 11 files changed, 842 insertions(+), 780 deletions(-) diff --git a/public/images/pokemon/867.png b/public/images/pokemon/867.png index bd07a7a48375858f3ed2506c81e51befc2c02db8..2fe8856d74df6113ece362f16ddf144140f82469 100644 GIT binary patch literal 1047 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 P00000NkvXXu0mjf&XWG+ diff --git a/public/images/pokemon/back/shiny/867.png b/public/images/pokemon/back/shiny/867.png index fce3fe4e37eaa10d77894bfd02c7d8da05536297..accb87844f1c680b67e46ba371405a992adc6d83 100644 GIT binary patch delta 750 zcmV5(BMe+P6@PE!E?|NsC0 z|D}JX`v3p}pGibPR9Jqa*agmie?PDZ&UyddX7~rT!GZm%V?Es2RjnQSRPJDBlM4HuAJda8HmT((A8Rw` z{B#EfbhRs-=o2*qe^53nmdFk9(M=D zhCK|_cDY4py*!63u(0(_>Cmfr?`?j*UMAWSwuZ4COtVhEI=jj3fp)%wcj`U5UR$9@ zOs0<`=a}3T#(x`Kz--ztZ(jKIuXn=Cps8%e<^dk{s8Kxfd(AYtfz<$ z$1*oRz?;i<$J~?mZZyaf*e68$$;qYb^(4j<&}h*?YF|go5D+*CZ-w$CXi-3lYIjm- z0_ZbZMqo9Z2N|S9k8$x~yv~f(e_CjC^btIwHkPDK->^lEZ6Mu(H+|0m-uhfa4A&vKT$K#o#tv{ju%09~ zMOV7?+t>pNua>{M=p4!O8trMo)$Efxdq}Su1c`VRbY0FK$p~B$Eoj8_jVy}^{eTWL zqh)$M-pTm|%uP(0wr{qdn{6~pF3QYd+1wYb7J#g>e=G>;gKfEV9{nzK$|k>bZNR0v zYr?#iFYRyVkXhD+I*|K4&yA2V$=|qSpg(Se%uAOKTI!riI2_kxEqT}lopqU-7LEI| zq@&S0e}@=TewB7IjhE1ygYI^6i`Df4jdMC=4`OaGXRZZpG`J$IgJ7}7UhG4Z zJy86;Oe&h@SI4yetE5)E9#8JOwq^X*Jd|C-fP?=&e}58ozTs{!=e;NH7KHXR#*6lQ gHMYNUz5d|Ae==DedM}{`Q~&?~07*qoM6N<$f`sjD`Tzg` delta 700 zcmV;t0z>_o2ImEk7YbPf0{{R3rzMq~kuD^E0d!JMQvg8b*k%9#0)R!%Iy%a=nku)zv+9krb^hCtxO(GoZl3U5D`z{ulmBA-ZP+HM{B52`nw@84Nx#c$hBASTAqs6EDU*#pe&7*4Z9u>QCUCSd2O&X+{a2myz`wHNw5SQDalhkUW z44hIDeO)<&k*=^{jjmR|LlP5*%E-KGNa_xjpSKwxc2km!+;Dl&6bR}J4(_e zo|F^m+N@}hH->e$X@HjasGLZMh(a`f$r3}|?UMTiPq0zTztvaW46RX}D1$XrCm)Oj zI;w*!As;N3$uoj3l1s+P!@xdhHQr#+i2%_l^)ids6+dd{u<6lbnd*`V{S+Eq(vSwW z2(p}{5v^18l!4G3SgGC4wrb}BKAV2Pd=6BzH7ajPUi?DV;mYG75Q77Q5Wasl9YW ztVwR9^MJJM>(niylzE=hAWHe3j`UH^Sv&Xjz%mP`ToJ|}fZ diff --git a/public/images/pokemon/exp/867.json b/public/images/pokemon/exp/867.json index 1a9c7572f43..52e0127509b 100644 --- a/public/images/pokemon/exp/867.json +++ b/public/images/pokemon/exp/867.json @@ -4,8 +4,8 @@ "image": "867.png", "format": "RGBA8888", "size": { - "w": 344, - "h": 344 + "w": 361, + "h": 361 }, "scale": 1, "frames": [ @@ -31,7 +31,7 @@ } }, { - "filename": "0020.png", + "filename": "0021.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -52,7 +52,7 @@ } }, { - "filename": "0035.png", + "filename": "0037.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -94,70 +94,7 @@ } }, { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 113, - "h": 56 - }, - "frame": { - "x": 114, - "y": 0, - "w": 113, - "h": 56 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 113, - "h": 56 - }, - "frame": { - "x": 114, - "y": 0, - "w": 113, - "h": 56 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 113, - "h": 56 - }, - "frame": { - "x": 114, - "y": 0, - "w": 113, - "h": 56 - } - }, - { - "filename": "0034.png", + "filename": "0020.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -199,7 +136,7 @@ } }, { - "filename": "0003.png", + "filename": "0006.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -207,20 +144,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 4, + "x": 2, "y": 0, - "w": 107, + "w": 112, "h": 56 }, "frame": { "x": 227, "y": 0, - "w": 107, + "w": 112, "h": 56 } }, { - "filename": "0018.png", + "filename": "0022.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -228,20 +165,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 4, + "x": 2, "y": 0, - "w": 107, + "w": 112, "h": 56 }, "frame": { "x": 227, "y": 0, - "w": 107, + "w": 112, "h": 56 } }, { - "filename": "0033.png", + "filename": "0038.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -249,62 +186,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 4, + "x": 2, "y": 0, - "w": 107, + "w": 112, "h": 56 }, "frame": { "x": 227, "y": 0, - "w": 107, + "w": 112, "h": 56 } }, { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 103, - "h": 65 - }, - "frame": { - "x": 0, - "y": 56, - "w": 103, - "h": 65 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 103, - "h": 65 - }, - "frame": { - "x": 0, - "y": 56, - "w": 103, - "h": 65 - } - }, - { - "filename": "0026.png", + "filename": "0012.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -346,7 +241,7 @@ } }, { - "filename": "0041.png", + "filename": "0044.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -367,7 +262,7 @@ } }, { - "filename": "0043.png", + "filename": "0003.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -375,16 +270,58 @@ "h": 66 }, "spriteSourceSize": { - "x": 5, + "x": 4, "y": 0, - "w": 103, - "h": 65 + "w": 107, + "h": 56 }, "frame": { - "x": 0, + "x": 103, "y": 56, - "w": 103, - "h": 65 + "w": 107, + "h": 56 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 107, + "h": 56 + }, + "frame": { + "x": 103, + "y": 56, + "w": 107, + "h": 56 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 107, + "h": 56 + }, + "frame": { + "x": 103, + "y": 56, + "w": 107, + "h": 56 } }, { @@ -402,14 +339,14 @@ "h": 56 }, "frame": { - "x": 103, + "x": 210, "y": 56, "w": 107, "h": 56 } }, { - "filename": "0022.png", + "filename": "0023.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -423,14 +360,14 @@ "h": 56 }, "frame": { - "x": 103, + "x": 210, "y": 56, "w": 107, "h": 56 } }, { - "filename": "0037.png", + "filename": "0039.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -444,12 +381,54 @@ "h": 56 }, "frame": { - "x": 103, + "x": 210, "y": 56, "w": 107, "h": 56 } }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 1, + "w": 44, + "h": 56 + }, + "frame": { + "x": 317, + "y": 56, + "w": 44, + "h": 56 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 1, + "w": 44, + "h": 56 + }, + "frame": { + "x": 317, + "y": 56, + "w": 44, + "h": 56 + } + }, { "filename": "0002.png", "rotated": false, @@ -465,14 +444,14 @@ "h": 57 }, "frame": { - "x": 210, - "y": 56, + "x": 103, + "y": 112, "w": 102, "h": 57 } }, { - "filename": "0017.png", + "filename": "0018.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -486,14 +465,14 @@ "h": 57 }, "frame": { - "x": 210, - "y": 56, + "x": 103, + "y": 112, "w": 102, "h": 57 } }, { - "filename": "0032.png", + "filename": "0034.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -507,12 +486,138 @@ "h": 57 }, "frame": { - "x": 210, - "y": 56, + "x": 103, + "y": 112, "w": 102, "h": 57 } }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 101, + "h": 65 + }, + "frame": { + "x": 0, + "y": 121, + "w": 101, + "h": 65 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 101, + "h": 65 + }, + "frame": { + "x": 0, + "y": 121, + "w": 101, + "h": 65 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 101, + "h": 65 + }, + "frame": { + "x": 0, + "y": 121, + "w": 101, + "h": 65 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 101, + "h": 61 + }, + "frame": { + "x": 205, + "y": 112, + "w": 101, + "h": 61 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 101, + "h": 61 + }, + "frame": { + "x": 205, + "y": 112, + "w": 101, + "h": 61 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 101, + "h": 61 + }, + "frame": { + "x": 205, + "y": 112, + "w": 101, + "h": 61 + } + }, { "filename": "0008.png", "rotated": false, @@ -524,266 +629,14 @@ "spriteSourceSize": { "x": 6, "y": 0, - "w": 102, + "w": 101, "h": 57 }, - "frame": { - "x": 103, - "y": 112, - "w": 102, - "h": 57 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 102, - "h": 57 - }, - "frame": { - "x": 103, - "y": 112, - "w": 102, - "h": 57 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 102, - "h": 57 - }, - "frame": { - "x": 103, - "y": 112, - "w": 102, - "h": 57 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 101, - "h": 65 - }, - "frame": { - "x": 0, - "y": 121, - "w": 101, - "h": 65 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 101, - "h": 65 - }, - "frame": { - "x": 0, - "y": 121, - "w": 101, - "h": 65 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 101, - "h": 65 - }, - "frame": { - "x": 0, - "y": 121, - "w": 101, - "h": 65 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 8, - "y": 0, - "w": 99, - "h": 59 - }, "frame": { "x": 101, "y": 169, - "w": 99, - "h": 59 + "w": 101, + "h": 57 } }, { @@ -795,20 +648,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 6, "y": 0, - "w": 99, - "h": 59 + "w": 101, + "h": 57 }, "frame": { "x": 101, "y": 169, - "w": 99, - "h": 59 + "w": 101, + "h": 57 } }, { - "filename": "0039.png", + "filename": "0040.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -816,20 +669,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 6, "y": 0, - "w": 99, - "h": 59 + "w": 101, + "h": 57 }, "frame": { "x": 101, "y": 169, - "w": 99, - "h": 59 + "w": 101, + "h": 57 } }, { - "filename": "0015.png", + "filename": "0010.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -849,6 +702,195 @@ "h": 59 } }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 99, + "h": 59 + }, + "frame": { + "x": 0, + "y": 186, + "w": 99, + "h": 59 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 99, + "h": 59 + }, + "frame": { + "x": 0, + "y": 186, + "w": 99, + "h": 59 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 0, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 39, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 166, + "w": 44, + "h": 54 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 39, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 166, + "w": 44, + "h": 54 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 97, + "h": 65 + }, + "frame": { + "x": 202, + "y": 173, + "w": 97, + "h": 65 + } + }, { "filename": "0030.png", "rotated": false, @@ -858,20 +900,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 5, "y": 0, - "w": 99, - "h": 59 + "w": 97, + "h": 65 }, "frame": { - "x": 0, - "y": 186, - "w": 99, - "h": 59 + "x": 202, + "y": 173, + "w": 97, + "h": 65 } }, { - "filename": "0045.png", + "filename": "0046.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -879,16 +921,58 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 5, "y": 0, - "w": 99, - "h": 59 + "w": 97, + "h": 65 }, "frame": { - "x": 0, - "y": 186, - "w": 99, - "h": 59 + "x": 202, + "y": 173, + "w": 97, + "h": 65 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 29, + "y": 1, + "w": 58, + "h": 57 + }, + "frame": { + "x": 299, + "y": 220, + "w": 58, + "h": 57 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 29, + "y": 1, + "w": 58, + "h": 57 + }, + "frame": { + "x": 299, + "y": 220, + "w": 58, + "h": 57 } }, { @@ -906,8 +990,134 @@ "h": 59 }, "frame": { - "x": 0, - "y": 245, + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, "w": 95, "h": 59 } @@ -921,146 +1131,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 95, - "h": 59 - }, - "frame": { - "x": 0, - "y": 245, - "w": 95, - "h": 59 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 95, - "h": 59 - }, - "frame": { - "x": 0, - "y": 245, - "w": 95, - "h": 59 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 95, - "h": 59 - }, - "frame": { - "x": 0, - "y": 245, - "w": 95, - "h": 59 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 12, - "y": 1, - "w": 90, - "h": 59 - }, - "frame": { - "x": 95, - "y": 245, - "w": 90, - "h": 59 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 22, - "y": 1, - "w": 70, - "h": 59 - }, - "frame": { - "x": 185, - "y": 228, - "w": 70, - "h": 59 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 22, - "y": 1, - "w": 70, - "h": 59 - }, - "frame": { - "x": 185, - "y": 228, - "w": 70, - "h": 59 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, + "x": 8, "y": 0, - "w": 44, - "h": 54 + "w": 95, + "h": 59 }, "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 + "x": 0, + "y": 245, + "w": 95, + "h": 59 } }, { - "filename": "0052.png", + "filename": "0032.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1068,100 +1152,16 @@ "h": 66 }, "spriteSourceSize": { - "x": 40, - "y": 3, - "w": 44, - "h": 54 + "x": 8, + "y": 0, + "w": 95, + "h": 59 }, "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 39, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 244, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 39, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 244, - "y": 174, - "w": 44, - "h": 54 + "x": 0, + "y": 245, + "w": 95, + "h": 59 } }, { @@ -1173,41 +1173,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 29, - "y": 1, - "w": 58, - "h": 57 + "x": 8, + "y": 0, + "w": 95, + "h": 59 }, "frame": { - "x": 185, - "y": 287, - "w": 58, - "h": 57 + "x": 0, + "y": 245, + "w": 95, + "h": 59 } }, { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 29, - "y": 1, - "w": 58, - "h": 57 - }, - "frame": { - "x": 185, - "y": 287, - "w": 58, - "h": 57 - } - }, - { - "filename": "0049.png", + "filename": "0052.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1221,14 +1200,14 @@ "h": 56 }, "frame": { - "x": 288, - "y": 174, + "x": 0, + "y": 304, "w": 56, "h": 56 } }, { - "filename": "0062.png", + "filename": "0065.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1242,56 +1221,14 @@ "h": 56 }, "frame": { - "x": 288, - "y": 174, + "x": 0, + "y": 304, "w": 56, "h": 56 } }, { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 1, - "w": 44, - "h": 56 - }, - "frame": { - "x": 243, - "y": 287, - "w": 44, - "h": 56 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 1, - "w": 44, - "h": 56 - }, - "frame": { - "x": 243, - "y": 287, - "w": 44, - "h": 56 - } - }, - { - "filename": "0053.png", + "filename": "0056.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1305,29 +1242,8 @@ "h": 53 }, "frame": { - "x": 255, - "y": 230, - "w": 44, - "h": 53 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 41, - "y": 4, - "w": 44, - "h": 53 - }, - "frame": { - "x": 255, - "y": 230, + "x": 56, + "y": 304, "w": 44, "h": 53 } @@ -1341,20 +1257,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 38, + "x": 41, "y": 4, "w": 44, "h": 53 }, "frame": { - "x": 299, - "y": 230, + "x": 56, + "y": 304, "w": 44, "h": 53 } }, { - "filename": "0054.png", + "filename": "0015.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1362,14 +1278,161 @@ "h": 66 }, "spriteSourceSize": { - "x": 42, + "x": 7, + "y": 0, + "w": 94, + "h": 61 + }, + "frame": { + "x": 194, + "y": 238, + "w": 94, + "h": 61 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 94, + "h": 61 + }, + "frame": { + "x": 194, + "y": 238, + "w": 94, + "h": 61 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 94, + "h": 61 + }, + "frame": { + "x": 194, + "y": 238, + "w": 94, + "h": 61 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 22, + "y": 1, + "w": 70, + "h": 59 + }, + "frame": { + "x": 288, + "y": 277, + "w": 70, + "h": 59 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 22, + "y": 1, + "w": 70, + "h": 59 + }, + "frame": { + "x": 288, + "y": 277, + "w": 70, + "h": 59 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 12, + "y": 1, + "w": 90, + "h": 59 + }, + "frame": { + "x": 100, + "y": 299, + "w": 90, + "h": 59 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 37, + "y": 4, + "w": 44, + "h": 53 + }, + "frame": { + "x": 190, + "y": 299, + "w": 44, + "h": 53 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 43, "y": 5, "w": 44, "h": 52 }, "frame": { - "x": 287, - "y": 283, + "x": 234, + "y": 299, "w": 44, "h": 52 } @@ -1379,7 +1442,6 @@ ], "meta": { "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:b8ca75f7f37906e78ed633b32d037b74:92bc79a7ca35490600679451c06105fc:58bcd289dd222ce646aec14ff657c9fc$" + "version": "3.0" } } diff --git a/public/images/pokemon/exp/867.png b/public/images/pokemon/exp/867.png index 689a72694ef90e5dd459be477119a20f39db8e6a..4f8f67842bdd6ee61b2ba0ca2afe8d7ddfd2c852 100644 GIT binary patch literal 13409 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

AEr)YqXKi=OLo?%#>feG5W8?bg!mY<> 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 z4));y diff --git a/public/images/pokemon/exp/back/867.png b/public/images/pokemon/exp/back/867.png index 766ce3f39ed80703b473d801230d7cedaf763998..b816f10a0def567345f8a9a7a87e041d4898b70a 100644 GIT binary patch literal 3724 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 M07*qoM6N<$g4SP4<^TWy delta 3246 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 g{(l;-f7U5(BVe+qO`PE!E?|NsC0 z|NsC0eu)%T000AxNklv-@}kI9IwyWjBS>=%FOxxDSo1p2<7xa(`${) z%q~PHUqqyC^rrMuC}$!{s>n%I^Y9R3FdYAq+!+hV#J`k9e;Qea#AqI+7=GMrI<7v8vXoDNN6!Jl8$5hU13p#+1}WtbaK3b{{ z>+?FKWe;BE1MBEMjRnxJ3XABgnrY2K8{v3af1=+h$w#B3vY>yfi}ZXN#{suw^VIR+ zeTP1Nu>Lms_ew5*)yCv?`mNgL@?$_6cV=Iv&j-%*id7vB(#!N)Cu(OFt&Nu6kxnc0 z+e4`i)lnNo8phNEtkdJ0MSNM%?!}snb1i#Fze+w@0T(A_>&^teXh?sZyP;s$UZV3` zKQ3bD?G(cb7k6T({kAszo~saPXotU1c<;{TYky$+CHsp1P4*n)l`H=Na`iFH*e8W{ P00000NkvXXu0mjfr?m7Y delta 926 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 delta 6214 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 diff --git a/public/images/pokemon/variant/back/female/178_3.png b/public/images/pokemon/variant/back/female/178_3.png index 32ffdd895c6950fdcec0edc08b2b41b37a01a7ae..9533621c6d696fda7b3f97abd127ba04cbcc78fd 100644 GIT binary patch delta 6215 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 delta 6214 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 diff --git a/public/images/pokemon/variant/exp/800-ultra.json b/public/images/pokemon/variant/exp/800-ultra.json index 53dd9b55df0..cab917ec271 100644 --- a/public/images/pokemon/variant/exp/800-ultra.json +++ b/public/images/pokemon/variant/exp/800-ultra.json @@ -1,21 +1,5 @@ { "1": { - "b0a080": "e552ec", - "f8f8e8": "ffe2ed", - "9b8259": "b021c5", - "e5e4c2": "ffb9f9", - "000000": "000000", - "bc9b4e": "900090", - "f8f8d0": "ff8ae9", - "e8e088": "ff49e7", - "d0b868": "d10cc7", - "7d673b": "510059", - "282828": "282828", - "f84040": "f84040", - "f88888": "1ae2e6", - "c81010": "00c2d2" - }, - "2": { "b0a080": "d96b23", "f8f8e8": "ffe1b8", "9b8259": "b43c06", @@ -30,5 +14,21 @@ "f84040": "f84040", "f88888": "f88888", "c81010": "c81010" + }, + "2": { + "b0a080": "e552ec", + "f8f8e8": "ffe2ed", + "9b8259": "b021c5", + "e5e4c2": "ffb9f9", + "000000": "000000", + "bc9b4e": "900090", + "f8f8d0": "ff8ae9", + "e8e088": "ff49e7", + "d0b868": "d10cc7", + "7d673b": "510059", + "282828": "282828", + "f84040": "f84040", + "f88888": "1ae2e6", + "c81010": "00c2d2" } } \ No newline at end of file From 65af7a56993e19c29681ca1ea9e9799ed4385373 Mon Sep 17 00:00:00 2001 From: Mumble Date: Sun, 18 Aug 2024 13:51:08 -0700 Subject: [PATCH 06/13] [Bug] Preventing the MBH from being stolen in Endless (#3630) * Endless MBH Fix * add import * Revert "add import" This reverts commit 814a4059c2830e972c348d698259535e117850bf. * Revert "Endless MBH Fix" This reverts commit 8eb448130132ff9eed614a2ec576926814008df0. * removed newline --------- Co-authored-by: Frederico Santos Co-authored-by: frutescens --- src/battle-scene.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 674b4e256f9..ae6cea2dcd1 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2419,9 +2419,14 @@ export default class BattleScene extends SceneBase { count = Math.max(count, Math.floor(chances / 2)); } getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance) - .map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this)); + .map(mt => { + const enemyModifier = mt.newModifier(enemyPokemon); + if (enemyModifier instanceof TurnHeldItemTransferModifier) { + enemyModifier.setTransferrableFalse(); + } + enemyModifier.add(this.enemyModifiers, false, this); + }); }); - this.updateModifiers(false).then(() => resolve()); }); } From a97803b99b53ae2bec104571fa3e63e70a21824c Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Sun, 18 Aug 2024 23:27:38 +0200 Subject: [PATCH 07/13] [Bug] Fix type-hints for immunity (#3620) * enable mock containers to be found by name * enable mock text to be found by name * add test coverage for type-hints Only for "immunity" and "status moves" --- src/field/pokemon.ts | 6 +- src/test/ui/transfer-item.test.ts | 1 - src/test/ui/type-hints.test.ts | 89 +++++++++++++++++++ src/test/utils/gameManager.ts | 3 + src/test/utils/helpers/settingsHelper.ts | 15 ++++ .../mocks/mocksContainer/mockContainer.ts | 7 +- .../utils/mocks/mocksContainer/mockText.ts | 16 ++-- src/ui/fight-ui-handler.ts | 13 +-- 8 files changed, 131 insertions(+), 19 deletions(-) create mode 100644 src/test/ui/type-hints.test.ts create mode 100644 src/test/utils/helpers/settingsHelper.ts diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 10851451a1a..e38813ed3c0 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1210,11 +1210,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * @param source - The Pokémon using the move. * @param move - The move being used. - * @returns The type damage multiplier or undefined if it's a status move + * @returns The type damage multiplier or 1 if it's a status move */ - getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier | undefined { + getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier { if (move.getMove().category === MoveCategory.STATUS) { - return undefined; + return 1; } return this.getAttackMoveEffectiveness(source, move, !this.battleData?.abilityRevealed); diff --git a/src/test/ui/transfer-item.test.ts b/src/test/ui/transfer-item.test.ts index bbb9a823ad9..9315971e484 100644 --- a/src/test/ui/transfer-item.test.ts +++ b/src/test/ui/transfer-item.test.ts @@ -87,7 +87,6 @@ describe("UI - Transfer Items", () => { handler.processInput(Button.ACTION); // select Pokemon expect(handler.optionsContainer.list.some((option) => (option as BBCodeText).text?.includes("Transfer"))).toBe(true); - game.phaseInterceptor.unlock(); }); diff --git a/src/test/ui/type-hints.test.ts b/src/test/ui/type-hints.test.ts new file mode 100644 index 00000000000..eb0191812e8 --- /dev/null +++ b/src/test/ui/type-hints.test.ts @@ -0,0 +1,89 @@ +import { Button } from "#app/enums/buttons.js"; +import { Moves } from "#app/enums/moves"; +import { Species } from "#app/enums/species"; +import { CommandPhase } from "#app/phases"; +import FightUiHandler from "#app/ui/fight-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import MockText from "../utils/mocks/mocksContainer/mockText"; +import { SPLASH_ONLY } from "../utils/testUtils"; + +describe("UI - Type Hints", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(async () => { + game = new GameManager(phaserGame); + game.settings.typeHints(true); //activate type hints + game.override.battleType("single").startingLevel(100).startingWave(1).enemyMoveset(SPLASH_ONLY); + }); + + it("check immunity color", async () => { + game.override + .battleType("single") + .startingLevel(100) + .startingWave(1) + .enemySpecies(Species.FLORGES) + .enemyMoveset(SPLASH_ONLY) + .moveset([Moves.DRAGON_CLAW]); + game.settings.typeHints(true); //activate type hints + + await game.startBattle([Species.RAYQUAZA]); + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + const { ui } = game.scene; + const handler = ui.getHandler(); + handler.processInput(Button.ACTION); // select "Fight" + game.phaseInterceptor.unlock(); + }); + + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const { ui } = game.scene; + const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); + const dragonClawText = movesContainer + .getAll() + .find((text) => text.text === "Dragon Claw")! as unknown as MockText; + + expect.soft(dragonClawText.color).toBe("#929292"); + ui.getHandler().processInput(Button.ACTION); + }); + await game.phaseInterceptor.to(CommandPhase); + }); + + it("check status move color", async () => { + game.override.enemySpecies(Species.FLORGES).moveset([Moves.GROWL]); + + await game.startBattle([Species.RAYQUAZA]); + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + const { ui } = game.scene; + const handler = ui.getHandler(); + handler.processInput(Button.ACTION); // select "Fight" + game.phaseInterceptor.unlock(); + }); + + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const { ui } = game.scene; + const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); + const growlText = movesContainer + .getAll() + .find((text) => text.text === "Growl")! as unknown as MockText; + + expect.soft(growlText.color).toBe(undefined); + ui.getHandler().processInput(Button.ACTION); + }); + await game.phaseInterceptor.to(CommandPhase); + }); +}); diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index 27ba7a215eb..6333179e3b2 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -30,6 +30,7 @@ import { MoveHelper } from "./helpers/moveHelper"; import { vi } from "vitest"; import { ClassicModeHelper } from "./helpers/classicModeHelper"; import { DailyModeHelper } from "./helpers/dailyModeHelper"; +import { SettingsHelper } from "./helpers/settingsHelper"; /** * Class to manage the game state and transitions between phases. @@ -44,6 +45,7 @@ export default class GameManager { public readonly move: MoveHelper; public readonly classicMode: ClassicModeHelper; public readonly dailyMode: DailyModeHelper; + public readonly settings: SettingsHelper; /** * Creates an instance of GameManager. @@ -63,6 +65,7 @@ export default class GameManager { this.move = new MoveHelper(this); this.classicMode = new ClassicModeHelper(this); this.dailyMode = new DailyModeHelper(this); + this.settings = new SettingsHelper(this); } /** diff --git a/src/test/utils/helpers/settingsHelper.ts b/src/test/utils/helpers/settingsHelper.ts new file mode 100644 index 00000000000..dec9e160d51 --- /dev/null +++ b/src/test/utils/helpers/settingsHelper.ts @@ -0,0 +1,15 @@ +import { GameManagerHelper } from "./gameManagerHelper"; + +/** + * Helper to handle settings for tests + */ +export class SettingsHelper extends GameManagerHelper { + + /** + * Disable/Enable type hints settings + * @param enable true to enabled, false to disabled + */ + typeHints(enable: boolean) { + this.game.scene.typeHints = enable; + } +} diff --git a/src/test/utils/mocks/mocksContainer/mockContainer.ts b/src/test/utils/mocks/mocksContainer/mockContainer.ts index d3672cb5235..5babd9e71b2 100644 --- a/src/test/utils/mocks/mocksContainer/mockContainer.ts +++ b/src/test/utils/mocks/mocksContainer/mockContainer.ts @@ -1,4 +1,5 @@ import MockTextureManager from "#test/utils/mocks/mockTextureManager"; +import { vi } from "vitest"; import { MockGameObject } from "../mockGameObject"; export default class MockContainer implements MockGameObject { @@ -13,6 +14,7 @@ export default class MockContainer implements MockGameObject { public frame; protected textureManager; public list: MockGameObject[] = []; + private name?: string; constructor(textureManager: MockTextureManager, x, y) { this.x = x; @@ -159,9 +161,10 @@ export default class MockContainer implements MockGameObject { // Moves this Game Object to be below the given Game Object in the display list. } - setName(name) { + setName = vi.fn((name: string) => { + this.name = name; // return this.phaserSprite.setName(name); - } + }); bringToTop(obj) { // Brings this Game Object to the top of its parents display list. diff --git a/src/test/utils/mocks/mocksContainer/mockText.ts b/src/test/utils/mocks/mocksContainer/mockText.ts index 5d405efadfd..6b9ecf083fd 100644 --- a/src/test/utils/mocks/mocksContainer/mockText.ts +++ b/src/test/utils/mocks/mocksContainer/mockText.ts @@ -1,4 +1,5 @@ import UI from "#app/ui/ui"; +import { vi } from "vitest"; import { MockGameObject } from "../mockGameObject"; export default class MockText implements MockGameObject { @@ -10,6 +11,8 @@ export default class MockText implements MockGameObject { public list: MockGameObject[] = []; public style; public text = ""; + private name?: string; + public color?: string; constructor(textureManager, x, y, content, styleOptions) { this.scene = textureManager.scene; @@ -190,10 +193,9 @@ export default class MockText implements MockGameObject { }; } - setColor(color) { - // Sets the tint of this Game Object. - // return this.phaserText.setColor(color); - } + setColor = vi.fn((color: string) => { + this.color = color; + }); setShadowColor(color) { // Sets the shadow color. @@ -219,9 +221,9 @@ export default class MockText implements MockGameObject { // return this.phaserText.setAlpha(alpha); } - setName(name) { - // return this.phaserText.setName(name); - } + setName = vi.fn((name: string) => { + this.name = name; + }); setAlign(align) { // return this.phaserText.setAlign(align); diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 8279ab72a70..4ade6ca5d20 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -12,6 +12,8 @@ import {Button} from "#enums/buttons"; import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; export default class FightUiHandler extends UiHandler { + public static readonly MOVES_CONTAINER_NAME = "moves"; + private movesContainer: Phaser.GameObjects.Container; private moveInfoContainer: Phaser.GameObjects.Container; private typeIcon: Phaser.GameObjects.Sprite; @@ -35,7 +37,7 @@ export default class FightUiHandler extends UiHandler { const ui = this.getUi(); this.movesContainer = this.scene.add.container(18, -38.7); - this.movesContainer.setName("moves"); + this.movesContainer.setName(FightUiHandler.MOVES_CONTAINER_NAME); ui.add(this.movesContainer); this.moveInfoContainer = this.scene.add.container(1, 0); @@ -271,11 +273,10 @@ export default class FightUiHandler extends UiHandler { return undefined; } - const moveColors = opponents.map((opponent) => { - return opponent.getMoveEffectiveness(pokemon, pokemonMove); - }).filter((eff) => !!eff).sort((a, b) => b - a).map((effectiveness) => { - return getTypeDamageMultiplierColor(effectiveness, "offense"); - }); + const moveColors = opponents + .map((opponent) => opponent.getMoveEffectiveness(pokemon, pokemonMove)) + .sort((a, b) => b - a) + .map((effectiveness) => getTypeDamageMultiplierColor(effectiveness ?? 0, "offense")); return moveColors[0]; } From 1db26dab961451b7185cf492e7666f5044bc2d0a Mon Sep 17 00:00:00 2001 From: Enoch Date: Mon, 19 Aug 2024 08:50:31 +0900 Subject: [PATCH 08/13] fix wrong message key of curse(ghost type) (#3631) Co-authored-by: Frederico Santos --- 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 79e67ece581..24651bacb2e 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4441,7 +4441,7 @@ export class CurseAttr extends MoveEffectAttr { const curseRecoilDamage = Math.max(1, Math.floor(user.getMaxHp() / 2)); user.damageAndUpdate(curseRecoilDamage, HitResult.OTHER, false, true, true); user.scene.queueMessage( - i18next.t("battle:cursedOnAdd", { + i18next.t("battlerTags:cursedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user), pokemonName: getPokemonNameWithAffix(target) }) From a46e35b8ddbabf0e7764dc73efe98c7267dd7440 Mon Sep 17 00:00:00 2001 From: Mumble Date: Sun, 18 Aug 2024 16:59:18 -0700 Subject: [PATCH 09/13] [Hotfix] Steal-able Mini Black Hole Pt 2 (#3632) * Still have no idea where Eternatus is given the MBH.... * typedocs --------- Co-authored-by: frutescens --- src/battle-scene.ts | 8 +------- src/phases.ts | 4 ++++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index ae6cea2dcd1..4faf3863e3c 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2419,13 +2419,7 @@ export default class BattleScene extends SceneBase { count = Math.max(count, Math.floor(chances / 2)); } getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance) - .map(mt => { - const enemyModifier = mt.newModifier(enemyPokemon); - if (enemyModifier instanceof TurnHeldItemTransferModifier) { - enemyModifier.setTransferrableFalse(); - } - enemyModifier.add(this.enemyModifiers, false, this); - }); + .map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this)); }); this.updateModifiers(false).then(() => resolve()); }); diff --git a/src/phases.ts b/src/phases.ts index 565914879e4..c50d25acf60 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -878,6 +878,10 @@ export class EncounterPhase extends BattlePhase { } else if (!(battle.waveIndex % 1000)) { enemyPokemon.formIndex = 1; enemyPokemon.updateScale(); + const bossMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier && m.pokemonId === enemyPokemon.id, false) as TurnHeldItemTransferModifier; + this.scene.removeModifier(bossMBH!); + bossMBH?.setTransferrableFalse(); + this.scene.addEnemyModifier(bossMBH!); } } From 747e4f9360c9a7f297aab82b3da85dbfc3c401e4 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 18 Aug 2024 17:05:53 -0700 Subject: [PATCH 10/13] [Hotfix] Abilities that prevent ATK drops no longer stop other stat drops (#3624) * Abilities that prevent ATK drops no longer stop other stat drops * Apply suggestions from code review Co-authored-by: Mumble * Add `isNullOrUndefined()` utility function --------- --- src/data/ability.ts | 6 +-- src/test/abilities/hyper_cutter.test.ts | 58 +++++++++++++++++++++++++ src/utils.ts | 8 ++++ 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 src/test/abilities/hyper_cutter.test.ts diff --git a/src/data/ability.ts b/src/data/ability.ts index cfd900d621c..38ca4eb25d0 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2395,16 +2395,16 @@ export class PreStatChangeAbAttr extends AbAttr { } export class ProtectStatAbAttr extends PreStatChangeAbAttr { - private protectedStat: BattleStat | null; + private protectedStat?: BattleStat; constructor(protectedStat?: BattleStat) { super(); - this.protectedStat = protectedStat ?? null; + this.protectedStat = protectedStat; } applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (!this.protectedStat || stat === this.protectedStat) { + if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) { cancelled.value = true; return true; } diff --git a/src/test/abilities/hyper_cutter.test.ts b/src/test/abilities/hyper_cutter.test.ts new file mode 100644 index 00000000000..9637a80ddb4 --- /dev/null +++ b/src/test/abilities/hyper_cutter.test.ts @@ -0,0 +1,58 @@ +import { BattleStat } from "#app/data/battle-stat"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { SPLASH_ONLY } from "#test/utils/testUtils"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Hyper Cutter", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .moveset([Moves.SAND_ATTACK, Moves.NOBLE_ROAR, Moves.DEFOG, Moves.OCTOLOCK]) + .ability(Abilities.BALL_FETCH) + .enemySpecies(Species.SHUCKLE) + .enemyAbility(Abilities.HYPER_CUTTER) + .enemyMoveset(SPLASH_ONLY); + }); + + // Reference Link: https://bulbapedia.bulbagarden.net/wiki/Hyper_Cutter_(Ability) + + it("only prevents ATK drops", async () => { + await game.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.OCTOLOCK)); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.DEFOG)); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.NOBLE_ROAR)); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.SAND_ATTACK)); + await game.toNextTurn(); + game.override.moveset([Moves.STRING_SHOT]); + game.doAttack(getMovePosition(game.scene, 0, Moves.STRING_SHOT)); + await game.toNextTurn(); + + expect(enemy.summonData.battleStats[BattleStat.ATK]).toEqual(0); + [BattleStat.ACC, BattleStat.DEF, BattleStat.EVA, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD].forEach((stat: number) => expect(enemy.summonData.battleStats[stat]).toBeLessThan(0)); + }); +}); diff --git a/src/utils.ts b/src/utils.ts index aa45c091286..c51ac2b5b0b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -552,3 +552,11 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar: boole } return null; } + +/** + * Returns if an object is null or undefined + * @param object + */ +export function isNullOrUndefined(object: any): boolean { + return null === object || undefined === object; +} From bdde03b0d5b340e256e7800c5571f60ca2aa60a9 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Sun, 18 Aug 2024 18:06:52 -0700 Subject: [PATCH 11/13] Grip Claw now shows the proper pokemon nickname (#3634) Co-authored-by: frutescens --- src/modifier/modifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 1dff041a14e..f4ec6c499f4 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2414,7 +2414,7 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif } getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string { - return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.name, typeName: this.type.name }); + return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: getPokemonNameWithAffix(pokemon), typeName: this.type.name }); } getMaxHeldItemCount(pokemon: Pokemon): integer { From 098811c0068eca6068b135d0cf16581fb563bdac Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 18 Aug 2024 18:18:43 -0700 Subject: [PATCH 12/13] Main -> Beta (#3635) * Fixed issue with falsy issue within condition to get a stat for IV scanner * add fix setting code to prevent form/variant bug when default form/variant setting is wrong. in addition, that fix code include gender fix, so i revert old gender fix. update wrong log message. * [Hotfix] Fix Memory Mushroom not showing relearner moves (#3619) * Fix Memory Mushroom not showing relearner moves * Fix rollout test * Rewrite player faint logic in FaintPhase (#3614) * 867 runerigus sprite (#3629) cropped static frames, fixed cropped sprite set runerigus exp to use the shiny exp's animation verified all hex colors are unchanged - fixed ultra necrozma exp front variant swapped arrays. - xatu female eye color fix * [Bug] Preventing the MBH from being stolen in Endless (#3630) * Endless MBH Fix * add import * Revert "add import" This reverts commit 814a4059c2830e972c348d698259535e117850bf. * Revert "Endless MBH Fix" This reverts commit 8eb448130132ff9eed614a2ec576926814008df0. * removed newline --------- Co-authored-by: Frederico Santos Co-authored-by: frutescens * [Bug] Fix type-hints for immunity (#3620) * enable mock containers to be found by name * enable mock text to be found by name * add test coverage for type-hints Only for "immunity" and "status moves" * fix wrong message key of curse(ghost type) (#3631) Co-authored-by: Frederico Santos * [Hotfix] Steal-able Mini Black Hole Pt 2 (#3632) * Still have no idea where Eternatus is given the MBH.... * typedocs --------- Co-authored-by: frutescens * [Hotfix] Abilities that prevent ATK drops no longer stop other stat drops (#3624) * Abilities that prevent ATK drops no longer stop other stat drops * Apply suggestions from code review Co-authored-by: Mumble * Add `isNullOrUndefined()` utility function --------- * Grip Claw now shows the proper pokemon nickname (#3634) Co-authored-by: frutescens --------- Co-authored-by: Opaque02 <66582645+Opaque02@users.noreply.github.com> Co-authored-by: KimJeongSun Co-authored-by: Frederico Santos Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> Co-authored-by: cam Co-authored-by: Mumble Co-authored-by: frutescens Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: Enoch Co-authored-by: Mumble <171087428+frutescens@users.noreply.github.com> --- public/images/pokemon/867.png | Bin 2128 -> 1047 bytes public/images/pokemon/back/867.png | Bin 1617 -> 800 bytes public/images/pokemon/back/shiny/867.png | Bin 743 -> 793 bytes public/images/pokemon/exp/867.json | 1590 +++++++++-------- public/images/pokemon/exp/867.png | Bin 23716 -> 13409 bytes public/images/pokemon/exp/back/867.png | Bin 7341 -> 3724 bytes public/images/pokemon/exp/back/shiny/867.png | Bin 3270 -> 3715 bytes public/images/pokemon/shiny/867.png | Bin 976 -> 1043 bytes .../pokemon/variant/back/female/178_2.png | Bin 6304 -> 6305 bytes .../pokemon/variant/back/female/178_3.png | Bin 6304 -> 6305 bytes .../images/pokemon/variant/exp/800-ultra.json | 32 +- src/battle-scene.ts | 1 - src/data/ability.ts | 6 +- src/data/move.ts | 2 +- src/field/pokemon.ts | 8 +- src/modifier/modifier.ts | 2 +- src/phases.ts | 25 +- src/test/abilities/hyper_cutter.test.ts | 58 + src/test/moves/rollout.test.ts | 3 +- src/test/ui/transfer-item.test.ts | 1 - src/test/ui/type-hints.test.ts | 89 + src/test/utils/gameManager.ts | 3 + src/test/utils/helpers/settingsHelper.ts | 15 + .../mocks/mocksContainer/mockContainer.ts | 7 +- .../utils/mocks/mocksContainer/mockText.ts | 16 +- src/ui/battle-message-ui-handler.ts | 2 +- src/ui/fight-ui-handler.ts | 13 +- src/ui/starter-select-ui-handler.ts | 96 +- src/utils.ts | 8 + 29 files changed, 1154 insertions(+), 823 deletions(-) create mode 100644 src/test/abilities/hyper_cutter.test.ts create mode 100644 src/test/ui/type-hints.test.ts create mode 100644 src/test/utils/helpers/settingsHelper.ts diff --git a/public/images/pokemon/867.png b/public/images/pokemon/867.png index bd07a7a48375858f3ed2506c81e51befc2c02db8..2fe8856d74df6113ece362f16ddf144140f82469 100644 GIT binary patch literal 1047 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 P00000NkvXXu0mjf&XWG+ diff --git a/public/images/pokemon/back/shiny/867.png b/public/images/pokemon/back/shiny/867.png index fce3fe4e37eaa10d77894bfd02c7d8da05536297..accb87844f1c680b67e46ba371405a992adc6d83 100644 GIT binary patch delta 750 zcmV5(BMe+P6@PE!E?|NsC0 z|D}JX`v3p}pGibPR9Jqa*agmie?PDZ&UyddX7~rT!GZm%V?Es2RjnQSRPJDBlM4HuAJda8HmT((A8Rw` z{B#EfbhRs-=o2*qe^53nmdFk9(M=D zhCK|_cDY4py*!63u(0(_>Cmfr?`?j*UMAWSwuZ4COtVhEI=jj3fp)%wcj`U5UR$9@ zOs0<`=a}3T#(x`Kz--ztZ(jKIuXn=Cps8%e<^dk{s8Kxfd(AYtfz<$ z$1*oRz?;i<$J~?mZZyaf*e68$$;qYb^(4j<&}h*?YF|go5D+*CZ-w$CXi-3lYIjm- z0_ZbZMqo9Z2N|S9k8$x~yv~f(e_CjC^btIwHkPDK->^lEZ6Mu(H+|0m-uhfa4A&vKT$K#o#tv{ju%09~ zMOV7?+t>pNua>{M=p4!O8trMo)$Efxdq}Su1c`VRbY0FK$p~B$Eoj8_jVy}^{eTWL zqh)$M-pTm|%uP(0wr{qdn{6~pF3QYd+1wYb7J#g>e=G>;gKfEV9{nzK$|k>bZNR0v zYr?#iFYRyVkXhD+I*|K4&yA2V$=|qSpg(Se%uAOKTI!riI2_kxEqT}lopqU-7LEI| zq@&S0e}@=TewB7IjhE1ygYI^6i`Df4jdMC=4`OaGXRZZpG`J$IgJ7}7UhG4Z zJy86;Oe&h@SI4yetE5)E9#8JOwq^X*Jd|C-fP?=&e}58ozTs{!=e;NH7KHXR#*6lQ gHMYNUz5d|Ae==DedM}{`Q~&?~07*qoM6N<$f`sjD`Tzg` delta 700 zcmV;t0z>_o2ImEk7YbPf0{{R3rzMq~kuD^E0d!JMQvg8b*k%9#0)R!%Iy%a=nku)zv+9krb^hCtxO(GoZl3U5D`z{ulmBA-ZP+HM{B52`nw@84Nx#c$hBASTAqs6EDU*#pe&7*4Z9u>QCUCSd2O&X+{a2myz`wHNw5SQDalhkUW z44hIDeO)<&k*=^{jjmR|LlP5*%E-KGNa_xjpSKwxc2km!+;Dl&6bR}J4(_e zo|F^m+N@}hH->e$X@HjasGLZMh(a`f$r3}|?UMTiPq0zTztvaW46RX}D1$XrCm)Oj zI;w*!As;N3$uoj3l1s+P!@xdhHQr#+i2%_l^)ids6+dd{u<6lbnd*`V{S+Eq(vSwW z2(p}{5v^18l!4G3SgGC4wrb}BKAV2Pd=6BzH7ajPUi?DV;mYG75Q77Q5Wasl9YW ztVwR9^MJJM>(niylzE=hAWHe3j`UH^Sv&Xjz%mP`ToJ|}fZ diff --git a/public/images/pokemon/exp/867.json b/public/images/pokemon/exp/867.json index 1a9c7572f43..52e0127509b 100644 --- a/public/images/pokemon/exp/867.json +++ b/public/images/pokemon/exp/867.json @@ -4,8 +4,8 @@ "image": "867.png", "format": "RGBA8888", "size": { - "w": 344, - "h": 344 + "w": 361, + "h": 361 }, "scale": 1, "frames": [ @@ -31,7 +31,7 @@ } }, { - "filename": "0020.png", + "filename": "0021.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -52,7 +52,7 @@ } }, { - "filename": "0035.png", + "filename": "0037.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -94,70 +94,7 @@ } }, { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 113, - "h": 56 - }, - "frame": { - "x": 114, - "y": 0, - "w": 113, - "h": 56 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 113, - "h": 56 - }, - "frame": { - "x": 114, - "y": 0, - "w": 113, - "h": 56 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 113, - "h": 56 - }, - "frame": { - "x": 114, - "y": 0, - "w": 113, - "h": 56 - } - }, - { - "filename": "0034.png", + "filename": "0020.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -199,7 +136,7 @@ } }, { - "filename": "0003.png", + "filename": "0006.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -207,20 +144,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 4, + "x": 2, "y": 0, - "w": 107, + "w": 112, "h": 56 }, "frame": { "x": 227, "y": 0, - "w": 107, + "w": 112, "h": 56 } }, { - "filename": "0018.png", + "filename": "0022.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -228,20 +165,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 4, + "x": 2, "y": 0, - "w": 107, + "w": 112, "h": 56 }, "frame": { "x": 227, "y": 0, - "w": 107, + "w": 112, "h": 56 } }, { - "filename": "0033.png", + "filename": "0038.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -249,62 +186,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 4, + "x": 2, "y": 0, - "w": 107, + "w": 112, "h": 56 }, "frame": { "x": 227, "y": 0, - "w": 107, + "w": 112, "h": 56 } }, { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 103, - "h": 65 - }, - "frame": { - "x": 0, - "y": 56, - "w": 103, - "h": 65 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 103, - "h": 65 - }, - "frame": { - "x": 0, - "y": 56, - "w": 103, - "h": 65 - } - }, - { - "filename": "0026.png", + "filename": "0012.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -346,7 +241,7 @@ } }, { - "filename": "0041.png", + "filename": "0044.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -367,7 +262,7 @@ } }, { - "filename": "0043.png", + "filename": "0003.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -375,16 +270,58 @@ "h": 66 }, "spriteSourceSize": { - "x": 5, + "x": 4, "y": 0, - "w": 103, - "h": 65 + "w": 107, + "h": 56 }, "frame": { - "x": 0, + "x": 103, "y": 56, - "w": 103, - "h": 65 + "w": 107, + "h": 56 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 107, + "h": 56 + }, + "frame": { + "x": 103, + "y": 56, + "w": 107, + "h": 56 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 107, + "h": 56 + }, + "frame": { + "x": 103, + "y": 56, + "w": 107, + "h": 56 } }, { @@ -402,14 +339,14 @@ "h": 56 }, "frame": { - "x": 103, + "x": 210, "y": 56, "w": 107, "h": 56 } }, { - "filename": "0022.png", + "filename": "0023.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -423,14 +360,14 @@ "h": 56 }, "frame": { - "x": 103, + "x": 210, "y": 56, "w": 107, "h": 56 } }, { - "filename": "0037.png", + "filename": "0039.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -444,12 +381,54 @@ "h": 56 }, "frame": { - "x": 103, + "x": 210, "y": 56, "w": 107, "h": 56 } }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 1, + "w": 44, + "h": 56 + }, + "frame": { + "x": 317, + "y": 56, + "w": 44, + "h": 56 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 1, + "w": 44, + "h": 56 + }, + "frame": { + "x": 317, + "y": 56, + "w": 44, + "h": 56 + } + }, { "filename": "0002.png", "rotated": false, @@ -465,14 +444,14 @@ "h": 57 }, "frame": { - "x": 210, - "y": 56, + "x": 103, + "y": 112, "w": 102, "h": 57 } }, { - "filename": "0017.png", + "filename": "0018.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -486,14 +465,14 @@ "h": 57 }, "frame": { - "x": 210, - "y": 56, + "x": 103, + "y": 112, "w": 102, "h": 57 } }, { - "filename": "0032.png", + "filename": "0034.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -507,12 +486,138 @@ "h": 57 }, "frame": { - "x": 210, - "y": 56, + "x": 103, + "y": 112, "w": 102, "h": 57 } }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 101, + "h": 65 + }, + "frame": { + "x": 0, + "y": 121, + "w": 101, + "h": 65 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 101, + "h": 65 + }, + "frame": { + "x": 0, + "y": 121, + "w": 101, + "h": 65 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 101, + "h": 65 + }, + "frame": { + "x": 0, + "y": 121, + "w": 101, + "h": 65 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 101, + "h": 61 + }, + "frame": { + "x": 205, + "y": 112, + "w": 101, + "h": 61 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 101, + "h": 61 + }, + "frame": { + "x": 205, + "y": 112, + "w": 101, + "h": 61 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 101, + "h": 61 + }, + "frame": { + "x": 205, + "y": 112, + "w": 101, + "h": 61 + } + }, { "filename": "0008.png", "rotated": false, @@ -524,266 +629,14 @@ "spriteSourceSize": { "x": 6, "y": 0, - "w": 102, + "w": 101, "h": 57 }, - "frame": { - "x": 103, - "y": 112, - "w": 102, - "h": 57 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 102, - "h": 57 - }, - "frame": { - "x": 103, - "y": 112, - "w": 102, - "h": 57 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 102, - "h": 57 - }, - "frame": { - "x": 103, - "y": 112, - "w": 102, - "h": 57 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 101, - "h": 65 - }, - "frame": { - "x": 0, - "y": 121, - "w": 101, - "h": 65 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 101, - "h": 65 - }, - "frame": { - "x": 0, - "y": 121, - "w": 101, - "h": 65 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 101, - "h": 65 - }, - "frame": { - "x": 0, - "y": 121, - "w": 101, - "h": 65 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 8, - "y": 0, - "w": 99, - "h": 59 - }, "frame": { "x": 101, "y": 169, - "w": 99, - "h": 59 + "w": 101, + "h": 57 } }, { @@ -795,20 +648,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 6, "y": 0, - "w": 99, - "h": 59 + "w": 101, + "h": 57 }, "frame": { "x": 101, "y": 169, - "w": 99, - "h": 59 + "w": 101, + "h": 57 } }, { - "filename": "0039.png", + "filename": "0040.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -816,20 +669,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 6, "y": 0, - "w": 99, - "h": 59 + "w": 101, + "h": 57 }, "frame": { "x": 101, "y": 169, - "w": 99, - "h": 59 + "w": 101, + "h": 57 } }, { - "filename": "0015.png", + "filename": "0010.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -849,6 +702,195 @@ "h": 59 } }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 99, + "h": 59 + }, + "frame": { + "x": 0, + "y": 186, + "w": 99, + "h": 59 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 99, + "h": 59 + }, + "frame": { + "x": 0, + "y": 186, + "w": 99, + "h": 59 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 0, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 39, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 166, + "w": 44, + "h": 54 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 39, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 166, + "w": 44, + "h": 54 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 97, + "h": 65 + }, + "frame": { + "x": 202, + "y": 173, + "w": 97, + "h": 65 + } + }, { "filename": "0030.png", "rotated": false, @@ -858,20 +900,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 5, "y": 0, - "w": 99, - "h": 59 + "w": 97, + "h": 65 }, "frame": { - "x": 0, - "y": 186, - "w": 99, - "h": 59 + "x": 202, + "y": 173, + "w": 97, + "h": 65 } }, { - "filename": "0045.png", + "filename": "0046.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -879,16 +921,58 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 5, "y": 0, - "w": 99, - "h": 59 + "w": 97, + "h": 65 }, "frame": { - "x": 0, - "y": 186, - "w": 99, - "h": 59 + "x": 202, + "y": 173, + "w": 97, + "h": 65 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 29, + "y": 1, + "w": 58, + "h": 57 + }, + "frame": { + "x": 299, + "y": 220, + "w": 58, + "h": 57 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 29, + "y": 1, + "w": 58, + "h": 57 + }, + "frame": { + "x": 299, + "y": 220, + "w": 58, + "h": 57 } }, { @@ -906,8 +990,134 @@ "h": 59 }, "frame": { - "x": 0, - "y": 245, + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, "w": 95, "h": 59 } @@ -921,146 +1131,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 95, - "h": 59 - }, - "frame": { - "x": 0, - "y": 245, - "w": 95, - "h": 59 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 95, - "h": 59 - }, - "frame": { - "x": 0, - "y": 245, - "w": 95, - "h": 59 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 95, - "h": 59 - }, - "frame": { - "x": 0, - "y": 245, - "w": 95, - "h": 59 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 12, - "y": 1, - "w": 90, - "h": 59 - }, - "frame": { - "x": 95, - "y": 245, - "w": 90, - "h": 59 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 22, - "y": 1, - "w": 70, - "h": 59 - }, - "frame": { - "x": 185, - "y": 228, - "w": 70, - "h": 59 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 22, - "y": 1, - "w": 70, - "h": 59 - }, - "frame": { - "x": 185, - "y": 228, - "w": 70, - "h": 59 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, + "x": 8, "y": 0, - "w": 44, - "h": 54 + "w": 95, + "h": 59 }, "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 + "x": 0, + "y": 245, + "w": 95, + "h": 59 } }, { - "filename": "0052.png", + "filename": "0032.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1068,100 +1152,16 @@ "h": 66 }, "spriteSourceSize": { - "x": 40, - "y": 3, - "w": 44, - "h": 54 + "x": 8, + "y": 0, + "w": 95, + "h": 59 }, "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 39, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 244, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 39, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 244, - "y": 174, - "w": 44, - "h": 54 + "x": 0, + "y": 245, + "w": 95, + "h": 59 } }, { @@ -1173,41 +1173,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 29, - "y": 1, - "w": 58, - "h": 57 + "x": 8, + "y": 0, + "w": 95, + "h": 59 }, "frame": { - "x": 185, - "y": 287, - "w": 58, - "h": 57 + "x": 0, + "y": 245, + "w": 95, + "h": 59 } }, { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 29, - "y": 1, - "w": 58, - "h": 57 - }, - "frame": { - "x": 185, - "y": 287, - "w": 58, - "h": 57 - } - }, - { - "filename": "0049.png", + "filename": "0052.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1221,14 +1200,14 @@ "h": 56 }, "frame": { - "x": 288, - "y": 174, + "x": 0, + "y": 304, "w": 56, "h": 56 } }, { - "filename": "0062.png", + "filename": "0065.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1242,56 +1221,14 @@ "h": 56 }, "frame": { - "x": 288, - "y": 174, + "x": 0, + "y": 304, "w": 56, "h": 56 } }, { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 1, - "w": 44, - "h": 56 - }, - "frame": { - "x": 243, - "y": 287, - "w": 44, - "h": 56 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 1, - "w": 44, - "h": 56 - }, - "frame": { - "x": 243, - "y": 287, - "w": 44, - "h": 56 - } - }, - { - "filename": "0053.png", + "filename": "0056.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1305,29 +1242,8 @@ "h": 53 }, "frame": { - "x": 255, - "y": 230, - "w": 44, - "h": 53 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 41, - "y": 4, - "w": 44, - "h": 53 - }, - "frame": { - "x": 255, - "y": 230, + "x": 56, + "y": 304, "w": 44, "h": 53 } @@ -1341,20 +1257,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 38, + "x": 41, "y": 4, "w": 44, "h": 53 }, "frame": { - "x": 299, - "y": 230, + "x": 56, + "y": 304, "w": 44, "h": 53 } }, { - "filename": "0054.png", + "filename": "0015.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1362,14 +1278,161 @@ "h": 66 }, "spriteSourceSize": { - "x": 42, + "x": 7, + "y": 0, + "w": 94, + "h": 61 + }, + "frame": { + "x": 194, + "y": 238, + "w": 94, + "h": 61 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 94, + "h": 61 + }, + "frame": { + "x": 194, + "y": 238, + "w": 94, + "h": 61 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 94, + "h": 61 + }, + "frame": { + "x": 194, + "y": 238, + "w": 94, + "h": 61 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 22, + "y": 1, + "w": 70, + "h": 59 + }, + "frame": { + "x": 288, + "y": 277, + "w": 70, + "h": 59 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 22, + "y": 1, + "w": 70, + "h": 59 + }, + "frame": { + "x": 288, + "y": 277, + "w": 70, + "h": 59 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 12, + "y": 1, + "w": 90, + "h": 59 + }, + "frame": { + "x": 100, + "y": 299, + "w": 90, + "h": 59 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 37, + "y": 4, + "w": 44, + "h": 53 + }, + "frame": { + "x": 190, + "y": 299, + "w": 44, + "h": 53 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 43, "y": 5, "w": 44, "h": 52 }, "frame": { - "x": 287, - "y": 283, + "x": 234, + "y": 299, "w": 44, "h": 52 } @@ -1379,7 +1442,6 @@ ], "meta": { "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:b8ca75f7f37906e78ed633b32d037b74:92bc79a7ca35490600679451c06105fc:58bcd289dd222ce646aec14ff657c9fc$" + "version": "3.0" } } diff --git a/public/images/pokemon/exp/867.png b/public/images/pokemon/exp/867.png index 689a72694ef90e5dd459be477119a20f39db8e6a..4f8f67842bdd6ee61b2ba0ca2afe8d7ddfd2c852 100644 GIT binary patch literal 13409 zcmYLw1yCGK)GaLT?(P=c-5nMd_XPLg!3pl}&IWha1Pc<}-F4C6?l0f}zgKUny1M4f zx%b?b>aOnTj#3B8qahO`LqS2IDJsZlKtVy<{+AG8{voy{On(0|Xg3XcDX7{>(&K+G zOetJLMh5fYI)_J8Aqk>m7~YWZ=QU7e;lMlg2wip<$Vk1R`%33i;BU=bbj-BTYASs z%fIJGdG$e1&2XE$Fv5(5V8>1Y0IGTJfQ{! z#Ak@VN533E{6ilwZ#cMT`|D$>TmPEv@3Kdc%yEVzz&AvDzt#A9Q6Oo?>B*V+sty0e zyrEnX*)8_&W=DEt+ho&lK8!|LqDZoX(+PVStD39rES>*MMyChFa%fCA&`CV0N1^Ht z-co>}J`>BLMhjs5Ha86q-cZZMSG#zqk)-5-XPIz}*!K_ywQP~TApL#0wCnuv1Jesl zzh?nX6o?S*Lx+5gZs709&r(R2<@;(Rzi8HLtbbEC$-sdK9KPN6P)(3)_Rf6kAJ4{R z$Yq|_>c8^8z4jAX-x3$tGU&aZ`eR&&;*M^Nu9Lk69Jco;ytOyzF@^_o5^N4Ca7R}X zMvD%;P{;_4cXAeqOm*c6GNWhrn#2YWM#v_GMNwZUbnh1r9nq&l0FF z#8We>=Z(RQdG?|FHwz^$wx}`-tQ5C6aTsRiK!+=JGG;=i>)5BTIHXZt{XK3MW@G{B&LgH zS#74+_WGCs??ONY(B)cRB%>21mC7yp*Dq0*AQP6Q;qUDFw=j{hD0lE(zz`q4Z$?VP zQd0QkmU`Jr=SolpBYt6Xh5K{-%j90GyfzrW`qBL78gPU~IO*P{X{#Y_a5F3(Qn>Qf z4tsCnSukp~EeXE&%n<_|Wd!FuQeZ;Tyhv7Ny< z6%rZGtV1IAwQrnfZ=6Ag1t+yu7IMEYTgORqC6>&q6~ux(d`khhINzvQbgVtPrP{L- zbzQ0Pf^;jkW-{eBA>l%=^YX3K^Bvji8bJYy(Nkji#8Bq~gTP|pW_L<#%ZMr@?^YZ< zb&eNR^L+0mA`bdu-&Qh(uHjWhO8%`2^;*^3VeCw~Uf1^Qlrq5?g|^Q;2Nj+jaI6t{ z5x4MpbgPN1+#5{r8gGPy3_y#?&zhfNz6vv_xfC{hoy#2Bg#7luZMO+N8|qXvkSHpo z%trQE$Pxri?F0s|O^(^a|6F$@cf4ln3GzG3Wv-A|!x9Z$e~wV!&JvtSc5aa}v7Q=h zI^Q-HAdyTdW!@4CcF!^4c^kWvU4aEc|{OUD19#T zJ*_jD9QxR|#R&r3h5G}%y-3s5maVouX3T-{;hZgW%940p(+Jv1Un)czZX{rGT^%~G z9y|tc8q{DvI(n;Scfu?^y&vY$2U99^0-IH~+aN6!yE+vFiP7e&jJDN$<`oUiYCe#2xg|Vf4=+O*@Lmg*PK6=o@`kJw4P4E+wLf z8drk{pog_i8*g@hMejzUAm%R8mmEpN#X(%4u^?lq8rjy>G~Ni!ef5SiG4H5|=EcPu z&zO<-1!-8d=*t$+{Ovo`QLw<080F>K^~q?ll5GY)LG;*u z()itfI>u$~Z`y#K&EFrZANntaif*yOgKq6yR4C9UqWIP-yxL@j|9)JMN4q*|o8atW`nUnAl8PO+x?a3Qu5Xdf9*6`dQ=vA-E+98}{3@W< zY%!HbP3SbDnEt9(!1qtAL17z{bVBHM&`E*Au^c|mJF9Aho)iv=ttIVLhXpu^dD`ub<^KxfNNBURokX6_65 zsg898yD2%hq?ufg8kd+J)0vFEiAd@Lw*bNWH?o=FUVvIZalOj$+tuh&=7QT4f01m97z*_c@Py zjG(5ceUQO!9^~s<@`C1lAi~jh{-=Y%3Kt$Qa1#0^K~EagtcZIlvuk)6?V`s$Z&qI8 zTzIgV=@y+ls%`ZU%*+lx5Hl}SI^R=W#hU7d{sxWYh!p?juSgzeB!Cq0$RS@hwlBLx zhc(0oRW$f}q!;TsJUMUqC7|-0o3?UaOhQ3;$qSI2@f?aKldY&n+9(7Htmms=u5~qR z;*Ol0rg@A2){)@#kBZpgnMf@7hs&g`4^5QR4ILLW$$bbER;CpBNDx!(?~FtNXqynI z;!JBo1Jfj9$8(&4iuA?Q3E4NYIRxsS1ymrohaY}Novq#TfQNeOnOG#x+GW&tv5}&w`6bxl#$ZKl zy4%)Bw-%#az{wdL59mGUc~jf6foggV2a7IO)P|5cdzDH?;COH}mK>3SCaRz9ZZt5fwvL z)3c)bR_*y@r2P?!CqF}alb&I(%Fl+G;V-;B>aXK)a9|pf-sG3@C=DClf=d?=7s{72 zqX(PyRKT*7p0L_Ma#gwIWcU1lG8vXIdq9vp65&TiBMjh0p$|pRwSW_p{?f_lH{J79 z@_hYmyHJy6!DoCm!a+C6fN8#DB_rM2@^kSQQ(E0gbVSo(9utZFe+`{=j+1z=nK*VP z&B-OIAfDpS#=*Ikrml=c2}M6Gcc4As>iuTA7`*~5dx+!&r#9v1m!8BM%M?^0?G-IKk$WbWI=T z0&=Vw)=^iAPm1=#N57kNS-s;G+0-W1XXp^T3|Y+)w8X{cbHqU5iakqUC)$(Y*eW_5 z9@DLoRN-e?DiG;}j<;>kPmf5YK=?M+Fy$j^u06Xau>0P$VhWo|*E1c{-@rW#BPwR- z=Vj?wHfl{Arz$*pF2&4%v{I8iRrdm&j)L6)LTtE*wl8fE*Yu!{Z`%mmSyu{X| zhP0U-CE)R~P#kpL{DzU4!326c5Vsb?#p=;chJH!TChavop4WrzY;5=XVDOU_f)SGv zr9TSTH!*43z-}V|CID{MRG=Zl9eMiUb0f?olEEkBzN;Z$R3e1~E~sn+5!a@4{5$0x z5DhRzJIOqcab2EN9=;J30bDUSMRMeJ{dn0naY)dix{s%qh?Ws^ea{~syDJ2)@W9dD z+`P2{Y3ilt)*6x)N-P<1WV{D5#uCwU-CeMVYs_DBPdPp?^oM6`tb~qQ<2o5?}6pA zt)`;EY$h{W*CYdb9|Ww>FH!cwONbve3g1UOpAE=GE`6A(yL@hrQ7C((?fzQdj)TQs zE!Q4Gov!${urY~c#<6FU@(a{?`c^55uH?N4sJmc8`LG^haW4_LQMtgftbM+(7O7W9 zYE9Qc0>8LE(TT{=z!)sc;I80z100YLqrxjA6{UVEsKxBrsm4&>S5g@R04!y-w4MK8 zAwR^Hm&a=Trr*_r-uY<>66jiLQUBpT4#V!yQ+}Qi@RG6_T}%aEN|9n`57wh*G8CuW zJO9YX_gHhX<56{W#1AFd@hjtr+4tK~T4kgn`-9eK8A~qYv1Vb&rAxs19`y$cE(uvG zM?Ln2c*2ZuB4*}WpAZ9Z$47ZtOvK})wS%vxG;swv@GUoM!5wp6KeUvjU>TVwrcPlO zj?&_myu=xD6w-!%1*FV7F|=O1v5yuoq}s~ptHuwZq<2_cj|<@-?@0W|=b&>NmjE0^ zCf1%^VjDlYV-_i@BPnRKsq*wa<>#D@Ie6T_?Zus0C`(FMFs)ybtq>9m$rn$5!mdbF zh9WVpo<}^k`?81E%3acN68iHKYUvWLcdHa~VW>VUwB#f`(PXafM5#V3ZOmTEFEw!$ zsh?&;mx+3EZY7|$Gzm5b;@;bAsS%x6M-@qnYiywuF0TcLY*~p$fvL2^uJ2p!`JKq~ zy`bglPKfw zyh%|z9$hAn95^YV1vSq<3`@9ntv{cvvihn^1237cDzkWEg#0rel+qXyFeh;USZ~B00al1-qU;iOV!;Ka*uzk>?3v1F zW_yyYde)upj1**rT*##lR;oIXn*{5xOCbzHZG7RfJP4rq|YCu(vyA?tDjx33ALURjVF1@8yQS$RRC7@g94saGdT9S@U zE&9h$u6k@ihU^rs^^(`<_d}E_cyNsc-1tKpca=V+L=XYPiU$5hD!Df{_qh&rv8L@T z2Nk*CL|Tr5-MpiSv;Hk^p%e}ZrKPDh+-j+gbT_-Aq>LJC5*&VFfQye}EOC-cem|&E zVx)`~)})P8#Pz+RnWw&dO)2*TcU>m#tP`*FLTEnBD6F4}DKay41|Ui32aKhW01}V} zu_Oqxwh>d2p-^S>bk}=OHp;kpsQdBuH}kF_oSnf9)nX+sf!X$%D=k6~=3OY7B)~HZ zQ7&9==rqL06au=Ud_WO8DWNR%L>denaB&B-W?LHl%gGwbaC{1{EZnJ7ZFId`K}8B2 z*EBtMyn#C20xZI~q?5Z#Ko!<3lO!@gesgCjg!CTK=5(=vT;jJE2p}J8QM!g%6K9kb z2;trTVll`3?2uwbHcsOJ(y+t8nBiEL`{ zJfHp44)g7XqIhiy#L>r!i>#FlQ(NlV|NNn;nQ$kYex$RP{vq}-DftC?{1P_mL81z0 z77mOTtxAz*99wE_)K1AxohLu7KB2N@5-4$MkIeWtA~qOP^NSLQdt5+UN(gaAv1pkV z&$?OXgc_-c+qeWR;#NMOB@)) z%U#*9!?S6i+1y!Lff5=D;MVrVw4pdeZniJ6*#3?5>i8CFcC{*@V<8n+(^zWbr8)zrt+^>f_uuj};Eub2kQd^!16uVyTT7PK&tPix<#_J2kQ z_?2f9**OS=v9MQQR@^TuhhrL%;~Bsz|ldfIKStpyGHg$RA%KEMa|>u4C?Zv zSul~&79&OYUJ{cpt25*%o;}0saC+tvP?)g|8uFw%vD@hBrSVBKC(l;nP^oI3{)V*= zoPc{YNu_@06M^oCwTpwrCD2h{M&YEW+?AjlZ*&Yl44%bZZJ`G;2}M%|9?TfH7Pht4 zOTFE7VQSiTU72bUfWue#imvvoBQ&p=l;UzV3W^Mh4*5S-gAeJTlUS=L#~U0=vHD>g z3+Km~cx4$E%RSSMf1T1nD^9CRWSXyyOL1rH@}#Z`mV60Uuh#c)GsO%1x`8~R0)#e= zcGP@{j!NbLGne72U`F#EDfDpQ>ug>_t+q*NTxwySOQv2$txmb8=3>nJZ0GYVi$KMS z9vMpYv1YB75xUtl`R9k$hEj#vI8aD3Dxx(?GnyRR&YK3`_?xc1DMW1Q!}hn_8iV>U zU?_-h)L%#`FIR&@nvuS_N!W(HI$6p*g;M)FQXlCw`+}sTY(<79vK7)G0LqTR9;J%B zcxAWmkPdMW`r=eA-XmN?yiCiT(#F{?m4zvo!;;1S3CTZ)Dmt*N=8!J7pbpH_%amrf zQI~vQ-`7`J@vg0-3-V>127R8t*9S65Q^@vUu#4l^Qb4aJONhTymZeriT{ry)!HI=5 zut_&d0xLk;cgfziPlEjw@76i-p2~ytKH=ZWo6d4h)FvQ@xMj~2PD;J!JE?%oZTva^ z-v$9d*UF5A$Hnauy*4Fywhpw$1>7H#_&T13Z&ik2PA^Dh;Q_f9re8T(X@VN-4SFlS zKI}AghR#jwiA=DTT7CGLwuEXLU-VmhBbAYalE9<14;C&%z9)XF#&@Y?@dw}AmJc-s z^*)&lu%-v*L5?B0C*2q~ouOp2awm8cyZA9^!q0eSXfd2?&PiN4&yFe@%T!h4^QTdwP`xYF4@qbI*M`D;KQ{5jn?X)b>gn|P9Ew3 zy%IbSAH!U1g&F4+-;ED8NAMBpgWys&Pmoqlx1g-oN3_SL%L7KURnq#QwlSm-iqOp z`xqDM#fT5_PebfGW$L&k?r*U1*+kNZ%aRV(ZtP|NievEF#StBYKE2t@qol|%`%vsZ zWT7hJEE&VjWIRHX*-oU=(G2?K#oP3Rbu?v(VxSvyGA=>VJvVyv1@|b$6El7{v?ehi z)gALdLzM`v$z)6LzfdKTYTFYsFsp?V^7iL4;@mCs#Q(6C29ZvdN6%iVfZ%(r0!er2 zw3vEE_vb|H9~Acf3nZ5)E^C;uO%Gl{gSB^-6V?(Tn(HeV6@EB_bghhx*F*|S-)xlK zO@PXTvO_m^y5YA`P_qu3qDzGBe0sQTT!AqhQ z!&0(OXpumh9)MaIP`26h;R)w9pcKrx@8WEfj8nBsH5xC+A1E&7+*U%}y z!bWCdmrdr(#npEzaJLDvunf^~p_5otWh>Q2xy}M*p5@e`OZBihiMD9b&E(EVV4L)z zUc5f7Ch!~mje*mkBAH(<4j&$jwUeX_y5**kq_Xc6Dt9@pSj_f93Q;_rQkU~uYwz7> zNS&e zc#JCl4sp7x7>_5WvfZcq3;mo1R344h&=z9KF&gJP-dz*l%}N3%wj|~suKN9WFmoJv1dtkcr=Uz)2i z_RTg+@3wbHN6PVsXRo}sT!ja`zH9ueD3n2D6RFcHI60QhW-Fq|irzyi}iGFVz|*gSLY24PbG~+_MLUyA;RDVDAI&G38;d z>H?Nnjox(K!W6*SH_yCl*O{{`zKefn-w$o3~It`|x`s_A+a;|$xiWkG>()u-wK}q*ErKB@- zF_dNko*~KR6+XX6XyT#D@f)tFmeZIQQVYBN%J%gb4aOVc)XdMz9h%@=ymjwBH2RGy zaL`ln^T|>gZ^ZT%(Vk%N4pGlr`>vdGnW83Vh2wRZ1Pg}5 zNy)rJe4}o#-28N0UAtQYvRPB(F^hxow?Dp-><9uNr82sIo7^-sQlC&iXsRdnj+;DC zh_?j+XQeWCxVoh(mH4gYj5k>C@_GeB+EmyP@>c9qSkl;a96mr72(&c&s9)#@*1@$` zMCN>`kXQUANhE~TN$ECsSHr>N-^aiYtkP?*o_fNQqPW9Ij?z;JGcr#y(X#f`?Lq=e zol9}Ke&iHyKd`JG|21hCPT#xG^b31nMrwS~A#MitU|iw2$OlcHUvCoebYXWRzjO#T%xjpl>S98vdcX>`W|FPTmGY6)GKO_tdgvUOGNL?}J0a~fY z2jATVZg@8z@lBM{JQ>!k@H9^y`o*N^-&Rf7%^D+-j_w^}f6<2vF^%3{iTmU_{rppf z)s9q5qtU08h83%!3XgH(&^`Ptm;H&33hbua7v5p7u@`UHPeq*n>BgTU1!;Fu0g1+s zSQ(jD6+Yu-vzTJR}gXwio@E5q_5lGLph>;`R#}vGDxU$6IWiw9>T4P zcwvh}N@!u-hXCigatXLzOeuC!;;puRBPUA|a{HzXv%ojn8qya++~JM2QD?>{YN4QKmc+hwJecM3tjOlkWKB;ZCmG9)UAz6X`R z7F~p(3F`QKpl)+7&S?ipm9@BxE&TVVQy$T<#lb!eDNH}{nT^!T@1(%UPc^<=mH$Ii ztQ6V%WBxM8ktKb^0G*NLiHV3#Z_1P$I9bqL16pd1B+iTt4oKjqaxVI1O3!3EH^`Cg z`Rx5AwX5SgvL?nO+oQcg3+Gdqor9QE0pcC$taJ(p>W$dR6@|TUbndGNpAjleBAhVs z9=daf4K$G~{;KV*XCM>kr8n3DtgCl(RqPZ>xSr%aA5P`A}drr6(%*c&JuNx;DZXpyGQc-Ba3+|d!xO$u8knXFB@K=*ApC(W6Z-P z@#78S3E}q{SfsqdjH>78W+a;&JSF^eLS<;x0|OSgm84%0`Au!6)d$^@Uyn-q;^kg# za{sIkj=qw?l9f-h>WTacW7@`Tn!3T~r!z2IKFrCBm6}J@A0$a2{hgL$D%GRxT0tY8 zUFYBn`n$_OkiDI|=9+tqOwQm0>Hjg|Cw4_D7vY45P6P{AX4~@j7->oa7U;eMjl-6K zxGK==M`;rf?Vp#+mh=i{D~R)p8H@PbI*YRQ*_?-m~LtRHN&mo5+MO{PN zg8acZ{%W`+EJZ_KR);UEJzy^Vu&P#6YkguFoVzg_MOPhx7_y9_ojDK~k(nw+eLzS%~kKLTXO zPc`;Wv`!I(NHgMN$0Eo-t_gAk!e1=(2pE{;=(L7Q^1}CLNQEzR-~E@4Slr)jJHFg- zQdQSgca^=@*e?L3$qCxfYo9^;iRp7&rtg;y!)Eb$pB}1UyJk|3nFv!ty$RjjR=atb z81J7f_@K=%D%?&D_@y{`agwI*N`jm9b*PxG6Tfw@*PZdu+MVYvlgiz0&(SmR=>As| z8-`2aJ(?WmI)^Ewm99s>epG81oGmv{l}YBAU&ESVZcs_ zyWj~%vIHU~?FgEtL*L`aO_UBoPrwUgtE~r6vpVD$=a#PEO8&Ycl-k+Ajz6hsUboIw zMgA$U{?z@GsZ&@q0LMQJt+}f&nPRQ-f7trO?t)YuSD8XbfZ_>A0Mt*JX%bn%eU;lh zG=92q+wTyF`EJNUR~Z|4j>4f6OGRQxp>A7-{=ow2)dGOQjO%V-Cit2J!zP>Ahr&{g z8+)>;$x>Ef#r6~@j~;deWks>FYzz7rDSiV5HYYvJwoucm?AS?To544U5@VjK&_0X2jn6Jml7bbY1uc9

AEr)YqXKi=OLo?%#>feG5W8?bg!mY<> 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 z4));y diff --git a/public/images/pokemon/exp/back/867.png b/public/images/pokemon/exp/back/867.png index 766ce3f39ed80703b473d801230d7cedaf763998..b816f10a0def567345f8a9a7a87e041d4898b70a 100644 GIT binary patch literal 3724 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 M07*qoM6N<$g4SP4<^TWy delta 3246 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 g{(l;-f7U5(BVe+qO`PE!E?|NsC0 z|NsC0eu)%T000AxNklv-@}kI9IwyWjBS>=%FOxxDSo1p2<7xa(`${) z%q~PHUqqyC^rrMuC}$!{s>n%I^Y9R3FdYAq+!+hV#J`k9e;Qea#AqI+7=GMrI<7v8vXoDNN6!Jl8$5hU13p#+1}WtbaK3b{{ z>+?FKWe;BE1MBEMjRnxJ3XABgnrY2K8{v3af1=+h$w#B3vY>yfi}ZXN#{suw^VIR+ zeTP1Nu>Lms_ew5*)yCv?`mNgL@?$_6cV=Iv&j-%*id7vB(#!N)Cu(OFt&Nu6kxnc0 z+e4`i)lnNo8phNEtkdJ0MSNM%?!}snb1i#Fze+w@0T(A_>&^teXh?sZyP;s$UZV3` zKQ3bD?G(cb7k6T({kAszo~saPXotU1c<;{TYky$+CHsp1P4*n)l`H=Na`iFH*e8W{ P00000NkvXXu0mjfr?m7Y delta 926 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 delta 6214 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 diff --git a/public/images/pokemon/variant/back/female/178_3.png b/public/images/pokemon/variant/back/female/178_3.png index 32ffdd895c6950fdcec0edc08b2b41b37a01a7ae..9533621c6d696fda7b3f97abd127ba04cbcc78fd 100644 GIT binary patch delta 6215 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 delta 6214 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 diff --git a/public/images/pokemon/variant/exp/800-ultra.json b/public/images/pokemon/variant/exp/800-ultra.json index 53dd9b55df0..cab917ec271 100644 --- a/public/images/pokemon/variant/exp/800-ultra.json +++ b/public/images/pokemon/variant/exp/800-ultra.json @@ -1,21 +1,5 @@ { "1": { - "b0a080": "e552ec", - "f8f8e8": "ffe2ed", - "9b8259": "b021c5", - "e5e4c2": "ffb9f9", - "000000": "000000", - "bc9b4e": "900090", - "f8f8d0": "ff8ae9", - "e8e088": "ff49e7", - "d0b868": "d10cc7", - "7d673b": "510059", - "282828": "282828", - "f84040": "f84040", - "f88888": "1ae2e6", - "c81010": "00c2d2" - }, - "2": { "b0a080": "d96b23", "f8f8e8": "ffe1b8", "9b8259": "b43c06", @@ -30,5 +14,21 @@ "f84040": "f84040", "f88888": "f88888", "c81010": "c81010" + }, + "2": { + "b0a080": "e552ec", + "f8f8e8": "ffe2ed", + "9b8259": "b021c5", + "e5e4c2": "ffb9f9", + "000000": "000000", + "bc9b4e": "900090", + "f8f8d0": "ff8ae9", + "e8e088": "ff49e7", + "d0b868": "d10cc7", + "7d673b": "510059", + "282828": "282828", + "f84040": "f84040", + "f88888": "1ae2e6", + "c81010": "00c2d2" } } \ No newline at end of file diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 674b4e256f9..4faf3863e3c 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2421,7 +2421,6 @@ export default class BattleScene extends SceneBase { getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance) .map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this)); }); - this.updateModifiers(false).then(() => resolve()); }); } diff --git a/src/data/ability.ts b/src/data/ability.ts index cfd900d621c..38ca4eb25d0 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2395,16 +2395,16 @@ export class PreStatChangeAbAttr extends AbAttr { } export class ProtectStatAbAttr extends PreStatChangeAbAttr { - private protectedStat: BattleStat | null; + private protectedStat?: BattleStat; constructor(protectedStat?: BattleStat) { super(); - this.protectedStat = protectedStat ?? null; + this.protectedStat = protectedStat; } applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (!this.protectedStat || stat === this.protectedStat) { + if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) { cancelled.value = true; return true; } diff --git a/src/data/move.ts b/src/data/move.ts index 79e67ece581..24651bacb2e 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4441,7 +4441,7 @@ export class CurseAttr extends MoveEffectAttr { const curseRecoilDamage = Math.max(1, Math.floor(user.getMaxHp() / 2)); user.damageAndUpdate(curseRecoilDamage, HitResult.OTHER, false, true, true); user.scene.queueMessage( - i18next.t("battle:cursedOnAdd", { + i18next.t("battlerTags:cursedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user), pokemonName: getPokemonNameWithAffix(target) }) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index f1721299ad0..e38813ed3c0 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -921,7 +921,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * by how many learnable moves there are for the {@linkcode Pokemon}. */ getLearnableLevelMoves(): Moves[] { - let levelMoves = this.getLevelMoves(1, true).map(lm => lm[1]); + let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]); if (this.metBiome === -1 && !this.scene.gameMode.isFreshStartChallenge() && !this.scene.gameMode.isDaily) { levelMoves = this.getUnlockedEggMoves().concat(levelMoves); } @@ -1210,11 +1210,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * @param source - The Pokémon using the move. * @param move - The move being used. - * @returns The type damage multiplier or undefined if it's a status move + * @returns The type damage multiplier or 1 if it's a status move */ - getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier | undefined { + getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier { if (move.getMove().category === MoveCategory.STATUS) { - return undefined; + return 1; } return this.getAttackMoveEffectiveness(source, move, !this.battleData?.abilityRevealed); diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 1dff041a14e..f4ec6c499f4 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2414,7 +2414,7 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif } getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string { - return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.name, typeName: this.type.name }); + return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: getPokemonNameWithAffix(pokemon), typeName: this.type.name }); } getMaxHeldItemCount(pokemon: Pokemon): integer { diff --git a/src/phases.ts b/src/phases.ts index 88acfc825ef..c50d25acf60 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -878,6 +878,10 @@ export class EncounterPhase extends BattlePhase { } else if (!(battle.waveIndex % 1000)) { enemyPokemon.formIndex = 1; enemyPokemon.updateScale(); + const bossMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier && m.pokemonId === enemyPokemon.id, false) as TurnHeldItemTransferModifier; + this.scene.removeModifier(bossMBH!); + bossMBH?.setTransferrableFalse(); + this.scene.addEnemyModifier(bossMBH!); } } @@ -4033,13 +4037,24 @@ export class FaintPhase extends PokemonPhase { } if (this.player) { - const nonFaintedLegalPartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()); - const nonFaintedPartyMemberCount = nonFaintedLegalPartyMembers.length; - if (!nonFaintedPartyMemberCount) { + /** The total number of Pokemon in the player's party that can legally fight */ + const legalPlayerPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); + /** The total number of legal player Pokemon that aren't currently on the field */ + const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); + if (!legalPlayerPokemon.length) { + /** If the player doesn't have any legal Pokemon, end the game */ this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } else if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) { + } else if (this.scene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { + /** + * If the player has exactly one Pokemon in total at this point in a double battle, and that Pokemon + * is already on the field, unshift a phase that moves that Pokemon to center position. + */ this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); - } else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount()) { + } else if (legalPlayerPartyPokemon.length > 0) { + /** + * If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field, + * push a phase that prompts the player to summon a Pokemon from their party. + */ this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); } } else { diff --git a/src/test/abilities/hyper_cutter.test.ts b/src/test/abilities/hyper_cutter.test.ts new file mode 100644 index 00000000000..9637a80ddb4 --- /dev/null +++ b/src/test/abilities/hyper_cutter.test.ts @@ -0,0 +1,58 @@ +import { BattleStat } from "#app/data/battle-stat"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { SPLASH_ONLY } from "#test/utils/testUtils"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Hyper Cutter", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .moveset([Moves.SAND_ATTACK, Moves.NOBLE_ROAR, Moves.DEFOG, Moves.OCTOLOCK]) + .ability(Abilities.BALL_FETCH) + .enemySpecies(Species.SHUCKLE) + .enemyAbility(Abilities.HYPER_CUTTER) + .enemyMoveset(SPLASH_ONLY); + }); + + // Reference Link: https://bulbapedia.bulbagarden.net/wiki/Hyper_Cutter_(Ability) + + it("only prevents ATK drops", async () => { + await game.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.OCTOLOCK)); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.DEFOG)); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.NOBLE_ROAR)); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.SAND_ATTACK)); + await game.toNextTurn(); + game.override.moveset([Moves.STRING_SHOT]); + game.doAttack(getMovePosition(game.scene, 0, Moves.STRING_SHOT)); + await game.toNextTurn(); + + expect(enemy.summonData.battleStats[BattleStat.ATK]).toEqual(0); + [BattleStat.ACC, BattleStat.DEF, BattleStat.EVA, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD].forEach((stat: number) => expect(enemy.summonData.battleStats[stat]).toBeLessThan(0)); + }); +}); diff --git a/src/test/moves/rollout.test.ts b/src/test/moves/rollout.test.ts index ad323c447f5..728fe1ecd45 100644 --- a/src/test/moves/rollout.test.ts +++ b/src/test/moves/rollout.test.ts @@ -12,6 +12,7 @@ import { SPLASH_ONLY } from "#test/utils/testUtils"; describe("Moves - Rollout", () => { let phaserGame: Phaser.Game; let game: GameManager; + const TIMEOUT = 20 * 1000; beforeAll(() => { phaserGame = new Phaser.Game({ @@ -77,5 +78,5 @@ describe("Moves - Rollout", () => { // reset expect(turn6Dmg).toBeGreaterThanOrEqual(turn1Dmg - variance); expect(turn6Dmg).toBeLessThanOrEqual(turn1Dmg + variance); - }); + }, TIMEOUT); }); diff --git a/src/test/ui/transfer-item.test.ts b/src/test/ui/transfer-item.test.ts index bbb9a823ad9..9315971e484 100644 --- a/src/test/ui/transfer-item.test.ts +++ b/src/test/ui/transfer-item.test.ts @@ -87,7 +87,6 @@ describe("UI - Transfer Items", () => { handler.processInput(Button.ACTION); // select Pokemon expect(handler.optionsContainer.list.some((option) => (option as BBCodeText).text?.includes("Transfer"))).toBe(true); - game.phaseInterceptor.unlock(); }); diff --git a/src/test/ui/type-hints.test.ts b/src/test/ui/type-hints.test.ts new file mode 100644 index 00000000000..eb0191812e8 --- /dev/null +++ b/src/test/ui/type-hints.test.ts @@ -0,0 +1,89 @@ +import { Button } from "#app/enums/buttons.js"; +import { Moves } from "#app/enums/moves"; +import { Species } from "#app/enums/species"; +import { CommandPhase } from "#app/phases"; +import FightUiHandler from "#app/ui/fight-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import MockText from "../utils/mocks/mocksContainer/mockText"; +import { SPLASH_ONLY } from "../utils/testUtils"; + +describe("UI - Type Hints", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(async () => { + game = new GameManager(phaserGame); + game.settings.typeHints(true); //activate type hints + game.override.battleType("single").startingLevel(100).startingWave(1).enemyMoveset(SPLASH_ONLY); + }); + + it("check immunity color", async () => { + game.override + .battleType("single") + .startingLevel(100) + .startingWave(1) + .enemySpecies(Species.FLORGES) + .enemyMoveset(SPLASH_ONLY) + .moveset([Moves.DRAGON_CLAW]); + game.settings.typeHints(true); //activate type hints + + await game.startBattle([Species.RAYQUAZA]); + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + const { ui } = game.scene; + const handler = ui.getHandler(); + handler.processInput(Button.ACTION); // select "Fight" + game.phaseInterceptor.unlock(); + }); + + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const { ui } = game.scene; + const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); + const dragonClawText = movesContainer + .getAll() + .find((text) => text.text === "Dragon Claw")! as unknown as MockText; + + expect.soft(dragonClawText.color).toBe("#929292"); + ui.getHandler().processInput(Button.ACTION); + }); + await game.phaseInterceptor.to(CommandPhase); + }); + + it("check status move color", async () => { + game.override.enemySpecies(Species.FLORGES).moveset([Moves.GROWL]); + + await game.startBattle([Species.RAYQUAZA]); + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + const { ui } = game.scene; + const handler = ui.getHandler(); + handler.processInput(Button.ACTION); // select "Fight" + game.phaseInterceptor.unlock(); + }); + + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const { ui } = game.scene; + const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); + const growlText = movesContainer + .getAll() + .find((text) => text.text === "Growl")! as unknown as MockText; + + expect.soft(growlText.color).toBe(undefined); + ui.getHandler().processInput(Button.ACTION); + }); + await game.phaseInterceptor.to(CommandPhase); + }); +}); diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index 27ba7a215eb..6333179e3b2 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -30,6 +30,7 @@ import { MoveHelper } from "./helpers/moveHelper"; import { vi } from "vitest"; import { ClassicModeHelper } from "./helpers/classicModeHelper"; import { DailyModeHelper } from "./helpers/dailyModeHelper"; +import { SettingsHelper } from "./helpers/settingsHelper"; /** * Class to manage the game state and transitions between phases. @@ -44,6 +45,7 @@ export default class GameManager { public readonly move: MoveHelper; public readonly classicMode: ClassicModeHelper; public readonly dailyMode: DailyModeHelper; + public readonly settings: SettingsHelper; /** * Creates an instance of GameManager. @@ -63,6 +65,7 @@ export default class GameManager { this.move = new MoveHelper(this); this.classicMode = new ClassicModeHelper(this); this.dailyMode = new DailyModeHelper(this); + this.settings = new SettingsHelper(this); } /** diff --git a/src/test/utils/helpers/settingsHelper.ts b/src/test/utils/helpers/settingsHelper.ts new file mode 100644 index 00000000000..dec9e160d51 --- /dev/null +++ b/src/test/utils/helpers/settingsHelper.ts @@ -0,0 +1,15 @@ +import { GameManagerHelper } from "./gameManagerHelper"; + +/** + * Helper to handle settings for tests + */ +export class SettingsHelper extends GameManagerHelper { + + /** + * Disable/Enable type hints settings + * @param enable true to enabled, false to disabled + */ + typeHints(enable: boolean) { + this.game.scene.typeHints = enable; + } +} diff --git a/src/test/utils/mocks/mocksContainer/mockContainer.ts b/src/test/utils/mocks/mocksContainer/mockContainer.ts index d3672cb5235..5babd9e71b2 100644 --- a/src/test/utils/mocks/mocksContainer/mockContainer.ts +++ b/src/test/utils/mocks/mocksContainer/mockContainer.ts @@ -1,4 +1,5 @@ import MockTextureManager from "#test/utils/mocks/mockTextureManager"; +import { vi } from "vitest"; import { MockGameObject } from "../mockGameObject"; export default class MockContainer implements MockGameObject { @@ -13,6 +14,7 @@ export default class MockContainer implements MockGameObject { public frame; protected textureManager; public list: MockGameObject[] = []; + private name?: string; constructor(textureManager: MockTextureManager, x, y) { this.x = x; @@ -159,9 +161,10 @@ export default class MockContainer implements MockGameObject { // Moves this Game Object to be below the given Game Object in the display list. } - setName(name) { + setName = vi.fn((name: string) => { + this.name = name; // return this.phaserSprite.setName(name); - } + }); bringToTop(obj) { // Brings this Game Object to the top of its parents display list. diff --git a/src/test/utils/mocks/mocksContainer/mockText.ts b/src/test/utils/mocks/mocksContainer/mockText.ts index 5d405efadfd..6b9ecf083fd 100644 --- a/src/test/utils/mocks/mocksContainer/mockText.ts +++ b/src/test/utils/mocks/mocksContainer/mockText.ts @@ -1,4 +1,5 @@ import UI from "#app/ui/ui"; +import { vi } from "vitest"; import { MockGameObject } from "../mockGameObject"; export default class MockText implements MockGameObject { @@ -10,6 +11,8 @@ export default class MockText implements MockGameObject { public list: MockGameObject[] = []; public style; public text = ""; + private name?: string; + public color?: string; constructor(textureManager, x, y, content, styleOptions) { this.scene = textureManager.scene; @@ -190,10 +193,9 @@ export default class MockText implements MockGameObject { }; } - setColor(color) { - // Sets the tint of this Game Object. - // return this.phaserText.setColor(color); - } + setColor = vi.fn((color: string) => { + this.color = color; + }); setShadowColor(color) { // Sets the shadow color. @@ -219,9 +221,9 @@ export default class MockText implements MockGameObject { // return this.phaserText.setAlpha(alpha); } - setName(name) { - // return this.phaserText.setName(name); - } + setName = vi.fn((name: string) => { + this.name = name; + }); setAlign(align) { // return this.phaserText.setAlign(align); diff --git a/src/ui/battle-message-ui-handler.ts b/src/ui/battle-message-ui-handler.ts index 1c7dfb27630..7a30e2787df 100644 --- a/src/ui/battle-message-ui-handler.ts +++ b/src/ui/battle-message-ui-handler.ts @@ -226,7 +226,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler { highestIv = ivs[s]; } }); - if (shownStat) { + if (shownStat !== null && shownStat !== undefined) { shownStats.push(shownStat); statsPool.splice(statsPool.indexOf(shownStat), 1); } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 8279ab72a70..4ade6ca5d20 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -12,6 +12,8 @@ import {Button} from "#enums/buttons"; import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; export default class FightUiHandler extends UiHandler { + public static readonly MOVES_CONTAINER_NAME = "moves"; + private movesContainer: Phaser.GameObjects.Container; private moveInfoContainer: Phaser.GameObjects.Container; private typeIcon: Phaser.GameObjects.Sprite; @@ -35,7 +37,7 @@ export default class FightUiHandler extends UiHandler { const ui = this.getUi(); this.movesContainer = this.scene.add.container(18, -38.7); - this.movesContainer.setName("moves"); + this.movesContainer.setName(FightUiHandler.MOVES_CONTAINER_NAME); ui.add(this.movesContainer); this.moveInfoContainer = this.scene.add.container(1, 0); @@ -271,11 +273,10 @@ export default class FightUiHandler extends UiHandler { return undefined; } - const moveColors = opponents.map((opponent) => { - return opponent.getMoveEffectiveness(pokemon, pokemonMove); - }).filter((eff) => !!eff).sort((a, b) => b - a).map((effectiveness) => { - return getTypeDamageMultiplierColor(effectiveness, "offense"); - }); + const moveColors = opponents + .map((opponent) => opponent.getMoveEffectiveness(pokemon, pokemonMove)) + .sort((a, b) => b - a) + .map((effectiveness) => getTypeDamageMultiplierColor(effectiveness ?? 0, "offense")); return moveColors[0]; } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 5e942f3e75a..9f2df1f2329 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2916,14 +2916,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const isCaught = this.scene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0); const isVariant3Caught = !!(isCaught & DexAttr.VARIANT_3); const isVariant2Caught = !!(isCaught & DexAttr.VARIANT_2); + const isDefaultVariantCaught = !!(isCaught & DexAttr.DEFAULT_VARIANT); const isVariantCaught = !!(isCaught & DexAttr.SHINY); const isMaleCaught = !!(isCaught & DexAttr.MALE); const isFemaleCaught = !!(isCaught & DexAttr.FEMALE); + const starterAttributes = this.starterPreferences[species.speciesId]; + + const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); + const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); + const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); + if (!dexEntry.caughtAttr) { - const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); - const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); - const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); if (shiny === undefined || shiny !== props.shiny) { shiny = props.shiny; } @@ -2942,6 +2946,83 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (natureIndex === undefined || natureIndex !== defaultNature) { natureIndex = defaultNature; } + } else { + // compare current shiny, formIndex, female, variant, abilityIndex, natureIndex with the caught ones + // if the current ones are not caught, we need to find the next caught ones + if (shiny) { + if (!(isVariantCaught || isVariant2Caught || isVariant3Caught)) { + shiny = false; + starterAttributes.shiny = false; + variant = 0; + starterAttributes.variant = 0; + } else { + shiny = true; + starterAttributes.shiny = true; + if (variant === 0 && !isDefaultVariantCaught) { + if (isVariant2Caught) { + variant = 1; + starterAttributes.variant = 1; + } else if (isVariant3Caught) { + variant = 2; + starterAttributes.variant = 2; + } else { + variant = 0; + starterAttributes.variant = 0; + } + } else if (variant === 1 && !isVariant2Caught) { + if (isVariantCaught) { + variant = 0; + starterAttributes.variant = 0; + } else if (isVariant3Caught) { + variant = 2; + starterAttributes.variant = 2; + } else { + variant = 0; + starterAttributes.variant = 0; + } + } else if (variant === 2 && !isVariant3Caught) { + if (isVariantCaught) { + variant = 0; + starterAttributes.variant = 0; + } else if (isVariant2Caught) { + variant = 1; + starterAttributes.variant = 1; + } else { + variant = 0; + starterAttributes.variant = 0; + } + } + } + } + if (female) { + if (!isFemaleCaught) { + female = false; + starterAttributes.female = false; + } + } else { + if (!isMaleCaught) { + female = true; + starterAttributes.female = true; + } + } + + if (species.forms) { + const formCount = species.forms.length; + let newFormIndex = formIndex??0; + if (species.forms[newFormIndex]) { + const isValidForm = species.forms[newFormIndex].isStarterSelectable && dexEntry.caughtAttr & this.scene.gameData.getFormAttr(newFormIndex); + if (!isValidForm) { + do { + newFormIndex = (newFormIndex + 1) % formCount; + if (species.forms[newFormIndex].isStarterSelectable && dexEntry.caughtAttr & this.scene.gameData.getFormAttr(newFormIndex)) { + break; + } + } while (newFormIndex !== props.formIndex); + formIndex = newFormIndex; + starterAttributes.form = formIndex; + } + } + } } this.shinyOverlay.setVisible(shiny ?? false); // TODO: is false the correct default? @@ -2993,12 +3074,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } if (dexEntry.caughtAttr && species.malePercent !== null) { - let gender: Gender; - if ((female && isFemaleCaught) || (!female && !isMaleCaught)) { - gender = Gender.FEMALE; - } else { - gender = Gender.MALE; - } + const gender = !female ? Gender.MALE : Gender.FEMALE; this.pokemonGenderText.setText(getGenderSymbol(gender)); this.pokemonGenderText.setColor(getGenderColor(gender)); this.pokemonGenderText.setShadowColor(getGenderColor(gender, true)); @@ -3479,7 +3555,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { checkIconId(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, female: boolean, formIndex: number, shiny: boolean, variant: number) { if (icon.frame.name !== species.getIconId(female, formIndex, shiny, variant)) { - console.log(`${species.name}'s variant icon does not exist. Replacing with default.`); + console.log(`${species.name}'s icon ${icon.frame.name} does not match getIconId with female: ${female}, formIndex: ${formIndex}, shiny: ${shiny}, variant: ${variant}`); icon.setTexture(species.getIconAtlasKey(formIndex, false, variant)); icon.setFrame(species.getIconId(female, formIndex, false, variant)); } diff --git a/src/utils.ts b/src/utils.ts index aa45c091286..c51ac2b5b0b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -552,3 +552,11 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar: boole } return null; } + +/** + * Returns if an object is null or undefined + * @param object + */ +export function isNullOrUndefined(object: any): boolean { + return null === object || undefined === object; +} From ae2ab120dce57cc2ff065f3c756a4a3696a3e909 Mon Sep 17 00:00:00 2001 From: Frederico Santos Date: Mon, 19 Aug 2024 03:23:52 +0100 Subject: [PATCH 13/13] Deleted phases.ts (#3618) --- src/battle-scene.ts | 18 +- src/data/ability.ts | 5 +- src/data/arena-tag.ts | 5 +- src/data/battler-tags.ts | 7 +- src/data/berry.ts | 3 +- src/data/move.ts | 10 +- src/field/arena.ts | 2 +- src/field/pokemon.ts | 10 +- src/modifier/modifier.ts | 6 +- src/phases.ts | 5861 ----------------- src/phases/add-enemy-buff-modifier-phase.ts | 26 + src/phases/attempt-capture-phase.ts | 288 + src/phases/attempt-run-phase.ts | 56 + src/phases/battle-end-phase.ts | 55 + src/phases/battle-phase.ts | 47 + src/phases/berry-phase.ts | 52 + src/phases/check-switch-phase.ts | 61 + src/phases/command-phase.ts | 288 + src/phases/common-anim-phase.ts | 26 + src/phases/damage-phase.ts | 84 + src/{ => phases}/egg-hatch-phase.ts | 27 +- src/phases/egg-lapse-phase.ts | 35 + src/phases/encounter-phase.ts | 379 ++ src/phases/end-card-phase.ts | 40 + src/phases/end-evolution-phase.ts | 16 + src/phases/enemy-command-phase.ts | 86 + .../enemy-party-member-pokemon-phase.ts | 13 + src/{ => phases}/evolution-phase.ts | 36 +- src/phases/exp-phase.ts | 35 + src/phases/faint-phase.ts | 171 + src/phases/field-phase.ts | 44 + src/{ => phases}/form-change-phase.ts | 148 +- src/phases/game-over-modifier-reward-phase.ts | 27 + src/phases/game-over-phase.ts | 203 + src/phases/hide-party-exp-bar-phase.ts | 14 + src/phases/learn-move-phase.ts | 103 + src/phases/level-cap-phase.ts | 20 + src/phases/level-up-phase.ts | 59 + src/phases/login-phase.ts | 116 + src/phases/message-phase.ts | 38 + src/phases/modifier-reward-phase.ts | 30 + src/phases/money-reward-phase.ts | 34 + src/phases/move-anim-test-phase.ts | 44 + src/phases/move-effect-phase.ts | 447 ++ src/phases/move-end-phase.ts | 23 + src/phases/move-header-phase.ts | 30 + src/phases/move-phase.ts | 329 + src/phases/new-battle-phase.ts | 11 + src/phases/new-biome-encounter-phase.ts | 38 + src/phases/next-encounter-phase.ts | 46 + src/phases/obtain-status-effect-phase.ts | 48 + src/phases/outdated-phase.ts | 13 + src/phases/party-heal-phase.ts | 40 + src/phases/party-member-pokemon-phase.ts | 27 + src/phases/party-status-cure-phase.ts | 48 + .../player-party-member-pokemon-phase.ts | 13 + src/phases/pokemon-heal-phase.ts | 104 + src/phases/pokemon-phase.ts | 29 + src/phases/post-game-over-phase.ts | 46 + src/phases/post-summon-phase.ts | 24 + src/phases/post-turn-status-effect-phase.ts | 61 + src/phases/quiet-form-change-phase.ts | 133 + src/phases/reload-session-phase.ts | 39 + src/phases/return-phase.ts | 26 + src/phases/ribbon-modifier-reward-phase.ts | 33 + src/phases/scan-ivs-phase.ts | 69 + src/phases/select-biome-phase.ts | 84 + src/phases/select-challenge-phase.ts | 17 + src/phases/select-gender-phase.ts | 46 + src/phases/select-modifier-phase.ts | 234 + src/phases/select-starter-phase.ts | 112 + src/phases/select-target-phase.ts | 32 + src/phases/shiny-sparkle-phase.ts | 16 + src/phases/show-ability-phase.ts | 29 + src/phases/show-party-exp-bar-phase.ts | 56 + src/phases/show-trainer-phase.ts | 24 + src/phases/stat-change-phase.ts | 234 + src/phases/summon-missing-phase.ts | 15 + src/phases/summon-phase.ts | 194 + src/phases/switch-biome-phase.ts | 65 + src/phases/switch-phase.ts | 65 + src/phases/switch-summon-phase.ts | 168 + src/phases/test-message-phase.ts | 8 + src/phases/title-phase.ts | 303 + src/phases/toggle-double-position-phase.ts | 31 + src/phases/trainer-message-test-phase.ts | 41 + src/phases/trainer-victory-phase.ts | 65 + src/phases/turn-end-phase.ts | 71 + src/phases/turn-init-phase.ts | 65 + src/phases/turn-start-phase.ts | 172 + src/phases/unavailable-phase.ts | 17 + src/phases/unlock-phase.ts | 27 + src/phases/victory-phase.ts | 151 + src/phases/weather-effect-phase.ts | 67 + src/system/game-data.ts | 3 +- src/system/settings/settings.ts | 10 +- src/system/voucher.ts | 67 +- src/test/abilities/ability_timing.test.ts | 4 +- src/test/abilities/aura_break.test.ts | 2 +- src/test/abilities/battery.test.ts | 3 +- src/test/abilities/battle_bond.test.ts | 4 +- src/test/abilities/costar.test.ts | 3 +- src/test/abilities/disguise.test.ts | 6 +- src/test/abilities/dry_skin.test.ts | 2 +- src/test/abilities/flash_fire.test.ts | 3 +- src/test/abilities/gulp_missile.test.ts | 10 +- src/test/abilities/heatproof.test.ts | 2 +- src/test/abilities/hustle.test.ts | 3 +- src/test/abilities/ice_face.test.ts | 7 +- src/test/abilities/intimidate.test.ts | 7 +- src/test/abilities/intrepid_sword.test.ts | 2 +- src/test/abilities/libero.test.ts | 2 +- src/test/abilities/magic_guard.test.ts | 2 +- src/test/abilities/moxie.test.ts | 4 +- src/test/abilities/mycelium_might.test.ts | 3 +- src/test/abilities/parental_bond.test.ts | 7 +- src/test/abilities/pastel_veil.test.ts | 3 +- src/test/abilities/power_construct.test.ts | 4 +- src/test/abilities/power_spot.test.ts | 3 +- src/test/abilities/protean.test.ts | 2 +- src/test/abilities/quick_draw.test.ts | 2 +- src/test/abilities/sand_veil.test.ts | 4 +- src/test/abilities/sap_sipper.test.ts | 3 +- src/test/abilities/schooling.test.ts | 4 +- src/test/abilities/screen_cleaner.test.ts | 3 +- src/test/abilities/serene_grace.test.ts | 3 +- src/test/abilities/sheer_force.test.ts | 3 +- src/test/abilities/shield_dust.test.ts | 3 +- src/test/abilities/shields_down.test.ts | 4 +- src/test/abilities/stall.test.ts | 2 +- src/test/abilities/steely_spirit.test.ts | 3 +- src/test/abilities/sturdy.test.ts | 3 +- src/test/abilities/sweet_veil.test.ts | 4 +- src/test/abilities/unseen_fist.test.ts | 2 +- src/test/abilities/volt_absorb.test.ts | 2 +- src/test/abilities/wind_power.test.ts | 2 +- src/test/abilities/wind_rider.test.ts | 2 +- src/test/abilities/wonder_skin.test.ts | 2 +- src/test/abilities/zen_mode.test.ts | 13 +- src/test/abilities/zero_to_hero.test.ts | 4 +- src/test/arena/arena_gravity.test.ts | 3 +- src/test/arena/weather_fog.test.ts | 2 +- src/test/arena/weather_strong_winds.test.ts | 2 +- src/test/battle/battle-order.test.ts | 5 +- src/test/battle/battle.test.ts | 30 +- src/test/battle/double_battle.test.ts | 3 +- src/test/battle/special_battle.test.ts | 2 +- src/test/battlerTags/octolock.test.ts | 2 +- src/test/battlerTags/stockpiling.test.ts | 2 +- src/test/items/grip_claw.test.ts | 4 +- src/test/items/leek.test.ts | 2 +- src/test/items/leftovers.test.ts | 3 +- src/test/items/lock_capsule.test.ts | 2 +- src/test/items/scope_lens.test.ts | 2 +- src/test/items/toxic_orb.test.ts | 5 +- src/test/moves/astonish.test.ts | 5 +- src/test/moves/aurora_veil.test.ts | 2 +- src/test/moves/baton_pass.test.ts | 3 +- src/test/moves/beak_blast.test.ts | 4 +- src/test/moves/beat_up.test.ts | 2 +- src/test/moves/belly_drum.test.ts | 2 +- src/test/moves/ceaseless_edge.test.ts | 3 +- src/test/moves/clangorous_soul.test.ts | 2 +- src/test/moves/crafty_shield.test.ts | 3 +- src/test/moves/double_team.test.ts | 2 +- src/test/moves/dragon_rage.test.ts | 2 +- src/test/moves/dragon_tail.test.ts | 4 +- src/test/moves/dynamax_cannon.test.ts | 4 +- src/test/moves/fillet_away.test.ts | 2 +- src/test/moves/fissure.test.ts | 3 +- src/test/moves/flame_burst.test.ts | 3 +- src/test/moves/flower_shield.test.ts | 2 +- src/test/moves/focus_punch.test.ts | 6 +- src/test/moves/follow_me.test.ts | 4 +- src/test/moves/foresight.test.ts | 2 +- src/test/moves/freezy_frost.test.ts | 3 +- src/test/moves/fusion_flare.test.ts | 2 +- src/test/moves/fusion_flare_bolt.test.ts | 5 +- src/test/moves/glaive_rush.test.ts | 3 +- src/test/moves/growth.test.ts | 4 +- src/test/moves/hard_press.test.ts | 2 +- src/test/moves/haze.test.ts | 3 +- src/test/moves/hyper_beam.test.ts | 3 +- src/test/moves/light_screen.test.ts | 2 +- src/test/moves/lucky_chant.test.ts | 3 +- src/test/moves/magnet_rise.test.ts | 3 +- src/test/moves/make_it_rain.test.ts | 3 +- src/test/moves/mat_block.test.ts | 4 +- src/test/moves/miracle_eye.test.ts | 2 +- src/test/moves/multi_target.test.ts | 2 +- src/test/moves/octolock.test.ts | 4 +- src/test/moves/parting_shot.test.ts | 5 +- src/test/moves/protect.test.ts | 2 +- src/test/moves/purify.test.ts | 2 +- src/test/moves/quick_guard.test.ts | 3 +- src/test/moves/rage_powder.test.ts | 4 +- src/test/moves/reflect.test.ts | 2 +- src/test/moves/rollout.test.ts | 2 +- src/test/moves/roost.test.ts | 3 +- src/test/moves/shell_trap.test.ts | 4 +- src/test/moves/spikes.test.ts | 2 +- src/test/moves/spit_up.test.ts | 3 +- src/test/moves/spotlight.test.ts | 4 +- src/test/moves/stockpile.test.ts | 3 +- src/test/moves/swallow.test.ts | 3 +- src/test/moves/tackle.test.ts | 4 +- src/test/moves/tail_whip.test.ts | 4 +- src/test/moves/tailwind.test.ts | 2 +- src/test/moves/thousand_arrows.test.ts | 3 +- src/test/moves/tidy_up.test.ts | 3 +- src/test/moves/u_turn.test.ts | 3 +- src/test/moves/wide_guard.test.ts | 3 +- src/test/phases/phases.test.ts | 4 +- src/test/ui/starter-select.test.ts | 4 +- src/test/ui/transfer-item.test.ts | 3 +- src/test/ui/type-hints.test.ts | 2 +- src/test/utils/gameManager.ts | 13 +- src/test/utils/helpers/classicModeHelper.ts | 3 +- src/test/utils/helpers/dailyModeHelper.ts | 3 +- src/test/utils/helpers/moveHelper.ts | 2 +- src/test/utils/phaseInterceptor.ts | 74 +- src/ui/ball-ui-handler.ts | 2 +- src/ui/challenges-select-ui-handler.ts | 3 +- src/ui/command-ui-handler.ts | 2 +- src/ui/egg-hatch-scene-handler.ts | 2 +- src/ui/fight-ui-handler.ts | 2 +- src/ui/party-ui-handler.ts | 3 +- src/ui/starter-select-ui-handler.ts | 3 +- 228 files changed, 7037 insertions(+), 6279 deletions(-) delete mode 100644 src/phases.ts create mode 100644 src/phases/add-enemy-buff-modifier-phase.ts create mode 100644 src/phases/attempt-capture-phase.ts create mode 100644 src/phases/attempt-run-phase.ts create mode 100644 src/phases/battle-end-phase.ts create mode 100644 src/phases/battle-phase.ts create mode 100644 src/phases/berry-phase.ts create mode 100644 src/phases/check-switch-phase.ts create mode 100644 src/phases/command-phase.ts create mode 100644 src/phases/common-anim-phase.ts create mode 100644 src/phases/damage-phase.ts rename src/{ => phases}/egg-hatch-phase.ts (95%) create mode 100644 src/phases/egg-lapse-phase.ts create mode 100644 src/phases/encounter-phase.ts create mode 100644 src/phases/end-card-phase.ts create mode 100644 src/phases/end-evolution-phase.ts create mode 100644 src/phases/enemy-command-phase.ts create mode 100644 src/phases/enemy-party-member-pokemon-phase.ts rename src/{ => phases}/evolution-phase.ts (96%) create mode 100644 src/phases/exp-phase.ts create mode 100644 src/phases/faint-phase.ts create mode 100644 src/phases/field-phase.ts rename src/{ => phases}/form-change-phase.ts (57%) create mode 100644 src/phases/game-over-modifier-reward-phase.ts create mode 100644 src/phases/game-over-phase.ts create mode 100644 src/phases/hide-party-exp-bar-phase.ts create mode 100644 src/phases/learn-move-phase.ts create mode 100644 src/phases/level-cap-phase.ts create mode 100644 src/phases/level-up-phase.ts create mode 100644 src/phases/login-phase.ts create mode 100644 src/phases/message-phase.ts create mode 100644 src/phases/modifier-reward-phase.ts create mode 100644 src/phases/money-reward-phase.ts create mode 100644 src/phases/move-anim-test-phase.ts create mode 100644 src/phases/move-effect-phase.ts create mode 100644 src/phases/move-end-phase.ts create mode 100644 src/phases/move-header-phase.ts create mode 100644 src/phases/move-phase.ts create mode 100644 src/phases/new-battle-phase.ts create mode 100644 src/phases/new-biome-encounter-phase.ts create mode 100644 src/phases/next-encounter-phase.ts create mode 100644 src/phases/obtain-status-effect-phase.ts create mode 100644 src/phases/outdated-phase.ts create mode 100644 src/phases/party-heal-phase.ts create mode 100644 src/phases/party-member-pokemon-phase.ts create mode 100644 src/phases/party-status-cure-phase.ts create mode 100644 src/phases/player-party-member-pokemon-phase.ts create mode 100644 src/phases/pokemon-heal-phase.ts create mode 100644 src/phases/pokemon-phase.ts create mode 100644 src/phases/post-game-over-phase.ts create mode 100644 src/phases/post-summon-phase.ts create mode 100644 src/phases/post-turn-status-effect-phase.ts create mode 100644 src/phases/quiet-form-change-phase.ts create mode 100644 src/phases/reload-session-phase.ts create mode 100644 src/phases/return-phase.ts create mode 100644 src/phases/ribbon-modifier-reward-phase.ts create mode 100644 src/phases/scan-ivs-phase.ts create mode 100644 src/phases/select-biome-phase.ts create mode 100644 src/phases/select-challenge-phase.ts create mode 100644 src/phases/select-gender-phase.ts create mode 100644 src/phases/select-modifier-phase.ts create mode 100644 src/phases/select-starter-phase.ts create mode 100644 src/phases/select-target-phase.ts create mode 100644 src/phases/shiny-sparkle-phase.ts create mode 100644 src/phases/show-ability-phase.ts create mode 100644 src/phases/show-party-exp-bar-phase.ts create mode 100644 src/phases/show-trainer-phase.ts create mode 100644 src/phases/stat-change-phase.ts create mode 100644 src/phases/summon-missing-phase.ts create mode 100644 src/phases/summon-phase.ts create mode 100644 src/phases/switch-biome-phase.ts create mode 100644 src/phases/switch-phase.ts create mode 100644 src/phases/switch-summon-phase.ts create mode 100644 src/phases/test-message-phase.ts create mode 100644 src/phases/title-phase.ts create mode 100644 src/phases/toggle-double-position-phase.ts create mode 100644 src/phases/trainer-message-test-phase.ts create mode 100644 src/phases/trainer-victory-phase.ts create mode 100644 src/phases/turn-end-phase.ts create mode 100644 src/phases/turn-init-phase.ts create mode 100644 src/phases/turn-start-phase.ts create mode 100644 src/phases/unavailable-phase.ts create mode 100644 src/phases/unlock-phase.ts create mode 100644 src/phases/victory-phase.ts create mode 100644 src/phases/weather-effect-phase.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 4faf3863e3c..b72e79c866d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1,6 +1,5 @@ import Phaser from "phaser"; import UI from "./ui/ui"; -import { NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, TurnInitPhase, ReturnPhase, LevelCapPhase, ShowTrainerPhase, LoginPhase, MovePhase, TitlePhase, SwitchPhase, SummonPhase, ToggleDoublePositionPhase } from "./phases"; import Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon"; import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species"; import { Constructor } from "#app/utils"; @@ -38,7 +37,7 @@ import { addUiThemeOverrides } from "./ui/ui-theme"; import PokemonData from "./system/pokemon-data"; import { Nature } from "./data/nature"; import { SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger, pokemonFormChanges, FormChangeItem, SpeciesFormChange } from "./data/pokemon-forms"; -import { FormChangePhase, QuietFormChangePhase } from "./form-change-phase"; +import { FormChangePhase } from "./phases/form-change-phase"; import { getTypeRgb } from "./data/type"; import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler"; import CharSprite from "./ui/char-sprite"; @@ -69,6 +68,21 @@ import i18next from "i18next"; import {TrainerType} from "#enums/trainer-type"; import { battleSpecDialogue } from "./data/dialogue"; import { LoadingScene } from "./loading-scene"; +import { LevelCapPhase } from "./phases/level-cap-phase"; +import { LoginPhase } from "./phases/login-phase"; +import { MessagePhase } from "./phases/message-phase"; +import { MovePhase } from "./phases/move-phase"; +import { NewBiomeEncounterPhase } from "./phases/new-biome-encounter-phase"; +import { NextEncounterPhase } from "./phases/next-encounter-phase"; +import { QuietFormChangePhase } from "./phases/quiet-form-change-phase"; +import { ReturnPhase } from "./phases/return-phase"; +import { SelectBiomePhase } from "./phases/select-biome-phase"; +import { ShowTrainerPhase } from "./phases/show-trainer-phase"; +import { SummonPhase } from "./phases/summon-phase"; +import { SwitchPhase } from "./phases/switch-phase"; +import { TitlePhase } from "./phases/title-phase"; +import { ToggleDoublePositionPhase } from "./phases/toggle-double-position-phase"; +import { TurnInitPhase } from "./phases/turn-init-phase"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; diff --git a/src/data/ability.ts b/src/data/ability.ts index 38ca4eb25d0..8e020849a17 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -3,7 +3,6 @@ import { Type } from "./type"; import { Constructor } from "#app/utils"; import * as Utils from "../utils"; import { BattleStat, getBattleStatName } from "./battle-stat"; -import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases"; import { getPokemonNameWithAffix } from "../messages"; import { Weather, WeatherType } from "./weather"; import { BattlerTag, GroundedTag, GulpMissileTag, SemiInvulnerableTag } from "./battler-tags"; @@ -26,6 +25,10 @@ import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; +import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; export class Ability implements Localizable { public id: Abilities; diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index fdfcd4d076a..3394df827fb 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -4,7 +4,6 @@ import * as Utils from "../utils"; import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "./move"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { HitResult, PokemonMove } from "../field/pokemon"; -import { MoveEffectPhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases"; import { StatusEffect } from "./status-effect"; import { BattlerIndex } from "../battle"; import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability"; @@ -15,6 +14,10 @@ import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; +import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; export enum ArenaTagSide { BOTH, diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index b059b4cf6b2..ede8d029327 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1,5 +1,4 @@ import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims"; -import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangeCallback, StatChangePhase } from "../phases"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; import { Stat, getStatName } from "./pokemon-stat"; @@ -18,6 +17,12 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import i18next from "#app/plugins/i18n.js"; +import { CommonAnimPhase } from "#app/phases/common-anim-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; +import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js"; +import { StatChangePhase, StatChangeCallback } from "#app/phases/stat-change-phase.js"; export enum BattlerTagLapseType { FAINT, diff --git a/src/data/berry.ts b/src/data/berry.ts index 30b89848452..e962219ca46 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -1,4 +1,3 @@ -import { PokemonHealPhase, StatChangePhase } from "../phases"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { HitResult } from "../field/pokemon"; import { BattleStat } from "./battle-stat"; @@ -8,6 +7,8 @@ import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } import i18next from "i18next"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; export function getBerryName(berryType: BerryType): string { return i18next.t(`berry:${BerryType[berryType]}.name`); diff --git a/src/data/move.ts b/src/data/move.ts index 24651bacb2e..af3f49bea0d 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1,5 +1,4 @@ import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; -import { BattleEndPhase, MoveEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchPhase, SwitchSummonPhase } from "../phases"; import { BattleStat, getBattleStatName } from "./battle-stat"; import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, TrappedTag, TypeBoostTag } from "./battler-tags"; import { getPokemonNameWithAffix } from "../messages"; @@ -28,6 +27,15 @@ import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { MoveUsedEvent } from "#app/events/battle-scene.js"; +import { PartyStatusCurePhase } from "#app/phases/party-status-cure-phase.js"; +import { BattleEndPhase } from "#app/phases/battle-end-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { NewBattlePhase } from "#app/phases/new-battle-phase.js"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; +import { SwitchPhase } from "#app/phases/switch-phase.js"; +import { SwitchSummonPhase } from "#app/phases/switch-summon-phase.js"; export enum MoveCategory { PHYSICAL, diff --git a/src/field/arena.ts b/src/field/arena.ts index 923a0a4e286..eb3770d61d5 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -4,7 +4,6 @@ import { Constructor } from "#app/utils"; import * as Utils from "../utils"; import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species"; import { Weather, WeatherType, getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage } from "../data/weather"; -import { CommonAnimPhase } from "../phases"; import { CommonAnim } from "../data/battle-anims"; import { Type } from "../data/type"; import Move from "../data/move"; @@ -21,6 +20,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; +import { CommonAnimPhase } from "#app/phases/common-anim-phase.js"; export class Arena { public scene: BattleScene; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e38813ed3c0..6a445a83b4e 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -17,7 +17,6 @@ import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims"; import { Status, StatusEffect, getRandomStatus } from "../data/status-effect"; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms"; -import { DamagePhase, FaintPhase, LearnMovePhase, MoveEffectPhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase, MoveEndPhase } from "../phases"; import { BattleStat } from "../data/battle-stat"; import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag } from "../data/battler-tags"; import { WeatherType } from "../data/weather"; @@ -51,6 +50,15 @@ import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { getPokemonNameWithAffix } from "#app/messages.js"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { FaintPhase } from "#app/phases/faint-phase.js"; +import { LearnMovePhase } from "#app/phases/learn-move-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; +import { SwitchSummonPhase } from "#app/phases/switch-summon-phase.js"; +import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase.js"; export enum FieldPosition { CENTER, diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index f4ec6c499f4..8a6598f5849 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,5 +1,4 @@ import * as ModifierTypes from "./modifier-type"; -import { LearnMovePhase, LevelUpPhase, PokemonHealPhase } from "../phases"; import BattleScene from "../battle-scene"; import { getLevelTotalExp } from "../data/exp"; import { MAX_PER_TYPE_POKEBALLS, PokeballType } from "../data/pokeball"; @@ -7,7 +6,7 @@ import Pokemon, { PlayerPokemon } from "../field/pokemon"; import { Stat } from "../data/pokemon-stat"; import { addTextObject, TextStyle } from "../ui/text"; import { Type } from "../data/type"; -import { EvolutionPhase } from "../evolution-phase"; +import { EvolutionPhase } from "../phases/evolution-phase"; import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions } from "../data/pokemon-evolutions"; import { getPokemonNameWithAffix } from "../messages"; import * as Utils from "../utils"; @@ -28,6 +27,9 @@ import i18next from "i18next"; import { allMoves } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; +import { LearnMovePhase } from "#app/phases/learn-move-phase.js"; +import { LevelUpPhase } from "#app/phases/level-up-phase.js"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; export type ModifierPredicate = (modifier: Modifier) => boolean; diff --git a/src/phases.ts b/src/phases.ts deleted file mode 100644 index c50d25acf60..00000000000 --- a/src/phases.ts +++ /dev/null @@ -1,5861 +0,0 @@ -import BattleScene, { bypassLogin } from "./battle-scene"; -import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon"; -import * as Utils from "./utils"; -import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr, MoveHeaderAttr, MoveCategory } from "./data/move"; -import { Mode } from "./ui/ui"; -import { Command } from "./ui/command-ui-handler"; -import { Stat } from "./data/pokemon-stat"; -import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier, PokemonResetNegativeStatStageModifier } from "./modifier/modifier"; -import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler"; -import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball"; -import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims"; -import { StatusEffect, getStatusEffectActivationText, getStatusEffectCatchRateMultiplier, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText } from "./data/status-effect"; -import { SummaryUiMode } from "./ui/summary-ui-handler"; -import EvolutionSceneHandler from "./ui/evolution-scene-handler"; -import { EvolutionPhase } from "./evolution-phase"; -import { Phase } from "./phase"; -import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "./data/battle-stat"; -import { biomeLinks, getBiomeName } from "./data/biomes"; -import { ModifierTier } from "./modifier/modifier-tier"; -import { FusePokemonModifierType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, RememberMoveModifierType, TmModifierType, getDailyRunStarterModifiers, getEnemyBuffModifierForWave, getModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, modifierTypes, regenerateModifierPoolThresholds } from "./modifier/modifier-type"; -import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import { BattlerTagLapseType, CenterOfAttentionTag, EncoreTag, ProtectedTag, SemiInvulnerableTag, TrappedTag } from "./data/battler-tags"; -import { getPokemonNameWithAffix } from "./messages"; -import { Starter } from "./ui/starter-select-ui-handler"; -import { Gender } from "./data/gender"; -import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; -import { ArenaTagSide, ArenaTrapTag, ConditionalProtectTag, MistTag, TrickRoomTag } from "./data/arena-tag"; -import { CheckTrappedAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, ChangeMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, PreventBypassSpeedChanceAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr, ReduceBurnDamageAbAttr } from "./data/ability"; -import { Unlockables, getUnlockableName } from "./system/unlockables"; -import { getBiomeKey } from "./field/arena"; -import { BattleType, BattlerIndex, TurnCommand } from "./battle"; -import { ChallengeAchv, HealAchv, LevelAchv, achvs } from "./system/achv"; -import { TrainerSlot, trainerConfigs } from "./data/trainer-config"; -import { EggHatchPhase } from "./egg-hatch-phase"; -import { Egg } from "./data/egg"; -import { vouchers } from "./system/voucher"; -import { clientSessionId, loggedInUser, updateUserInfo } from "./account"; -import { SessionSaveData } from "./system/game-data"; -import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims"; -import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms"; -import { battleSpecDialogue, getCharVariantFromDialogue, miscDialogue } from "./data/dialogue"; -import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "./ui/modifier-select-ui-handler"; -import { SettingKeys } from "./system/settings/settings"; -import { Tutorial, handleTutorial } from "./tutorial"; -import { TerrainType } from "./data/terrain"; -import { OptionSelectConfig, OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; -import { SaveSlotUiMode } from "./ui/save-slot-select-ui-handler"; -import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run"; -import { GameMode, GameModes, getGameMode } from "./game-mode"; -import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./data/pokemon-species"; -import i18next from "./plugins/i18n"; -import Overrides from "#app/overrides"; -import { TextStyle, addTextObject, getTextColor } from "./ui/text"; -import { Type } from "./data/type"; -import { BerryUsedEvent, EncounterPhaseEvent, MoveUsedEvent, TurnEndEvent, TurnInitEvent } from "./events/battle-scene"; -import { Abilities } from "#enums/abilities"; -import { ArenaTagType } from "#enums/arena-tag-type"; -import { BattleSpec } from "#enums/battle-spec"; -import { BattleStyle } from "#enums/battle-style"; -import { BattlerTagType } from "#enums/battler-tag-type"; -import { Biome } from "#enums/biome"; -import { ExpNotification } from "#enums/exp-notification"; -import { Moves } from "#enums/moves"; -import { PlayerGender } from "#enums/player-gender"; -import { Species } from "#enums/species"; -import { TrainerType } from "#enums/trainer-type"; -import { applyChallenges, ChallengeType } from "./data/challenge"; -import { pokemonEvolutions } from "./data/pokemon-evolutions"; - -const { t } = i18next; - -export class LoginPhase extends Phase { - private showText: boolean; - - constructor(scene: BattleScene, showText?: boolean) { - super(scene); - - this.showText = showText === undefined || !!showText; - } - - start(): void { - super.start(); - - const hasSession = !!Utils.getCookie(Utils.sessionIdKey); - - this.scene.ui.setMode(Mode.LOADING, { buttonActions: [] }); - Utils.executeIf(bypassLogin || hasSession, updateUserInfo).then(response => { - const success = response ? response[0] : false; - const statusCode = response ? response[1] : null; - if (!success) { - if (!statusCode || statusCode === 400) { - if (this.showText) { - this.scene.ui.showText(i18next.t("menu:logInOrCreateAccount")); - } - - this.scene.playSound("menu_open"); - - const loadData = () => { - updateUserInfo().then(success => { - if (!success[0]) { - Utils.removeCookie(Utils.sessionIdKey); - this.scene.reset(true, true); - return; - } - this.scene.gameData.loadSystem().then(() => this.end()); - }); - }; - - this.scene.ui.setMode(Mode.LOGIN_FORM, { - buttonActions: [ - () => { - this.scene.ui.playSelect(); - loadData(); - }, () => { - this.scene.playSound("menu_open"); - this.scene.ui.setMode(Mode.REGISTRATION_FORM, { - buttonActions: [ - () => { - this.scene.ui.playSelect(); - updateUserInfo().then(success => { - if (!success[0]) { - Utils.removeCookie(Utils.sessionIdKey); - this.scene.reset(true, true); - return; - } - this.end(); - } ); - }, () => { - this.scene.unshiftPhase(new LoginPhase(this.scene, false)); - this.end(); - } - ] - }); - }, () => { - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); - const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; - const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&prompt=none`; - window.open(discordUrl, "_self"); - }, () => { - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); - const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; - const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&redirect_uri=${redirectUri}&response_type=code&scope=openid`; - window.open(googleUrl, "_self"); - } - ] - }); - } else if (statusCode === 401) { - Utils.removeCookie(Utils.sessionIdKey); - this.scene.reset(true, true); - } else { - this.scene.unshiftPhase(new UnavailablePhase(this.scene)); - super.end(); - } - return null; - } else { - this.scene.gameData.loadSystem().then(success => { - if (success || bypassLogin) { - this.end(); - } else { - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(t("menu:failedToLoadSaveData")); - } - }); - } - }); - } - - end(): void { - this.scene.ui.setMode(Mode.MESSAGE); - - if (!this.scene.gameData.gender) { - this.scene.unshiftPhase(new SelectGenderPhase(this.scene)); - } - - handleTutorial(this.scene, Tutorial.Intro).then(() => super.end()); - } -} - -export class TitlePhase extends Phase { - private loaded: boolean; - private lastSessionData: SessionSaveData; - public gameMode: GameModes; - - constructor(scene: BattleScene) { - super(scene); - - this.loaded = false; - } - - start(): void { - super.start(); - - this.scene.ui.clearText(); - this.scene.ui.fadeIn(250); - - this.scene.playBgm("title", true); - - this.scene.gameData.getSession(loggedInUser?.lastSessionSlot ?? -1).then(sessionData => { - if (sessionData) { - this.lastSessionData = sessionData; - const biomeKey = getBiomeKey(sessionData.arena.biome); - const bgTexture = `${biomeKey}_bg`; - this.scene.arenaBg.setTexture(bgTexture); - } - this.showOptions(); - }).catch(err => { - console.error(err); - this.showOptions(); - }); - } - - showOptions(): void { - const options: OptionSelectItem[] = []; - if (loggedInUser && loggedInUser.lastSessionSlot > -1) { - options.push({ - label: i18next.t("continue", {ns: "menu"}), - handler: () => { - this.loadSaveSlot(this.lastSessionData || !loggedInUser ? -1 : loggedInUser.lastSessionSlot); - return true; - } - }); - } - options.push({ - label: i18next.t("menu:newGame"), - handler: () => { - const setModeAndEnd = (gameMode: GameModes) => { - this.gameMode = gameMode; - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.clearText(); - this.end(); - }; - if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { - const options: OptionSelectItem[] = [ - { - label: GameMode.getModeName(GameModes.CLASSIC), - handler: () => { - setModeAndEnd(GameModes.CLASSIC); - return true; - } - }, - { - label: GameMode.getModeName(GameModes.CHALLENGE), - handler: () => { - setModeAndEnd(GameModes.CHALLENGE); - return true; - } - }, - { - label: GameMode.getModeName(GameModes.ENDLESS), - handler: () => { - setModeAndEnd(GameModes.ENDLESS); - return true; - } - } - ]; - if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { - options.push({ - label: GameMode.getModeName(GameModes.SPLICED_ENDLESS), - handler: () => { - setModeAndEnd(GameModes.SPLICED_ENDLESS); - return true; - } - }); - } - options.push({ - label: i18next.t("menu:cancel"), - handler: () => { - this.scene.clearPhaseQueue(); - this.scene.pushPhase(new TitlePhase(this.scene)); - super.end(); - return true; - } - }); - this.scene.ui.showText(i18next.t("menu:selectGameMode"), null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); - } else { - this.gameMode = GameModes.CLASSIC; - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.clearText(); - this.end(); - } - return true; - } - }, - { - label: i18next.t("menu:loadGame"), - handler: () => { - this.scene.ui.setOverlayMode(Mode.SAVE_SLOT, SaveSlotUiMode.LOAD, - (slotId: integer) => { - if (slotId === -1) { - return this.showOptions(); - } - this.loadSaveSlot(slotId); - }); - return true; - } - }, - { - label: i18next.t("menu:dailyRun"), - handler: () => { - this.initDailyRun(); - return true; - }, - keepOpen: true - }, - { - label: i18next.t("menu:settings"), - handler: () => { - this.scene.ui.setOverlayMode(Mode.SETTINGS); - return true; - }, - keepOpen: true - }); - const config: OptionSelectConfig = { - options: options, - noCancel: true, - yOffset: 47 - }; - this.scene.ui.setMode(Mode.TITLE, config); - } - - loadSaveSlot(slotId: integer): void { - this.scene.sessionSlotId = slotId > -1 || !loggedInUser ? slotId : loggedInUser.lastSessionSlot; - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.resetModeChain(); - this.scene.gameData.loadSession(this.scene, slotId, slotId === -1 ? this.lastSessionData : undefined).then((success: boolean) => { - if (success) { - this.loaded = true; - this.scene.ui.showText(i18next.t("menu:sessionSuccess"), null, () => this.end()); - } else { - this.end(); - } - }).catch(err => { - console.error(err); - this.scene.ui.showText(i18next.t("menu:failedToLoadSession"), null); - }); - } - - initDailyRun(): void { - this.scene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { - this.scene.clearPhaseQueue(); - if (slotId === -1) { - this.scene.pushPhase(new TitlePhase(this.scene)); - return super.end(); - } - this.scene.sessionSlotId = slotId; - - const generateDaily = (seed: string) => { - this.scene.gameMode = getGameMode(GameModes.DAILY); - - this.scene.setSeed(seed); - this.scene.resetSeed(1); - - this.scene.money = this.scene.gameMode.getStartingMoney(); - - const starters = getDailyRunStarters(this.scene, seed); - const startingLevel = this.scene.gameMode.getStartingLevel(); - - const party = this.scene.getParty(); - const loadPokemonAssets: Promise[] = []; - for (const starter of starters) { - const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); - const starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); - const starterGender = starter.species.malePercent !== null - ? !starterProps.female ? Gender.MALE : Gender.FEMALE - : Gender.GENDERLESS; - const starterPokemon = this.scene.addPlayerPokemon(starter.species, startingLevel, starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, undefined, starter.nature); - starterPokemon.setVisible(false); - party.push(starterPokemon); - loadPokemonAssets.push(starterPokemon.loadAssets()); - } - - regenerateModifierPoolThresholds(party, ModifierPoolType.DAILY_STARTER); - const modifiers: Modifier[] = Array(3).fill(null).map(() => modifierTypes.EXP_SHARE().withIdFromFunc(modifierTypes.EXP_SHARE).newModifier()) - .concat(Array(3).fill(null).map(() => modifierTypes.GOLDEN_EXP_CHARM().withIdFromFunc(modifierTypes.GOLDEN_EXP_CHARM).newModifier())) - .concat(getDailyRunStarterModifiers(party)) - .filter((m) => m !== null); - - for (const m of modifiers) { - this.scene.addModifier(m, true, false, false, true); - } - this.scene.updateModifiers(true, true); - - Promise.all(loadPokemonAssets).then(() => { - this.scene.time.delayedCall(500, () => this.scene.playBgm()); - this.scene.gameData.gameStats.dailyRunSessionsPlayed++; - this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene)); - this.scene.newBattle(); - this.scene.arena.init(); - this.scene.sessionPlayTime = 0; - this.scene.lastSavePlayTime = 0; - this.end(); - }); - }; - - // If Online, calls seed fetch from db to generate daily run. If Offline, generates a daily run based on current date. - if (!Utils.isLocal) { - fetchDailyRunSeed().then(seed => { - if (seed) { - generateDaily(seed); - } else { - throw new Error("Daily run seed is null!"); - } - }).catch(err => { - console.error("Failed to load daily run:\n", err); - }); - } else { - generateDaily(btoa(new Date().toISOString().substring(0, 10))); - } - }); - } - - end(): void { - if (!this.loaded && !this.scene.gameMode.isDaily) { - this.scene.arena.preloadBgm(); - this.scene.gameMode = getGameMode(this.gameMode); - if (this.gameMode === GameModes.CHALLENGE) { - this.scene.pushPhase(new SelectChallengePhase(this.scene)); - } else { - this.scene.pushPhase(new SelectStarterPhase(this.scene)); - } - this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene)); - } else { - this.scene.playBgm(); - } - - this.scene.pushPhase(new EncounterPhase(this.scene, this.loaded)); - - if (this.loaded) { - const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length; - - this.scene.pushPhase(new SummonPhase(this.scene, 0, true, true)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) { - this.scene.pushPhase(new SummonPhase(this.scene, 1, true, true)); - } - - if (this.scene.currentBattle.battleType !== BattleType.TRAINER && (this.scene.currentBattle.waveIndex > 1 || !this.scene.gameMode.isDaily)) { - const minPartySize = this.scene.currentBattle.double ? 2 : 1; - if (availablePartyMembers > minPartySize) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); - if (this.scene.currentBattle.double) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); - } - } - } - } - - for (const achv of Object.keys(this.scene.gameData.achvUnlocks)) { - if (vouchers.hasOwnProperty(achv)) { - this.scene.validateVoucher(vouchers[achv]); - } - } - - super.end(); - } -} - -export class UnavailablePhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start(): void { - this.scene.ui.setMode(Mode.UNAVAILABLE, () => { - this.scene.unshiftPhase(new LoginPhase(this.scene, true)); - this.end(); - }); - } -} - -export class ReloadSessionPhase extends Phase { - private systemDataStr: string | null; - - constructor(scene: BattleScene, systemDataStr?: string) { - super(scene); - - this.systemDataStr = systemDataStr ?? null; - } - - start(): void { - this.scene.ui.setMode(Mode.SESSION_RELOAD); - - let delayElapsed = false; - let loaded = false; - - this.scene.time.delayedCall(Utils.fixedInt(1500), () => { - if (loaded) { - this.end(); - } else { - delayElapsed = true; - } - }); - - this.scene.gameData.clearLocalData(); - - (this.systemDataStr ? this.scene.gameData.initSystem(this.systemDataStr) : this.scene.gameData.loadSystem()).then(() => { - if (delayElapsed) { - this.end(); - } else { - loaded = true; - } - }); - } -} - -export class OutdatedPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start(): void { - this.scene.ui.setMode(Mode.OUTDATED); - } -} - -export class SelectGenderPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start(): void { - super.start(); - - this.scene.ui.showText(i18next.t("menu:boyOrGirl"), null, () => { - this.scene.ui.setMode(Mode.OPTION_SELECT, { - options: [ - { - label: i18next.t("settings:boy"), - handler: () => { - this.scene.gameData.gender = PlayerGender.MALE; - this.scene.gameData.saveSetting(SettingKeys.Player_Gender, 0); - this.scene.gameData.saveSystem().then(() => this.end()); - return true; - } - }, - { - label: i18next.t("settings:girl"), - handler: () => { - this.scene.gameData.gender = PlayerGender.FEMALE; - this.scene.gameData.saveSetting(SettingKeys.Player_Gender, 1); - this.scene.gameData.saveSystem().then(() => this.end()); - return true; - } - } - ] - }); - }); - } - - end(): void { - this.scene.ui.setMode(Mode.MESSAGE); - super.end(); - } -} - -export class SelectChallengePhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.playBgm("menu"); - - this.scene.ui.setMode(Mode.CHALLENGE_SELECT); - } -} - -export class SelectStarterPhase extends Phase { - - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.playBgm("menu"); - - this.scene.ui.setMode(Mode.STARTER_SELECT, (starters: Starter[]) => { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { - if (slotId === -1) { - this.scene.clearPhaseQueue(); - this.scene.pushPhase(new TitlePhase(this.scene)); - return this.end(); - } - this.scene.sessionSlotId = slotId; - this.initBattle(starters); - }); - }); - } - - /** - * Initialize starters before starting the first battle - * @param starters {@linkcode Pokemon} with which to start the first battle - */ - initBattle(starters: Starter[]) { - const party = this.scene.getParty(); - const loadPokemonAssets: Promise[] = []; - starters.forEach((starter: Starter, i: integer) => { - if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { - starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species); - } - const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); - let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); - if ( - starter.species.speciesId in Overrides.STARTER_FORM_OVERRIDES && - starter.species.forms[Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!] - ) { - starterFormIndex = Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!; - } - - let starterGender = starter.species.malePercent !== null - ? !starterProps.female ? Gender.MALE : Gender.FEMALE - : Gender.GENDERLESS; - if (Overrides.GENDER_OVERRIDE !== null) { - starterGender = Overrides.GENDER_OVERRIDE; - } - const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0); - const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature); - starter.moveset && starterPokemon.tryPopulateMoveset(starter.moveset); - if (starter.passive) { - starterPokemon.passive = true; - } - starterPokemon.luck = this.scene.gameData.getDexAttrLuck(this.scene.gameData.dexData[starter.species.speciesId].caughtAttr); - if (starter.pokerus) { - starterPokemon.pokerus = true; - } - - if (starter.nickname) { - starterPokemon.nickname = starter.nickname; - } - - if (this.scene.gameMode.isSplicedOnly) { - starterPokemon.generateFusionSpecies(true); - } - starterPokemon.setVisible(false); - applyChallenges(this.scene.gameMode, ChallengeType.STARTER_MODIFY, starterPokemon); - party.push(starterPokemon); - loadPokemonAssets.push(starterPokemon.loadAssets()); - }); - overrideModifiers(this.scene); - overrideHeldItems(this.scene, party[0]); - Promise.all(loadPokemonAssets).then(() => { - SoundFade.fadeOut(this.scene, this.scene.sound.get("menu"), 500, true); - this.scene.time.delayedCall(500, () => this.scene.playBgm()); - if (this.scene.gameMode.isClassic) { - this.scene.gameData.gameStats.classicSessionsPlayed++; - } else { - this.scene.gameData.gameStats.endlessSessionsPlayed++; - } - this.scene.newBattle(); - this.scene.arena.init(); - this.scene.sessionPlayTime = 0; - this.scene.lastSavePlayTime = 0; - // Ensures Keldeo (or any future Pokemon that have this type of form change) starts in the correct form - this.scene.getParty().forEach((p: PlayerPokemon) => { - this.scene.triggerPokemonFormChange(p, SpeciesFormChangeMoveLearnedTrigger); - }); - this.end(); - }); - } -} - -export class BattlePhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void { - const sprites = this.scene.currentBattle.trainer?.getSprites()!; // TODO: is this bang correct? - const tintSprites = this.scene.currentBattle.trainer?.getTintSprites()!; // TODO: is this bang correct? - for (let i = 0; i < sprites.length; i++) { - const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2; - [sprites[i], tintSprites[i]].map(sprite => { - if (visible) { - sprite.x = trainerSlot || sprites.length < 2 ? 0 : i ? 16 : -16; - } - sprite.setVisible(visible); - sprite.clearTint(); - }); - sprites[i].setVisible(visible); - tintSprites[i].setVisible(visible); - sprites[i].clearTint(); - tintSprites[i].clearTint(); - } - this.scene.tweens.add({ - targets: this.scene.currentBattle.trainer, - x: "-=16", - y: "+=16", - alpha: 1, - ease: "Sine.easeInOut", - duration: 750 - }); - } - - hideEnemyTrainer(): void { - this.scene.tweens.add({ - targets: this.scene.currentBattle.trainer, - x: "+=16", - y: "-=16", - alpha: 0, - ease: "Sine.easeInOut", - duration: 750 - }); - } -} - -type PokemonFunc = (pokemon: Pokemon) => void; - -export abstract class FieldPhase extends BattlePhase { - getOrder(): BattlerIndex[] { - const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[]; - const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[]; - - // We shuffle the list before sorting so speed ties produce random results - let orderedTargets: Pokemon[] = playerField.concat(enemyField); - // We seed it with the current turn to prevent an inconsistency where it - // was varying based on how long since you last reloaded - this.scene.executeWithSeedOffset(() => { - orderedTargets = Utils.randSeedShuffle(orderedTargets); - }, this.scene.currentBattle.turn, this.scene.waveSeed); - - orderedTargets.sort((a: Pokemon, b: Pokemon) => { - const aSpeed = a?.getBattleStat(Stat.SPD) || 0; - const bSpeed = b?.getBattleStat(Stat.SPD) || 0; - - return bSpeed - aSpeed; - }); - - const speedReversed = new Utils.BooleanHolder(false); - this.scene.arena.applyTags(TrickRoomTag, speedReversed); - - if (speedReversed.value) { - orderedTargets = orderedTargets.reverse(); - } - - return orderedTargets.map(t => t.getFieldIndex() + (!t.isPlayer() ? BattlerIndex.ENEMY : 0)); - } - - executeForAll(func: PokemonFunc): void { - const field = this.scene.getField(true).filter(p => p.summonData); - field.forEach(pokemon => func(pokemon)); - } -} - -export abstract class PokemonPhase extends FieldPhase { - protected battlerIndex: BattlerIndex | integer; - public player: boolean; - public fieldIndex: integer; - - constructor(scene: BattleScene, battlerIndex?: BattlerIndex | integer) { - super(scene); - - if (battlerIndex === undefined) { - battlerIndex = scene.getField().find(p => p?.isActive())!.getBattlerIndex(); // TODO: is the bang correct here? - } - - this.battlerIndex = battlerIndex; - this.player = battlerIndex < 2; - this.fieldIndex = battlerIndex % 2; - } - - getPokemon(): Pokemon { - if (this.battlerIndex > BattlerIndex.ENEMY_2) { - return this.scene.getPokemonById(this.battlerIndex)!; //TODO: is this bang correct? - } - return this.scene.getField()[this.battlerIndex]!; //TODO: is this bang correct? - } -} - -export abstract class PartyMemberPokemonPhase extends FieldPhase { - protected partyMemberIndex: integer; - protected fieldIndex: integer; - protected player: boolean; - - constructor(scene: BattleScene, partyMemberIndex: integer, player: boolean) { - super(scene); - - this.partyMemberIndex = partyMemberIndex; - this.fieldIndex = partyMemberIndex < this.scene.currentBattle.getBattlerCount() - ? partyMemberIndex - : -1; - this.player = player; - } - - getParty(): Pokemon[] { - return this.player ? this.scene.getParty() : this.scene.getEnemyParty(); - } - - getPokemon(): Pokemon { - return this.getParty()[this.partyMemberIndex]; - } -} - -export abstract class PlayerPartyMemberPokemonPhase extends PartyMemberPokemonPhase { - constructor(scene: BattleScene, partyMemberIndex: integer) { - super(scene, partyMemberIndex, true); - } - - getPlayerPokemon(): PlayerPokemon { - return super.getPokemon() as PlayerPokemon; - } -} - -export abstract class EnemyPartyMemberPokemonPhase extends PartyMemberPokemonPhase { - constructor(scene: BattleScene, partyMemberIndex: integer) { - super(scene, partyMemberIndex, false); - } - - getEnemyPokemon(): EnemyPokemon { - return super.getPokemon() as EnemyPokemon; - } -} - -export class EncounterPhase extends BattlePhase { - private loaded: boolean; - - constructor(scene: BattleScene, loaded?: boolean) { - super(scene); - - this.loaded = !!loaded; - } - - start() { - super.start(); - - this.scene.updateGameInfo(); - - this.scene.initSession(); - - this.scene.eventTarget.dispatchEvent(new EncounterPhaseEvent()); - - // Failsafe if players somehow skip floor 200 in classic mode - if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) { - this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } - - const loadEnemyAssets: Promise[] = []; - - const battle = this.scene.currentBattle; - - let totalBst = 0; - - battle.enemyLevels?.forEach((level, e) => { - if (!this.loaded) { - if (battle.battleType === BattleType.TRAINER) { - battle.enemyParty[e] = battle.trainer?.genPartyMember(e)!; // TODO:: is the bang correct here? - } else { - const enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true); - battle.enemyParty[e] = this.scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, !!this.scene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies)); - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { - battle.enemyParty[e].ivs = new Array(6).fill(31); - } - this.scene.getParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => { - applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, battle.enemyParty[e]); - }); - } - } - const enemyPokemon = this.scene.getEnemyParty()[e]; - if (e < (battle.double ? 2 : 1)) { - enemyPokemon.setX(-66 + enemyPokemon.getFieldPositionOffset()[0]); - enemyPokemon.resetSummonData(); - } - - if (!this.loaded) { - this.scene.gameData.setPokemonSeen(enemyPokemon, true, battle.battleType === BattleType.TRAINER); - } - - if (enemyPokemon.species.speciesId === Species.ETERNATUS) { - if (this.scene.gameMode.isClassic && (battle.battleSpec === BattleSpec.FINAL_BOSS || this.scene.gameMode.isWaveFinal(battle.waveIndex))) { - if (battle.battleSpec !== BattleSpec.FINAL_BOSS) { - enemyPokemon.formIndex = 1; - enemyPokemon.updateScale(); - } - enemyPokemon.setBoss(); - } else if (!(battle.waveIndex % 1000)) { - enemyPokemon.formIndex = 1; - enemyPokemon.updateScale(); - const bossMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier && m.pokemonId === enemyPokemon.id, false) as TurnHeldItemTransferModifier; - this.scene.removeModifier(bossMBH!); - bossMBH?.setTransferrableFalse(); - this.scene.addEnemyModifier(bossMBH!); - } - } - - totalBst += enemyPokemon.getSpeciesForm().baseTotal; - - loadEnemyAssets.push(enemyPokemon.loadAssets()); - - console.log(getPokemonNameWithAffix(enemyPokemon), enemyPokemon.species.speciesId, enemyPokemon.stats); - }); - - if (this.scene.getParty().filter(p => p.isShiny()).length === 6) { - this.scene.validateAchv(achvs.SHINY_PARTY); - } - - if (battle.battleType === BattleType.TRAINER) { - loadEnemyAssets.push(battle.trainer?.loadAssets().then(() => battle.trainer?.initSprite())!); // TODO: is this bang correct? - } else { - // This block only applies for double battles to init the boss segments (idk why it's split up like this) - if (battle.enemyParty.filter(p => p.isBoss()).length > 1) { - for (const enemyPokemon of battle.enemyParty) { - // If the enemy pokemon is a boss and wasn't populated from data source, then set it up - if (enemyPokemon.isBoss() && !enemyPokemon.isPopulatedFromDataSource) { - enemyPokemon.setBoss(true, Math.ceil(enemyPokemon.bossSegments * (enemyPokemon.getSpeciesForm().baseTotal / totalBst))); - enemyPokemon.initBattleInfo(); - } - } - } - } - - Promise.all(loadEnemyAssets).then(() => { - battle.enemyParty.forEach((enemyPokemon, e) => { - if (e < (battle.double ? 2 : 1)) { - if (battle.battleType === BattleType.WILD) { - this.scene.field.add(enemyPokemon); - battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); - const playerPokemon = this.scene.getPlayerPokemon(); - if (playerPokemon?.visible) { - this.scene.field.moveBelow(enemyPokemon as Pokemon, playerPokemon); - } - enemyPokemon.tint(0, 0.5); - } else if (battle.battleType === BattleType.TRAINER) { - enemyPokemon.setVisible(false); - this.scene.currentBattle.trainer?.tint(0, 0.5); - } - if (battle.double) { - enemyPokemon.setFieldPosition(e ? FieldPosition.RIGHT : FieldPosition.LEFT); - } - } - }); - - if (!this.loaded) { - regenerateModifierPoolThresholds(this.scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD); - this.scene.generateEnemyModifiers(); - } - - this.scene.ui.setMode(Mode.MESSAGE).then(() => { - if (!this.loaded) { - //@ts-ignore - this.scene.gameData.saveAll(this.scene, true, battle.waveIndex % 10 === 1 || this.scene.lastSavePlayTime >= 300).then(success => { // TODO: get rid of ts-ignore - this.scene.disableMenu = false; - if (!success) { - return this.scene.reset(true); - } - this.doEncounter(); - }); - } else { - this.doEncounter(); - } - }); - }); - } - - doEncounter() { - this.scene.playBgm(undefined, true); - this.scene.updateModifiers(false); - this.scene.setFieldScale(1); - - /*if (startingWave > 10) { - for (let m = 0; m < Math.min(Math.floor(startingWave / 10), 99); m++) - this.scene.addModifier(getPlayerModifierTypeOptionsForWave((m + 1) * 10, 1, this.scene.getParty())[0].type.newModifier(), true); - this.scene.updateModifiers(true); - }*/ - - for (const pokemon of this.scene.getParty()) { - if (pokemon) { - pokemon.resetBattleData(); - } - } - - if (!this.loaded) { - this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false); - } - - const enemyField = this.scene.getEnemyField(); - this.scene.tweens.add({ - targets: [this.scene.arenaEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.arenaPlayer, this.scene.trainer].flat(), - x: (_target, _key, value, fieldIndex: integer) => fieldIndex < 2 + (enemyField.length) ? value + 300 : value - 300, - duration: 2000, - onComplete: () => { - if (!this.tryOverrideForBattleSpec()) { - this.doEncounterCommon(); - } - } - }); - } - - getEncounterMessage(): string { - const enemyField = this.scene.getEnemyField(); - - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { - return i18next.t("battle:bossAppeared", { bossName: getPokemonNameWithAffix(enemyField[0])}); - } - - if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - if (this.scene.currentBattle.double) { - return i18next.t("battle:trainerAppearedDouble", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); - - } else { - return i18next.t("battle:trainerAppeared", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); - } - } - - return enemyField.length === 1 - ? i18next.t("battle:singleWildAppeared", { pokemonName: enemyField[0].getNameToRender() }) - : i18next.t("battle:multiWildAppeared", { pokemonName1: enemyField[0].getNameToRender(), pokemonName2: enemyField[1].getNameToRender() }); - } - - doEncounterCommon(showEncounterMessage: boolean = true) { - const enemyField = this.scene.getEnemyField(); - - if (this.scene.currentBattle.battleType === BattleType.WILD) { - enemyField.forEach(enemyPokemon => { - enemyPokemon.untint(100, "Sine.easeOut"); - enemyPokemon.cry(); - enemyPokemon.showInfo(); - if (enemyPokemon.isShiny()) { - this.scene.validateAchv(achvs.SEE_SHINY); - } - }); - this.scene.updateFieldScale(); - if (showEncounterMessage) { - this.scene.ui.showText(this.getEncounterMessage(), null, () => this.end(), 1500); - } else { - this.end(); - } - } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - const trainer = this.scene.currentBattle.trainer; - trainer?.untint(100, "Sine.easeOut"); - trainer?.playAnim(); - - const doSummon = () => { - this.scene.currentBattle.started = true; - this.scene.playBgm(undefined); - this.scene.pbTray.showPbTray(this.scene.getParty()); - this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty()); - const doTrainerSummon = () => { - this.hideEnemyTrainer(); - const availablePartyMembers = this.scene.getEnemyParty().filter(p => !p.isFainted()).length; - this.scene.unshiftPhase(new SummonPhase(this.scene, 0, false)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) { - this.scene.unshiftPhase(new SummonPhase(this.scene, 1, false)); - } - this.end(); - }; - if (showEncounterMessage) { - this.scene.ui.showText(this.getEncounterMessage(), null, doTrainerSummon, 1500, true); - } else { - doTrainerSummon(); - } - }; - - const encounterMessages = this.scene.currentBattle.trainer?.getEncounterMessages(); - - if (!encounterMessages?.length) { - doSummon(); - } else { - let message: string; - this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.waveIndex); - message = message!; // tell TS compiler it's defined now - const showDialogueAndSummon = () => { - this.scene.ui.showDialogue(message, trainer?.getName(TrainerSlot.NONE, true), null, () => { - this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => doSummon())); - }); - }; - if (this.scene.currentBattle.trainer?.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { - this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(trainer?.getKey()!, getCharVariantFromDialogue(encounterMessages[0])).then(() => showDialogueAndSummon())); // TODO: is this bang correct? - } else { - showDialogueAndSummon(); - } - } - } - } - - end() { - const enemyField = this.scene.getEnemyField(); - - enemyField.forEach((enemyPokemon, e) => { - if (enemyPokemon.isShiny()) { - this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e)); - } - }); - - if (this.scene.currentBattle.battleType !== BattleType.TRAINER) { - enemyField.map(p => this.scene.pushConditionalPhase(new PostSummonPhase(this.scene, p.getBattlerIndex()), () => { - // if there is not a player party, we can't continue - if (!this.scene.getParty()?.length) { - return false; - } - // how many player pokemon are on the field ? - const pokemonsOnFieldCount = this.scene.getParty().filter(p => p.isOnField()).length; - // if it's a 2vs1, there will never be a 2nd pokemon on our field even - const requiredPokemonsOnField = Math.min(this.scene.getParty().filter((p) => !p.isFainted()).length, 2); - // if it's a double, there should be 2, otherwise 1 - if (this.scene.currentBattle.double) { - return pokemonsOnFieldCount === requiredPokemonsOnField; - } - return pokemonsOnFieldCount === 1; - })); - const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier); - if (ivScannerModifier) { - enemyField.map(p => this.scene.pushPhase(new ScanIvsPhase(this.scene, p.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)))); - } - } - - if (!this.loaded) { - const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()); - - if (!availablePartyMembers[0].isOnField()) { - this.scene.pushPhase(new SummonPhase(this.scene, 0)); - } - - if (this.scene.currentBattle.double) { - if (availablePartyMembers.length > 1) { - this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, true)); - if (!availablePartyMembers[1].isOnField()) { - this.scene.pushPhase(new SummonPhase(this.scene, 1)); - } - } - } else { - if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { - this.scene.pushPhase(new ReturnPhase(this.scene, 1)); - } - this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, false)); - } - - if (this.scene.currentBattle.battleType !== BattleType.TRAINER && (this.scene.currentBattle.waveIndex > 1 || !this.scene.gameMode.isDaily)) { - const minPartySize = this.scene.currentBattle.double ? 2 : 1; - if (availablePartyMembers.length > minPartySize) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); - if (this.scene.currentBattle.double) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); - } - } - } - } - handleTutorial(this.scene, Tutorial.Access_Menu).then(() => super.end()); - } - - tryOverrideForBattleSpec(): boolean { - switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - const enemy = this.scene.getEnemyPokemon(); - this.scene.ui.showText(this.getEncounterMessage(), null, () => { - const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed; - //The two lines below check if English ordinals (1st, 2nd, 3rd, Xth) are used and determine which one to use. - //Otherwise, it defaults to an empty string. - //As of 08-07-24: Spanish and Italian default to the English translations - const ordinalUse = ["en", "es", "it"]; - const currentLanguage = i18next.resolvedLanguage ?? "en"; - const ordinalIndex = (ordinalUse.includes(currentLanguage)) ? ["st", "nd", "rd"][((count + 90) % 100 - 10) % 10 - 1] ?? "th" : ""; - const cycleCount = count.toLocaleString() + ordinalIndex; - const encounterDialogue = i18next.t(`${(this.scene.gameData.gender === PlayerGender.FEMALE) ? "PGF" : "PGM"}battleSpecDialogue:encounter`, {cycleCount: cycleCount}); - this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => { - this.doEncounterCommon(false); - }); - }, 1500, true); - return true; - } - - return false; - } -} - -export class NextEncounterPhase extends EncounterPhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - } - - doEncounter(): void { - this.scene.playBgm(undefined, true); - - for (const pokemon of this.scene.getParty()) { - if (pokemon) { - pokemon.resetBattleData(); - } - } - - this.scene.arenaNextEnemy.setBiome(this.scene.arena.biomeType); - this.scene.arenaNextEnemy.setVisible(true); - - const enemyField = this.scene.getEnemyField(); - this.scene.tweens.add({ - targets: [this.scene.arenaEnemy, this.scene.arenaNextEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.lastEnemyTrainer].flat(), - x: "+=300", - duration: 2000, - onComplete: () => { - this.scene.arenaEnemy.setBiome(this.scene.arena.biomeType); - this.scene.arenaEnemy.setX(this.scene.arenaNextEnemy.x); - this.scene.arenaEnemy.setAlpha(1); - this.scene.arenaNextEnemy.setX(this.scene.arenaNextEnemy.x - 300); - this.scene.arenaNextEnemy.setVisible(false); - if (this.scene.lastEnemyTrainer) { - this.scene.lastEnemyTrainer.destroy(); - } - - if (!this.tryOverrideForBattleSpec()) { - this.doEncounterCommon(); - } - } - }); - } -} - -export class NewBiomeEncounterPhase extends NextEncounterPhase { - constructor(scene: BattleScene) { - super(scene); - } - - doEncounter(): void { - this.scene.playBgm(undefined, true); - - for (const pokemon of this.scene.getParty()) { - if (pokemon) { - pokemon.resetBattleData(); - } - } - - this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false); - - for (const pokemon of this.scene.getParty().filter(p => p.isOnField())) { - applyAbAttrs(PostBiomeChangeAbAttr, pokemon, null); - } - - const enemyField = this.scene.getEnemyField(); - this.scene.tweens.add({ - targets: [this.scene.arenaEnemy, enemyField].flat(), - x: "+=300", - duration: 2000, - onComplete: () => { - if (!this.tryOverrideForBattleSpec()) { - this.doEncounterCommon(); - } - } - }); - } -} - -export class PostSummonPhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); - } - - start() { - super.start(); - - const pokemon = this.getPokemon(); - - if (pokemon.status?.effect === StatusEffect.TOXIC) { - pokemon.status.turnCount = 0; - } - this.scene.arena.applyTags(ArenaTrapTag, pokemon); - applyPostSummonAbAttrs(PostSummonAbAttr, pokemon).then(() => this.end()); - } -} - -export class SelectBiomePhase extends BattlePhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - const currentBiome = this.scene.arena.biomeType; - - const setNextBiome = (nextBiome: Biome) => { - if (this.scene.currentBattle.waveIndex % 10 === 1) { - this.scene.applyModifiers(MoneyInterestModifier, true, this.scene); - this.scene.unshiftPhase(new PartyHealPhase(this.scene, false)); - } - this.scene.unshiftPhase(new SwitchBiomePhase(this.scene, nextBiome)); - this.end(); - }; - - if ((this.scene.gameMode.isClassic && this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex + 9)) - || (this.scene.gameMode.isDaily && this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) - || (this.scene.gameMode.hasShortBiomes && !(this.scene.currentBattle.waveIndex % 50))) { - setNextBiome(Biome.END); - } else if (this.scene.gameMode.hasRandomBiomes) { - setNextBiome(this.generateNextBiome()); - } else if (Array.isArray(biomeLinks[currentBiome])) { - let biomes: Biome[] = []; - this.scene.executeWithSeedOffset(() => { - biomes = (biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) - .filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1])) - .map(b => !Array.isArray(b) ? b : b[0]); - }, this.scene.currentBattle.waveIndex); - if (biomes.length > 1 && this.scene.findModifier(m => m instanceof MapModifier)) { - let biomeChoices: Biome[] = []; - this.scene.executeWithSeedOffset(() => { - biomeChoices = (!Array.isArray(biomeLinks[currentBiome]) - ? [biomeLinks[currentBiome] as Biome] - : biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) - .filter((b, i) => !Array.isArray(b) || !Utils.randSeedInt(b[1])) - .map(b => Array.isArray(b) ? b[0] : b); - }, this.scene.currentBattle.waveIndex); - const biomeSelectItems = biomeChoices.map(b => { - const ret: OptionSelectItem = { - label: getBiomeName(b), - handler: () => { - this.scene.ui.setMode(Mode.MESSAGE); - setNextBiome(b); - return true; - } - }; - return ret; - }); - this.scene.ui.setMode(Mode.OPTION_SELECT, { - options: biomeSelectItems, - delay: 1000 - }); - } else { - setNextBiome(biomes[Utils.randSeedInt(biomes.length)]); - } - } else if (biomeLinks.hasOwnProperty(currentBiome)) { - setNextBiome(biomeLinks[currentBiome] as Biome); - } else { - setNextBiome(this.generateNextBiome()); - } - } - - generateNextBiome(): Biome { - if (!(this.scene.currentBattle.waveIndex % 50)) { - return Biome.END; - } - return this.scene.generateRandomBiome(this.scene.currentBattle.waveIndex); - } -} - -export class SwitchBiomePhase extends BattlePhase { - private nextBiome: Biome; - - constructor(scene: BattleScene, nextBiome: Biome) { - super(scene); - - this.nextBiome = nextBiome; - } - - start() { - super.start(); - - if (this.nextBiome === undefined) { - return this.end(); - } - - this.scene.tweens.add({ - targets: [this.scene.arenaEnemy, this.scene.lastEnemyTrainer], - x: "+=300", - duration: 2000, - onComplete: () => { - this.scene.arenaEnemy.setX(this.scene.arenaEnemy.x - 600); - - this.scene.newArena(this.nextBiome); - - const biomeKey = getBiomeKey(this.nextBiome); - const bgTexture = `${biomeKey}_bg`; - this.scene.arenaBgTransition.setTexture(bgTexture); - this.scene.arenaBgTransition.setAlpha(0); - this.scene.arenaBgTransition.setVisible(true); - this.scene.arenaPlayerTransition.setBiome(this.nextBiome); - this.scene.arenaPlayerTransition.setAlpha(0); - this.scene.arenaPlayerTransition.setVisible(true); - - this.scene.tweens.add({ - targets: [this.scene.arenaPlayer, this.scene.arenaBgTransition, this.scene.arenaPlayerTransition], - duration: 1000, - delay: 1000, - ease: "Sine.easeInOut", - alpha: (target: any) => target === this.scene.arenaPlayer ? 0 : 1, - onComplete: () => { - this.scene.arenaBg.setTexture(bgTexture); - this.scene.arenaPlayer.setBiome(this.nextBiome); - this.scene.arenaPlayer.setAlpha(1); - this.scene.arenaEnemy.setBiome(this.nextBiome); - this.scene.arenaEnemy.setAlpha(1); - this.scene.arenaNextEnemy.setBiome(this.nextBiome); - this.scene.arenaBgTransition.setVisible(false); - this.scene.arenaPlayerTransition.setVisible(false); - if (this.scene.lastEnemyTrainer) { - this.scene.lastEnemyTrainer.destroy(); - } - - this.end(); - } - }); - } - }); - } -} - -export class SummonPhase extends PartyMemberPokemonPhase { - private loaded: boolean; - - constructor(scene: BattleScene, fieldIndex: integer, player: boolean = true, loaded: boolean = false) { - super(scene, fieldIndex, player); - - this.loaded = loaded; - } - - start() { - super.start(); - - this.preSummon(); - } - - /** - * Sends out a Pokemon before the battle begins and shows the appropriate messages - */ - preSummon(): void { - const partyMember = this.getPokemon(); - // If the Pokemon about to be sent out is fainted or illegal under a challenge, switch to the first non-fainted legal Pokemon - if (!partyMember.isAllowedInBattle()) { - console.warn("The Pokemon about to be sent out is fainted or illegal under a challenge. Attempting to resolve..."); - - // First check if they're somehow still in play, if so remove them. - if (partyMember.isOnField()) { - partyMember.leaveField(); - } - - const party = this.getParty(); - - // Find the first non-fainted Pokemon index above the current one - const legalIndex = party.findIndex((p, i) => i > this.partyMemberIndex && p.isAllowedInBattle()); - if (legalIndex === -1) { - console.error("Party Details:\n", party); - console.error("All available Pokemon were fainted or illegal!"); - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new GameOverPhase(this.scene)); - this.end(); - return; - } - - // Swaps the fainted Pokemon and the first non-fainted legal Pokemon in the party - [party[this.partyMemberIndex], party[legalIndex]] = [party[legalIndex], party[this.partyMemberIndex]]; - console.warn("Swapped %s %O with %s %O", getPokemonNameWithAffix(partyMember), partyMember, getPokemonNameWithAffix(party[0]), party[0]); - } - - if (this.player) { - this.scene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(this.getPokemon()) })); - if (this.player) { - this.scene.pbTray.hide(); - } - this.scene.trainer.setTexture(`trainer_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); - this.scene.time.delayedCall(562, () => { - this.scene.trainer.setFrame("2"); - this.scene.time.delayedCall(64, () => { - this.scene.trainer.setFrame("3"); - }); - }); - this.scene.tweens.add({ - targets: this.scene.trainer, - x: -36, - duration: 1000, - onComplete: () => this.scene.trainer.setVisible(false) - }); - this.scene.time.delayedCall(750, () => this.summon()); - } else { - const trainerName = this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); - const pokemonName = this.getPokemon().getNameToRender(); - const message = i18next.t("battle:trainerSendOut", { trainerName, pokemonName }); - - this.scene.pbTrayEnemy.hide(); - this.scene.ui.showText(message, null, () => this.summon()); - } - } - - summon(): void { - const pokemon = this.getPokemon(); - - const pokeball = this.scene.addFieldSprite(this.player ? 36 : 248, this.player ? 80 : 44, "pb", getPokeballAtlasKey(pokemon.pokeball)); - pokeball.setVisible(false); - pokeball.setOrigin(0.5, 0.625); - this.scene.field.add(pokeball); - - if (this.fieldIndex === 1) { - pokemon.setFieldPosition(FieldPosition.RIGHT, 0); - } else { - const availablePartyMembers = this.getParty().filter(p => p.isAllowedInBattle()).length; - pokemon.setFieldPosition(!this.scene.currentBattle.double || availablePartyMembers === 1 ? FieldPosition.CENTER : FieldPosition.LEFT); - } - - const fpOffset = pokemon.getFieldPositionOffset(); - - pokeball.setVisible(true); - - this.scene.tweens.add({ - targets: pokeball, - duration: 650, - x: (this.player ? 100 : 236) + fpOffset[0] - }); - - this.scene.tweens.add({ - targets: pokeball, - duration: 150, - ease: "Cubic.easeOut", - y: (this.player ? 70 : 34) + fpOffset[1], - onComplete: () => { - this.scene.tweens.add({ - targets: pokeball, - duration: 500, - ease: "Cubic.easeIn", - angle: 1440, - y: (this.player ? 132 : 86) + fpOffset[1], - onComplete: () => { - this.scene.playSound("pb_rel"); - pokeball.destroy(); - this.scene.add.existing(pokemon); - this.scene.field.add(pokemon); - if (!this.player) { - const playerPokemon = this.scene.getPlayerPokemon() as Pokemon; - if (playerPokemon?.visible) { - this.scene.field.moveBelow(pokemon, playerPokemon); - } - this.scene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); - } - addPokeballOpenParticles(this.scene, pokemon.x, pokemon.y - 16, pokemon.pokeball); - this.scene.updateModifiers(this.player); - this.scene.updateFieldScale(); - pokemon.showInfo(); - pokemon.playAnim(); - pokemon.setVisible(true); - pokemon.getSprite().setVisible(true); - pokemon.setScale(0.5); - pokemon.tint(getPokeballTintColor(pokemon.pokeball)); - pokemon.untint(250, "Sine.easeIn"); - this.scene.updateFieldScale(); - this.scene.tweens.add({ - targets: pokemon, - duration: 250, - ease: "Sine.easeIn", - scale: pokemon.getSpriteScale(), - onComplete: () => { - pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); - pokemon.getSprite().clearTint(); - pokemon.resetSummonData(); - this.scene.time.delayedCall(1000, () => this.end()); - } - }); - } - }); - } - }); - } - - onEnd(): void { - const pokemon = this.getPokemon(); - - if (pokemon.isShiny()) { - this.scene.unshiftPhase(new ShinySparklePhase(this.scene, pokemon.getBattlerIndex())); - } - - pokemon.resetTurnData(); - - if (!this.loaded || this.scene.currentBattle.battleType === BattleType.TRAINER || (this.scene.currentBattle.waveIndex % 10) === 1) { - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); - this.queuePostSummon(); - } - } - - queuePostSummon(): void { - this.scene.pushPhase(new PostSummonPhase(this.scene, this.getPokemon().getBattlerIndex())); - } - - end() { - this.onEnd(); - - super.end(); - } -} - -export class SwitchSummonPhase extends SummonPhase { - private slotIndex: integer; - private doReturn: boolean; - private batonPass: boolean; - - private lastPokemon: Pokemon; - - /** - * Constructor for creating a new SwitchSummonPhase - * @param scene {@linkcode BattleScene} the scene the phase is associated with - * @param fieldIndex integer representing position on the battle field - * @param slotIndex integer for the index of pokemon (in party of 6) to switch into - * @param doReturn boolean whether to render "comeback" dialogue - * @param batonPass boolean if the switch is from baton pass - * @param player boolean if the switch is from the player - */ - constructor(scene: BattleScene, fieldIndex: integer, slotIndex: integer, doReturn: boolean, batonPass: boolean, player?: boolean) { - super(scene, fieldIndex, player !== undefined ? player : true); - - this.slotIndex = slotIndex; - this.doReturn = doReturn; - this.batonPass = batonPass; - } - - start(): void { - super.start(); - } - - preSummon(): void { - if (!this.player) { - if (this.slotIndex === -1) { - //@ts-ignore - this.slotIndex = this.scene.currentBattle.trainer?.getNextSummonIndex(!this.fieldIndex ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); // TODO: what would be the default trainer-slot fallback? - } - if (this.slotIndex > -1) { - this.showEnemyTrainer(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); - this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty()); - } - } - - if (!this.doReturn || (this.slotIndex !== -1 && !(this.player ? this.scene.getParty() : this.scene.getEnemyParty())[this.slotIndex])) { - if (this.player) { - return this.switchAndSummon(); - } else { - this.scene.time.delayedCall(750, () => this.switchAndSummon()); - return; - } - } - - const pokemon = this.getPokemon(); - - if (!this.batonPass) { - (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); - } - - this.scene.ui.showText(this.player ? - i18next.t("battle:playerComeBack", { pokemonName: getPokemonNameWithAffix(pokemon) }) : - i18next.t("battle:trainerComeBack", { - trainerName: this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER), - pokemonName: getPokemonNameWithAffix(pokemon) - }) - ); - this.scene.playSound("pb_rel"); - pokemon.hideInfo(); - pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, "Sine.easeIn"); - this.scene.tweens.add({ - targets: pokemon, - duration: 250, - ease: "Sine.easeIn", - scale: 0.5, - onComplete: () => { - pokemon.leaveField(!this.batonPass, false); - this.scene.time.delayedCall(750, () => this.switchAndSummon()); - } - }); - } - - switchAndSummon() { - const party = this.player ? this.getParty() : this.scene.getEnemyParty(); - const switchedInPokemon = party[this.slotIndex]; - this.lastPokemon = this.getPokemon(); - applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon); - if (this.batonPass && switchedInPokemon) { - (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedInPokemon.id)); - if (!this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { - const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier - && (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as SwitchEffectTransferModifier; - if (batonPassModifier && !this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { - this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedInPokemon, false); - } - } - } - if (switchedInPokemon) { - party[this.slotIndex] = this.lastPokemon; - party[this.fieldIndex] = switchedInPokemon; - const showTextAndSummon = () => { - this.scene.ui.showText(this.player ? - i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(switchedInPokemon) }) : - i18next.t("battle:trainerGo", { - trainerName: this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER), - pokemonName: this.getPokemon().getNameToRender() - }) - ); - // Ensure improperly persisted summon data (such as tags) is cleared upon switching - if (!this.batonPass) { - switchedInPokemon.resetBattleData(); - switchedInPokemon.resetSummonData(); - } - this.summon(); - }; - if (this.player) { - showTextAndSummon(); - } else { - this.scene.time.delayedCall(1500, () => { - this.hideEnemyTrainer(); - this.scene.pbTrayEnemy.hide(); - showTextAndSummon(); - }); - } - } else { - this.end(); - } - } - - onEnd(): void { - super.onEnd(); - - const pokemon = this.getPokemon(); - - const moveId = this.lastPokemon?.scene.currentBattle.lastMove; - const lastUsedMove = moveId ? allMoves[moveId] : undefined; - - const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command; - const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted(); - - // Compensate for turn spent summoning - // Or compensate for force switch move if switched out pokemon is not fainted - if (currentCommand === Command.POKEMON || lastPokemonIsForceSwitchedAndNotFainted) { - pokemon.battleSummonData.turnCount--; - } - - if (this.batonPass && pokemon) { - pokemon.transferSummon(this.lastPokemon); - } - - this.lastPokemon?.resetSummonData(); - - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); - } - - queuePostSummon(): void { - this.scene.unshiftPhase(new PostSummonPhase(this.scene, this.getPokemon().getBattlerIndex())); - } -} - -export class ReturnPhase extends SwitchSummonPhase { - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, fieldIndex, -1, true, false); - } - - switchAndSummon(): void { - this.end(); - } - - summon(): void { } - - onEnd(): void { - const pokemon = this.getPokemon(); - - pokemon.resetTurnData(); - pokemon.resetSummonData(); - - this.scene.updateFieldScale(); - - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger); - } -} - -export class ShowTrainerPhase extends BattlePhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.trainer.setVisible(true); - - this.scene.trainer.setTexture(`trainer_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); - - this.scene.tweens.add({ - targets: this.scene.trainer, - x: 106, - duration: 1000, - onComplete: () => this.end() - }); - } -} - -export class ToggleDoublePositionPhase extends BattlePhase { - private double: boolean; - - constructor(scene: BattleScene, double: boolean) { - super(scene); - - this.double = double; - } - - start() { - super.start(); - - const playerPokemon = this.scene.getPlayerField().find(p => p.isActive(true)); - if (playerPokemon) { - playerPokemon.setFieldPosition(this.double && this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? FieldPosition.LEFT : FieldPosition.CENTER, 500).then(() => { - if (playerPokemon.getFieldIndex() === 1) { - const party = this.scene.getParty(); - party[1] = party[0]; - party[0] = playerPokemon; - } - this.end(); - }); - } else { - this.end(); - } - } -} - -export class CheckSwitchPhase extends BattlePhase { - protected fieldIndex: integer; - protected useName: boolean; - - constructor(scene: BattleScene, fieldIndex: integer, useName: boolean) { - super(scene); - - this.fieldIndex = fieldIndex; - this.useName = useName; - } - - start() { - super.start(); - - const pokemon = this.scene.getPlayerField()[this.fieldIndex]; - - if (this.scene.battleStyle === BattleStyle.SET) { - super.end(); - return; - } - - if (this.scene.field.getAll().indexOf(pokemon) === -1) { - this.scene.unshiftPhase(new SummonMissingPhase(this.scene, this.fieldIndex)); - super.end(); - return; - } - - if (!this.scene.getParty().slice(1).filter(p => p.isActive()).length) { - super.end(); - return; - } - - if (pokemon.getTag(BattlerTagType.FRENZY)) { - super.end(); - return; - } - - this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? getPokemonNameWithAffix(pokemon) : i18next.t("battle:pokemon") }), null, () => { - this.scene.ui.setMode(Mode.CONFIRM, () => { - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); - this.scene.unshiftPhase(new SwitchPhase(this.scene, this.fieldIndex, false, true)); - this.end(); - }, () => { - this.scene.ui.setMode(Mode.MESSAGE); - this.end(); - }); - }); - } -} - -export class SummonMissingPhase extends SummonPhase { - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, fieldIndex); - } - - preSummon(): void { - this.scene.ui.showText(i18next.t("battle:sendOutPokemon", { pokemonName: getPokemonNameWithAffix(this.getPokemon()) })); - this.scene.time.delayedCall(250, () => this.summon()); - } -} - -export class LevelCapPhase extends FieldPhase { - constructor(scene: BattleScene) { - super(scene); - } - - start(): void { - super.start(); - - this.scene.ui.setMode(Mode.MESSAGE).then(() => { - this.scene.playSound("level_up_fanfare"); - this.scene.ui.showText(i18next.t("battle:levelCapUp", { levelCap: this.scene.getMaxExpLevel() }), null, () => this.end(), null, true); - this.executeForAll(pokemon => pokemon.updateInfo(true)); - }); - } -} - -export class TurnInitPhase extends FieldPhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.getPlayerField().forEach(p => { - // If this pokemon is in play and evolved into something illegal under the current challenge, force a switch - if (p.isOnField() && !p.isAllowedInBattle()) { - this.scene.queueMessage(i18next.t("challenges:illegalEvolution", { "pokemon": p.name }), null, true); - - const allowedPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); - - if (!allowedPokemon.length) { - // If there are no longer any legal pokemon in the party, game over. - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } else if (allowedPokemon.length >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !allowedPokemon[0].isActive(true))) { - // If there is at least one pokemon in the back that is legal to switch in, force a switch. - p.switchOut(false); - } else { - // If there are no pokemon in the back but we're not game overing, just hide the pokemon. - // This should only happen in double battles. - p.leaveField(); - } - if (allowedPokemon.length === 1 && this.scene.currentBattle.double) { - this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); - } - } - }); - - //this.scene.pushPhase(new MoveAnimTestPhase(this.scene)); - this.scene.eventTarget.dispatchEvent(new TurnInitEvent()); - - this.scene.getField().forEach((pokemon, i) => { - if (pokemon?.isActive()) { - if (pokemon.isPlayer()) { - this.scene.currentBattle.addParticipant(pokemon as PlayerPokemon); - } - - pokemon.resetTurnData(); - - this.scene.pushPhase(pokemon.isPlayer() ? new CommandPhase(this.scene, i) : new EnemyCommandPhase(this.scene, i - BattlerIndex.ENEMY)); - } - }); - - this.scene.pushPhase(new TurnStartPhase(this.scene)); - - this.end(); - } -} - -export class CommandPhase extends FieldPhase { - protected fieldIndex: integer; - - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene); - - this.fieldIndex = fieldIndex; - } - - start() { - super.start(); - - if (this.fieldIndex) { - // If we somehow are attempting to check the right pokemon but there's only one pokemon out - // Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching - if (this.scene.getPlayerField().filter(p => p.isActive()).length === 1) { - this.fieldIndex = FieldPosition.CENTER; - } else { - const allyCommand = this.scene.currentBattle.turnCommands[this.fieldIndex - 1]; - if (allyCommand?.command === Command.BALL || allyCommand?.command === Command.RUN) { - this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: allyCommand?.command, skip: true }; - } - } - } - - if (this.scene.currentBattle.turnCommands[this.fieldIndex]?.skip) { - return this.end(); - } - - const playerPokemon = this.scene.getPlayerField()[this.fieldIndex]; - - const moveQueue = playerPokemon.getMoveQueue(); - - while (moveQueue.length && moveQueue[0] - && moveQueue[0].move && (!playerPokemon.getMoveset().find(m => m?.moveId === moveQueue[0].move) - || !playerPokemon.getMoveset()[playerPokemon.getMoveset().findIndex(m => m?.moveId === moveQueue[0].move)]!.isUsable(playerPokemon, moveQueue[0].ignorePP))) { // TODO: is the bang correct? - moveQueue.shift(); - } - - if (moveQueue.length) { - const queuedMove = moveQueue[0]; - if (!queuedMove.move) { - this.handleCommand(Command.FIGHT, -1, false); - } else { - const moveIndex = playerPokemon.getMoveset().findIndex(m => m?.moveId === queuedMove.move); - if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) { // TODO: is the bang correct? - this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, { targets: queuedMove.targets, multiple: queuedMove.targets.length > 1 }); - } else { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - } - } - } else { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - } - } - - handleCommand(command: Command, cursor: integer, ...args: any[]): boolean { - const playerPokemon = this.scene.getPlayerField()[this.fieldIndex]; - const enemyField = this.scene.getEnemyField(); - let success: boolean; - - switch (command) { - case Command.FIGHT: - let useStruggle = false; - if (cursor === -1 || - playerPokemon.trySelectMove(cursor, args[0] as boolean) || - (useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) { - const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? - const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; - const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; - if (!moveId) { - turnCommand.targets = [this.fieldIndex]; - } - console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); - if (moveTargets.targets.length > 1 && moveTargets.multiple) { - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); - } - if (moveTargets.targets.length <= 1 || moveTargets.multiple) { - turnCommand.move!.targets = moveTargets.targets; //TODO: is the bang correct here? - } else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) { - turnCommand.move!.targets = playerPokemon.getMoveQueue()[0].targets; //TODO: is the bang correct here? - } else { - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); - } - this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; - success = true; - } else if (cursor < playerPokemon.getMoveset().length) { - const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct? - this.scene.ui.setMode(Mode.MESSAGE); - - // Decides between a Disabled, Not Implemented, or No PP translation message - const errorMessage = - playerPokemon.summonData.disabledMove === move.moveId ? "battle:moveDisabled" : - move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP"; - const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator - - this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); - }, null, true); - } - break; - case Command.BALL: - const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarters).length - 1); - if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else { - const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex()); - if (targets.length > 1) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (cursor < 5) { - const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true)); - if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else { - this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; - this.scene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets; - if (this.fieldIndex) { - this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; - } - success = true; - } - } - } - break; - case Command.POKEMON: - case Command.RUN: - const isSwitch = command === Command.POKEMON; - if (!isSwitch && this.scene.arena.biomeType === Biome.END) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (!isSwitch && this.scene.currentBattle.battleType === BattleType.TRAINER) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else { - const trapTag = playerPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; - const trapped = new Utils.BooleanHolder(false); - const batonPass = isSwitch && args[0] as boolean; - const trappedAbMessages: string[] = []; - if (!batonPass) { - enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, true, trappedAbMessages)); - } - if (batonPass || (!trapTag && !trapped.value)) { - this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch - ? { command: Command.POKEMON, cursor: cursor, args: args } - : { command: Command.RUN }; - success = true; - if (!isSwitch && this.fieldIndex) { - this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; - } - } else if (trapTag) { - if (trapTag.sourceMove === Moves.INGRAIN && trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId)?.isOfType(Type.GHOST)) { - success = true; - this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch - ? { command: Command.POKEMON, cursor: cursor, args: args } - : { command: Command.RUN }; - break; - } - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - } - this.scene.ui.showText( - i18next.t("battle:noEscapePokemon", { - pokemonName: trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(trapTag.sourceId)!) : "", - moveName: trapTag.getMoveName(), - escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee") - }), - null, - () => { - this.scene.ui.showText("", 0); - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - } - }, null, true); - } else if (trapped.value && trappedAbMessages.length > 0) { - if (!isSwitch) { - this.scene.ui.setMode(Mode.MESSAGE); - } - this.scene.ui.showText(trappedAbMessages[0], null, () => { - this.scene.ui.showText("", 0); - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - } - }, null, true); - } - } - break; - } - - if (success!) { // TODO: is the bang correct? - this.end(); - } - - return success!; // TODO: is the bang correct? - } - - cancel() { - if (this.fieldIndex) { - this.scene.unshiftPhase(new CommandPhase(this.scene, 0)); - this.scene.unshiftPhase(new CommandPhase(this.scene, 1)); - this.end(); - } - } - - checkFightOverride(): boolean { - const pokemon = this.getPokemon(); - - const encoreTag = pokemon.getTag(EncoreTag) as EncoreTag; - - if (!encoreTag) { - return false; - } - - const moveIndex = pokemon.getMoveset().findIndex(m => m?.moveId === encoreTag.moveId); - - if (moveIndex === -1 || !pokemon.getMoveset()[moveIndex]!.isUsable(pokemon)) { // TODO: is this bang correct? - return false; - } - - this.handleCommand(Command.FIGHT, moveIndex, false); - - return true; - } - - getFieldIndex(): integer { - return this.fieldIndex; - } - - getPokemon(): PlayerPokemon { - return this.scene.getPlayerField()[this.fieldIndex]; - } - - end() { - this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); - } -} - -/** - * Phase for determining an enemy AI's action for the next turn. - * During this phase, the enemy decides whether to switch (if it has a trainer) - * or to use a move from its moveset. - * - * For more information on how the Enemy AI works, see docs/enemy-ai.md - * @see {@linkcode Pokemon.getMatchupScore} - * @see {@linkcode EnemyPokemon.getNextMove} - */ -export class EnemyCommandPhase extends FieldPhase { - protected fieldIndex: integer; - - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene); - - this.fieldIndex = fieldIndex; - } - - start() { - super.start(); - - const enemyPokemon = this.scene.getEnemyField()[this.fieldIndex]; - - const battle = this.scene.currentBattle; - - const trainer = battle.trainer; - - /** - * If the enemy has a trainer, decide whether or not the enemy should switch - * to another member in its party. - * - * This block compares the active enemy Pokemon's {@linkcode Pokemon.getMatchupScore | matchup score} - * against the active player Pokemon with the enemy party's other non-fainted Pokemon. If a party - * member's matchup score is 3x the active enemy's score (or 2x for "boss" trainers), - * the enemy will switch to that Pokemon. - */ - if (trainer && !enemyPokemon.getMoveQueue().length) { - const opponents = enemyPokemon.getOpponents(); - - const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; - const trapped = new Utils.BooleanHolder(false); - opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, true, [])); - if (!trapTag && !trapped.value) { - const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true); - - if (partyMemberScores.length) { - const matchupScores = opponents.map(opp => enemyPokemon.getMatchupScore(opp)); - const matchupScore = matchupScores.reduce((total, score) => total += score, 0) / matchupScores.length; - - const sortedPartyMemberScores = trainer.getSortedPartyMemberMatchupScores(partyMemberScores); - - const switchMultiplier = 1 - (battle.enemySwitchCounter ? Math.pow(0.1, (1 / battle.enemySwitchCounter)) : 0); - - if (sortedPartyMemberScores[0][1] * switchMultiplier >= matchupScore * (trainer.config.isBoss ? 2 : 3)) { - const index = trainer.getNextSummonIndex(enemyPokemon.trainerSlot, partyMemberScores); - - battle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = - { command: Command.POKEMON, cursor: index, args: [false] }; - - battle.enemySwitchCounter++; - - return this.end(); - } - } - } - } - - /** Select a move to use (and a target to use it against, if applicable) */ - const nextMove = enemyPokemon.getNextMove(); - - this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = - { command: Command.FIGHT, move: nextMove }; - - this.scene.currentBattle.enemySwitchCounter = Math.max(this.scene.currentBattle.enemySwitchCounter - 1, 0); - - this.end(); - } -} - -export class SelectTargetPhase extends PokemonPhase { - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, fieldIndex); - } - - start() { - super.start(); - - const turnCommand = this.scene.currentBattle.turnCommands[this.fieldIndex]; - const move = turnCommand?.move?.move; - this.scene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (targets: BattlerIndex[]) => { - this.scene.ui.setMode(Mode.MESSAGE); - if (targets.length < 1) { - this.scene.currentBattle.turnCommands[this.fieldIndex] = null; - this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex)); - } else { - turnCommand!.targets = targets; //TODO: is the bang correct here? - } - if (turnCommand?.command === Command.BALL && this.fieldIndex) { - this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; //TODO: is the bang correct here? - } - this.end(); - }); - } -} - -export class TurnStartPhase extends FieldPhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - const field = this.scene.getField(); - const order = this.getOrder(); - - const battlerBypassSpeed = {}; - - this.scene.getField(true).filter(p => p.summonData).map(p => { - const bypassSpeed = new Utils.BooleanHolder(false); - const canCheckHeldItems = new Utils.BooleanHolder(true); - applyAbAttrs(BypassSpeedChanceAbAttr, p, null, bypassSpeed); - applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, bypassSpeed, canCheckHeldItems); - if (canCheckHeldItems.value) { - this.scene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); - } - battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; - }); - - const moveOrder = order.slice(0); - - moveOrder.sort((a, b) => { - const aCommand = this.scene.currentBattle.turnCommands[a]; - const bCommand = this.scene.currentBattle.turnCommands[b]; - - if (aCommand?.command !== bCommand?.command) { - if (aCommand?.command === Command.FIGHT) { - return 1; - } else if (bCommand?.command === Command.FIGHT) { - return -1; - } - } else if (aCommand?.command === Command.FIGHT) { - const aMove = allMoves[aCommand.move!.move];//TODO: is the bang correct here? - const bMove = allMoves[bCommand!.move!.move];//TODO: is the bang correct here? - - const aPriority = new Utils.IntegerHolder(aMove.priority); - const bPriority = new Utils.IntegerHolder(bMove.priority); - - applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here? - applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here? - - applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here? - applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here? - - if (aPriority.value !== bPriority.value) { - const bracketDifference = Math.ceil(aPriority.value) - Math.ceil(bPriority.value); - const hasSpeedDifference = battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value; - if (bracketDifference === 0 && hasSpeedDifference) { - return battlerBypassSpeed[a].value ? -1 : 1; - } - return aPriority.value < bPriority.value ? 1 : -1; - } - } - - if (battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) { - return battlerBypassSpeed[a].value ? -1 : 1; - } - - const aIndex = order.indexOf(a); - const bIndex = order.indexOf(b); - - return aIndex < bIndex ? -1 : aIndex > bIndex ? 1 : 0; - }); - - let orderIndex = 0; - - for (const o of moveOrder) { - - const pokemon = field[o]; - const turnCommand = this.scene.currentBattle.turnCommands[o]; - - if (turnCommand?.skip) { - continue; - } - - switch (turnCommand?.command) { - case Command.FIGHT: - const queuedMove = turnCommand.move; - pokemon.turnData.order = orderIndex++; - if (!queuedMove) { - continue; - } - const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); - if (move.getMove().hasAttr(MoveHeaderAttr)) { - this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); - } - if (pokemon.isPlayer()) { - if (turnCommand.cursor === -1) { - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here? - } else { - const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here? - this.scene.pushPhase(playerPhase); - } - } else { - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? - } - break; - case Command.BALL: - this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? - break; - case Command.POKEMON: - this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor!, true, turnCommand.args![0] as boolean, pokemon.isPlayer()));//TODO: is the bang correct here? - break; - case Command.RUN: - let runningPokemon = pokemon; - if (this.scene.currentBattle.double) { - const playerActivePokemon = field.filter(pokemon => { - if (!!pokemon) { - return pokemon.isPlayer() && pokemon.isActive(); - } else { - return; - } - }); - // if only one pokemon is alive, use that one - if (playerActivePokemon.length > 1) { - // find which active pokemon has faster speed - const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1]; - // check if either active pokemon has the ability "Run Away" - const hasRunAway = playerActivePokemon.find(p => p.hasAbility(Abilities.RUN_AWAY)); - runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; - } - } - this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex())); - break; - } - } - - - this.scene.pushPhase(new WeatherEffectPhase(this.scene)); - - for (const o of order) { - if (field[o].status && field[o].status.isPostTurn()) { - this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o)); - } - } - - this.scene.pushPhase(new BerryPhase(this.scene)); - this.scene.pushPhase(new TurnEndPhase(this.scene)); - - /** - * this.end() will call shiftPhase(), which dumps everything from PrependQueue (aka everything that is unshifted()) to the front - * of the queue and dequeues to start the next phase - * this is important since stuff like SwitchSummon, AttemptRun, AttemptCapture Phases break the "flow" and should take precedence - */ - this.end(); - } -} - -/** The phase after attacks where the pokemon eat berries */ -export class BerryPhase extends FieldPhase { - start() { - super.start(); - - this.executeForAll((pokemon) => { - const hasUsableBerry = !!this.scene.findModifier((m) => { - return m instanceof BerryModifier && m.shouldApply([pokemon]); - }, pokemon.isPlayer()); - - if (hasUsableBerry) { - const cancelled = new Utils.BooleanHolder(false); - pokemon.getOpponents().map((opp) => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); - - if (cancelled.value) { - pokemon.scene.queueMessage(i18next.t("abilityTriggers:preventBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); - } else { - this.scene.unshiftPhase( - new CommonAnimPhase(this.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM) - ); - - for (const berryModifier of this.scene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon) as BerryModifier[]) { - if (berryModifier.consumed) { - if (!--berryModifier.stackCount) { - this.scene.removeModifier(berryModifier); - } else { - berryModifier.consumed = false; - } - } - this.scene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier)); // Announce a berry was used - } - - this.scene.updateModifiers(pokemon.isPlayer()); - - applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new Utils.BooleanHolder(false)); - } - } - }); - - this.end(); - } -} - -export class TurnEndPhase extends FieldPhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.currentBattle.incrementTurn(this.scene); - this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn)); - - const handlePokemon = (pokemon: Pokemon) => { - pokemon.lapseTags(BattlerTagLapseType.TURN_END); - - if (pokemon.summonData.disabledMove && !--pokemon.summonData.disabledTurns) { - this.scene.pushPhase(new MessagePhase(this.scene, i18next.t("battle:notDisabled", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: allMoves[pokemon.summonData.disabledMove].name }))); - pokemon.summonData.disabledMove = Moves.NONE; - } - - this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); - - if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { - this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), - Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true)); - } - - if (!pokemon.isPlayer()) { - this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon); - this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); - } - - applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); - - this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); - - this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); - - pokemon.battleSummonData.turnCount++; - }; - - this.executeForAll(handlePokemon); - - this.scene.arena.lapseTags(); - - if (this.scene.arena.weather && !this.scene.arena.weather.lapse()) { - this.scene.arena.trySetWeather(WeatherType.NONE, false); - } - - if (this.scene.arena.terrain && !this.scene.arena.terrain.lapse()) { - this.scene.arena.trySetTerrain(TerrainType.NONE, false); - } - - this.end(); - } -} - -export class BattleEndPhase extends BattlePhase { - start() { - super.start(); - - this.scene.currentBattle.addBattleScore(this.scene); - - this.scene.gameData.gameStats.battles++; - if (this.scene.currentBattle.trainer) { - this.scene.gameData.gameStats.trainersDefeated++; - } - if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex + 1 > this.scene.gameData.gameStats.highestEndlessWave) { - this.scene.gameData.gameStats.highestEndlessWave = this.scene.currentBattle.waveIndex + 1; - } - - // Endless graceful end - if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex >= 5850) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new GameOverPhase(this.scene, true)); - } - - for (const pokemon of this.scene.getField()) { - if (pokemon) { - pokemon.resetBattleSummonData(); - } - } - - for (const pokemon of this.scene.getParty().filter(p => p.isAllowedInBattle())) { - applyPostBattleAbAttrs(PostBattleAbAttr, pokemon); - } - - if (this.scene.currentBattle.moneyScattered) { - this.scene.currentBattle.pickUpScatteredMoney(this.scene); - } - - this.scene.clearEnemyHeldItemModifiers(); - - const lapsingModifiers = this.scene.findModifiers(m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[]; - for (const m of lapsingModifiers) { - const args: any[] = []; - if (m instanceof LapsingPokemonHeldItemModifier) { - args.push(this.scene.getPokemonById(m.pokemonId)); - } - if (!m.lapse(args)) { - this.scene.removeModifier(m); - } - } - - this.scene.updateModifiers().then(() => this.end()); - } -} - -export class NewBattlePhase extends BattlePhase { - start() { - super.start(); - - this.scene.newBattle(); - - this.end(); - } -} - -export class CommonAnimPhase extends PokemonPhase { - private anim: CommonAnim | null; - private targetIndex: integer | undefined; - - constructor(scene: BattleScene, battlerIndex?: BattlerIndex, targetIndex?: BattlerIndex | undefined, anim?: CommonAnim) { - super(scene, battlerIndex); - - this.anim = anim!; // TODO: is this bang correct? - this.targetIndex = targetIndex; - } - - setAnimation(anim: CommonAnim) { - this.anim = anim; - } - - start() { - new CommonBattleAnim(this.anim, this.getPokemon(), this.targetIndex !== undefined ? (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField())[this.targetIndex] : this.getPokemon()).play(this.scene, () => { - this.end(); - }); - } -} - -export class MoveHeaderPhase extends BattlePhase { - public pokemon: Pokemon; - public move: PokemonMove; - - constructor(scene: BattleScene, pokemon: Pokemon, move: PokemonMove) { - super(scene); - - this.pokemon = pokemon; - this.move = move; - } - - canMove(): boolean { - return this.pokemon.isActive(true) && this.move.isUsable(this.pokemon); - } - - start() { - super.start(); - - if (this.canMove()) { - applyMoveAttrs(MoveHeaderAttr, this.pokemon, null, this.move.getMove()).then(() => this.end()); - } else { - this.end(); - } - } -} - -export class MovePhase extends BattlePhase { - public pokemon: Pokemon; - public move: PokemonMove; - public targets: BattlerIndex[]; - protected followUp: boolean; - protected ignorePp: boolean; - protected failed: boolean; - protected cancelled: boolean; - - constructor(scene: BattleScene, pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp?: boolean, ignorePp?: boolean) { - super(scene); - - this.pokemon = pokemon; - this.targets = targets; - this.move = move; - this.followUp = !!followUp; - this.ignorePp = !!ignorePp; - this.failed = false; - this.cancelled = false; - } - - canMove(): boolean { - return this.pokemon.isActive(true) && this.move.isUsable(this.pokemon, this.ignorePp) && !!this.targets.length; - } - - /**Signifies the current move should fail but still use PP */ - fail(): void { - this.failed = true; - } - - /**Signifies the current move should cancel and retain PP */ - cancel(): void { - this.cancelled = true; - } - - start() { - super.start(); - - console.log(Moves[this.move.moveId]); - - if (!this.canMove()) { - if (this.move.moveId && this.pokemon.summonData?.disabledMove === this.move.moveId) { - this.scene.queueMessage(`${this.move.getName()} is disabled!`); - } - if (this.pokemon.isActive(true) && this.move.ppUsed >= this.move.getMovePp()) { // if the move PP was reduced from Spite or otherwise, the move fails - this.fail(); - this.showMoveText(); - this.showFailedText(); - } - return this.end(); - } - - if (!this.followUp) { - if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) { - this.scene.arena.setIgnoreAbilities(); - } - } else { - this.pokemon.turnData.hitsLeft = 0; // TODO: is `0` correct? - this.pokemon.turnData.hitCount = 0; // TODO: is `0` correct? - } - - // Move redirection abilities (ie. Storm Drain) only support single target moves - const moveTarget = this.targets.length === 1 - ? new Utils.IntegerHolder(this.targets[0]) - : null; - if (moveTarget) { - const oldTarget = moveTarget.value; - this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget)); - this.pokemon.getOpponents().forEach(p => { - const redirectTag = p.getTag(CenterOfAttentionTag) as CenterOfAttentionTag; - if (redirectTag && (!redirectTag.powder || (!this.pokemon.isOfType(Type.GRASS) && !this.pokemon.hasAbility(Abilities.OVERCOAT)))) { - moveTarget.value = p.getBattlerIndex(); - } - }); - //Check if this move is immune to being redirected, and restore its target to the intended target if it is. - if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().hasAttr(BypassRedirectAttr))) { - //If an ability prevented this move from being redirected, display its ability pop up. - if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().hasAttr(BypassRedirectAttr)) && oldTarget !== moveTarget.value) { - this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr))); - } - moveTarget.value = oldTarget; - } - this.targets[0] = moveTarget.value; - } - - // Check for counterattack moves to switch target - if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) { - if (this.pokemon.turnData.attacksReceived.length) { - const attack = this.pokemon.turnData.attacksReceived[0]; - this.targets[0] = attack.sourceBattlerIndex; - - // account for metal burst and comeuppance hitting remaining targets in double battles - // counterattack will redirect to remaining ally if original attacker faints - if (this.scene.currentBattle.double && this.move.getMove().hasFlag(MoveFlags.REDIRECT_COUNTER)) { - if (this.scene.getField()[this.targets[0]].hp === 0) { - const opposingField = this.pokemon.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField(); - //@ts-ignore - this.targets[0] = opposingField.find(p => p.hp > 0)?.getBattlerIndex(); //TODO: fix ts-ignore - } - } - } - if (this.targets[0] === BattlerIndex.ATTACKER) { - this.fail(); // Marks the move as failed for later in doMove - this.showMoveText(); - this.showFailedText(); - } - } - - const targets = this.scene.getField(true).filter(p => { - if (this.targets.indexOf(p.getBattlerIndex()) > -1) { - return true; - } - return false; - }); - - const doMove = () => { - this.pokemon.turnData.acted = true; // Record that the move was attempted, even if it fails - - this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE); - - let ppUsed = 1; - // Filter all opponents to include only those this move is targeting - const targetedOpponents = this.pokemon.getOpponents().filter(o => this.targets.includes(o.getBattlerIndex())); - for (const opponent of targetedOpponents) { - if (this.move.ppUsed + ppUsed >= this.move.getMovePp()) { // If we're already at max PP usage, stop checking - break; - } - if (opponent.hasAbilityWithAttr(IncreasePpAbAttr)) { // Accounting for abilities like Pressure - ppUsed++; - } - } - - if (!this.followUp && this.canMove() && !this.cancelled) { - this.pokemon.lapseTags(BattlerTagLapseType.MOVE); - } - - const moveQueue = this.pokemon.getMoveQueue(); - if (this.cancelled || this.failed) { - if (this.failed) { - this.move.usePp(ppUsed); // Only use PP if the move failed - this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); - } - - // Record a failed move so Abilities like Truant don't trigger next turn and soft-lock - this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL }); - - this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); // Remove any tags from moves like Fly/Dive/etc. - moveQueue.shift(); // Remove the second turn of charge moves - return this.end(); - } - - this.scene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); - - if (this.move.moveId) { - this.showMoveText(); - } - - // This should only happen when there are no valid targets left on the field - if ((moveQueue.length && moveQueue[0].move === Moves.NONE) || !targets.length) { - this.showFailedText(); - this.cancel(); - - // Record a failed move so Abilities like Truant don't trigger next turn and soft-lock - this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL }); - - this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); // Remove any tags from moves like Fly/Dive/etc. - - moveQueue.shift(); - return this.end(); - } - - if (!moveQueue.length || !moveQueue.shift()?.ignorePP) { // using .shift here clears out two turn moves once they've been used - this.move.usePp(ppUsed); - this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); - } - - if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { - this.scene.currentBattle.lastMove = this.move.moveId; - } - - // Assume conditions affecting targets only apply to moves with a single target - let success = this.move.getMove().applyConditions(this.pokemon, targets[0], this.move.getMove()); - const cancelled = new Utils.BooleanHolder(false); - let failedText = this.move.getMove().getFailedText(this.pokemon, targets[0], this.move.getMove(), cancelled); - if (success && this.scene.arena.isMoveWeatherCancelled(this.move.getMove())) { - success = false; - } else if (success && this.scene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, this.move.getMove())) { - success = false; - if (failedText === null) { - failedText = getTerrainBlockMessage(targets[0], this.scene.arena.terrain?.terrainType!); // TODO: is this bang correct? - } - } - - /** - * Trigger pokemon type change before playing the move animation - * Will still change the user's type when using Roar, Whirlwind, Trick-or-Treat, and Forest's Curse, - * regardless of whether the move successfully executes or not. - */ - if (success || [Moves.ROAR, Moves.WHIRLWIND, Moves.TRICK_OR_TREAT, Moves.FORESTS_CURSE].includes(this.move.moveId)) { - applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); - } - - if (success) { - this.scene.unshiftPhase(this.getEffectPhase()); - } else { - this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual }); - if (!cancelled.value) { - this.showFailedText(failedText); - } - } - // Checks if Dancer ability is triggered - if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) { - // Pokemon with Dancer can be on either side of the battle so we check in both cases - this.scene.getPlayerField().forEach(pokemon => { - applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); - }); - this.scene.getEnemyField().forEach(pokemon => { - applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); - }); - } - this.end(); - }; - - if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) { - this.pokemon.status.incrementTurn(); - let activated = false; - let healed = false; - - switch (this.pokemon.status.effect) { - case StatusEffect.PARALYSIS: - if (!this.pokemon.randSeedInt(4)) { - activated = true; - this.cancelled = true; - } - break; - case StatusEffect.SLEEP: - applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); - healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; - activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); - this.cancelled = activated; - break; - case StatusEffect.FREEZE: - healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); - activated = !healed; - this.cancelled = activated; - break; - } - - if (activated) { - this.scene.queueMessage(getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); - this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.POISON + (this.pokemon.status.effect - 1))); - doMove(); - } else { - if (healed) { - this.scene.queueMessage(getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); - this.pokemon.resetStatus(); - this.pokemon.updateInfo(); - } - doMove(); - } - } else { - doMove(); - } - } - - getEffectPhase(): MoveEffectPhase { - return new MoveEffectPhase(this.scene, this.pokemon.getBattlerIndex(), this.targets, this.move); - } - - showMoveText(): void { - if (this.move.getMove().hasAttr(ChargeAttr)) { - const lastMove = this.pokemon.getLastXMoves() as TurnMove[]; - if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) { - this.scene.queueMessage(i18next.t("battle:useMove", { - pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), - moveName: this.move.getName() - }), 500); - return; - } - } - - if (this.pokemon.getTag(BattlerTagType.RECHARGING || BattlerTagType.INTERRUPTED)) { - return; - } - - this.scene.queueMessage(i18next.t("battle:useMove", { - pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), - moveName: this.move.getName() - }), 500); - applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents().find(() => true)!, this.move.getMove()); //TODO: is the bang correct here? - } - - showFailedText(failedText: string | null = null): void { - this.scene.queueMessage(failedText || i18next.t("battle:attackFailed")); - } - - end() { - if (!this.followUp && this.canMove()) { - this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex())); - } - - super.end(); - } -} - -export class MoveEffectPhase extends PokemonPhase { - public move: PokemonMove; - protected targets: BattlerIndex[]; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, targets: BattlerIndex[], move: PokemonMove) { - super(scene, battlerIndex); - this.move = move; - /** - * In double battles, if the right Pokemon selects a spread move and the left Pokemon dies - * with no party members available to switch in, then the right Pokemon takes the index - * of the left Pokemon and gets hit unless this is checked. - */ - if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) { - const i = targets.indexOf(battlerIndex); - targets.splice(i, i + 1); - } - this.targets = targets; - } - - start() { - super.start(); - - /** The Pokemon using this phase's invoked move */ - const user = this.getUserPokemon(); - /** All Pokemon targeted by this phase's invoked move */ - const targets = this.getTargets(); - - /** If the user was somehow removed from the field, end this phase */ - if (!user?.isOnField()) { - return super.end(); - } - - /** - * Does an effect from this move override other effects on this turn? - * e.g. Charging moves (Fly, etc.) on their first turn of use. - */ - const overridden = new Utils.BooleanHolder(false); - /** The {@linkcode Move} object from {@linkcode allMoves} invoked by this phase */ - const move = this.move.getMove(); - - // Assume single target for override - applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget() ?? null, move, overridden, this.move.virtual).then(() => { - // If other effects were overriden, stop this phase before they can be applied - if (overridden.value) { - return this.end(); - } - - user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); - - /** - * If this phase is for the first hit of the invoked move, - * resolve the move's total hit count. This block combines the - * effects of the move itself, Parental Bond, and Multi-Lens to do so. - */ - if (user.turnData.hitsLeft === undefined) { - const hitCount = new Utils.IntegerHolder(1); - // Assume single target for multi hit - applyMoveAttrs(MultiHitAttr, user, this.getTarget() ?? null, move, hitCount); - // If Parental Bond is applicable, double the hit count - applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, targets.length, hitCount, new Utils.IntegerHolder(0)); - // If Multi-Lens is applicable, multiply the hit count by 1 + the number of Multi-Lenses held by the user - if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) { - this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0)); - } - // Set the user's relevant turnData fields to reflect the final hit count - user.turnData.hitCount = hitCount.value; - user.turnData.hitsLeft = hitCount.value; - } - - /** - * Log to be entered into the user's move history once the move result is resolved. - * Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully - * used in the sense of "Does it have an effect on the user?". - */ - const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual }; - - /** - * Stores results of hit checks of the invoked move against all targets, organized by battler index. - * @see {@linkcode hitCheck} - */ - const targetHitChecks = Object.fromEntries(targets.map(p => [p.getBattlerIndex(), this.hitCheck(p)])); - const hasActiveTargets = targets.some(t => t.isActive(true)); - /** - * If no targets are left for the move to hit (FAIL), or the invoked move is single-target - * (and not random target) and failed the hit check against its target (MISS), log the move - * as FAILed or MISSed (depending on the conditions above) and end this phase. - */ - if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) { - this.stopMultiHit(); - if (hasActiveTargets) { - this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getTarget()? getPokemonNameWithAffix(this.getTarget()!) : "" })); - moveHistoryEntry.result = MoveResult.MISS; - applyMoveAttrs(MissEffectAttr, user, null, move); - } else { - this.scene.queueMessage(i18next.t("battle:attackFailed")); - moveHistoryEntry.result = MoveResult.FAIL; - } - user.pushMoveHistory(moveHistoryEntry); - return this.end(); - } - - /** All move effect attributes are chained together in this array to be applied asynchronously. */ - const applyAttrs: Promise[] = []; - - // Move animation only needs one target - new MoveAnim(move.id as Moves, user, this.getTarget()?.getBattlerIndex()!).play(this.scene, () => { // TODO: is the bang correct here? - /** Has the move successfully hit a target (for damage) yet? */ - let hasHit: boolean = false; - for (const target of targets) { - /** - * If the move missed a target, stop all future hits against that target - * and move on to the next target (if there is one). - */ - if (!targetHitChecks[target.getBattlerIndex()]) { - this.stopMultiHit(target); - this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); - if (moveHistoryEntry.result === MoveResult.PENDING) { - moveHistoryEntry.result = MoveResult.MISS; - } - user.pushMoveHistory(moveHistoryEntry); - applyMoveAttrs(MissEffectAttr, user, null, move); - continue; - } - - /** The {@linkcode ArenaTagSide} to which the target belongs */ - const targetSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - /** Has the invoked move been cancelled by conditional protection (e.g Quick Guard)? */ - const hasConditionalProtectApplied = new Utils.BooleanHolder(false); - /** Does the applied conditional protection bypass Protect-ignoring effects? */ - const bypassIgnoreProtect = new Utils.BooleanHolder(false); - // If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects - if (!this.move.getMove().isAllyTarget()) { - this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect); - } - - /** Is the target protected by Protect, etc. or a relevant conditional protection effect? */ - const isProtected = (bypassIgnoreProtect.value || !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target)) - && (hasConditionalProtectApplied.value || target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))); - - /** Does this phase represent the invoked move's first strike? */ - const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount); - - // Only log the move's result on the first strike - if (firstHit) { - user.pushMoveHistory(moveHistoryEntry); - } - - /** - * Since all fail/miss checks have applied, the move is considered successfully applied. - * It's worth noting that if the move has no effect or is protected against, this assignment - * is overwritten and the move is logged as a FAIL. - */ - moveHistoryEntry.result = MoveResult.SUCCESS; - - /** - * Stores the result of applying the invoked move to the target. - * If the target is protected, the result is always `NO_EFFECT`. - * Otherwise, the hit result is based on type effectiveness, immunities, - * and other factors that may negate the attack or status application. - * - * Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated - * (for attack moves) and the target's HP is updated. However, this isn't - * made visible to the user until the resulting {@linkcode DamagePhase} - * is invoked. - */ - const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT; - - /** Does {@linkcode hitResult} indicate that damage was dealt to the target? */ - const dealsDamage = [ - HitResult.EFFECTIVE, - HitResult.SUPER_EFFECTIVE, - HitResult.NOT_VERY_EFFECTIVE, - HitResult.ONE_HIT_KO - ].includes(hitResult); - - /** Is this target the first one hit by the move on its current strike? */ - const firstTarget = dealsDamage && !hasHit; - if (firstTarget) { - hasHit = true; - } - - /** - * If the move has no effect on the target (i.e. the target is protected or immune), - * change the logged move result to FAIL. - */ - if (hitResult === HitResult.NO_EFFECT) { - moveHistoryEntry.result = MoveResult.FAIL; - } - - /** Does this phase represent the invoked move's last strike? */ - const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()); - - /** - * If the user can change forms by using the invoked move, - * it only changes forms after the move's last hit - * (see Relic Song's interaction with Parental Bond when used by Meloetta). - */ - if (lastHit) { - this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); - } - - /** - * Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs. - * These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger - * type requires different conditions to be met with respect to the move's hit result. - */ - applyAttrs.push(new Promise(resolve => { - // Apply all effects with PRE_MOVE triggers (if the target isn't immune to the move) - applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && hitResult !== HitResult.NO_EFFECT, - user, target, move).then(() => { - // All other effects require the move to not have failed or have been cancelled to trigger - if (hitResult !== HitResult.FAIL) { - /** Are the move's effects tied to the first turn of a charge move? */ - const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget() ?? null, move)); - /** - * If the invoked move's effects are meant to trigger during the move's "charge turn," - * ignore all effects after this point. - * Otherwise, apply all self-targeted POST_APPLY effects. - */ - Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY - && attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move)).then(() => { - // All effects past this point require the move to have hit the target - if (hitResult !== HitResult.NO_EFFECT) { - // Apply all non-self-targeted POST_APPLY effects - applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY - && !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => { - /** - * If the move hit, and the target doesn't have Shield Dust, - * apply the chance to flinch the target gained from King's Rock - */ - if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr)) { - const flinched = new Utils.BooleanHolder(false); - user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); - if (flinched.value) { - target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id); - } - } - // If the move was not protected against, apply all HIT effects - Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT - && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && (!attr.firstTargetOnly || firstTarget), user, target, this.move.getMove()).then(() => { - // Apply the target's post-defend ability effects (as long as the target is active or can otherwise apply them) - return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => { - // If the invoked move is an enemy attack, apply the enemy's status effect-inflicting tags and tokens - target.lapseTag(BattlerTagType.BEAK_BLAST_CHARGING); - if (move.category === MoveCategory.PHYSICAL && user.isPlayer() !== target.isPlayer()) { - target.lapseTag(BattlerTagType.SHELL_TRAP); - } - if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { - user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); - } - })).then(() => { - // Apply the user's post-attack ability effects - applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => { - /** - * If the invoked move is an attack, apply the user's chance to - * steal an item from the target granted by Grip Claw - */ - if (this.move.getMove() instanceof AttackMove) { - this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); - } - resolve(); - }); - }); - }) - ).then(() => resolve()); - }); - } else { - applyMoveAttrs(NoEffectAttr, user, null, move).then(() => resolve()); - } - }); - } else { - resolve(); - } - }); - })); - } - // Apply the move's POST_TARGET effects on the move's last hit, after all targeted effects have resolved - const postTarget = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()) ? - applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, user, null, move) : - null; - - if (!!postTarget) { - if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after - applyAttrs[applyAttrs.length - 1]?.then(() => postTarget); - } else { // Otherwise, push a new asynchronous move effect - applyAttrs.push(postTarget); - } - } - - // Wait for all move effects to finish applying, then end this phase - Promise.allSettled(applyAttrs).then(() => this.end()); - }); - }); - } - - end() { - const move = this.move.getMove(); - move.type = move.defaultType; - const user = this.getUserPokemon(); - /** - * If this phase isn't for the invoked move's last strike, - * unshift another MoveEffectPhase for the next strike. - * Otherwise, queue a message indicating the number of times the move has struck - * (if the move has struck more than once), then apply the heal from Shell Bell - * to the user. - */ - if (user) { - if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) { - this.scene.unshiftPhase(this.getNewHitPhase()); - } else { - // Queue message for number of hits made by multi-move - // If multi-hit attack only hits once, still want to render a message - const hitsTotal = user.turnData.hitCount! - Math.max(user.turnData.hitsLeft!, 0); // TODO: are those bangs correct? - if (hitsTotal > 1 || (user.turnData.hitsLeft && user.turnData.hitsLeft > 0)) { - // If there are multiple hits, or if there are hits of the multi-hit move left - this.scene.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); - } - this.scene.applyModifiers(HitHealModifier, this.player, user); - } - } - - super.end(); - } - - /** - * Resolves whether this phase's invoked move hits or misses the given target - * @param target {@linkcode Pokemon} the Pokemon targeted by the invoked move - * @returns `true` if the move does not miss the target; `false` otherwise - */ - hitCheck(target: Pokemon): boolean { - // Moves targeting the user and entry hazards can't miss - if ([MoveTarget.USER, MoveTarget.ENEMY_SIDE].includes(this.move.getMove().moveTarget)) { - return true; - } - - const user = this.getUserPokemon()!; // TODO: is this bang correct? - - // Hit check only calculated on first hit for multi-hit moves unless flag is set to check all hits. - // However, if an ability with the MaxMultiHitAbAttr, namely Skill Link, is present, act as a normal - // multi-hit move and proceed with all hits - if (user.turnData.hitsLeft < user.turnData.hitCount) { - if (!this.move.getMove().hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr(MaxMultiHitAbAttr)) { - return true; - } - } - - if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) { - return true; - } - - // If the user should ignore accuracy on a target, check who the user targeted last turn and see if they match - if (user.getTag(BattlerTagType.IGNORE_ACCURACY) && (user.getLastXMoves().find(() => true)?.targets || []).indexOf(target.getBattlerIndex()) !== -1) { - return true; - } - - if (target.getTag(BattlerTagType.ALWAYS_GET_HIT)) { - return true; - } - - const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); - if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) { - return false; - } - - const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user!, target); // TODO: is the bang correct here? - - if (moveAccuracy === -1) { - return true; - } - - const accuracyMultiplier = user.getAccuracyMultiplier(target, this.move.getMove()); - const rand = user.randSeedInt(100, 1); - - return rand <= moveAccuracy * (accuracyMultiplier!); // TODO: is this bang correct? - } - - /** Returns the {@linkcode Pokemon} using this phase's invoked move */ - getUserPokemon(): Pokemon | undefined { - if (this.battlerIndex > BattlerIndex.ENEMY_2) { - return this.scene.getPokemonById(this.battlerIndex) ?? undefined; - } - return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex]; - } - - /** Returns an array of all {@linkcode Pokemon} targeted by this phase's invoked move */ - getTargets(): Pokemon[] { - return this.scene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1); - } - - /** Returns the first target of this phase's invoked move */ - getTarget(): Pokemon | undefined { - return this.getTargets()[0]; - } - - /** - * Removes the given {@linkcode Pokemon} from this phase's target list - * @param target {@linkcode Pokemon} the Pokemon to be removed - */ - removeTarget(target: Pokemon): void { - const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex()); - if (targetIndex !== -1) { - this.targets.splice(this.targets.findIndex(ind => ind === target.getBattlerIndex()), 1); - } - } - - /** - * Prevents subsequent strikes of this phase's invoked move from occurring - * @param target {@linkcode Pokemon} if defined, only stop subsequent - * strikes against this Pokemon - */ - stopMultiHit(target?: Pokemon): void { - /** If given a specific target, remove the target from subsequent strikes */ - if (target) { - this.removeTarget(target); - } - /** - * If no target specified, or the specified target was the last of this move's - * targets, completely cancel all subsequent strikes. - */ - if (!target || this.targets.length === 0 ) { - this.getUserPokemon()!.turnData.hitCount = 1; // TODO: is the bang correct here? - this.getUserPokemon()!.turnData.hitsLeft = 1; // TODO: is the bang correct here? - } - } - - /** Returns a new MoveEffectPhase with the same properties as this phase */ - getNewHitPhase() { - return new MoveEffectPhase(this.scene, this.battlerIndex, this.targets, this.move); - } -} - -export class MoveEndPhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); - } - - start() { - super.start(); - - const pokemon = this.getPokemon(); - if (pokemon.isActive(true)) { - pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE); - } - - this.scene.arena.setIgnoreAbilities(false); - - this.end(); - } -} - -export class MoveAnimTestPhase extends BattlePhase { - private moveQueue: Moves[]; - - constructor(scene: BattleScene, moveQueue?: Moves[]) { - super(scene); - - this.moveQueue = moveQueue || Utils.getEnumValues(Moves).slice(1); - } - - start() { - const moveQueue = this.moveQueue.slice(0); - this.playMoveAnim(moveQueue, true); - } - - playMoveAnim(moveQueue: Moves[], player: boolean) { - const moveId = player ? moveQueue[0] : moveQueue.shift(); - if (moveId === undefined) { - this.playMoveAnim(this.moveQueue.slice(0), true); - return; - } else if (player) { - console.log(Moves[moveId]); - } - - initMoveAnim(this.scene, moveId).then(() => { - loadMoveAnimAssets(this.scene, [moveId], true) - .then(() => { - new MoveAnim(moveId, player ? this.scene.getPlayerPokemon()! : this.scene.getEnemyPokemon()!, (player !== (allMoves[moveId] instanceof SelfStatusMove) ? this.scene.getEnemyPokemon()! : this.scene.getPlayerPokemon()!).getBattlerIndex()).play(this.scene, () => { // TODO: are the bangs correct here? - if (player) { - this.playMoveAnim(moveQueue, false); - } else { - this.playMoveAnim(moveQueue, true); - } - }); - }); - }); - } -} - -export class ShowAbilityPhase extends PokemonPhase { - private passive: boolean; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, passive: boolean = false) { - super(scene, battlerIndex); - - this.passive = passive; - } - - start() { - super.start(); - - const pokemon = this.getPokemon(); - - if (pokemon) { - this.scene.abilityBar.showAbility(pokemon, this.passive); - - if (pokemon?.battleData) { - pokemon.battleData.abilityRevealed = true; - } - } - - this.end(); - } -} - -export type StatChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void; - -export class StatChangePhase extends PokemonPhase { - private stats: BattleStat[]; - private selfTarget: boolean; - private levels: integer; - private showMessage: boolean; - private ignoreAbilities: boolean; - private canBeCopied: boolean; - private onChange: StatChangeCallback | null; - - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: integer, showMessage: boolean = true, ignoreAbilities: boolean = false, canBeCopied: boolean = true, onChange: StatChangeCallback | null = null) { - super(scene, battlerIndex); - - this.selfTarget = selfTarget; - this.stats = stats; - this.levels = levels; - this.showMessage = showMessage; - this.ignoreAbilities = ignoreAbilities; - this.canBeCopied = canBeCopied; - this.onChange = onChange; - } - - start() { - const pokemon = this.getPokemon(); - - let random = false; - - if (this.stats.length === 1 && this.stats[0] === BattleStat.RAND) { - this.stats[0] = this.getRandomStat(); - random = true; - } - - this.aggregateStatChanges(random); - - if (!pokemon.isActive(true)) { - return this.end(); - } - - const filteredStats = this.stats.map(s => s !== BattleStat.RAND ? s : this.getRandomStat()).filter(stat => { - const cancelled = new Utils.BooleanHolder(false); - - if (!this.selfTarget && this.levels < 0) { - this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled); - } - - if (!cancelled.value && !this.selfTarget && this.levels < 0) { - applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled); - } - - return !cancelled.value; - }); - - const levels = new Utils.IntegerHolder(this.levels); - - if (!this.ignoreAbilities) { - applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, levels); - } - - const battleStats = this.getPokemon().summonData.battleStats; - const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats![stat] + levels.value, 6) : Math.max(battleStats![stat] + levels.value, -6)) - battleStats![stat]); - - this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels); - - const end = () => { - if (this.showMessage) { - const messages = this.getStatChangeMessages(filteredStats, levels.value, relLevels); - for (const message of messages) { - this.scene.queueMessage(message); - } - } - - for (const stat of filteredStats) { - pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6); - } - - if (levels.value > 0 && this.canBeCopied) { - for (const opponent of pokemon.getOpponents()) { - applyAbAttrs(StatChangeCopyAbAttr, opponent, null, this.stats, levels.value); - } - } - - applyPostStatChangeAbAttrs(PostStatChangeAbAttr, pokemon, filteredStats, this.levels, this.selfTarget); - - // Look for any other stat change phases; if this is the last one, do White Herb check - const existingPhase = this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex); - if (!(existingPhase instanceof StatChangePhase)) { - // Apply White Herb if needed - const whiteHerb = this.scene.applyModifier(PokemonResetNegativeStatStageModifier, this.player, pokemon) as PokemonResetNegativeStatStageModifier; - // If the White Herb was applied, consume it - if (whiteHerb) { - --whiteHerb.stackCount; - if (whiteHerb.stackCount <= 0) { - this.scene.removeModifier(whiteHerb); - } - this.scene.updateModifiers(this.player); - } - } - - pokemon.updateInfo(); - - handleTutorial(this.scene, Tutorial.Stat_Change).then(() => super.end()); - }; - - if (relLevels.filter(l => l).length && this.scene.moveAnimations) { - pokemon.enableMask(); - const pokemonMaskSprite = pokemon.maskSprite; - - const tileX = (this.player ? 106 : 236) * pokemon.getSpriteScale() * this.scene.field.scale; - const tileY = ((this.player ? 148 : 84) + (levels.value >= 1 ? 160 : 0)) * pokemon.getSpriteScale() * this.scene.field.scale; - const tileWidth = 156 * this.scene.field.scale * pokemon.getSpriteScale(); - const tileHeight = 316 * this.scene.field.scale * pokemon.getSpriteScale(); - - // On increase, show the red sprite located at ATK - // On decrease, show the blue sprite located at SPD - const spriteColor = levels.value >= 1 ? BattleStat[BattleStat.ATK].toLowerCase() : BattleStat[BattleStat.SPD].toLowerCase(); - const statSprite = this.scene.add.tileSprite(tileX, tileY, tileWidth, tileHeight, "battle_stats", spriteColor); - statSprite.setPipeline(this.scene.fieldSpritePipeline); - statSprite.setAlpha(0); - statSprite.setScale(6); - statSprite.setOrigin(0.5, 1); - - this.scene.playSound(`stat_${levels.value >= 1 ? "up" : "down"}`); - - statSprite.setMask(new Phaser.Display.Masks.BitmapMask(this.scene, pokemonMaskSprite ?? undefined)); - - this.scene.tweens.add({ - targets: statSprite, - duration: 250, - alpha: 0.8375, - onComplete: () => { - this.scene.tweens.add({ - targets: statSprite, - delay: 1000, - duration: 250, - alpha: 0 - }); - } - }); - - this.scene.tweens.add({ - targets: statSprite, - duration: 1500, - y: `${levels.value >= 1 ? "-" : "+"}=${160 * 6}` - }); - - this.scene.time.delayedCall(1750, () => { - pokemon.disableMask(); - end(); - }); - } else { - end(); - } - } - - getRandomStat(): BattleStat { - const allStats = Utils.getEnumValues(BattleStat); - return this.getPokemon() ? allStats[this.getPokemon()!.randSeedInt(BattleStat.SPD + 1)] : BattleStat.ATK; // TODO: return default ATK on random? idk... - } - - aggregateStatChanges(random: boolean = false): void { - const isAccEva = [BattleStat.ACC, BattleStat.EVA].some(s => this.stats.includes(s)); - let existingPhase: StatChangePhase; - if (this.stats.length === 1) { - while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.stats.length === 1 - && (p.stats[0] === this.stats[0] || (random && p.stats[0] === BattleStat.RAND)) - && p.selfTarget === this.selfTarget && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) { - if (existingPhase.stats[0] === BattleStat.RAND) { - existingPhase.stats[0] = this.getRandomStat(); - if (existingPhase.stats[0] !== this.stats[0]) { - continue; - } - } - this.levels += existingPhase.levels; - - if (!this.scene.tryRemovePhase(p => p === existingPhase)) { - break; - } - } - } - while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget - && ([BattleStat.ACC, BattleStat.EVA].some(s => p.stats.includes(s)) === isAccEva) - && p.levels === this.levels && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) { - this.stats.push(...existingPhase.stats); - if (!this.scene.tryRemovePhase(p => p === existingPhase)) { - break; - } - } - } - - getStatChangeMessages(stats: BattleStat[], levels: integer, relLevels: integer[]): string[] { - const messages: string[] = []; - - const relLevelStatIndexes = {}; - for (let rl = 0; rl < relLevels.length; rl++) { - const relLevel = relLevels[rl]; - if (!relLevelStatIndexes[relLevel]) { - relLevelStatIndexes[relLevel] = []; - } - relLevelStatIndexes[relLevel].push(rl); - } - - Object.keys(relLevelStatIndexes).forEach(rl => { - const relLevelStats = stats.filter((_, i) => relLevelStatIndexes[rl].includes(i)); - let statsFragment = ""; - - if (relLevelStats.length > 1) { - statsFragment = relLevelStats.length >= 5 - ? i18next.t("battle:stats") - : `${relLevelStats.slice(0, -1).map(s => getBattleStatName(s)).join(", ")}${relLevelStats.length > 2 ? "," : ""} ${i18next.t("battle:statsAnd")} ${getBattleStatName(relLevelStats[relLevelStats.length - 1])}`; - messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1,relLevelStats.length)); - } else { - statsFragment = getBattleStatName(relLevelStats[0]); - messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1,relLevelStats.length)); - } - }); - - return messages; - } -} - -export class WeatherEffectPhase extends CommonAnimPhase { - public weather: Weather | null; - - constructor(scene: BattleScene) { - super(scene, undefined, undefined, CommonAnim.SUNNY + ((scene?.arena?.weather?.weatherType || WeatherType.NONE) - 1)); - this.weather = scene?.arena?.weather; - } - - start() { - // Update weather state with any changes that occurred during the turn - this.weather = this.scene?.arena?.weather; - - if (!this.weather) { - this.end(); - return; - } - - this.setAnimation(CommonAnim.SUNNY + (this.weather.weatherType - 1)); - - if (this.weather.isDamaging()) { - - const cancelled = new Utils.BooleanHolder(false); - - this.executeForAll((pokemon: Pokemon) => applyPreWeatherEffectAbAttrs(SuppressWeatherEffectAbAttr, pokemon, this.weather, cancelled)); - - if (!cancelled.value) { - const inflictDamage = (pokemon: Pokemon) => { - const cancelled = new Utils.BooleanHolder(false); - - applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather , cancelled); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); - - if (cancelled.value) { - return; - } - - const damage = Math.ceil(pokemon.getMaxHp() / 16); - - this.scene.queueMessage(getWeatherDamageMessage(this.weather?.weatherType!, pokemon)!); // TODO: are those bangs correct? - pokemon.damageAndUpdate(damage, HitResult.EFFECTIVE, false, false, true); - }; - - this.executeForAll((pokemon: Pokemon) => { - const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length; - if (!immune) { - inflictDamage(pokemon); - } - }); - } - } - - this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType)!, null, () => { // TODO: is this bang correct? - this.executeForAll((pokemon: Pokemon) => applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather)); - - super.start(); - }); - } -} - -export class ObtainStatusEffectPhase extends PokemonPhase { - private statusEffect: StatusEffect | undefined; - private cureTurn: integer | null; - private sourceText: string | null; - private sourcePokemon: Pokemon | null; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, cureTurn?: integer | null, sourceText?: string, sourcePokemon?: Pokemon) { - super(scene, battlerIndex); - - this.statusEffect = statusEffect; - this.cureTurn = cureTurn!; // TODO: is this bang correct? - this.sourceText = sourceText!; // TODO: is this bang correct? - this.sourcePokemon = sourcePokemon!; // For tracking which Pokemon caused the status effect // TODO: is this bang correct? - } - - start() { - const pokemon = this.getPokemon(); - if (!pokemon?.status) { - if (pokemon?.trySetStatus(this.statusEffect, false, this.sourcePokemon)) { - if (this.cureTurn) { - pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct? - } - pokemon.updateInfo(true); - new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, () => { - this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined)); - if (pokemon.status?.isPostTurn()) { - this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex)); - } - this.end(); - }); - return; - } - } else if (pokemon.status.effect === this.statusEffect) { - this.scene.queueMessage(getStatusEffectOverlapText(this.statusEffect, getPokemonNameWithAffix(pokemon))); - } - this.end(); - } -} - -export class PostTurnStatusEffectPhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); - } - - start() { - const pokemon = this.getPokemon(); - if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) { - pokemon.status.incrementTurn(); - const cancelled = new Utils.BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); - applyAbAttrs(BlockStatusDamageAbAttr, pokemon, cancelled); - - if (!cancelled.value) { - this.scene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); - const damage = new Utils.NumberHolder(0); - switch (pokemon.status.effect) { - case StatusEffect.POISON: - damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); - break; - case StatusEffect.TOXIC: - damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); - break; - case StatusEffect.BURN: - damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); - applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, damage); - break; - } - if (damage.value) { - // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... - this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true)); - pokemon.updateInfo(); - } - new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end()); - } else { - this.end(); - } - } else { - this.end(); - } - } - - override end() { - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { - this.scene.initFinalBossPhaseTwo(this.getPokemon()); - } else { - super.end(); - } - } -} - -export class MessagePhase extends Phase { - private text: string; - private callbackDelay: integer | null; - private prompt: boolean | null; - private promptDelay: integer | null; - - constructor(scene: BattleScene, text: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) { - super(scene); - - this.text = text; - this.callbackDelay = callbackDelay!; // TODO: is this bang correct? - this.prompt = prompt!; // TODO: is this bang correct? - this.promptDelay = promptDelay!; // TODO: is this bang correct? - } - - start() { - super.start(); - - if (this.text.indexOf("$") > -1) { - const pageIndex = this.text.indexOf("$"); - this.scene.unshiftPhase(new MessagePhase(this.scene, this.text.slice(pageIndex + 1), this.callbackDelay, this.prompt, this.promptDelay)); - this.text = this.text.slice(0, pageIndex).trim(); - } - - this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt, this.promptDelay); - } - - end() { - if (this.scene.abilityBar.shown) { - this.scene.abilityBar.hide(); - } - - super.end(); - } -} - -export class DamagePhase extends PokemonPhase { - private amount: integer; - private damageResult: DamageResult; - private critical: boolean; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, amount: integer, damageResult?: DamageResult, critical: boolean = false) { - super(scene, battlerIndex); - - this.amount = amount; - this.damageResult = damageResult || HitResult.EFFECTIVE; - this.critical = critical; - } - - start() { - super.start(); - - if (this.damageResult === HitResult.ONE_HIT_KO) { - if (this.scene.moveAnimations) { - this.scene.toggleInvert(true); - } - this.scene.time.delayedCall(Utils.fixedInt(1000), () => { - this.scene.toggleInvert(false); - this.applyDamage(); - }); - return; - } - - this.applyDamage(); - } - - updateAmount(amount: integer): void { - this.amount = amount; - } - - applyDamage() { - switch (this.damageResult) { - case HitResult.EFFECTIVE: - this.scene.playSound("hit"); - break; - case HitResult.SUPER_EFFECTIVE: - case HitResult.ONE_HIT_KO: - this.scene.playSound("hit_strong"); - break; - case HitResult.NOT_VERY_EFFECTIVE: - this.scene.playSound("hit_weak"); - break; - } - - if (this.amount) { - this.scene.damageNumberHandler.add(this.getPokemon(), this.amount, this.damageResult, this.critical); - } - - if (this.damageResult !== HitResult.OTHER) { - const flashTimer = this.scene.time.addEvent({ - delay: 100, - repeat: 5, - startAt: 200, - callback: () => { - this.getPokemon().getSprite().setVisible(flashTimer.repeatCount % 2 === 0); - if (!flashTimer.repeatCount) { - this.getPokemon().updateInfo().then(() => this.end()); - } - } - }); - } else { - this.getPokemon().updateInfo().then(() => this.end()); - } - } - - override end() { - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { - this.scene.initFinalBossPhaseTwo(this.getPokemon()); - } else { - super.end(); - } - } -} - -export class FaintPhase extends PokemonPhase { - private preventEndure: boolean; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure?: boolean) { - super(scene, battlerIndex); - - this.preventEndure = preventEndure!; // TODO: is this bang correct? - } - - start() { - super.start(); - - if (!this.preventEndure) { - const instantReviveModifier = this.scene.applyModifier(PokemonInstantReviveModifier, this.player, this.getPokemon()) as PokemonInstantReviveModifier; - - if (instantReviveModifier) { - if (!--instantReviveModifier.stackCount) { - this.scene.removeModifier(instantReviveModifier); - } - this.scene.updateModifiers(this.player); - return this.end(); - } - } - - if (!this.tryOverrideForBattleSpec()) { - this.doFaint(); - } - } - - doFaint(): void { - const pokemon = this.getPokemon(); - - - // Track total times pokemon have been KO'd for supreme overlord/last respects - if (pokemon.isPlayer()) { - this.scene.currentBattle.playerFaints += 1; - } else { - this.scene.currentBattle.enemyFaints += 1; - } - - this.scene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true); - - if (pokemon.turnData?.attacksReceived?.length) { - const lastAttack = pokemon.turnData.attacksReceived[0]; - applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId)!, new PokemonMove(lastAttack.move).getMove(), lastAttack.result); // TODO: is this bang correct? - } - - const alivePlayField = this.scene.getField(true); - alivePlayField.forEach(p => applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p, pokemon)); - if (pokemon.turnData?.attacksReceived?.length) { - const defeatSource = this.scene.getPokemonById(pokemon.turnData.attacksReceived[0].sourceId); - if (defeatSource?.isOnField()) { - applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); - const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; - const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr); - if (pvattrs.length) { - for (const pvattr of pvattrs) { - pvattr.applyPostVictory(defeatSource, defeatSource, pvmove); - } - } - } - } - - if (this.player) { - /** The total number of Pokemon in the player's party that can legally fight */ - const legalPlayerPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); - /** The total number of legal player Pokemon that aren't currently on the field */ - const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); - if (!legalPlayerPokemon.length) { - /** If the player doesn't have any legal Pokemon, end the game */ - this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } else if (this.scene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { - /** - * If the player has exactly one Pokemon in total at this point in a double battle, and that Pokemon - * is already on the field, unshift a phase that moves that Pokemon to center position. - */ - this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); - } else if (legalPlayerPartyPokemon.length > 0) { - /** - * If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field, - * push a phase that prompts the player to summon a Pokemon from their party. - */ - this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); - } - } else { - this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); - if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - const hasReservePartyMember = !!this.scene.getEnemyParty().filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot).length; - if (hasReservePartyMember) { - this.scene.pushPhase(new SwitchSummonPhase(this.scene, this.fieldIndex, -1, false, false, false)); - } - } - } - - // in double battles redirect potential moves off fainted pokemon - if (this.scene.currentBattle.double) { - const allyPokemon = pokemon.getAlly(); - this.scene.redirectPokemonMoves(pokemon, allyPokemon); - } - - pokemon.lapseTags(BattlerTagLapseType.FAINT); - this.scene.getField(true).filter(p => p !== pokemon).forEach(p => p.removeTagsBySourceId(pokemon.id)); - - pokemon.faintCry(() => { - if (pokemon instanceof PlayerPokemon) { - pokemon.addFriendship(-10); - } - pokemon.hideInfo(); - this.scene.playSound("faint"); - this.scene.tweens.add({ - targets: pokemon, - duration: 500, - y: pokemon.y + 150, - ease: "Sine.easeIn", - onComplete: () => { - pokemon.setVisible(false); - pokemon.y -= 150; - pokemon.trySetStatus(StatusEffect.FAINT); - if (pokemon.isPlayer()) { - this.scene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon); - } else { - this.scene.addFaintedEnemyScore(pokemon as EnemyPokemon); - this.scene.currentBattle.addPostBattleLoot(pokemon as EnemyPokemon); - } - this.scene.field.remove(pokemon); - this.end(); - } - }); - }); - } - - tryOverrideForBattleSpec(): boolean { - switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - if (!this.player) { - const enemy = this.getPokemon(); - if (enemy.formIndex) { - this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); - } else { - // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase - enemy.hp++; - this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); - this.end(); - } - return true; - } - } - - return false; - } -} - -export class VictoryPhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); - } - - start() { - super.start(); - - this.scene.gameData.gameStats.pokemonDefeated++; - - const participantIds = this.scene.currentBattle.playerParticipantIds; - const party = this.scene.getParty(); - const expShareModifier = this.scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier; - const expBalanceModifier = this.scene.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier; - const multipleParticipantExpBonusModifier = this.scene.findModifier(m => m instanceof MultipleParticipantExpBonusModifier) as MultipleParticipantExpBonusModifier; - const nonFaintedPartyMembers = party.filter(p => p.hp); - const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < this.scene.getMaxExpLevel()); - const partyMemberExp: number[] = []; - - if (participantIds.size) { - let expValue = this.getPokemon().getExpValue(); - if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - expValue = Math.floor(expValue * 1.5); - } - for (const partyMember of nonFaintedPartyMembers) { - const pId = partyMember.id; - const participated = participantIds.has(pId); - if (participated) { - partyMember.addFriendship(2); - } - if (!expPartyMembers.includes(partyMember)) { - continue; - } - if (!participated && !expShareModifier) { - partyMemberExp.push(0); - continue; - } - let expMultiplier = 0; - if (participated) { - expMultiplier += (1 / participantIds.size); - if (participantIds.size > 1 && multipleParticipantExpBonusModifier) { - expMultiplier += multipleParticipantExpBonusModifier.getStackCount() * 0.2; - } - } else if (expShareModifier) { - expMultiplier += (expShareModifier.getStackCount() * 0.2) / participantIds.size; - } - if (partyMember.pokerus) { - expMultiplier *= 1.5; - } - if (Overrides.XP_MULTIPLIER_OVERRIDE !== null) { - expMultiplier = Overrides.XP_MULTIPLIER_OVERRIDE; - } - const pokemonExp = new Utils.NumberHolder(expValue * expMultiplier); - this.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp); - partyMemberExp.push(Math.floor(pokemonExp.value)); - } - - if (expBalanceModifier) { - let totalLevel = 0; - let totalExp = 0; - expPartyMembers.forEach((expPartyMember, epm) => { - totalExp += partyMemberExp[epm]; - totalLevel += expPartyMember.level; - }); - - const medianLevel = Math.floor(totalLevel / expPartyMembers.length); - - const recipientExpPartyMemberIndexes: number[] = []; - expPartyMembers.forEach((expPartyMember, epm) => { - if (expPartyMember.level <= medianLevel) { - recipientExpPartyMemberIndexes.push(epm); - } - }); - - const splitExp = Math.floor(totalExp / recipientExpPartyMemberIndexes.length); - - expPartyMembers.forEach((_partyMember, pm) => { - partyMemberExp[pm] = Phaser.Math.Linear(partyMemberExp[pm], recipientExpPartyMemberIndexes.indexOf(pm) > -1 ? splitExp : 0, 0.2 * expBalanceModifier.getStackCount()); - }); - } - - for (let pm = 0; pm < expPartyMembers.length; pm++) { - const exp = partyMemberExp[pm]; - - if (exp) { - const partyMemberIndex = party.indexOf(expPartyMembers[pm]); - this.scene.unshiftPhase(expPartyMembers[pm].isOnField() ? new ExpPhase(this.scene, partyMemberIndex, exp) : new ShowPartyExpBarPhase(this.scene, partyMemberIndex, exp)); - } - } - } - - if (!this.scene.getEnemyParty().find(p => this.scene.currentBattle.battleType ? !p?.isFainted(true) : p.isOnField())) { - this.scene.pushPhase(new BattleEndPhase(this.scene)); - if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - this.scene.pushPhase(new TrainerVictoryPhase(this.scene)); - } - if (this.scene.gameMode.isEndless || !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { - this.scene.pushPhase(new EggLapsePhase(this.scene)); - if (this.scene.currentBattle.waveIndex % 10) { - this.scene.pushPhase(new SelectModifierPhase(this.scene)); - } else if (this.scene.gameMode.isDaily) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_CHARM)); - if (this.scene.currentBattle.waveIndex > 10 && !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.GOLDEN_POKEBALL)); - } - } else { - const superExpWave = !this.scene.gameMode.isEndless ? (this.scene.offsetGym ? 0 : 20) : 10; - if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex === 10) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_SHARE)); - } - if (this.scene.currentBattle.waveIndex <= 750 && (this.scene.currentBattle.waveIndex <= 500 || (this.scene.currentBattle.waveIndex % 30) === superExpWave)) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, (this.scene.currentBattle.waveIndex % 30) !== superExpWave || this.scene.currentBattle.waveIndex > 250 ? modifierTypes.EXP_CHARM : modifierTypes.SUPER_EXP_CHARM)); - } - if (this.scene.currentBattle.waveIndex <= 150 && !(this.scene.currentBattle.waveIndex % 50)) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.GOLDEN_POKEBALL)); - } - if (this.scene.gameMode.isEndless && !(this.scene.currentBattle.waveIndex % 50)) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, !(this.scene.currentBattle.waveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS)); - this.scene.pushPhase(new AddEnemyBuffModifierPhase(this.scene)); - } - } - this.scene.pushPhase(new NewBattlePhase(this.scene)); - } else { - this.scene.currentBattle.battleType = BattleType.CLEAR; - this.scene.score += this.scene.gameMode.getClearScoreBonus(); - this.scene.updateScoreText(); - this.scene.pushPhase(new GameOverPhase(this.scene, true)); - } - } - - this.end(); - } -} - -export class TrainerVictoryPhase extends BattlePhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - this.scene.disableMenu = true; - - this.scene.playBgm(this.scene.currentBattle.trainer?.config.victoryBgm); - - this.scene.unshiftPhase(new MoneyRewardPhase(this.scene, this.scene.currentBattle.trainer?.config.moneyMultiplier!)); // TODO: is this bang correct? - - const modifierRewardFuncs = this.scene.currentBattle.trainer?.config.modifierRewardFuncs!; // TODO: is this bang correct? - for (const modifierRewardFunc of modifierRewardFuncs) { - this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, modifierRewardFunc)); - } - - const trainerType = this.scene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? - if (vouchers.hasOwnProperty(TrainerType[trainerType])) { - if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer?.config.isBoss) { - this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, [modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][vouchers[TrainerType[trainerType]].voucherType])); - } - } - - this.scene.ui.showText(i18next.t("battle:trainerDefeated", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }), null, () => { - const victoryMessages = this.scene.currentBattle.trainer?.getVictoryMessages()!; // TODO: is this bang correct? - let message: string; - this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(victoryMessages), this.scene.currentBattle.waveIndex); - message = message!; // tell TS compiler it's defined now - - const showMessage = () => { - const originalFunc = showMessageOrEnd; - showMessageOrEnd = () => this.scene.ui.showDialogue(message, this.scene.currentBattle.trainer?.getName(), null, originalFunc); - - showMessageOrEnd(); - }; - let showMessageOrEnd = () => this.end(); - if (victoryMessages?.length) { - if (this.scene.currentBattle.trainer?.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { - const originalFunc = showMessageOrEnd; - showMessageOrEnd = () => this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => originalFunc())); - this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(this.scene.currentBattle.trainer?.getKey()!, getCharVariantFromDialogue(victoryMessages[0])).then(() => showMessage())); // TODO: is this bang correct? - } else { - showMessage(); - } - } else { - showMessageOrEnd(); - } - }, null, true); - - this.showEnemyTrainer(); - } -} - -export class MoneyRewardPhase extends BattlePhase { - private moneyMultiplier: number; - - constructor(scene: BattleScene, moneyMultiplier: number) { - super(scene); - - this.moneyMultiplier = moneyMultiplier; - } - - start() { - const moneyAmount = new Utils.IntegerHolder(this.scene.getWaveMoneyAmount(this.moneyMultiplier)); - - this.scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - - if (this.scene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { - moneyAmount.value *= 2; - } - - this.scene.addMoney(moneyAmount.value); - - const userLocale = navigator.language || "en-US"; - const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale); - const message = i18next.t("battle:moneyWon", { moneyAmount: formattedMoneyAmount }); - - this.scene.ui.showText(message, null, () => this.end(), null, true); - } -} - -export class ModifierRewardPhase extends BattlePhase { - protected modifierType: ModifierType; - - constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc) { - super(scene); - - this.modifierType = getModifierType(modifierTypeFunc); - } - - start() { - super.start(); - - this.doReward().then(() => this.end()); - } - - doReward(): Promise { - return new Promise(resolve => { - const newModifier = this.modifierType.newModifier(); - this.scene.addModifier(newModifier).then(() => { - this.scene.playSound("item_fanfare"); - this.scene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => resolve(), null, true); - }); - }); - } -} - -export class GameOverModifierRewardPhase extends ModifierRewardPhase { - constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc) { - super(scene, modifierTypeFunc); - } - - doReward(): Promise { - return new Promise(resolve => { - const newModifier = this.modifierType.newModifier(); - this.scene.addModifier(newModifier).then(() => { - this.scene.playSound("level_up_fanfare"); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.fadeIn(250).then(() => { - this.scene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => { - this.scene.time.delayedCall(1500, () => this.scene.arenaBg.setVisible(true)); - resolve(); - }, null, true, 1500); - }); - }); - }); - } -} - -export class RibbonModifierRewardPhase extends ModifierRewardPhase { - private species: PokemonSpecies; - - constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc, species: PokemonSpecies) { - super(scene, modifierTypeFunc); - - this.species = species; - } - - doReward(): Promise { - return new Promise(resolve => { - const newModifier = this.modifierType.newModifier(); - this.scene.addModifier(newModifier).then(() => { - this.scene.playSound("level_up_fanfare"); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:beatModeFirstTime", { - speciesName: this.species.name, - gameMode: this.scene.gameMode.getName(), - newModifier: newModifier?.type.name - }), null, () => { - resolve(); - }, null, true, 1500); - }); - }); - } -} - -export class GameOverPhase extends BattlePhase { - private victory: boolean; - private firstRibbons: PokemonSpecies[] = []; - - constructor(scene: BattleScene, victory?: boolean) { - super(scene); - - this.victory = !!victory; - } - - start() { - super.start(); - - // Failsafe if players somehow skip floor 200 in classic mode - if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) { - this.victory = true; - } - - if (this.victory && this.scene.gameMode.isEndless) { - this.scene.ui.showDialogue(i18next.t("PGMmiscDialogue:ending_endless"), i18next.t("PGMmiscDialogue:ending_name"), 0, () => this.handleGameOver()); - } else if (this.victory || !this.scene.enableRetries) { - this.handleGameOver(); - } else { - this.scene.ui.showText(i18next.t("battle:retryBattle"), null, () => { - this.scene.ui.setMode(Mode.CONFIRM, () => { - this.scene.ui.fadeOut(1250).then(() => { - this.scene.reset(); - this.scene.clearPhaseQueue(); - this.scene.gameData.loadSession(this.scene, this.scene.sessionSlotId).then(() => { - this.scene.pushPhase(new EncounterPhase(this.scene, true)); - - const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length; - - this.scene.pushPhase(new SummonPhase(this.scene, 0)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) { - this.scene.pushPhase(new SummonPhase(this.scene, 1)); - } - if (this.scene.currentBattle.waveIndex > 1 && this.scene.currentBattle.battleType !== BattleType.TRAINER) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); - } - } - - this.scene.ui.fadeIn(1250); - this.end(); - }); - }); - }, () => this.handleGameOver(), false, 0, 0, 1000); - }); - } - } - - handleGameOver(): void { - const doGameOver = (newClear: boolean) => { - this.scene.disableMenu = true; - this.scene.time.delayedCall(1000, () => { - let firstClear = false; - if (this.victory && newClear) { - if (this.scene.gameMode.isClassic) { - firstClear = this.scene.validateAchv(achvs.CLASSIC_VICTORY); - this.scene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY); - this.scene.gameData.gameStats.sessionsWon++; - for (const pokemon of this.scene.getParty()) { - this.awardRibbon(pokemon); - - if (pokemon.species.getRootSpeciesId() !== pokemon.species.getRootSpeciesId(true)) { - this.awardRibbon(pokemon, true); - } - } - } else if (this.scene.gameMode.isDaily && newClear) { - this.scene.gameData.gameStats.dailyRunSessionsWon++; - } - } - const fadeDuration = this.victory ? 10000 : 5000; - this.scene.fadeOutBgm(fadeDuration, true); - const activeBattlers = this.scene.getField().filter(p => p?.isActive(true)); - activeBattlers.map(p => p.hideInfo()); - this.scene.ui.fadeOut(fadeDuration).then(() => { - activeBattlers.map(a => a.setVisible(false)); - this.scene.setFieldScale(1, true); - this.scene.clearPhaseQueue(); - this.scene.ui.clearText(); - - if (this.victory && this.scene.gameMode.isChallenge) { - this.scene.gameMode.challenges.forEach(c => this.scene.validateAchvs(ChallengeAchv, c)); - } - - const clear = (endCardPhase?: EndCardPhase) => { - if (newClear) { - this.handleUnlocks(); - } - if (this.victory && newClear) { - for (const species of this.firstRibbons) { - this.scene.unshiftPhase(new RibbonModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PLUS, species)); - } - if (!firstClear) { - this.scene.unshiftPhase(new GameOverModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PREMIUM)); - } - } - this.scene.pushPhase(new PostGameOverPhase(this.scene, endCardPhase)); - this.end(); - }; - - if (this.victory && this.scene.gameMode.isClassic) { - const message = miscDialogue.ending[this.scene.gameData.gender === PlayerGender.FEMALE ? 0 : 1]; - - if (!this.scene.ui.shouldSkipDialogue(message)) { - this.scene.ui.fadeIn(500).then(() => { - this.scene.charSprite.showCharacter(`rival_${this.scene.gameData.gender === PlayerGender.FEMALE ? "m" : "f"}`, getCharVariantFromDialogue(miscDialogue.ending[this.scene.gameData.gender === PlayerGender.FEMALE ? 0 : 1])).then(() => { - this.scene.ui.showDialogue(message, this.scene.gameData.gender === PlayerGender.FEMALE ? trainerConfigs[TrainerType.RIVAL].name : trainerConfigs[TrainerType.RIVAL].nameFemale, null, () => { - this.scene.ui.fadeOut(500).then(() => { - this.scene.charSprite.hide().then(() => { - const endCardPhase = new EndCardPhase(this.scene); - this.scene.unshiftPhase(endCardPhase); - clear(endCardPhase); - }); - }); - }); - }); - }); - } else { - const endCardPhase = new EndCardPhase(this.scene); - this.scene.unshiftPhase(endCardPhase); - clear(endCardPhase); - } - } else { - clear(); - } - }); - }); - }; - - /* Added a local check to see if the game is running offline on victory - If Online, execute apiFetch as intended - If Offline, execute offlineNewClear(), a localStorage implementation of newClear daily run checks */ - if (this.victory) { - if (!Utils.isLocal) { - Utils.apiFetch(`savedata/session/newclear?slot=${this.scene.sessionSlotId}&clientSessionId=${clientSessionId}`, true) - .then(response => response.json()) - .then(newClear => doGameOver(newClear)); - } else { - this.scene.gameData.offlineNewClear(this.scene).then(result => { - doGameOver(result); - }); - } - } else { - doGameOver(false); - } - } - - handleUnlocks(): void { - if (this.victory && this.scene.gameMode.isClassic) { - if (!this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { - this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.ENDLESS_MODE)); - } - if (this.scene.getParty().filter(p => p.fusionSpecies).length && !this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { - this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.SPLICED_ENDLESS_MODE)); - } - if (!this.scene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE]) { - this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.MINI_BLACK_HOLE)); - } - if (!this.scene.gameData.unlocks[Unlockables.EVIOLITE] && this.scene.getParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)) { - this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.EVIOLITE)); - } - } - } - - awardRibbon(pokemon: Pokemon, forStarter: boolean = false): void { - const speciesId = getPokemonSpecies(pokemon.species.speciesId); - const speciesRibbonCount = this.scene.gameData.incrementRibbonCount(speciesId, forStarter); - // first time classic win, award voucher - if (speciesRibbonCount === 1) { - this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter))); - } - } -} - -export class EndCardPhase extends Phase { - public endCard: Phaser.GameObjects.Image; - public text: Phaser.GameObjects.Text; - - constructor(scene: BattleScene) { - super(scene); - } - - start(): void { - super.start(); - - this.scene.ui.getMessageHandler().bg.setVisible(false); - this.scene.ui.getMessageHandler().nameBoxContainer.setVisible(false); - - this.endCard = this.scene.add.image(0, 0, `end_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}`); - this.endCard.setOrigin(0); - this.endCard.setScale(0.5); - this.scene.field.add(this.endCard); - - this.text = addTextObject(this.scene, this.scene.game.canvas.width / 12, (this.scene.game.canvas.height / 6) - 16, i18next.t("battle:congratulations"), TextStyle.SUMMARY, { fontSize: "128px" }); - this.text.setOrigin(0.5); - this.scene.field.add(this.text); - - this.scene.ui.clearText(); - - this.scene.ui.fadeIn(1000).then(() => { - - this.scene.ui.showText("", null, () => { - this.scene.ui.getMessageHandler().bg.setVisible(true); - this.end(); - }, null, true); - }); - } -} - -export class UnlockPhase extends Phase { - private unlockable: Unlockables; - - constructor(scene: BattleScene, unlockable: Unlockables) { - super(scene); - - this.unlockable = unlockable; - } - - start(): void { - this.scene.time.delayedCall(2000, () => { - this.scene.gameData.unlocks[this.unlockable] = true; - this.scene.playSound("level_up_fanfare"); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:unlockedSomething", { unlockedThing: getUnlockableName(this.unlockable) }), null, () => { - this.scene.time.delayedCall(1500, () => this.scene.arenaBg.setVisible(true)); - this.end(); - }, null, true, 1500); - }); - } -} - -export class PostGameOverPhase extends Phase { - private endCardPhase: EndCardPhase | null; - - constructor(scene: BattleScene, endCardPhase?: EndCardPhase) { - super(scene); - - this.endCardPhase = endCardPhase!; // TODO: is this bang correct? - } - - start() { - super.start(); - - const saveAndReset = () => { - this.scene.gameData.saveAll(this.scene, true, true, true).then(success => { - if (!success) { - return this.scene.reset(true); - } - this.scene.gameData.tryClearSession(this.scene, this.scene.sessionSlotId).then((success: boolean | [boolean, boolean]) => { - if (!success[0]) { - return this.scene.reset(true); - } - this.scene.reset(); - this.scene.unshiftPhase(new TitlePhase(this.scene)); - this.end(); - }); - }); - }; - - if (this.endCardPhase) { - this.scene.ui.fadeOut(500).then(() => { - this.scene.ui.getMessageHandler().bg.setVisible(true); - - this.endCardPhase?.endCard.destroy(); - this.endCardPhase?.text.destroy(); - saveAndReset(); - }); - } else { - saveAndReset(); - } - } -} - -/** - * Opens the party selector UI and transitions into a {@linkcode SwitchSummonPhase} - * for the player (if a switch would be valid for the current battle state). - */ -export class SwitchPhase extends BattlePhase { - protected fieldIndex: integer; - private isModal: boolean; - private doReturn: boolean; - - /** - * Creates a new SwitchPhase - * @param scene {@linkcode BattleScene} Current battle scene - * @param fieldIndex Field index to switch out - * @param isModal Indicates if the switch should be forced (true) or is - * optional (false). - * @param doReturn Indicates if the party member on the field should be - * recalled to ball or has already left the field. Passed to {@linkcode SwitchSummonPhase}. - */ - constructor(scene: BattleScene, fieldIndex: integer, isModal: boolean, doReturn: boolean) { - super(scene); - - this.fieldIndex = fieldIndex; - this.isModal = isModal; - this.doReturn = doReturn; - } - - start() { - super.start(); - - // Skip modal switch if impossible (no remaining party members that aren't in battle) - if (this.isModal && !this.scene.getParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length) { - return super.end(); - } - - // Skip if the fainted party member has been revived already. doReturn is - // only passed as `false` from FaintPhase (as opposed to other usages such - // as ForceSwitchOutAttr or CheckSwitchPhase), so we only want to check this - // if the mon should have already been returned but is still alive and well - // on the field. see also; battle.test.ts - if (this.isModal && !this.doReturn && !this.scene.getParty()[this.fieldIndex].isFainted()) { - return super.end(); - } - - // Check if there is any space still in field - if (this.isModal && this.scene.getPlayerField().filter(p => p.isAllowedInBattle() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) { - return super.end(); - } - - // Override field index to 0 in case of double battle where 2/3 remaining legal party members fainted at once - const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? this.fieldIndex : 0; - - this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { - if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { - this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON)); - } - this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); - }, PartyUiHandler.FilterNonFainted); - } -} - -export class ExpPhase extends PlayerPartyMemberPokemonPhase { - private expValue: number; - - constructor(scene: BattleScene, partyMemberIndex: integer, expValue: number) { - super(scene, partyMemberIndex); - - this.expValue = expValue; - } - - start() { - super.start(); - - const pokemon = this.getPokemon(); - const exp = new Utils.NumberHolder(this.expValue); - this.scene.applyModifiers(ExpBoosterModifier, true, exp); - exp.value = Math.floor(exp.value); - this.scene.ui.showText(i18next.t("battle:expGain", { pokemonName: getPokemonNameWithAffix(pokemon), exp: exp.value }), null, () => { - const lastLevel = pokemon.level; - pokemon.addExp(exp.value); - const newLevel = pokemon.level; - if (newLevel > lastLevel) { - this.scene.unshiftPhase(new LevelUpPhase(this.scene, this.partyMemberIndex, lastLevel, newLevel)); - } - pokemon.updateInfo().then(() => this.end()); - }, null, true); - } -} - -export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { - private expValue: number; - - constructor(scene: BattleScene, partyMemberIndex: integer, expValue: number) { - super(scene, partyMemberIndex); - - this.expValue = expValue; - } - - start() { - super.start(); - - const pokemon = this.getPokemon(); - const exp = new Utils.NumberHolder(this.expValue); - this.scene.applyModifiers(ExpBoosterModifier, true, exp); - exp.value = Math.floor(exp.value); - - const lastLevel = pokemon.level; - pokemon.addExp(exp.value); - const newLevel = pokemon.level; - if (newLevel > lastLevel) { - this.scene.unshiftPhase(new LevelUpPhase(this.scene, this.partyMemberIndex, lastLevel, newLevel)); - } - this.scene.unshiftPhase(new HidePartyExpBarPhase(this.scene)); - pokemon.updateInfo(); - - if (this.scene.expParty === ExpNotification.SKIP) { - this.end(); - } else if (this.scene.expParty === ExpNotification.ONLY_LEVEL_UP) { - if (newLevel > lastLevel) { // this means if we level up - // instead of displaying the exp gain in the small frame, we display the new level - // we use the same method for mode 0 & 1, by giving a parameter saying to display the exp or the level - this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, this.scene.expParty === ExpNotification.ONLY_LEVEL_UP, newLevel).then(() => { - setTimeout(() => this.end(), 800 / Math.pow(2, this.scene.expGainsSpeed)); - }); - } else { - this.end(); - } - } else if (this.scene.expGainsSpeed < 3) { - this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, false, newLevel).then(() => { - setTimeout(() => this.end(), 500 / Math.pow(2, this.scene.expGainsSpeed)); - }); - } else { - this.end(); - } - - } -} - -export class HidePartyExpBarPhase extends BattlePhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.partyExpBar.hide().then(() => this.end()); - } -} - -export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { - private lastLevel: integer; - private level: integer; - - constructor(scene: BattleScene, partyMemberIndex: integer, lastLevel: integer, level: integer) { - super(scene, partyMemberIndex); - - this.lastLevel = lastLevel; - this.level = level; - this.scene = scene; - } - - start() { - super.start(); - - if (this.level > this.scene.gameData.gameStats.highestLevel) { - this.scene.gameData.gameStats.highestLevel = this.level; - } - - this.scene.validateAchvs(LevelAchv, new Utils.IntegerHolder(this.level)); - - const pokemon = this.getPokemon(); - const prevStats = pokemon.stats.slice(0); - pokemon.calculateStats(); - pokemon.updateInfo(); - if (this.scene.expParty === ExpNotification.DEFAULT) { - this.scene.playSound("level_up_fanfare"); - this.scene.ui.showText(i18next.t("battle:levelUp", { pokemonName: getPokemonNameWithAffix(this.getPokemon()), level: this.level }), null, () => this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()), null, true); - } else if (this.scene.expParty === ExpNotification.SKIP) { - this.end(); - } else { - // we still want to display the stats if activated - this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()); - } - if (this.lastLevel < 100) { // this feels like an unnecessary optimization - const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); - for (const lm of levelMoves) { - this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, lm[1])); - } - } - if (!pokemon.pauseEvolutions) { - const evolution = pokemon.getEvolution(); - if (evolution) { - this.scene.unshiftPhase(new EvolutionPhase(this.scene, pokemon as PlayerPokemon, evolution, this.lastLevel)); - } - } - } -} - -export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { - private moveId: Moves; - - constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves) { - super(scene, partyMemberIndex); - - this.moveId = moveId; - } - - start() { - super.start(); - - const pokemon = this.getPokemon(); - const move = allMoves[this.moveId]; - - const existingMoveIndex = pokemon.getMoveset().findIndex(m => m?.moveId === move.id); - - if (existingMoveIndex > -1) { - return this.end(); - } - - const emptyMoveIndex = pokemon.getMoveset().length < 4 - ? pokemon.getMoveset().length - : pokemon.getMoveset().findIndex(m => m === null); - - const messageMode = this.scene.ui.getHandler() instanceof EvolutionSceneHandler - ? Mode.EVOLUTION_SCENE - : Mode.MESSAGE; - - if (emptyMoveIndex > -1) { - pokemon.setMove(emptyMoveIndex, this.moveId); - initMoveAnim(this.scene, this.moveId).then(() => { - loadMoveAnimAssets(this.scene, [this.moveId], true) - .then(() => { - this.scene.ui.setMode(messageMode).then(() => { - this.scene.playSound("level_up_fanfare"); - this.scene.ui.showText(i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), null, () => { - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); - this.end(); - }, messageMode === Mode.EVOLUTION_SCENE ? 1000 : null, true); - }); - }); - }); - } else { - this.scene.ui.setMode(messageMode).then(() => { - this.scene.ui.showText(i18next.t("battle:learnMovePrompt", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), null, () => { - this.scene.ui.showText(i18next.t("battle:learnMoveLimitReached", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { - this.scene.ui.showText(i18next.t("battle:learnMoveReplaceQuestion", { moveName: move.name }), null, () => { - const noHandler = () => { - this.scene.ui.setMode(messageMode).then(() => { - this.scene.ui.showText(i18next.t("battle:learnMoveStopTeaching", { moveName: move.name }), null, () => { - this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { - this.scene.ui.setMode(messageMode); - this.scene.ui.showText(i18next.t("battle:learnMoveNotLearned", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), null, () => this.end(), null, true); - }, () => { - this.scene.ui.setMode(messageMode); - this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); - this.end(); - }); - }); - }); - }; - this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { - this.scene.ui.setMode(messageMode); - this.scene.ui.showText(i18next.t("battle:learnMoveForgetQuestion"), null, () => { - this.scene.ui.setModeWithoutClear(Mode.SUMMARY, this.getPokemon(), SummaryUiMode.LEARN_MOVE, move, (moveIndex: integer) => { - if (moveIndex === 4) { - noHandler(); - return; - } - this.scene.ui.setMode(messageMode).then(() => { - this.scene.ui.showText(i18next.t("battle:countdownPoof"), null, () => { - this.scene.ui.showText(i18next.t("battle:learnMoveForgetSuccess", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: pokemon.moveset[moveIndex]!.getName() }), null, () => { // TODO: is the bang correct? - this.scene.ui.showText(i18next.t("battle:learnMoveAnd"), null, () => { - pokemon.setMove(moveIndex, Moves.NONE); - this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); - this.end(); - }, null, true); - }, null, true); - }, null, true); - }); - }); - }, null, true); - }, noHandler); - }); - }, null, true); - }, null, true); - }); - } - } -} - -export class PokemonHealPhase extends CommonAnimPhase { - private hpHealed: integer; - private message: string | null; - private showFullHpMessage: boolean; - private skipAnim: boolean; - private revive: boolean; - private healStatus: boolean; - private preventFullHeal: boolean; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, hpHealed: integer, message: string | null, showFullHpMessage: boolean, skipAnim: boolean = false, revive: boolean = false, healStatus: boolean = false, preventFullHeal: boolean = false) { - super(scene, battlerIndex, undefined, CommonAnim.HEALTH_UP); - - this.hpHealed = hpHealed; - this.message = message; - this.showFullHpMessage = showFullHpMessage; - this.skipAnim = skipAnim; - this.revive = revive; - this.healStatus = healStatus; - this.preventFullHeal = preventFullHeal; - } - - start() { - if (!this.skipAnim && (this.revive || this.getPokemon().hp) && !this.getPokemon().isFullHp()) { - super.start(); - } else { - this.end(); - } - } - - end() { - const pokemon = this.getPokemon(); - - if (!pokemon.isOnField() || (!this.revive && !pokemon.isActive())) { - super.end(); - return; - } - - const hasMessage = !!this.message; - const healOrDamage = (!pokemon.isFullHp() || this.hpHealed < 0); - let lastStatusEffect = StatusEffect.NONE; - - if (healOrDamage) { - const hpRestoreMultiplier = new Utils.IntegerHolder(1); - if (!this.revive) { - this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); - } - const healAmount = new Utils.NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value)); - if (healAmount.value < 0) { - pokemon.damageAndUpdate(healAmount.value * -1, HitResult.HEAL as DamageResult); - healAmount.value = 0; - } - // Prevent healing to full if specified (in case of healing tokens so Sturdy doesn't cause a softlock) - if (this.preventFullHeal && pokemon.hp + healAmount.value >= pokemon.getMaxHp()) { - healAmount.value = (pokemon.getMaxHp() - pokemon.hp) - 1; - } - healAmount.value = pokemon.heal(healAmount.value); - if (healAmount.value) { - this.scene.damageNumberHandler.add(pokemon, healAmount.value, HitResult.HEAL); - } - if (pokemon.isPlayer()) { - this.scene.validateAchvs(HealAchv, healAmount); - if (healAmount.value > this.scene.gameData.gameStats.highestHeal) { - this.scene.gameData.gameStats.highestHeal = healAmount.value; - } - } - if (this.healStatus && !this.revive && pokemon.status) { - lastStatusEffect = pokemon.status.effect; - pokemon.resetStatus(); - } - pokemon.updateInfo().then(() => super.end()); - } else if (this.healStatus && !this.revive && pokemon.status) { - lastStatusEffect = pokemon.status.effect; - pokemon.resetStatus(); - pokemon.updateInfo().then(() => super.end()); - } else if (this.showFullHpMessage) { - this.message = i18next.t("battle:hpIsFull", { pokemonName: getPokemonNameWithAffix(pokemon) }); - } - - if (this.message) { - this.scene.queueMessage(this.message); - } - - if (this.healStatus && lastStatusEffect && !hasMessage) { - this.scene.queueMessage(getStatusEffectHealText(lastStatusEffect, getPokemonNameWithAffix(pokemon))); - } - - if (!healOrDamage && !lastStatusEffect) { - super.end(); - } - } -} - -export class AttemptCapturePhase extends PokemonPhase { - private pokeballType: PokeballType; - private pokeball: Phaser.GameObjects.Sprite; - private originalY: number; - - constructor(scene: BattleScene, targetIndex: integer, pokeballType: PokeballType) { - super(scene, BattlerIndex.ENEMY + targetIndex); - - this.pokeballType = pokeballType; - } - - start() { - super.start(); - - const pokemon = this.getPokemon() as EnemyPokemon; - - if (!pokemon?.hp) { - return this.end(); - } - - this.scene.pokeballCounts[this.pokeballType]--; - - this.originalY = pokemon.y; - - const _3m = 3 * pokemon.getMaxHp(); - const _2h = 2 * pokemon.hp; - const catchRate = pokemon.species.catchRate; - const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType); - const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1; - const x = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier); - const y = Math.round(65536 / Math.sqrt(Math.sqrt(255 / x))); - const fpOffset = pokemon.getFieldPositionOffset(); - - const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType); - this.pokeball = this.scene.addFieldSprite(16, 80, "pb", pokeballAtlasKey); - this.pokeball.setOrigin(0.5, 0.625); - this.scene.field.add(this.pokeball); - - this.scene.playSound("pb_throw"); - this.scene.time.delayedCall(300, () => { - this.scene.field.moveBelow(this.pokeball as Phaser.GameObjects.GameObject, pokemon); - }); - - this.scene.tweens.add({ - targets: this.pokeball, - x: { value: 236 + fpOffset[0], ease: "Linear" }, - y: { value: 16 + fpOffset[1], ease: "Cubic.easeOut" }, - duration: 500, - onComplete: () => { - this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); - this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); - this.scene.playSound("pb_rel"); - pokemon.tint(getPokeballTintColor(this.pokeballType)); - - addPokeballOpenParticles(this.scene, this.pokeball.x, this.pokeball.y, this.pokeballType); - - this.scene.tweens.add({ - targets: pokemon, - duration: 500, - ease: "Sine.easeIn", - scale: 0.25, - y: 20, - onComplete: () => { - this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); - pokemon.setVisible(false); - this.scene.playSound("pb_catch"); - this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}`)); - - const doShake = () => { - let shakeCount = 0; - const pbX = this.pokeball.x; - const shakeCounter = this.scene.tweens.addCounter({ - from: 0, - to: 1, - repeat: 4, - yoyo: true, - ease: "Cubic.easeOut", - duration: 250, - repeatDelay: 500, - onUpdate: t => { - if (shakeCount && shakeCount < 4) { - const value = t.getValue(); - const directionMultiplier = shakeCount % 2 === 1 ? 1 : -1; - this.pokeball.setX(pbX + value * 4 * directionMultiplier); - this.pokeball.setAngle(value * 27.5 * directionMultiplier); - } - }, - onRepeat: () => { - if (!pokemon.species.isObtainable()) { - shakeCounter.stop(); - this.failCatch(shakeCount); - } else if (shakeCount++ < 3) { - if (pokeballMultiplier === -1 || pokemon.randSeedInt(65536) < y) { - this.scene.playSound("pb_move"); - } else { - shakeCounter.stop(); - this.failCatch(shakeCount); - } - } else { - this.scene.playSound("pb_lock"); - addPokeballCaptureStars(this.scene, this.pokeball); - - const pbTint = this.scene.add.sprite(this.pokeball.x, this.pokeball.y, "pb", "pb"); - pbTint.setOrigin(this.pokeball.originX, this.pokeball.originY); - pbTint.setTintFill(0); - pbTint.setAlpha(0); - this.scene.field.add(pbTint); - this.scene.tweens.add({ - targets: pbTint, - alpha: 0.375, - duration: 200, - easing: "Sine.easeOut", - onComplete: () => { - this.scene.tweens.add({ - targets: pbTint, - alpha: 0, - duration: 200, - easing: "Sine.easeIn", - onComplete: () => pbTint.destroy() - }); - } - }); - } - }, - onComplete: () => { - this.catch(); - } - }); - }; - - this.scene.time.delayedCall(250, () => doPokeballBounceAnim(this.scene, this.pokeball, 16, 72, 350, doShake)); - } - }); - } - }); - } - - failCatch(shakeCount: integer) { - const pokemon = this.getPokemon(); - - this.scene.playSound("pb_rel"); - pokemon.setY(this.originalY); - if (pokemon.status?.effect !== StatusEffect.SLEEP) { - pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); - } - pokemon.tint(getPokeballTintColor(this.pokeballType)); - pokemon.setVisible(true); - pokemon.untint(250, "Sine.easeOut"); - - const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType); - this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); - this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); - - this.scene.tweens.add({ - targets: pokemon, - duration: 250, - ease: "Sine.easeOut", - scale: 1 - }); - - this.scene.currentBattle.lastUsedPokeball = this.pokeballType; - this.removePb(); - this.end(); - } - - catch() { - const pokemon = this.getPokemon() as EnemyPokemon; - - const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); - - if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) { - this.scene.validateAchv(achvs.HIDDEN_ABILITY); - } - - if (pokemon.species.subLegendary) { - this.scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); - } - - if (pokemon.species.legendary) { - this.scene.validateAchv(achvs.CATCH_LEGENDARY); - } - - if (pokemon.species.mythical) { - this.scene.validateAchv(achvs.CATCH_MYTHICAL); - } - - this.scene.pokemonInfoContainer.show(pokemon, true); - - this.scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); - - this.scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { - const end = () => { - this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); - this.scene.pokemonInfoContainer.hide(); - this.removePb(); - this.end(); - }; - const removePokemon = () => { - this.scene.addFaintedEnemyScore(pokemon); - this.scene.getPlayerField().filter(p => p.isActive(true)).forEach(playerPokemon => playerPokemon.removeTagsBySourceId(pokemon.id)); - pokemon.hp = 0; - pokemon.trySetStatus(StatusEffect.FAINT); - this.scene.clearEnemyHeldItemModifiers(); - this.scene.field.remove(pokemon, true); - }; - const addToParty = () => { - const newPokemon = pokemon.addToParty(this.pokeballType); - const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); - if (this.scene.getParty().filter(p => p.isShiny()).length === 6) { - this.scene.validateAchv(achvs.SHINY_PARTY); - } - Promise.all(modifiers.map(m => this.scene.addModifier(m, true))).then(() => { - this.scene.updateModifiers(true); - removePokemon(); - if (newPokemon) { - newPokemon.loadAssets().then(end); - } else { - end(); - } - }); - }; - Promise.all([pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon)]).then(() => { - if (this.scene.getParty().length === 6) { - const promptRelease = () => { - this.scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => { - this.scene.pokemonInfoContainer.makeRoomForConfirmUi(1, true); - this.scene.ui.setMode(Mode.CONFIRM, () => { - const newPokemon = this.scene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon); - this.scene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => { - this.scene.ui.setMode(Mode.MESSAGE).then(() => { - promptRelease(); - }); - }, false); - }, () => { - this.scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { - this.scene.ui.setMode(Mode.MESSAGE).then(() => { - if (slotIndex < 6) { - addToParty(); - } else { - promptRelease(); - } - }); - }); - }, () => { - this.scene.ui.setMode(Mode.MESSAGE).then(() => { - removePokemon(); - end(); - }); - }, "fullParty"); - }); - }; - promptRelease(); - } else { - addToParty(); - } - }); - }, 0, true); - } - - removePb() { - this.scene.tweens.add({ - targets: this.pokeball, - duration: 250, - delay: 250, - ease: "Sine.easeIn", - alpha: 0, - onComplete: () => this.pokeball.destroy() - }); - } -} - -export class AttemptRunPhase extends PokemonPhase { - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, fieldIndex); - } - - start() { - super.start(); - - const playerPokemon = this.getPokemon(); - const enemyField = this.scene.getEnemyField(); - - const enemySpeed = enemyField.reduce((total: integer, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD), 0) / enemyField.length; - - const escapeChance = new Utils.IntegerHolder((((playerPokemon.getStat(Stat.SPD) * 128) / enemySpeed) + (30 * this.scene.currentBattle.escapeAttempts++)) % 256); - applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, escapeChance); - - if (playerPokemon.randSeedInt(256) < escapeChance.value) { - this.scene.playSound("flee"); - this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); - - this.scene.tweens.add({ - targets: [this.scene.arenaEnemy, enemyField].flat(), - alpha: 0, - duration: 250, - ease: "Sine.easeIn", - onComplete: () => enemyField.forEach(enemyPokemon => enemyPokemon.destroy()) - }); - - this.scene.clearEnemyHeldItemModifiers(); - - enemyField.forEach(enemyPokemon => { - enemyPokemon.hideInfo().then(() => enemyPokemon.destroy()); - enemyPokemon.hp = 0; - enemyPokemon.trySetStatus(StatusEffect.FAINT); - }); - - this.scene.pushPhase(new BattleEndPhase(this.scene)); - this.scene.pushPhase(new NewBattlePhase(this.scene)); - } else { - this.scene.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500); - } - - this.end(); - } -} - -export class SelectModifierPhase extends BattlePhase { - private rerollCount: integer; - private modifierTiers: ModifierTier[]; - - constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[]) { - super(scene); - - this.rerollCount = rerollCount; - this.modifierTiers = modifierTiers!; // TODO: is this bang correct? - } - - start() { - super.start(); - - if (!this.rerollCount) { - this.updateSeed(); - } else { - this.scene.reroll = false; - } - - const party = this.scene.getParty(); - regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); - const modifierCount = new Utils.IntegerHolder(3); - if (this.isPlayer()) { - this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); - } - const typeOptions: ModifierTypeOption[] = this.getModifierTypeOptions(modifierCount.value); - - const modifierSelectCallback = (rowCursor: integer, cursor: integer) => { - if (rowCursor < 0 || cursor < 0) { - this.scene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { - this.scene.ui.setOverlayMode(Mode.CONFIRM, () => { - this.scene.ui.revertMode(); - this.scene.ui.setMode(Mode.MESSAGE); - super.end(); - }, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers))); - }); - return false; - } - let modifierType: ModifierType; - let cost: integer; - switch (rowCursor) { - case 0: - switch (cursor) { - case 0: - const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers); - if (this.scene.money < rerollCost) { - this.scene.ui.playError(); - return false; - } else { - this.scene.reroll = true; - this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); - if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - this.scene.money -= rerollCost; - this.scene.updateMoneyText(); - this.scene.animateMoneyChanged(false); - } - this.scene.playSound("buy"); - } - break; - case 1: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { - if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { - const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.isTransferrable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; - const itemModifier = itemModifiers[itemIndex]; - this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); - } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - } - }, PartyUiHandler.FilterItemMaxStacks); - break; - case 2: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - }); - break; - case 3: - this.scene.lockModifierTiers = !this.scene.lockModifierTiers; - const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; - uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - uiHandler.updateLockRaritiesText(); - uiHandler.updateRerollCostText(); - return false; - } - return true; - case 1: - if (typeOptions[cursor].type) { - modifierType = typeOptions[cursor].type; - } - break; - default: - const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); - const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; - if (shopOption.type) { - modifierType = shopOption.type; - } - cost = shopOption.cost; - break; - } - - if (cost! && (this.scene.money < cost) && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { // TODO: is the bang on cost correct? - this.scene.ui.playError(); - return false; - } - - const applyModifier = (modifier: Modifier, playSound: boolean = false) => { - const result = this.scene.addModifier(modifier, false, playSound); - if (cost) { - result.then(success => { - if (success) { - if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - this.scene.money -= cost; - this.scene.updateMoneyText(); - this.scene.animateMoneyChanged(false); - } - this.scene.playSound("buy"); - (this.scene.ui.getHandler() as ModifierSelectUiHandler).updateCostText(); - } else { - this.scene.ui.playError(); - } - }); - } else { - const doEnd = () => { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.MESSAGE); - super.end(); - }; - if (result instanceof Promise) { - result.then(() => doEnd()); - } else { - doEnd(); - } - } - }; - - if (modifierType! instanceof PokemonModifierType) { //TODO: is the bang correct? - if (modifierType instanceof FusePokemonModifierType) { - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.SPLICE, -1, (fromSlotIndex: integer, spliceSlotIndex: integer) => { - if (spliceSlotIndex !== undefined && fromSlotIndex < 6 && spliceSlotIndex < 6 && fromSlotIndex !== spliceSlotIndex) { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { - const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct? - applyModifier(modifier, true); - }); - } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - } - }, modifierType.selectFilter); - } else { - const pokemonModifierType = modifierType as PokemonModifierType; - const isMoveModifier = modifierType instanceof PokemonMoveModifierType; - const isTmModifier = modifierType instanceof TmModifierType; - const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType; - const isPpRestoreModifier = (modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType); - const partyUiMode = isMoveModifier ? PartyUiMode.MOVE_MODIFIER - : isTmModifier ? PartyUiMode.TM_MODIFIER - : isRememberMoveModifier ? PartyUiMode.REMEMBER_MOVE_MODIFIER - : PartyUiMode.MODIFIER; - const tmMoveId = isTmModifier - ? (modifierType as TmModifierType).moveId - : undefined; - this.scene.ui.setModeWithoutClear(Mode.PARTY, partyUiMode, -1, (slotIndex: integer, option: PartyOption) => { - if (slotIndex < 6) { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { - const modifier = !isMoveModifier - ? !isRememberMoveModifier - ? modifierType.newModifier(party[slotIndex]) - : modifierType.newModifier(party[slotIndex], option as integer) - : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); - applyModifier(modifier!, true); // TODO: is the bang correct? - }); - } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - } - }, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined, tmMoveId, isPpRestoreModifier); - } - } else { - applyModifier(modifierType!.newModifier()!); // TODO: is the bang correct? - } - - return !cost!;// TODO: is the bang correct? - }; - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - } - - updateSeed(): void { - this.scene.resetSeed(); - } - - isPlayer(): boolean { - return true; - } - - getRerollCost(typeOptions: ModifierTypeOption[], lockRarities: boolean): integer { - let baseValue = 0; - if (Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - return baseValue; - } else if (lockRarities) { - const tierValues = [50, 125, 300, 750, 2000]; - for (const opt of typeOptions) { - baseValue += tierValues[opt.type.tier ?? 0]; - } - } else { - baseValue = 250; - } - return Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount), Number.MAX_SAFE_INTEGER); - } - - getPoolType(): ModifierPoolType { - return ModifierPoolType.PLAYER; - } - - getModifierTypeOptions(modifierCount: integer): ModifierTypeOption[] { - return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined); - } - - addModifier(modifier: Modifier): Promise { - return this.scene.addModifier(modifier, false, true); - } -} - -export class EggLapsePhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - const eggsToHatch: Egg[] = this.scene.gameData.eggs.filter((egg: Egg) => { - return Overrides.EGG_IMMEDIATE_HATCH_OVERRIDE ? true : --egg.hatchWaves < 1; - }); - - let eggCount: integer = eggsToHatch.length; - - if (eggCount) { - this.scene.queueMessage(i18next.t("battle:eggHatching")); - - for (const egg of eggsToHatch) { - this.scene.unshiftPhase(new EggHatchPhase(this.scene, egg, eggCount)); - if (eggCount > 0) { - eggCount--; - } - } - - } - this.end(); - } -} - -export class AddEnemyBuffModifierPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - const waveIndex = this.scene.currentBattle.waveIndex; - const tier = !(waveIndex % 1000) ? ModifierTier.ULTRA : !(waveIndex % 250) ? ModifierTier.GREAT : ModifierTier.COMMON; - - regenerateModifierPoolThresholds(this.scene.getEnemyParty(), ModifierPoolType.ENEMY_BUFF); - - const count = Math.ceil(waveIndex / 250); - for (let i = 0; i < count; i++) { - this.scene.addEnemyModifier(getEnemyBuffModifierForWave(tier, this.scene.findModifiers(m => m instanceof EnemyPersistentModifier, false), this.scene), true, true); - } - this.scene.updateModifiers(false, true).then(() => this.end()); - } -} - -/** - * Cures the party of all non-volatile status conditions, shows a message - * @param {BattleScene} scene The current scene - * @param {Pokemon} user The user of the move that cures the party - * @param {string} message The message that should be displayed - * @param {Abilities} abilityCondition Pokemon with this ability will not be affected ie. Soundproof - */ -export class PartyStatusCurePhase extends BattlePhase { - private user: Pokemon; - private message: string; - private abilityCondition: Abilities; - - constructor(scene: BattleScene, user: Pokemon, message: string, abilityCondition: Abilities) { - super(scene); - - this.user = user; - this.message = message; - this.abilityCondition = abilityCondition; - } - - start() { - super.start(); - for (const pokemon of this.scene.getParty()) { - if (!pokemon.isOnField() || pokemon === this.user) { - pokemon.resetStatus(false); - pokemon.updateInfo(true); - } else { - if (!pokemon.hasAbility(this.abilityCondition)) { - pokemon.resetStatus(); - pokemon.updateInfo(true); - } else { - // Manually show ability bar, since we're not hooked into the targeting system - pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, pokemon.getPassiveAbility()?.id === this.abilityCondition)); - } - } - } - if (this.message) { - this.scene.queueMessage(this.message); - } - this.end(); - } -} - -export class PartyHealPhase extends BattlePhase { - private resumeBgm: boolean; - - constructor(scene: BattleScene, resumeBgm: boolean) { - super(scene); - - this.resumeBgm = resumeBgm; - } - - start() { - super.start(); - - const bgmPlaying = this.scene.isBgmPlaying(); - if (bgmPlaying) { - this.scene.fadeOutBgm(1000, false); - } - this.scene.ui.fadeOut(1000).then(() => { - for (const pokemon of this.scene.getParty()) { - pokemon.hp = pokemon.getMaxHp(); - pokemon.resetStatus(); - for (const move of pokemon.moveset) { - move!.ppUsed = 0; // TODO: is this bang correct? - } - pokemon.updateInfo(true); - } - const healSong = this.scene.playSoundWithoutBgm("heal"); - this.scene.time.delayedCall(Utils.fixedInt(healSong.totalDuration * 1000), () => { - healSong.destroy(); - if (this.resumeBgm && bgmPlaying) { - this.scene.playBgm(); - } - this.scene.ui.fadeIn(500).then(() => this.end()); - }); - }); - } -} - -export class ShinySparklePhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); - } - - start() { - super.start(); - - this.getPokemon().sparkle(); - this.scene.time.delayedCall(1000, () => this.end()); - } -} - -export class ScanIvsPhase extends PokemonPhase { - private shownIvs: integer; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, shownIvs: integer) { - super(scene, battlerIndex); - - this.shownIvs = shownIvs; - } - - start() { - super.start(); - - if (!this.shownIvs) { - return this.end(); - } - - const pokemon = this.getPokemon(); - - let enemyIvs: number[] = []; - let statsContainer: Phaser.GameObjects.Sprite[] = []; - let statsContainerLabels: Phaser.GameObjects.Sprite[] = []; - const enemyField = this.scene.getEnemyField(); - const uiTheme = (this.scene as BattleScene).uiTheme; // Assuming uiTheme is accessible - for (let e = 0; e < enemyField.length; e++) { - enemyIvs = enemyField[e].ivs; - const currentIvs = this.scene.gameData.dexData[enemyField[e].species.getRootSpeciesId()].ivs; // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists - const ivsToShow = this.scene.ui.getMessageHandler().getTopIvs(enemyIvs, this.shownIvs); - statsContainer = enemyField[e].getBattleInfo().getStatsValueContainer().list as Phaser.GameObjects.Sprite[]; - statsContainerLabels = statsContainer.filter(m => m.name.indexOf("icon_stat_label") >= 0); - for (let s = 0; s < statsContainerLabels.length; s++) { - const ivStat = Stat[statsContainerLabels[s].frame.name]; - if (enemyIvs[ivStat] > currentIvs[ivStat] && ivsToShow.indexOf(Number(ivStat)) >= 0) { - const hexColour = enemyIvs[ivStat] === 31 ? getTextColor(TextStyle.PERFECT_IV, false, uiTheme) : getTextColor(TextStyle.SUMMARY_GREEN, false, uiTheme); - const hexTextColour = Phaser.Display.Color.HexStringToColor(hexColour).color; - statsContainerLabels[s].setTint(hexTextColour); - } - statsContainerLabels[s].setVisible(true); - } - } - - if (!this.scene.hideIvs) { - this.scene.ui.showText(i18next.t("battle:ivScannerUseQuestion", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { - this.scene.ui.setMode(Mode.CONFIRM, () => { - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.clearText(); - new CommonBattleAnim(CommonAnim.LOCK_ON, pokemon, pokemon).play(this.scene, () => { - this.scene.ui.getMessageHandler().promptIvs(pokemon.id, pokemon.ivs, this.shownIvs).then(() => this.end()); - }); - }, () => { - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.clearText(); - this.end(); - }); - }); - } else { - this.end(); - } - } -} - -export class TrainerMessageTestPhase extends BattlePhase { - private trainerTypes: TrainerType[]; - - constructor(scene: BattleScene, ...trainerTypes: TrainerType[]) { - super(scene); - - this.trainerTypes = trainerTypes; - } - - start() { - super.start(); - - const testMessages: string[] = []; - - for (const t of Object.keys(trainerConfigs)) { - const type = parseInt(t); - if (this.trainerTypes.length && !this.trainerTypes.find(tt => tt === type as TrainerType)) { - continue; - } - const config = trainerConfigs[type]; - [config.encounterMessages, config.femaleEncounterMessages, config.victoryMessages, config.femaleVictoryMessages, config.defeatMessages, config.femaleDefeatMessages] - .map(messages => { - if (messages?.length) { - testMessages.push(...messages); - } - }); - } - - for (const message of testMessages) { - this.scene.pushPhase(new TestMessagePhase(this.scene, message)); - } - - this.end(); - } -} - -export class TestMessagePhase extends MessagePhase { - constructor(scene: BattleScene, message: string) { - super(scene, message, null, true); - } -} diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts new file mode 100644 index 00000000000..a9936eb765d --- /dev/null +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -0,0 +1,26 @@ +import BattleScene from "#app/battle-scene.js"; +import { ModifierTier } from "#app/modifier/modifier-tier.js"; +import { regenerateModifierPoolThresholds, ModifierPoolType, getEnemyBuffModifierForWave } from "#app/modifier/modifier-type.js"; +import { EnemyPersistentModifier } from "#app/modifier/modifier.js"; +import { Phase } from "#app/phase.js"; + +export class AddEnemyBuffModifierPhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + const waveIndex = this.scene.currentBattle.waveIndex; + const tier = !(waveIndex % 1000) ? ModifierTier.ULTRA : !(waveIndex % 250) ? ModifierTier.GREAT : ModifierTier.COMMON; + + regenerateModifierPoolThresholds(this.scene.getEnemyParty(), ModifierPoolType.ENEMY_BUFF); + + const count = Math.ceil(waveIndex / 250); + for (let i = 0; i < count; i++) { + this.scene.addEnemyModifier(getEnemyBuffModifierForWave(tier, this.scene.findModifiers(m => m instanceof EnemyPersistentModifier, false), this.scene), true, true); + } + this.scene.updateModifiers(false, true).then(() => this.end()); + } +} diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts new file mode 100644 index 00000000000..3c165a25157 --- /dev/null +++ b/src/phases/attempt-capture-phase.ts @@ -0,0 +1,288 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { getPokeballCatchMultiplier, getPokeballAtlasKey, getPokeballTintColor, doPokeballBounceAnim } from "#app/data/pokeball.js"; +import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect.js"; +import { PokeballType } from "#app/enums/pokeball.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { addPokeballOpenParticles, addPokeballCaptureStars } from "#app/field/anims.js"; +import { EnemyPokemon } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { PokemonHeldItemModifier } from "#app/modifier/modifier.js"; +import { achvs } from "#app/system/achv.js"; +import { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler.js"; +import { SummaryUiMode } from "#app/ui/summary-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { PokemonPhase } from "./pokemon-phase"; +import { VictoryPhase } from "./victory-phase"; + +export class AttemptCapturePhase extends PokemonPhase { + private pokeballType: PokeballType; + private pokeball: Phaser.GameObjects.Sprite; + private originalY: number; + + constructor(scene: BattleScene, targetIndex: integer, pokeballType: PokeballType) { + super(scene, BattlerIndex.ENEMY + targetIndex); + + this.pokeballType = pokeballType; + } + + start() { + super.start(); + + const pokemon = this.getPokemon() as EnemyPokemon; + + if (!pokemon?.hp) { + return this.end(); + } + + this.scene.pokeballCounts[this.pokeballType]--; + + this.originalY = pokemon.y; + + const _3m = 3 * pokemon.getMaxHp(); + const _2h = 2 * pokemon.hp; + const catchRate = pokemon.species.catchRate; + const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType); + const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1; + const x = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier); + const y = Math.round(65536 / Math.sqrt(Math.sqrt(255 / x))); + const fpOffset = pokemon.getFieldPositionOffset(); + + const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType); + this.pokeball = this.scene.addFieldSprite(16, 80, "pb", pokeballAtlasKey); + this.pokeball.setOrigin(0.5, 0.625); + this.scene.field.add(this.pokeball); + + this.scene.playSound("pb_throw"); + this.scene.time.delayedCall(300, () => { + this.scene.field.moveBelow(this.pokeball as Phaser.GameObjects.GameObject, pokemon); + }); + + this.scene.tweens.add({ + targets: this.pokeball, + x: { value: 236 + fpOffset[0], ease: "Linear" }, + y: { value: 16 + fpOffset[1], ease: "Cubic.easeOut" }, + duration: 500, + onComplete: () => { + this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); + this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); + this.scene.playSound("pb_rel"); + pokemon.tint(getPokeballTintColor(this.pokeballType)); + + addPokeballOpenParticles(this.scene, this.pokeball.x, this.pokeball.y, this.pokeballType); + + this.scene.tweens.add({ + targets: pokemon, + duration: 500, + ease: "Sine.easeIn", + scale: 0.25, + y: 20, + onComplete: () => { + this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); + pokemon.setVisible(false); + this.scene.playSound("pb_catch"); + this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}`)); + + const doShake = () => { + let shakeCount = 0; + const pbX = this.pokeball.x; + const shakeCounter = this.scene.tweens.addCounter({ + from: 0, + to: 1, + repeat: 4, + yoyo: true, + ease: "Cubic.easeOut", + duration: 250, + repeatDelay: 500, + onUpdate: t => { + if (shakeCount && shakeCount < 4) { + const value = t.getValue(); + const directionMultiplier = shakeCount % 2 === 1 ? 1 : -1; + this.pokeball.setX(pbX + value * 4 * directionMultiplier); + this.pokeball.setAngle(value * 27.5 * directionMultiplier); + } + }, + onRepeat: () => { + if (!pokemon.species.isObtainable()) { + shakeCounter.stop(); + this.failCatch(shakeCount); + } else if (shakeCount++ < 3) { + if (pokeballMultiplier === -1 || pokemon.randSeedInt(65536) < y) { + this.scene.playSound("pb_move"); + } else { + shakeCounter.stop(); + this.failCatch(shakeCount); + } + } else { + this.scene.playSound("pb_lock"); + addPokeballCaptureStars(this.scene, this.pokeball); + + const pbTint = this.scene.add.sprite(this.pokeball.x, this.pokeball.y, "pb", "pb"); + pbTint.setOrigin(this.pokeball.originX, this.pokeball.originY); + pbTint.setTintFill(0); + pbTint.setAlpha(0); + this.scene.field.add(pbTint); + this.scene.tweens.add({ + targets: pbTint, + alpha: 0.375, + duration: 200, + easing: "Sine.easeOut", + onComplete: () => { + this.scene.tweens.add({ + targets: pbTint, + alpha: 0, + duration: 200, + easing: "Sine.easeIn", + onComplete: () => pbTint.destroy() + }); + } + }); + } + }, + onComplete: () => { + this.catch(); + } + }); + }; + + this.scene.time.delayedCall(250, () => doPokeballBounceAnim(this.scene, this.pokeball, 16, 72, 350, doShake)); + } + }); + } + }); + } + + failCatch(shakeCount: integer) { + const pokemon = this.getPokemon(); + + this.scene.playSound("pb_rel"); + pokemon.setY(this.originalY); + if (pokemon.status?.effect !== StatusEffect.SLEEP) { + pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); + } + pokemon.tint(getPokeballTintColor(this.pokeballType)); + pokemon.setVisible(true); + pokemon.untint(250, "Sine.easeOut"); + + const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType); + this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); + this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); + + this.scene.tweens.add({ + targets: pokemon, + duration: 250, + ease: "Sine.easeOut", + scale: 1 + }); + + this.scene.currentBattle.lastUsedPokeball = this.pokeballType; + this.removePb(); + this.end(); + } + + catch() { + const pokemon = this.getPokemon() as EnemyPokemon; + + const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); + + if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) { + this.scene.validateAchv(achvs.HIDDEN_ABILITY); + } + + if (pokemon.species.subLegendary) { + this.scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); + } + + if (pokemon.species.legendary) { + this.scene.validateAchv(achvs.CATCH_LEGENDARY); + } + + if (pokemon.species.mythical) { + this.scene.validateAchv(achvs.CATCH_MYTHICAL); + } + + this.scene.pokemonInfoContainer.show(pokemon, true); + + this.scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); + + this.scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { + const end = () => { + this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); + this.scene.pokemonInfoContainer.hide(); + this.removePb(); + this.end(); + }; + const removePokemon = () => { + this.scene.addFaintedEnemyScore(pokemon); + this.scene.getPlayerField().filter(p => p.isActive(true)).forEach(playerPokemon => playerPokemon.removeTagsBySourceId(pokemon.id)); + pokemon.hp = 0; + pokemon.trySetStatus(StatusEffect.FAINT); + this.scene.clearEnemyHeldItemModifiers(); + this.scene.field.remove(pokemon, true); + }; + const addToParty = () => { + const newPokemon = pokemon.addToParty(this.pokeballType); + const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); + if (this.scene.getParty().filter(p => p.isShiny()).length === 6) { + this.scene.validateAchv(achvs.SHINY_PARTY); + } + Promise.all(modifiers.map(m => this.scene.addModifier(m, true))).then(() => { + this.scene.updateModifiers(true); + removePokemon(); + if (newPokemon) { + newPokemon.loadAssets().then(end); + } else { + end(); + } + }); + }; + Promise.all([pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon)]).then(() => { + if (this.scene.getParty().length === 6) { + const promptRelease = () => { + this.scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => { + this.scene.pokemonInfoContainer.makeRoomForConfirmUi(1, true); + this.scene.ui.setMode(Mode.CONFIRM, () => { + const newPokemon = this.scene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon); + this.scene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => { + this.scene.ui.setMode(Mode.MESSAGE).then(() => { + promptRelease(); + }); + }, false); + }, () => { + this.scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { + this.scene.ui.setMode(Mode.MESSAGE).then(() => { + if (slotIndex < 6) { + addToParty(); + } else { + promptRelease(); + } + }); + }); + }, () => { + this.scene.ui.setMode(Mode.MESSAGE).then(() => { + removePokemon(); + end(); + }); + }, "fullParty"); + }); + }; + promptRelease(); + } else { + addToParty(); + } + }); + }, 0, true); + } + + removePb() { + this.scene.tweens.add({ + targets: this.pokeball, + duration: 250, + delay: 250, + ease: "Sine.easeIn", + alpha: 0, + onComplete: () => this.pokeball.destroy() + }); + } +} diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts new file mode 100644 index 00000000000..9781ca6d360 --- /dev/null +++ b/src/phases/attempt-run-phase.ts @@ -0,0 +1,56 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyAbAttrs, RunSuccessAbAttr } from "#app/data/ability.js"; +import { Stat } from "#app/enums/stat.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import Pokemon from "#app/field/pokemon.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { BattleEndPhase } from "./battle-end-phase"; +import { NewBattlePhase } from "./new-battle-phase"; +import { PokemonPhase } from "./pokemon-phase"; + +export class AttemptRunPhase extends PokemonPhase { + constructor(scene: BattleScene, fieldIndex: integer) { + super(scene, fieldIndex); + } + + start() { + super.start(); + + const playerPokemon = this.getPokemon(); + const enemyField = this.scene.getEnemyField(); + + const enemySpeed = enemyField.reduce((total: integer, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD), 0) / enemyField.length; + + const escapeChance = new Utils.IntegerHolder((((playerPokemon.getStat(Stat.SPD) * 128) / enemySpeed) + (30 * this.scene.currentBattle.escapeAttempts++)) % 256); + applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, escapeChance); + + if (playerPokemon.randSeedInt(256) < escapeChance.value) { + this.scene.playSound("flee"); + this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); + + this.scene.tweens.add({ + targets: [this.scene.arenaEnemy, enemyField].flat(), + alpha: 0, + duration: 250, + ease: "Sine.easeIn", + onComplete: () => enemyField.forEach(enemyPokemon => enemyPokemon.destroy()) + }); + + this.scene.clearEnemyHeldItemModifiers(); + + enemyField.forEach(enemyPokemon => { + enemyPokemon.hideInfo().then(() => enemyPokemon.destroy()); + enemyPokemon.hp = 0; + enemyPokemon.trySetStatus(StatusEffect.FAINT); + }); + + this.scene.pushPhase(new BattleEndPhase(this.scene)); + this.scene.pushPhase(new NewBattlePhase(this.scene)); + } else { + this.scene.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500); + } + + this.end(); + } +} diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts new file mode 100644 index 00000000000..a9999370cdd --- /dev/null +++ b/src/phases/battle-end-phase.ts @@ -0,0 +1,55 @@ +import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/ability.js"; +import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier.js"; +import { BattlePhase } from "./battle-phase"; +import { GameOverPhase } from "./game-over-phase"; + +export class BattleEndPhase extends BattlePhase { + start() { + super.start(); + + this.scene.currentBattle.addBattleScore(this.scene); + + this.scene.gameData.gameStats.battles++; + if (this.scene.currentBattle.trainer) { + this.scene.gameData.gameStats.trainersDefeated++; + } + if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex + 1 > this.scene.gameData.gameStats.highestEndlessWave) { + this.scene.gameData.gameStats.highestEndlessWave = this.scene.currentBattle.waveIndex + 1; + } + + // Endless graceful end + if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex >= 5850) { + this.scene.clearPhaseQueue(); + this.scene.unshiftPhase(new GameOverPhase(this.scene, true)); + } + + for (const pokemon of this.scene.getField()) { + if (pokemon) { + pokemon.resetBattleSummonData(); + } + } + + for (const pokemon of this.scene.getParty().filter(p => p.isAllowedInBattle())) { + applyPostBattleAbAttrs(PostBattleAbAttr, pokemon); + } + + if (this.scene.currentBattle.moneyScattered) { + this.scene.currentBattle.pickUpScatteredMoney(this.scene); + } + + this.scene.clearEnemyHeldItemModifiers(); + + const lapsingModifiers = this.scene.findModifiers(m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[]; + for (const m of lapsingModifiers) { + const args: any[] = []; + if (m instanceof LapsingPokemonHeldItemModifier) { + args.push(this.scene.getPokemonById(m.pokemonId)); + } + if (!m.lapse(args)) { + this.scene.removeModifier(m); + } + } + + this.scene.updateModifiers().then(() => this.end()); + } +} diff --git a/src/phases/battle-phase.ts b/src/phases/battle-phase.ts new file mode 100644 index 00000000000..3e7e0e28596 --- /dev/null +++ b/src/phases/battle-phase.ts @@ -0,0 +1,47 @@ +import BattleScene from "#app/battle-scene.js"; +import { TrainerSlot } from "#app/data/trainer-config.js"; +import { Phase } from "#app/phase.js"; + +export class BattlePhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void { + const sprites = this.scene.currentBattle.trainer?.getSprites()!; // TODO: is this bang correct? + const tintSprites = this.scene.currentBattle.trainer?.getTintSprites()!; // TODO: is this bang correct? + for (let i = 0; i < sprites.length; i++) { + const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2; + [sprites[i], tintSprites[i]].map(sprite => { + if (visible) { + sprite.x = trainerSlot || sprites.length < 2 ? 0 : i ? 16 : -16; + } + sprite.setVisible(visible); + sprite.clearTint(); + }); + sprites[i].setVisible(visible); + tintSprites[i].setVisible(visible); + sprites[i].clearTint(); + tintSprites[i].clearTint(); + } + this.scene.tweens.add({ + targets: this.scene.currentBattle.trainer, + x: "-=16", + y: "+=16", + alpha: 1, + ease: "Sine.easeInOut", + duration: 750 + }); + } + + hideEnemyTrainer(): void { + this.scene.tweens.add({ + targets: this.scene.currentBattle.trainer, + x: "+=16", + y: "-=16", + alpha: 0, + ease: "Sine.easeInOut", + duration: 750 + }); + } +} diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts new file mode 100644 index 00000000000..504fb6ec163 --- /dev/null +++ b/src/phases/berry-phase.ts @@ -0,0 +1,52 @@ +import { applyAbAttrs, PreventBerryUseAbAttr, HealFromBerryUseAbAttr } from "#app/data/ability.js"; +import { CommonAnim } from "#app/data/battle-anims.js"; +import { BerryUsedEvent } from "#app/events/battle-scene.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { BerryModifier } from "#app/modifier/modifier.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { FieldPhase } from "./field-phase"; +import { CommonAnimPhase } from "./common-anim-phase"; + +/** The phase after attacks where the pokemon eat berries */ +export class BerryPhase extends FieldPhase { + start() { + super.start(); + + this.executeForAll((pokemon) => { + const hasUsableBerry = !!this.scene.findModifier((m) => { + return m instanceof BerryModifier && m.shouldApply([pokemon]); + }, pokemon.isPlayer()); + + if (hasUsableBerry) { + const cancelled = new Utils.BooleanHolder(false); + pokemon.getOpponents().map((opp) => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); + + if (cancelled.value) { + pokemon.scene.queueMessage(i18next.t("abilityTriggers:preventBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } else { + this.scene.unshiftPhase( + new CommonAnimPhase(this.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM) + ); + + for (const berryModifier of this.scene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon) as BerryModifier[]) { + if (berryModifier.consumed) { + if (!--berryModifier.stackCount) { + this.scene.removeModifier(berryModifier); + } else { + berryModifier.consumed = false; + } + } + this.scene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier)); // Announce a berry was used + } + + this.scene.updateModifiers(pokemon.isPlayer()); + + applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new Utils.BooleanHolder(false)); + } + } + }); + + this.end(); + } +} diff --git a/src/phases/check-switch-phase.ts b/src/phases/check-switch-phase.ts new file mode 100644 index 00000000000..cd8f2b00c46 --- /dev/null +++ b/src/phases/check-switch-phase.ts @@ -0,0 +1,61 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattleStyle } from "#app/enums/battle-style.js"; +import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { BattlePhase } from "./battle-phase"; +import { PostSummonPhase } from "./post-summon-phase"; +import { SummonMissingPhase } from "./summon-missing-phase"; +import { SwitchPhase } from "./switch-phase"; + +export class CheckSwitchPhase extends BattlePhase { + protected fieldIndex: integer; + protected useName: boolean; + + constructor(scene: BattleScene, fieldIndex: integer, useName: boolean) { + super(scene); + + this.fieldIndex = fieldIndex; + this.useName = useName; + } + + start() { + super.start(); + + const pokemon = this.scene.getPlayerField()[this.fieldIndex]; + + if (this.scene.battleStyle === BattleStyle.SET) { + super.end(); + return; + } + + if (this.scene.field.getAll().indexOf(pokemon) === -1) { + this.scene.unshiftPhase(new SummonMissingPhase(this.scene, this.fieldIndex)); + super.end(); + return; + } + + if (!this.scene.getParty().slice(1).filter(p => p.isActive()).length) { + super.end(); + return; + } + + if (pokemon.getTag(BattlerTagType.FRENZY)) { + super.end(); + return; + } + + this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? getPokemonNameWithAffix(pokemon) : i18next.t("battle:pokemon") }), null, () => { + this.scene.ui.setMode(Mode.CONFIRM, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); + this.scene.unshiftPhase(new SwitchPhase(this.scene, this.fieldIndex, false, true)); + this.end(); + }, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.end(); + }); + }); + } +} diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts new file mode 100644 index 00000000000..5d466e5d3b6 --- /dev/null +++ b/src/phases/command-phase.ts @@ -0,0 +1,288 @@ +import BattleScene from "#app/battle-scene.js"; +import { TurnCommand, BattleType } from "#app/battle.js"; +import { applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "#app/data/ability.js"; +import { TrappedTag, EncoreTag } from "#app/data/battler-tags.js"; +import { MoveTargetSet, getMoveTargets } from "#app/data/move.js"; +import { speciesStarters } from "#app/data/pokemon-species.js"; +import { Type } from "#app/data/type.js"; +import { Abilities } from "#app/enums/abilities.js"; +import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { Biome } from "#app/enums/biome.js"; +import { Moves } from "#app/enums/moves.js"; +import { PokeballType } from "#app/enums/pokeball.js"; +import { FieldPosition, PlayerPokemon } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { Command } from "#app/ui/command-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { FieldPhase } from "./field-phase"; +import { SelectTargetPhase } from "./select-target-phase"; + +export class CommandPhase extends FieldPhase { + protected fieldIndex: integer; + + constructor(scene: BattleScene, fieldIndex: integer) { + super(scene); + + this.fieldIndex = fieldIndex; + } + + start() { + super.start(); + + if (this.fieldIndex) { + // If we somehow are attempting to check the right pokemon but there's only one pokemon out + // Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching + if (this.scene.getPlayerField().filter(p => p.isActive()).length === 1) { + this.fieldIndex = FieldPosition.CENTER; + } else { + const allyCommand = this.scene.currentBattle.turnCommands[this.fieldIndex - 1]; + if (allyCommand?.command === Command.BALL || allyCommand?.command === Command.RUN) { + this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: allyCommand?.command, skip: true }; + } + } + } + + if (this.scene.currentBattle.turnCommands[this.fieldIndex]?.skip) { + return this.end(); + } + + const playerPokemon = this.scene.getPlayerField()[this.fieldIndex]; + + const moveQueue = playerPokemon.getMoveQueue(); + + while (moveQueue.length && moveQueue[0] + && moveQueue[0].move && (!playerPokemon.getMoveset().find(m => m?.moveId === moveQueue[0].move) + || !playerPokemon.getMoveset()[playerPokemon.getMoveset().findIndex(m => m?.moveId === moveQueue[0].move)]!.isUsable(playerPokemon, moveQueue[0].ignorePP))) { // TODO: is the bang correct? + moveQueue.shift(); + } + + if (moveQueue.length) { + const queuedMove = moveQueue[0]; + if (!queuedMove.move) { + this.handleCommand(Command.FIGHT, -1, false); + } else { + const moveIndex = playerPokemon.getMoveset().findIndex(m => m?.moveId === queuedMove.move); + if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) { // TODO: is the bang correct? + this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, { targets: queuedMove.targets, multiple: queuedMove.targets.length > 1 }); + } else { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } + } + } else { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } + } + + handleCommand(command: Command, cursor: integer, ...args: any[]): boolean { + const playerPokemon = this.scene.getPlayerField()[this.fieldIndex]; + const enemyField = this.scene.getEnemyField(); + let success: boolean; + + switch (command) { + case Command.FIGHT: + let useStruggle = false; + if (cursor === -1 || + playerPokemon.trySelectMove(cursor, args[0] as boolean) || + (useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) { + const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? + const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; + const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; + if (!moveId) { + turnCommand.targets = [this.fieldIndex]; + } + console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); + if (moveTargets.targets.length > 1 && moveTargets.multiple) { + this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + } + if (moveTargets.targets.length <= 1 || moveTargets.multiple) { + turnCommand.move!.targets = moveTargets.targets; //TODO: is the bang correct here? + } else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) { + turnCommand.move!.targets = playerPokemon.getMoveQueue()[0].targets; //TODO: is the bang correct here? + } else { + this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + } + this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; + success = true; + } else if (cursor < playerPokemon.getMoveset().length) { + const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct? + this.scene.ui.setMode(Mode.MESSAGE); + + // Decides between a Disabled, Not Implemented, or No PP translation message + const errorMessage = + playerPokemon.summonData.disabledMove === move.moveId ? "battle:moveDisabled" : + move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP"; + const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator + + this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); + }, null, true); + } + break; + case Command.BALL: + const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarters).length - 1); + if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else { + const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex()); + if (targets.length > 1) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else if (cursor < 5) { + const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true)); + if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else { + this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; + this.scene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets; + if (this.fieldIndex) { + this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; + } + success = true; + } + } + } + break; + case Command.POKEMON: + case Command.RUN: + const isSwitch = command === Command.POKEMON; + if (!isSwitch && this.scene.arena.biomeType === Biome.END) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else if (!isSwitch && this.scene.currentBattle.battleType === BattleType.TRAINER) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else { + const trapTag = playerPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; + const trapped = new Utils.BooleanHolder(false); + const batonPass = isSwitch && args[0] as boolean; + const trappedAbMessages: string[] = []; + if (!batonPass) { + enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, true, trappedAbMessages)); + } + if (batonPass || (!trapTag && !trapped.value)) { + this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch + ? { command: Command.POKEMON, cursor: cursor, args: args } + : { command: Command.RUN }; + success = true; + if (!isSwitch && this.fieldIndex) { + this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; + } + } else if (trapTag) { + if (trapTag.sourceMove === Moves.INGRAIN && trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId)?.isOfType(Type.GHOST)) { + success = true; + this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch + ? { command: Command.POKEMON, cursor: cursor, args: args } + : { command: Command.RUN }; + break; + } + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + } + this.scene.ui.showText( + i18next.t("battle:noEscapePokemon", { + pokemonName: trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(trapTag.sourceId)!) : "", + moveName: trapTag.getMoveName(), + escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee") + }), + null, + () => { + this.scene.ui.showText("", 0); + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } + }, null, true); + } else if (trapped.value && trappedAbMessages.length > 0) { + if (!isSwitch) { + this.scene.ui.setMode(Mode.MESSAGE); + } + this.scene.ui.showText(trappedAbMessages[0], null, () => { + this.scene.ui.showText("", 0); + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } + }, null, true); + } + } + break; + } + + if (success!) { // TODO: is the bang correct? + this.end(); + } + + return success!; // TODO: is the bang correct? + } + + cancel() { + if (this.fieldIndex) { + this.scene.unshiftPhase(new CommandPhase(this.scene, 0)); + this.scene.unshiftPhase(new CommandPhase(this.scene, 1)); + this.end(); + } + } + + checkFightOverride(): boolean { + const pokemon = this.getPokemon(); + + const encoreTag = pokemon.getTag(EncoreTag) as EncoreTag; + + if (!encoreTag) { + return false; + } + + const moveIndex = pokemon.getMoveset().findIndex(m => m?.moveId === encoreTag.moveId); + + if (moveIndex === -1 || !pokemon.getMoveset()[moveIndex]!.isUsable(pokemon)) { // TODO: is this bang correct? + return false; + } + + this.handleCommand(Command.FIGHT, moveIndex, false); + + return true; + } + + getFieldIndex(): integer { + return this.fieldIndex; + } + + getPokemon(): PlayerPokemon { + return this.scene.getPlayerField()[this.fieldIndex]; + } + + end() { + this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); + } +} diff --git a/src/phases/common-anim-phase.ts b/src/phases/common-anim-phase.ts new file mode 100644 index 00000000000..d3663abe3b6 --- /dev/null +++ b/src/phases/common-anim-phase.ts @@ -0,0 +1,26 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class CommonAnimPhase extends PokemonPhase { + private anim: CommonAnim | null; + private targetIndex: integer | undefined; + + constructor(scene: BattleScene, battlerIndex?: BattlerIndex, targetIndex?: BattlerIndex | undefined, anim?: CommonAnim) { + super(scene, battlerIndex); + + this.anim = anim!; // TODO: is this bang correct? + this.targetIndex = targetIndex; + } + + setAnimation(anim: CommonAnim) { + this.anim = anim; + } + + start() { + new CommonBattleAnim(this.anim, this.getPokemon(), this.targetIndex !== undefined ? (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField())[this.targetIndex] : this.getPokemon()).play(this.scene, () => { + this.end(); + }); + } +} diff --git a/src/phases/damage-phase.ts b/src/phases/damage-phase.ts new file mode 100644 index 00000000000..9f63ce35cf2 --- /dev/null +++ b/src/phases/damage-phase.ts @@ -0,0 +1,84 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { BattleSpec } from "#app/enums/battle-spec.js"; +import { DamageResult, HitResult } from "#app/field/pokemon.js"; +import * as Utils from "#app/utils.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class DamagePhase extends PokemonPhase { + private amount: integer; + private damageResult: DamageResult; + private critical: boolean; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, amount: integer, damageResult?: DamageResult, critical: boolean = false) { + super(scene, battlerIndex); + + this.amount = amount; + this.damageResult = damageResult || HitResult.EFFECTIVE; + this.critical = critical; + } + + start() { + super.start(); + + if (this.damageResult === HitResult.ONE_HIT_KO) { + if (this.scene.moveAnimations) { + this.scene.toggleInvert(true); + } + this.scene.time.delayedCall(Utils.fixedInt(1000), () => { + this.scene.toggleInvert(false); + this.applyDamage(); + }); + return; + } + + this.applyDamage(); + } + + updateAmount(amount: integer): void { + this.amount = amount; + } + + applyDamage() { + switch (this.damageResult) { + case HitResult.EFFECTIVE: + this.scene.playSound("hit"); + break; + case HitResult.SUPER_EFFECTIVE: + case HitResult.ONE_HIT_KO: + this.scene.playSound("hit_strong"); + break; + case HitResult.NOT_VERY_EFFECTIVE: + this.scene.playSound("hit_weak"); + break; + } + + if (this.amount) { + this.scene.damageNumberHandler.add(this.getPokemon(), this.amount, this.damageResult, this.critical); + } + + if (this.damageResult !== HitResult.OTHER) { + const flashTimer = this.scene.time.addEvent({ + delay: 100, + repeat: 5, + startAt: 200, + callback: () => { + this.getPokemon().getSprite().setVisible(flashTimer.repeatCount % 2 === 0); + if (!flashTimer.repeatCount) { + this.getPokemon().updateInfo().then(() => this.end()); + } + } + }); + } else { + this.getPokemon().updateInfo().then(() => this.end()); + } + } + + override end() { + if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + this.scene.initFinalBossPhaseTwo(this.getPokemon()); + } else { + super.end(); + } + } +} diff --git a/src/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts similarity index 95% rename from src/egg-hatch-phase.ts rename to src/phases/egg-hatch-phase.ts index 73c88cbde37..6f3f0b37905 100644 --- a/src/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -1,18 +1,17 @@ -import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; +import BattleScene, { AnySound } from "#app/battle-scene.js"; +import { Egg, EGG_SEED } from "#app/data/egg.js"; +import { EggCountChangedEvent } from "#app/events/egg.js"; +import { PlayerPokemon } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { Phase } from "#app/phase.js"; +import { achvs } from "#app/system/achv.js"; +import EggCounterContainer from "#app/ui/egg-counter-container.js"; +import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler.js"; +import PokemonInfoContainer from "#app/ui/pokemon-info-container.js"; +import { Mode } from "#app/ui/ui.js"; import i18next from "i18next"; -import { Phase } from "./phase"; -import BattleScene, { AnySound } from "./battle-scene"; -import * as Utils from "./utils"; -import { Mode } from "./ui/ui"; -import { EGG_SEED, Egg } from "./data/egg"; -import EggHatchSceneHandler from "./ui/egg-hatch-scene-handler"; -import { PlayerPokemon } from "./field/pokemon"; -import { achvs } from "./system/achv"; -import PokemonInfoContainer from "./ui/pokemon-info-container"; -import EggCounterContainer from "./ui/egg-counter-container"; -import { EggCountChangedEvent } from "./events/egg"; -import { getPokemonNameWithAffix } from "./messages"; - +import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; +import * as Utils from "#app/utils.js"; /** * Class that represents egg hatching */ diff --git a/src/phases/egg-lapse-phase.ts b/src/phases/egg-lapse-phase.ts new file mode 100644 index 00000000000..50d7106f229 --- /dev/null +++ b/src/phases/egg-lapse-phase.ts @@ -0,0 +1,35 @@ +import BattleScene from "#app/battle-scene.js"; +import { Egg } from "#app/data/egg.js"; +import { Phase } from "#app/phase.js"; +import i18next from "i18next"; +import Overrides from "#app/overrides"; +import { EggHatchPhase } from "./egg-hatch-phase"; + +export class EggLapsePhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + const eggsToHatch: Egg[] = this.scene.gameData.eggs.filter((egg: Egg) => { + return Overrides.EGG_IMMEDIATE_HATCH_OVERRIDE ? true : --egg.hatchWaves < 1; + }); + + let eggCount: integer = eggsToHatch.length; + + if (eggCount) { + this.scene.queueMessage(i18next.t("battle:eggHatching")); + + for (const egg of eggsToHatch) { + this.scene.unshiftPhase(new EggHatchPhase(this.scene, egg, eggCount)); + if (eggCount > 0) { + eggCount--; + } + } + + } + this.end(); + } +} diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts new file mode 100644 index 00000000000..739bb1d93f1 --- /dev/null +++ b/src/phases/encounter-phase.ts @@ -0,0 +1,379 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattleType, BattlerIndex } from "#app/battle.js"; +import { applyAbAttrs, SyncEncounterNatureAbAttr } from "#app/data/ability.js"; +import { getCharVariantFromDialogue } from "#app/data/dialogue.js"; +import { TrainerSlot } from "#app/data/trainer-config.js"; +import { getRandomWeatherType } from "#app/data/weather.js"; +import { BattleSpec } from "#app/enums/battle-spec.js"; +import { PlayerGender } from "#app/enums/player-gender.js"; +import { Species } from "#app/enums/species.js"; +import { EncounterPhaseEvent } from "#app/events/battle-scene.js"; +import Pokemon, { FieldPosition } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { regenerateModifierPoolThresholds, ModifierPoolType } from "#app/modifier/modifier-type.js"; +import { IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier.js"; +import { achvs } from "#app/system/achv.js"; +import { handleTutorial, Tutorial } from "#app/tutorial.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { BattlePhase } from "./battle-phase"; +import * as Utils from "#app/utils.js"; +import { CheckSwitchPhase } from "./check-switch-phase"; +import { GameOverPhase } from "./game-over-phase"; +import { PostSummonPhase } from "./post-summon-phase"; +import { ReturnPhase } from "./return-phase"; +import { ScanIvsPhase } from "./scan-ivs-phase"; +import { ShinySparklePhase } from "./shiny-sparkle-phase"; +import { SummonPhase } from "./summon-phase"; +import { ToggleDoublePositionPhase } from "./toggle-double-position-phase"; + +export class EncounterPhase extends BattlePhase { + private loaded: boolean; + + constructor(scene: BattleScene, loaded?: boolean) { + super(scene); + + this.loaded = !!loaded; + } + + start() { + super.start(); + + this.scene.updateGameInfo(); + + this.scene.initSession(); + + this.scene.eventTarget.dispatchEvent(new EncounterPhaseEvent()); + + // Failsafe if players somehow skip floor 200 in classic mode + if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) { + this.scene.unshiftPhase(new GameOverPhase(this.scene)); + } + + const loadEnemyAssets: Promise[] = []; + + const battle = this.scene.currentBattle; + + let totalBst = 0; + + battle.enemyLevels?.forEach((level, e) => { + if (!this.loaded) { + if (battle.battleType === BattleType.TRAINER) { + battle.enemyParty[e] = battle.trainer?.genPartyMember(e)!; // TODO:: is the bang correct here? + } else { + const enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true); + battle.enemyParty[e] = this.scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, !!this.scene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies)); + if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + battle.enemyParty[e].ivs = new Array(6).fill(31); + } + this.scene.getParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => { + applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, battle.enemyParty[e]); + }); + } + } + const enemyPokemon = this.scene.getEnemyParty()[e]; + if (e < (battle.double ? 2 : 1)) { + enemyPokemon.setX(-66 + enemyPokemon.getFieldPositionOffset()[0]); + enemyPokemon.resetSummonData(); + } + + if (!this.loaded) { + this.scene.gameData.setPokemonSeen(enemyPokemon, true, battle.battleType === BattleType.TRAINER); + } + + if (enemyPokemon.species.speciesId === Species.ETERNATUS) { + if (this.scene.gameMode.isClassic && (battle.battleSpec === BattleSpec.FINAL_BOSS || this.scene.gameMode.isWaveFinal(battle.waveIndex))) { + if (battle.battleSpec !== BattleSpec.FINAL_BOSS) { + enemyPokemon.formIndex = 1; + enemyPokemon.updateScale(); + } + enemyPokemon.setBoss(); + } else if (!(battle.waveIndex % 1000)) { + enemyPokemon.formIndex = 1; + enemyPokemon.updateScale(); + const bossMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier && m.pokemonId === enemyPokemon.id, false) as TurnHeldItemTransferModifier; + this.scene.removeModifier(bossMBH!); + bossMBH?.setTransferrableFalse(); + this.scene.addEnemyModifier(bossMBH!); + } + } + + totalBst += enemyPokemon.getSpeciesForm().baseTotal; + + loadEnemyAssets.push(enemyPokemon.loadAssets()); + + console.log(getPokemonNameWithAffix(enemyPokemon), enemyPokemon.species.speciesId, enemyPokemon.stats); + }); + + if (this.scene.getParty().filter(p => p.isShiny()).length === 6) { + this.scene.validateAchv(achvs.SHINY_PARTY); + } + + if (battle.battleType === BattleType.TRAINER) { + loadEnemyAssets.push(battle.trainer?.loadAssets().then(() => battle.trainer?.initSprite())!); // TODO: is this bang correct? + } else { + // This block only applies for double battles to init the boss segments (idk why it's split up like this) + if (battle.enemyParty.filter(p => p.isBoss()).length > 1) { + for (const enemyPokemon of battle.enemyParty) { + // If the enemy pokemon is a boss and wasn't populated from data source, then set it up + if (enemyPokemon.isBoss() && !enemyPokemon.isPopulatedFromDataSource) { + enemyPokemon.setBoss(true, Math.ceil(enemyPokemon.bossSegments * (enemyPokemon.getSpeciesForm().baseTotal / totalBst))); + enemyPokemon.initBattleInfo(); + } + } + } + } + + Promise.all(loadEnemyAssets).then(() => { + battle.enemyParty.forEach((enemyPokemon, e) => { + if (e < (battle.double ? 2 : 1)) { + if (battle.battleType === BattleType.WILD) { + this.scene.field.add(enemyPokemon); + battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); + const playerPokemon = this.scene.getPlayerPokemon(); + if (playerPokemon?.visible) { + this.scene.field.moveBelow(enemyPokemon as Pokemon, playerPokemon); + } + enemyPokemon.tint(0, 0.5); + } else if (battle.battleType === BattleType.TRAINER) { + enemyPokemon.setVisible(false); + this.scene.currentBattle.trainer?.tint(0, 0.5); + } + if (battle.double) { + enemyPokemon.setFieldPosition(e ? FieldPosition.RIGHT : FieldPosition.LEFT); + } + } + }); + + if (!this.loaded) { + regenerateModifierPoolThresholds(this.scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD); + this.scene.generateEnemyModifiers(); + } + + this.scene.ui.setMode(Mode.MESSAGE).then(() => { + if (!this.loaded) { + //@ts-ignore + this.scene.gameData.saveAll(this.scene, true, battle.waveIndex % 10 === 1 || this.scene.lastSavePlayTime >= 300).then(success => { // TODO: get rid of ts-ignore + this.scene.disableMenu = false; + if (!success) { + return this.scene.reset(true); + } + this.doEncounter(); + }); + } else { + this.doEncounter(); + } + }); + }); + } + + doEncounter() { + this.scene.playBgm(undefined, true); + this.scene.updateModifiers(false); + this.scene.setFieldScale(1); + + /*if (startingWave > 10) { + for (let m = 0; m < Math.min(Math.floor(startingWave / 10), 99); m++) + this.scene.addModifier(getPlayerModifierTypeOptionsForWave((m + 1) * 10, 1, this.scene.getParty())[0].type.newModifier(), true); + this.scene.updateModifiers(true); + }*/ + + for (const pokemon of this.scene.getParty()) { + if (pokemon) { + pokemon.resetBattleData(); + } + } + + if (!this.loaded) { + this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false); + } + + const enemyField = this.scene.getEnemyField(); + this.scene.tweens.add({ + targets: [this.scene.arenaEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.arenaPlayer, this.scene.trainer].flat(), + x: (_target, _key, value, fieldIndex: integer) => fieldIndex < 2 + (enemyField.length) ? value + 300 : value - 300, + duration: 2000, + onComplete: () => { + if (!this.tryOverrideForBattleSpec()) { + this.doEncounterCommon(); + } + } + }); + } + + getEncounterMessage(): string { + const enemyField = this.scene.getEnemyField(); + + if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + return i18next.t("battle:bossAppeared", { bossName: getPokemonNameWithAffix(enemyField[0])}); + } + + if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + if (this.scene.currentBattle.double) { + return i18next.t("battle:trainerAppearedDouble", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); + + } else { + return i18next.t("battle:trainerAppeared", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); + } + } + + return enemyField.length === 1 + ? i18next.t("battle:singleWildAppeared", { pokemonName: enemyField[0].getNameToRender() }) + : i18next.t("battle:multiWildAppeared", { pokemonName1: enemyField[0].getNameToRender(), pokemonName2: enemyField[1].getNameToRender() }); + } + + doEncounterCommon(showEncounterMessage: boolean = true) { + const enemyField = this.scene.getEnemyField(); + + if (this.scene.currentBattle.battleType === BattleType.WILD) { + enemyField.forEach(enemyPokemon => { + enemyPokemon.untint(100, "Sine.easeOut"); + enemyPokemon.cry(); + enemyPokemon.showInfo(); + if (enemyPokemon.isShiny()) { + this.scene.validateAchv(achvs.SEE_SHINY); + } + }); + this.scene.updateFieldScale(); + if (showEncounterMessage) { + this.scene.ui.showText(this.getEncounterMessage(), null, () => this.end(), 1500); + } else { + this.end(); + } + } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + const trainer = this.scene.currentBattle.trainer; + trainer?.untint(100, "Sine.easeOut"); + trainer?.playAnim(); + + const doSummon = () => { + this.scene.currentBattle.started = true; + this.scene.playBgm(undefined); + this.scene.pbTray.showPbTray(this.scene.getParty()); + this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty()); + const doTrainerSummon = () => { + this.hideEnemyTrainer(); + const availablePartyMembers = this.scene.getEnemyParty().filter(p => !p.isFainted()).length; + this.scene.unshiftPhase(new SummonPhase(this.scene, 0, false)); + if (this.scene.currentBattle.double && availablePartyMembers > 1) { + this.scene.unshiftPhase(new SummonPhase(this.scene, 1, false)); + } + this.end(); + }; + if (showEncounterMessage) { + this.scene.ui.showText(this.getEncounterMessage(), null, doTrainerSummon, 1500, true); + } else { + doTrainerSummon(); + } + }; + + const encounterMessages = this.scene.currentBattle.trainer?.getEncounterMessages(); + + if (!encounterMessages?.length) { + doSummon(); + } else { + let message: string; + this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.waveIndex); + message = message!; // tell TS compiler it's defined now + const showDialogueAndSummon = () => { + this.scene.ui.showDialogue(message, trainer?.getName(TrainerSlot.NONE, true), null, () => { + this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => doSummon())); + }); + }; + if (this.scene.currentBattle.trainer?.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { + this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(trainer?.getKey()!, getCharVariantFromDialogue(encounterMessages[0])).then(() => showDialogueAndSummon())); // TODO: is this bang correct? + } else { + showDialogueAndSummon(); + } + } + } + } + + end() { + const enemyField = this.scene.getEnemyField(); + + enemyField.forEach((enemyPokemon, e) => { + if (enemyPokemon.isShiny()) { + this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e)); + } + }); + + if (this.scene.currentBattle.battleType !== BattleType.TRAINER) { + enemyField.map(p => this.scene.pushConditionalPhase(new PostSummonPhase(this.scene, p.getBattlerIndex()), () => { + // if there is not a player party, we can't continue + if (!this.scene.getParty()?.length) { + return false; + } + // how many player pokemon are on the field ? + const pokemonsOnFieldCount = this.scene.getParty().filter(p => p.isOnField()).length; + // if it's a 2vs1, there will never be a 2nd pokemon on our field even + const requiredPokemonsOnField = Math.min(this.scene.getParty().filter((p) => !p.isFainted()).length, 2); + // if it's a double, there should be 2, otherwise 1 + if (this.scene.currentBattle.double) { + return pokemonsOnFieldCount === requiredPokemonsOnField; + } + return pokemonsOnFieldCount === 1; + })); + const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier); + if (ivScannerModifier) { + enemyField.map(p => this.scene.pushPhase(new ScanIvsPhase(this.scene, p.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)))); + } + } + + if (!this.loaded) { + const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()); + + if (!availablePartyMembers[0].isOnField()) { + this.scene.pushPhase(new SummonPhase(this.scene, 0)); + } + + if (this.scene.currentBattle.double) { + if (availablePartyMembers.length > 1) { + this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, true)); + if (!availablePartyMembers[1].isOnField()) { + this.scene.pushPhase(new SummonPhase(this.scene, 1)); + } + } + } else { + if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { + this.scene.pushPhase(new ReturnPhase(this.scene, 1)); + } + this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, false)); + } + + if (this.scene.currentBattle.battleType !== BattleType.TRAINER && (this.scene.currentBattle.waveIndex > 1 || !this.scene.gameMode.isDaily)) { + const minPartySize = this.scene.currentBattle.double ? 2 : 1; + if (availablePartyMembers.length > minPartySize) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); + if (this.scene.currentBattle.double) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + } + } + } + } + handleTutorial(this.scene, Tutorial.Access_Menu).then(() => super.end()); + } + + tryOverrideForBattleSpec(): boolean { + switch (this.scene.currentBattle.battleSpec) { + case BattleSpec.FINAL_BOSS: + const enemy = this.scene.getEnemyPokemon(); + this.scene.ui.showText(this.getEncounterMessage(), null, () => { + const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed; + //The two lines below check if English ordinals (1st, 2nd, 3rd, Xth) are used and determine which one to use. + //Otherwise, it defaults to an empty string. + //As of 08-07-24: Spanish and Italian default to the English translations + const ordinalUse = ["en", "es", "it"]; + const currentLanguage = i18next.resolvedLanguage ?? "en"; + const ordinalIndex = (ordinalUse.includes(currentLanguage)) ? ["st", "nd", "rd"][((count + 90) % 100 - 10) % 10 - 1] ?? "th" : ""; + const cycleCount = count.toLocaleString() + ordinalIndex; + const encounterDialogue = i18next.t(`${(this.scene.gameData.gender === PlayerGender.FEMALE) ? "PGF" : "PGM"}battleSpecDialogue:encounter`, {cycleCount: cycleCount}); + this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => { + this.doEncounterCommon(false); + }); + }, 1500, true); + return true; + } + + return false; + } +} diff --git a/src/phases/end-card-phase.ts b/src/phases/end-card-phase.ts new file mode 100644 index 00000000000..0b70664b993 --- /dev/null +++ b/src/phases/end-card-phase.ts @@ -0,0 +1,40 @@ +import BattleScene from "#app/battle-scene.js"; +import { PlayerGender } from "#app/enums/player-gender.js"; +import { Phase } from "#app/phase.js"; +import { addTextObject, TextStyle } from "#app/ui/text.js"; +import i18next from "i18next"; + +export class EndCardPhase extends Phase { + public endCard: Phaser.GameObjects.Image; + public text: Phaser.GameObjects.Text; + + constructor(scene: BattleScene) { + super(scene); + } + + start(): void { + super.start(); + + this.scene.ui.getMessageHandler().bg.setVisible(false); + this.scene.ui.getMessageHandler().nameBoxContainer.setVisible(false); + + this.endCard = this.scene.add.image(0, 0, `end_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}`); + this.endCard.setOrigin(0); + this.endCard.setScale(0.5); + this.scene.field.add(this.endCard); + + this.text = addTextObject(this.scene, this.scene.game.canvas.width / 12, (this.scene.game.canvas.height / 6) - 16, i18next.t("battle:congratulations"), TextStyle.SUMMARY, { fontSize: "128px" }); + this.text.setOrigin(0.5); + this.scene.field.add(this.text); + + this.scene.ui.clearText(); + + this.scene.ui.fadeIn(1000).then(() => { + + this.scene.ui.showText("", null, () => { + this.scene.ui.getMessageHandler().bg.setVisible(true); + this.end(); + }, null, true); + }); + } +} diff --git a/src/phases/end-evolution-phase.ts b/src/phases/end-evolution-phase.ts new file mode 100644 index 00000000000..2a6d492a425 --- /dev/null +++ b/src/phases/end-evolution-phase.ts @@ -0,0 +1,16 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { Mode } from "#app/ui/ui.js"; + +export class EndEvolutionPhase extends Phase { + + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.ui.setModeForceTransition(Mode.MESSAGE).then(() => this.end()); + } +} diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts new file mode 100644 index 00000000000..d7f553681c2 --- /dev/null +++ b/src/phases/enemy-command-phase.ts @@ -0,0 +1,86 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "#app/data/ability.js"; +import { TrappedTag } from "#app/data/battler-tags.js"; +import { Command } from "#app/ui/command-ui-handler.js"; +import * as Utils from "#app/utils.js"; +import { FieldPhase } from "./field-phase"; + +/** + * Phase for determining an enemy AI's action for the next turn. + * During this phase, the enemy decides whether to switch (if it has a trainer) + * or to use a move from its moveset. + * + * For more information on how the Enemy AI works, see docs/enemy-ai.md + * @see {@linkcode Pokemon.getMatchupScore} + * @see {@linkcode EnemyPokemon.getNextMove} + */ +export class EnemyCommandPhase extends FieldPhase { + protected fieldIndex: integer; + + constructor(scene: BattleScene, fieldIndex: integer) { + super(scene); + + this.fieldIndex = fieldIndex; + } + + start() { + super.start(); + + const enemyPokemon = this.scene.getEnemyField()[this.fieldIndex]; + + const battle = this.scene.currentBattle; + + const trainer = battle.trainer; + + /** + * If the enemy has a trainer, decide whether or not the enemy should switch + * to another member in its party. + * + * This block compares the active enemy Pokemon's {@linkcode Pokemon.getMatchupScore | matchup score} + * against the active player Pokemon with the enemy party's other non-fainted Pokemon. If a party + * member's matchup score is 3x the active enemy's score (or 2x for "boss" trainers), + * the enemy will switch to that Pokemon. + */ + if (trainer && !enemyPokemon.getMoveQueue().length) { + const opponents = enemyPokemon.getOpponents(); + + const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; + const trapped = new Utils.BooleanHolder(false); + opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, true, [])); + if (!trapTag && !trapped.value) { + const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true); + + if (partyMemberScores.length) { + const matchupScores = opponents.map(opp => enemyPokemon.getMatchupScore(opp)); + const matchupScore = matchupScores.reduce((total, score) => total += score, 0) / matchupScores.length; + + const sortedPartyMemberScores = trainer.getSortedPartyMemberMatchupScores(partyMemberScores); + + const switchMultiplier = 1 - (battle.enemySwitchCounter ? Math.pow(0.1, (1 / battle.enemySwitchCounter)) : 0); + + if (sortedPartyMemberScores[0][1] * switchMultiplier >= matchupScore * (trainer.config.isBoss ? 2 : 3)) { + const index = trainer.getNextSummonIndex(enemyPokemon.trainerSlot, partyMemberScores); + + battle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = + { command: Command.POKEMON, cursor: index, args: [false] }; + + battle.enemySwitchCounter++; + + return this.end(); + } + } + } + } + + /** Select a move to use (and a target to use it against, if applicable) */ + const nextMove = enemyPokemon.getNextMove(); + + this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = + { command: Command.FIGHT, move: nextMove }; + + this.scene.currentBattle.enemySwitchCounter = Math.max(this.scene.currentBattle.enemySwitchCounter - 1, 0); + + this.end(); + } +} diff --git a/src/phases/enemy-party-member-pokemon-phase.ts b/src/phases/enemy-party-member-pokemon-phase.ts new file mode 100644 index 00000000000..10af0913f93 --- /dev/null +++ b/src/phases/enemy-party-member-pokemon-phase.ts @@ -0,0 +1,13 @@ +import BattleScene from "#app/battle-scene.js"; +import { EnemyPokemon } from "#app/field/pokemon.js"; +import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; + +export abstract class EnemyPartyMemberPokemonPhase extends PartyMemberPokemonPhase { + constructor(scene: BattleScene, partyMemberIndex: integer) { + super(scene, partyMemberIndex, false); + } + + getEnemyPokemon(): EnemyPokemon { + return super.getPokemon() as EnemyPokemon; + } +} diff --git a/src/evolution-phase.ts b/src/phases/evolution-phase.ts similarity index 96% rename from src/evolution-phase.ts rename to src/phases/evolution-phase.ts index 7b50a6368f6..398450ec693 100644 --- a/src/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -1,16 +1,17 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import { Phase } from "./phase"; -import BattleScene from "./battle-scene"; -import { SpeciesFormEvolution } from "./data/pokemon-evolutions"; -import EvolutionSceneHandler from "./ui/evolution-scene-handler"; -import * as Utils from "./utils"; -import { Mode } from "./ui/ui"; -import { LearnMovePhase } from "./phases"; -import { cos, sin } from "./field/anims"; -import { PlayerPokemon } from "./field/pokemon"; -import { getTypeRgb } from "./data/type"; +import { Phase } from "../phase"; +import BattleScene from "../battle-scene"; +import { SpeciesFormEvolution } from "../data/pokemon-evolutions"; +import EvolutionSceneHandler from "../ui/evolution-scene-handler"; +import * as Utils from "../utils"; +import { Mode } from "../ui/ui"; +import { cos, sin } from "../field/anims"; +import { PlayerPokemon } from "../field/pokemon"; +import { getTypeRgb } from "../data/type"; import i18next from "i18next"; -import { getPokemonNameWithAffix } from "./messages"; +import { getPokemonNameWithAffix } from "../messages"; +import { LearnMovePhase } from "./learn-move-phase"; +import { EndEvolutionPhase } from "./end-evolution-phase"; export class EvolutionPhase extends Phase { protected pokemon: PlayerPokemon; @@ -530,16 +531,3 @@ export class EvolutionPhase extends Phase { updateParticle(); } } - -export class EndEvolutionPhase extends Phase { - - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.ui.setModeForceTransition(Mode.MESSAGE).then(() => this.end()); - } -} diff --git a/src/phases/exp-phase.ts b/src/phases/exp-phase.ts new file mode 100644 index 00000000000..9c2ba95d550 --- /dev/null +++ b/src/phases/exp-phase.ts @@ -0,0 +1,35 @@ +import BattleScene from "#app/battle-scene.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { ExpBoosterModifier } from "#app/modifier/modifier.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; +import { LevelUpPhase } from "./level-up-phase"; + +export class ExpPhase extends PlayerPartyMemberPokemonPhase { + private expValue: number; + + constructor(scene: BattleScene, partyMemberIndex: integer, expValue: number) { + super(scene, partyMemberIndex); + + this.expValue = expValue; + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + const exp = new Utils.NumberHolder(this.expValue); + this.scene.applyModifiers(ExpBoosterModifier, true, exp); + exp.value = Math.floor(exp.value); + this.scene.ui.showText(i18next.t("battle:expGain", { pokemonName: getPokemonNameWithAffix(pokemon), exp: exp.value }), null, () => { + const lastLevel = pokemon.level; + pokemon.addExp(exp.value); + const newLevel = pokemon.level; + if (newLevel > lastLevel) { + this.scene.unshiftPhase(new LevelUpPhase(this.scene, this.partyMemberIndex, lastLevel, newLevel)); + } + pokemon.updateInfo().then(() => this.end()); + }, null, true); + } +} diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts new file mode 100644 index 00000000000..14727f992d2 --- /dev/null +++ b/src/phases/faint-phase.ts @@ -0,0 +1,171 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex, BattleType } from "#app/battle.js"; +import { applyPostFaintAbAttrs, PostFaintAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr } from "#app/data/ability.js"; +import { BattlerTagLapseType } from "#app/data/battler-tags.js"; +import { battleSpecDialogue } from "#app/data/dialogue.js"; +import { allMoves, PostVictoryStatChangeAttr } from "#app/data/move.js"; +import { BattleSpec } from "#app/enums/battle-spec.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { PokemonMove, EnemyPokemon, PlayerPokemon, HitResult } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { PokemonInstantReviveModifier } from "#app/modifier/modifier.js"; +import i18next from "i18next"; +import { DamagePhase } from "./damage-phase"; +import { PokemonPhase } from "./pokemon-phase"; +import { SwitchSummonPhase } from "./switch-summon-phase"; +import { ToggleDoublePositionPhase } from "./toggle-double-position-phase"; +import { GameOverPhase } from "./game-over-phase"; +import { SwitchPhase } from "./switch-phase"; +import { VictoryPhase } from "./victory-phase"; + +export class FaintPhase extends PokemonPhase { + private preventEndure: boolean; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure?: boolean) { + super(scene, battlerIndex); + + this.preventEndure = preventEndure!; // TODO: is this bang correct? + } + + start() { + super.start(); + + if (!this.preventEndure) { + const instantReviveModifier = this.scene.applyModifier(PokemonInstantReviveModifier, this.player, this.getPokemon()) as PokemonInstantReviveModifier; + + if (instantReviveModifier) { + if (!--instantReviveModifier.stackCount) { + this.scene.removeModifier(instantReviveModifier); + } + this.scene.updateModifiers(this.player); + return this.end(); + } + } + + if (!this.tryOverrideForBattleSpec()) { + this.doFaint(); + } + } + + doFaint(): void { + const pokemon = this.getPokemon(); + + + // Track total times pokemon have been KO'd for supreme overlord/last respects + if (pokemon.isPlayer()) { + this.scene.currentBattle.playerFaints += 1; + } else { + this.scene.currentBattle.enemyFaints += 1; + } + + this.scene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true); + + if (pokemon.turnData?.attacksReceived?.length) { + const lastAttack = pokemon.turnData.attacksReceived[0]; + applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId)!, new PokemonMove(lastAttack.move).getMove(), lastAttack.result); // TODO: is this bang correct? + } + + const alivePlayField = this.scene.getField(true); + alivePlayField.forEach(p => applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p, pokemon)); + if (pokemon.turnData?.attacksReceived?.length) { + const defeatSource = this.scene.getPokemonById(pokemon.turnData.attacksReceived[0].sourceId); + if (defeatSource?.isOnField()) { + applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); + const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; + const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr); + if (pvattrs.length) { + for (const pvattr of pvattrs) { + pvattr.applyPostVictory(defeatSource, defeatSource, pvmove); + } + } + } + } + + if (this.player) { + /** The total number of Pokemon in the player's party that can legally fight */ + const legalPlayerPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); + /** The total number of legal player Pokemon that aren't currently on the field */ + const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); + if (!legalPlayerPokemon.length) { + /** If the player doesn't have any legal Pokemon, end the game */ + this.scene.unshiftPhase(new GameOverPhase(this.scene)); + } else if (this.scene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { + /** + * If the player has exactly one Pokemon in total at this point in a double battle, and that Pokemon + * is already on the field, unshift a phase that moves that Pokemon to center position. + */ + this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + } else if (legalPlayerPartyPokemon.length > 0) { + /** + * If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field, + * push a phase that prompts the player to summon a Pokemon from their party. + */ + this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); + } + } else { + this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); + if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + const hasReservePartyMember = !!this.scene.getEnemyParty().filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot).length; + if (hasReservePartyMember) { + this.scene.pushPhase(new SwitchSummonPhase(this.scene, this.fieldIndex, -1, false, false, false)); + } + } + } + + // in double battles redirect potential moves off fainted pokemon + if (this.scene.currentBattle.double) { + const allyPokemon = pokemon.getAlly(); + this.scene.redirectPokemonMoves(pokemon, allyPokemon); + } + + pokemon.lapseTags(BattlerTagLapseType.FAINT); + this.scene.getField(true).filter(p => p !== pokemon).forEach(p => p.removeTagsBySourceId(pokemon.id)); + + pokemon.faintCry(() => { + if (pokemon instanceof PlayerPokemon) { + pokemon.addFriendship(-10); + } + pokemon.hideInfo(); + this.scene.playSound("faint"); + this.scene.tweens.add({ + targets: pokemon, + duration: 500, + y: pokemon.y + 150, + ease: "Sine.easeIn", + onComplete: () => { + pokemon.setVisible(false); + pokemon.y -= 150; + pokemon.trySetStatus(StatusEffect.FAINT); + if (pokemon.isPlayer()) { + this.scene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon); + } else { + this.scene.addFaintedEnemyScore(pokemon as EnemyPokemon); + this.scene.currentBattle.addPostBattleLoot(pokemon as EnemyPokemon); + } + this.scene.field.remove(pokemon); + this.end(); + } + }); + }); + } + + tryOverrideForBattleSpec(): boolean { + switch (this.scene.currentBattle.battleSpec) { + case BattleSpec.FINAL_BOSS: + if (!this.player) { + const enemy = this.getPokemon(); + if (enemy.formIndex) { + this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); + } else { + // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase + enemy.hp++; + this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); + this.end(); + } + return true; + } + } + + return false; + } +} diff --git a/src/phases/field-phase.ts b/src/phases/field-phase.ts new file mode 100644 index 00000000000..a9622271f14 --- /dev/null +++ b/src/phases/field-phase.ts @@ -0,0 +1,44 @@ +import { BattlerIndex } from "#app/battle.js"; +import { TrickRoomTag } from "#app/data/arena-tag.js"; +import { Stat } from "#app/enums/stat.js"; +import Pokemon from "#app/field/pokemon.js"; +import { BattlePhase } from "./battle-phase"; +import * as Utils from "#app/utils.js"; + +type PokemonFunc = (pokemon: Pokemon) => void; + +export abstract class FieldPhase extends BattlePhase { + getOrder(): BattlerIndex[] { + const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[]; + const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[]; + + // We shuffle the list before sorting so speed ties produce random results + let orderedTargets: Pokemon[] = playerField.concat(enemyField); + // We seed it with the current turn to prevent an inconsistency where it + // was varying based on how long since you last reloaded + this.scene.executeWithSeedOffset(() => { + orderedTargets = Utils.randSeedShuffle(orderedTargets); + }, this.scene.currentBattle.turn, this.scene.waveSeed); + + orderedTargets.sort((a: Pokemon, b: Pokemon) => { + const aSpeed = a?.getBattleStat(Stat.SPD) || 0; + const bSpeed = b?.getBattleStat(Stat.SPD) || 0; + + return bSpeed - aSpeed; + }); + + const speedReversed = new Utils.BooleanHolder(false); + this.scene.arena.applyTags(TrickRoomTag, speedReversed); + + if (speedReversed.value) { + orderedTargets = orderedTargets.reverse(); + } + + return orderedTargets.map(t => t.getFieldIndex() + (!t.isPlayer() ? BattlerIndex.ENEMY : 0)); + } + + executeForAll(func: PokemonFunc): void { + const field = this.scene.getField(true).filter(p => p.summonData); + field.forEach(pokemon => func(pokemon)); + } +} diff --git a/src/form-change-phase.ts b/src/phases/form-change-phase.ts similarity index 57% rename from src/form-change-phase.ts rename to src/phases/form-change-phase.ts index 5acbc4fb77c..88e0dd00ce1 100644 --- a/src/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -1,17 +1,14 @@ -import BattleScene from "./battle-scene"; -import * as Utils from "./utils"; -import { SpeciesFormKey } from "./data/pokemon-species"; -import { achvs } from "./system/achv"; -import { SpeciesFormChange, getSpeciesFormChangeMessage } from "./data/pokemon-forms"; -import { EndEvolutionPhase, EvolutionPhase } from "./evolution-phase"; -import Pokemon, { EnemyPokemon, PlayerPokemon } from "./field/pokemon"; -import { Mode } from "./ui/ui"; -import PartyUiHandler from "./ui/party-ui-handler"; -import { BattleSpec } from "#enums/battle-spec"; -import { BattlePhase, MovePhase, PokemonHealPhase } from "./phases"; -import { getTypeRgb } from "./data/type"; -import { getPokemonNameWithAffix } from "./messages"; -import { SemiInvulnerableTag } from "./data/battler-tags"; +import BattleScene from "../battle-scene"; +import * as Utils from "../utils"; +import { SpeciesFormKey } from "../data/pokemon-species"; +import { achvs } from "../system/achv"; +import { SpeciesFormChange, getSpeciesFormChangeMessage } from "../data/pokemon-forms"; +import { PlayerPokemon } from "../field/pokemon"; +import { Mode } from "../ui/ui"; +import PartyUiHandler from "../ui/party-ui-handler"; +import { getPokemonNameWithAffix } from "../messages"; +import { EndEvolutionPhase } from "./end-evolution-phase"; +import { EvolutionPhase } from "./evolution-phase"; export class FormChangePhase extends EvolutionPhase { private formChange: SpeciesFormChange; @@ -175,126 +172,3 @@ export class FormChangePhase extends EvolutionPhase { } } } - -export class QuietFormChangePhase extends BattlePhase { - protected pokemon: Pokemon; - protected formChange: SpeciesFormChange; - - constructor(scene: BattleScene, pokemon: Pokemon, formChange: SpeciesFormChange) { - super(scene); - this.pokemon = pokemon; - this.formChange = formChange; - } - - start(): void { - super.start(); - - if (this.pokemon.formIndex === this.pokemon.species.forms.findIndex(f => f.formKey === this.formChange.formKey)) { - return this.end(); - } - - const preName = getPokemonNameWithAffix(this.pokemon); - - if (!this.pokemon.isOnField() || this.pokemon.getTag(SemiInvulnerableTag)) { - this.pokemon.changeForm(this.formChange).then(() => { - this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); - }); - return; - } - - const getPokemonSprite = () => { - const sprite = this.scene.addPokemonSprite(this.pokemon, this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y, "pkmn__sub"); - sprite.setOrigin(0.5, 1); - sprite.play(this.pokemon.getBattleSpriteKey()).stop(); - sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); - [ "spriteColors", "fusionSpriteColors" ].map(k => { - if (this.pokemon.summonData?.speciesForm) { - k += "Base"; - } - sprite.pipelineData[k] = this.pokemon.getSprite().pipelineData[k]; - }); - this.scene.field.add(sprite); - return sprite; - }; - - const [ pokemonTintSprite, pokemonFormTintSprite ] = [ getPokemonSprite(), getPokemonSprite() ]; - - this.pokemon.getSprite().on("animationupdate", (_anim, frame) => { - if (frame.textureKey === pokemonTintSprite.texture.key) { - pokemonTintSprite.setFrame(frame.textureFrame); - } else { - pokemonFormTintSprite.setFrame(frame.textureFrame); - } - }); - - pokemonTintSprite.setAlpha(0); - pokemonTintSprite.setTintFill(0xFFFFFF); - pokemonFormTintSprite.setVisible(false); - pokemonFormTintSprite.setTintFill(0xFFFFFF); - - this.scene.playSound("PRSFX- Transform"); - - this.scene.tweens.add({ - targets: pokemonTintSprite, - alpha: 1, - duration: 1000, - ease: "Cubic.easeIn", - onComplete: () => { - this.pokemon.setVisible(false); - this.pokemon.changeForm(this.formChange).then(() => { - pokemonFormTintSprite.setScale(0.01); - pokemonFormTintSprite.play(this.pokemon.getBattleSpriteKey()).stop(); - pokemonFormTintSprite.setVisible(true); - this.scene.tweens.add({ - targets: pokemonTintSprite, - delay: 250, - scale: 0.01, - ease: "Cubic.easeInOut", - duration: 500, - onComplete: () => pokemonTintSprite.destroy() - }); - this.scene.tweens.add({ - targets: pokemonFormTintSprite, - delay: 250, - scale: this.pokemon.getSpriteScale(), - ease: "Cubic.easeInOut", - duration: 500, - onComplete: () => { - this.pokemon.setVisible(true); - this.scene.tweens.add({ - targets: pokemonFormTintSprite, - delay: 250, - alpha: 0, - ease: "Cubic.easeOut", - duration: 1000, - onComplete: () => { - pokemonTintSprite.setVisible(false); - this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); - } - }); - } - }); - }); - } - }); - } - - end(): void { - if (this.pokemon.scene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon instanceof EnemyPokemon) { - this.scene.playBgm(); - this.scene.unshiftPhase(new PokemonHealPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getMaxHp(), null, false, false, false, true)); - this.pokemon.findAndRemoveTags(() => true); - this.pokemon.bossSegments = 5; - this.pokemon.bossSegmentIndex = 4; - this.pokemon.initBattleInfo(); - this.pokemon.cry(); - - const movePhase = this.scene.findPhase(p => p instanceof MovePhase && p.pokemon === this.pokemon) as MovePhase; - if (movePhase) { - movePhase.cancel(); - } - } - - super.end(); - } -} diff --git a/src/phases/game-over-modifier-reward-phase.ts b/src/phases/game-over-modifier-reward-phase.ts new file mode 100644 index 00000000000..e2f4d134cba --- /dev/null +++ b/src/phases/game-over-modifier-reward-phase.ts @@ -0,0 +1,27 @@ +import BattleScene from "#app/battle-scene.js"; +import { ModifierTypeFunc } from "#app/modifier/modifier-type.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { ModifierRewardPhase } from "./modifier-reward-phase"; + +export class GameOverModifierRewardPhase extends ModifierRewardPhase { + constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc) { + super(scene, modifierTypeFunc); + } + + doReward(): Promise { + return new Promise(resolve => { + const newModifier = this.modifierType.newModifier(); + this.scene.addModifier(newModifier).then(() => { + this.scene.playSound("level_up_fanfare"); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.fadeIn(250).then(() => { + this.scene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => { + this.scene.time.delayedCall(1500, () => this.scene.arenaBg.setVisible(true)); + resolve(); + }, null, true, 1500); + }); + }); + }); + } +} diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts new file mode 100644 index 00000000000..4beed489f29 --- /dev/null +++ b/src/phases/game-over-phase.ts @@ -0,0 +1,203 @@ +import { clientSessionId } from "#app/account.js"; +import BattleScene from "#app/battle-scene.js"; +import { BattleType } from "#app/battle.js"; +import { miscDialogue, getCharVariantFromDialogue } from "#app/data/dialogue.js"; +import { pokemonEvolutions } from "#app/data/pokemon-evolutions.js"; +import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species.js"; +import { trainerConfigs } from "#app/data/trainer-config.js"; +import { PlayerGender } from "#app/enums/player-gender.js"; +import { TrainerType } from "#app/enums/trainer-type.js"; +import Pokemon from "#app/field/pokemon.js"; +import { modifierTypes } from "#app/modifier/modifier-type.js"; +import { achvs, ChallengeAchv } from "#app/system/achv.js"; +import { Unlockables } from "#app/system/unlockables.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; +import { CheckSwitchPhase } from "./check-switch-phase"; +import { EncounterPhase } from "./encounter-phase"; +import { GameOverModifierRewardPhase } from "./game-over-modifier-reward-phase"; +import { RibbonModifierRewardPhase } from "./ribbon-modifier-reward-phase"; +import { SummonPhase } from "./summon-phase"; +import { EndCardPhase } from "./end-card-phase"; +import { PostGameOverPhase } from "./post-game-over-phase"; +import { UnlockPhase } from "./unlock-phase"; + +export class GameOverPhase extends BattlePhase { + private victory: boolean; + private firstRibbons: PokemonSpecies[] = []; + + constructor(scene: BattleScene, victory?: boolean) { + super(scene); + + this.victory = !!victory; + } + + start() { + super.start(); + + // Failsafe if players somehow skip floor 200 in classic mode + if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) { + this.victory = true; + } + + if (this.victory && this.scene.gameMode.isEndless) { + this.scene.ui.showDialogue(i18next.t("PGMmiscDialogue:ending_endless"), i18next.t("PGMmiscDialogue:ending_name"), 0, () => this.handleGameOver()); + } else if (this.victory || !this.scene.enableRetries) { + this.handleGameOver(); + } else { + this.scene.ui.showText(i18next.t("battle:retryBattle"), null, () => { + this.scene.ui.setMode(Mode.CONFIRM, () => { + this.scene.ui.fadeOut(1250).then(() => { + this.scene.reset(); + this.scene.clearPhaseQueue(); + this.scene.gameData.loadSession(this.scene, this.scene.sessionSlotId).then(() => { + this.scene.pushPhase(new EncounterPhase(this.scene, true)); + + const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length; + + this.scene.pushPhase(new SummonPhase(this.scene, 0)); + if (this.scene.currentBattle.double && availablePartyMembers > 1) { + this.scene.pushPhase(new SummonPhase(this.scene, 1)); + } + if (this.scene.currentBattle.waveIndex > 1 && this.scene.currentBattle.battleType !== BattleType.TRAINER) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); + if (this.scene.currentBattle.double && availablePartyMembers > 1) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + } + } + + this.scene.ui.fadeIn(1250); + this.end(); + }); + }); + }, () => this.handleGameOver(), false, 0, 0, 1000); + }); + } + } + + handleGameOver(): void { + const doGameOver = (newClear: boolean) => { + this.scene.disableMenu = true; + this.scene.time.delayedCall(1000, () => { + let firstClear = false; + if (this.victory && newClear) { + if (this.scene.gameMode.isClassic) { + firstClear = this.scene.validateAchv(achvs.CLASSIC_VICTORY); + this.scene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY); + this.scene.gameData.gameStats.sessionsWon++; + for (const pokemon of this.scene.getParty()) { + this.awardRibbon(pokemon); + + if (pokemon.species.getRootSpeciesId() !== pokemon.species.getRootSpeciesId(true)) { + this.awardRibbon(pokemon, true); + } + } + } else if (this.scene.gameMode.isDaily && newClear) { + this.scene.gameData.gameStats.dailyRunSessionsWon++; + } + } + const fadeDuration = this.victory ? 10000 : 5000; + this.scene.fadeOutBgm(fadeDuration, true); + const activeBattlers = this.scene.getField().filter(p => p?.isActive(true)); + activeBattlers.map(p => p.hideInfo()); + this.scene.ui.fadeOut(fadeDuration).then(() => { + activeBattlers.map(a => a.setVisible(false)); + this.scene.setFieldScale(1, true); + this.scene.clearPhaseQueue(); + this.scene.ui.clearText(); + + if (this.victory && this.scene.gameMode.isChallenge) { + this.scene.gameMode.challenges.forEach(c => this.scene.validateAchvs(ChallengeAchv, c)); + } + + const clear = (endCardPhase?: EndCardPhase) => { + if (newClear) { + this.handleUnlocks(); + } + if (this.victory && newClear) { + for (const species of this.firstRibbons) { + this.scene.unshiftPhase(new RibbonModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PLUS, species)); + } + if (!firstClear) { + this.scene.unshiftPhase(new GameOverModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PREMIUM)); + } + } + this.scene.pushPhase(new PostGameOverPhase(this.scene, endCardPhase)); + this.end(); + }; + + if (this.victory && this.scene.gameMode.isClassic) { + const message = miscDialogue.ending[this.scene.gameData.gender === PlayerGender.FEMALE ? 0 : 1]; + + if (!this.scene.ui.shouldSkipDialogue(message)) { + this.scene.ui.fadeIn(500).then(() => { + this.scene.charSprite.showCharacter(`rival_${this.scene.gameData.gender === PlayerGender.FEMALE ? "m" : "f"}`, getCharVariantFromDialogue(miscDialogue.ending[this.scene.gameData.gender === PlayerGender.FEMALE ? 0 : 1])).then(() => { + this.scene.ui.showDialogue(message, this.scene.gameData.gender === PlayerGender.FEMALE ? trainerConfigs[TrainerType.RIVAL].name : trainerConfigs[TrainerType.RIVAL].nameFemale, null, () => { + this.scene.ui.fadeOut(500).then(() => { + this.scene.charSprite.hide().then(() => { + const endCardPhase = new EndCardPhase(this.scene); + this.scene.unshiftPhase(endCardPhase); + clear(endCardPhase); + }); + }); + }); + }); + }); + } else { + const endCardPhase = new EndCardPhase(this.scene); + this.scene.unshiftPhase(endCardPhase); + clear(endCardPhase); + } + } else { + clear(); + } + }); + }); + }; + + /* Added a local check to see if the game is running offline on victory + If Online, execute apiFetch as intended + If Offline, execute offlineNewClear(), a localStorage implementation of newClear daily run checks */ + if (this.victory) { + if (!Utils.isLocal) { + Utils.apiFetch(`savedata/session/newclear?slot=${this.scene.sessionSlotId}&clientSessionId=${clientSessionId}`, true) + .then(response => response.json()) + .then(newClear => doGameOver(newClear)); + } else { + this.scene.gameData.offlineNewClear(this.scene).then(result => { + doGameOver(result); + }); + } + } else { + doGameOver(false); + } + } + + handleUnlocks(): void { + if (this.victory && this.scene.gameMode.isClassic) { + if (!this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { + this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.ENDLESS_MODE)); + } + if (this.scene.getParty().filter(p => p.fusionSpecies).length && !this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { + this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.SPLICED_ENDLESS_MODE)); + } + if (!this.scene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE]) { + this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.MINI_BLACK_HOLE)); + } + if (!this.scene.gameData.unlocks[Unlockables.EVIOLITE] && this.scene.getParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)) { + this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.EVIOLITE)); + } + } + } + + awardRibbon(pokemon: Pokemon, forStarter: boolean = false): void { + const speciesId = getPokemonSpecies(pokemon.species.speciesId); + const speciesRibbonCount = this.scene.gameData.incrementRibbonCount(speciesId, forStarter); + // first time classic win, award voucher + if (speciesRibbonCount === 1) { + this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter))); + } + } +} diff --git a/src/phases/hide-party-exp-bar-phase.ts b/src/phases/hide-party-exp-bar-phase.ts new file mode 100644 index 00000000000..c2c9d96462e --- /dev/null +++ b/src/phases/hide-party-exp-bar-phase.ts @@ -0,0 +1,14 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlePhase } from "./battle-phase"; + +export class HidePartyExpBarPhase extends BattlePhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.partyExpBar.hide().then(() => this.end()); + } +} diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts new file mode 100644 index 00000000000..e30fc0c3d10 --- /dev/null +++ b/src/phases/learn-move-phase.ts @@ -0,0 +1,103 @@ +import BattleScene from "#app/battle-scene.js"; +import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims.js"; +import { allMoves } from "#app/data/move.js"; +import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms.js"; +import { Moves } from "#app/enums/moves.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import EvolutionSceneHandler from "#app/ui/evolution-scene-handler.js"; +import { SummaryUiMode } from "#app/ui/summary-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; + +export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { + private moveId: Moves; + + constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves) { + super(scene, partyMemberIndex); + + this.moveId = moveId; + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + const move = allMoves[this.moveId]; + + const existingMoveIndex = pokemon.getMoveset().findIndex(m => m?.moveId === move.id); + + if (existingMoveIndex > -1) { + return this.end(); + } + + const emptyMoveIndex = pokemon.getMoveset().length < 4 + ? pokemon.getMoveset().length + : pokemon.getMoveset().findIndex(m => m === null); + + const messageMode = this.scene.ui.getHandler() instanceof EvolutionSceneHandler + ? Mode.EVOLUTION_SCENE + : Mode.MESSAGE; + + if (emptyMoveIndex > -1) { + pokemon.setMove(emptyMoveIndex, this.moveId); + initMoveAnim(this.scene, this.moveId).then(() => { + loadMoveAnimAssets(this.scene, [this.moveId], true) + .then(() => { + this.scene.ui.setMode(messageMode).then(() => { + this.scene.playSound("level_up_fanfare"); + this.scene.ui.showText(i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), null, () => { + this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); + this.end(); + }, messageMode === Mode.EVOLUTION_SCENE ? 1000 : null, true); + }); + }); + }); + } else { + this.scene.ui.setMode(messageMode).then(() => { + this.scene.ui.showText(i18next.t("battle:learnMovePrompt", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), null, () => { + this.scene.ui.showText(i18next.t("battle:learnMoveLimitReached", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { + this.scene.ui.showText(i18next.t("battle:learnMoveReplaceQuestion", { moveName: move.name }), null, () => { + const noHandler = () => { + this.scene.ui.setMode(messageMode).then(() => { + this.scene.ui.showText(i18next.t("battle:learnMoveStopTeaching", { moveName: move.name }), null, () => { + this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { + this.scene.ui.setMode(messageMode); + this.scene.ui.showText(i18next.t("battle:learnMoveNotLearned", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), null, () => this.end(), null, true); + }, () => { + this.scene.ui.setMode(messageMode); + this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); + this.end(); + }); + }); + }); + }; + this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { + this.scene.ui.setMode(messageMode); + this.scene.ui.showText(i18next.t("battle:learnMoveForgetQuestion"), null, () => { + this.scene.ui.setModeWithoutClear(Mode.SUMMARY, this.getPokemon(), SummaryUiMode.LEARN_MOVE, move, (moveIndex: integer) => { + if (moveIndex === 4) { + noHandler(); + return; + } + this.scene.ui.setMode(messageMode).then(() => { + this.scene.ui.showText(i18next.t("battle:countdownPoof"), null, () => { + this.scene.ui.showText(i18next.t("battle:learnMoveForgetSuccess", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: pokemon.moveset[moveIndex]!.getName() }), null, () => { // TODO: is the bang correct? + this.scene.ui.showText(i18next.t("battle:learnMoveAnd"), null, () => { + pokemon.setMove(moveIndex, Moves.NONE); + this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); + this.end(); + }, null, true); + }, null, true); + }, null, true); + }); + }); + }, null, true); + }, noHandler); + }); + }, null, true); + }, null, true); + }); + } + } +} diff --git a/src/phases/level-cap-phase.ts b/src/phases/level-cap-phase.ts new file mode 100644 index 00000000000..4a07e7d131e --- /dev/null +++ b/src/phases/level-cap-phase.ts @@ -0,0 +1,20 @@ +import BattleScene from "#app/battle-scene.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { FieldPhase } from "./field-phase"; + +export class LevelCapPhase extends FieldPhase { + constructor(scene: BattleScene) { + super(scene); + } + + start(): void { + super.start(); + + this.scene.ui.setMode(Mode.MESSAGE).then(() => { + this.scene.playSound("level_up_fanfare"); + this.scene.ui.showText(i18next.t("battle:levelCapUp", { levelCap: this.scene.getMaxExpLevel() }), null, () => this.end(), null, true); + this.executeForAll(pokemon => pokemon.updateInfo(true)); + }); + } +} diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts new file mode 100644 index 00000000000..a8a6b8f3d80 --- /dev/null +++ b/src/phases/level-up-phase.ts @@ -0,0 +1,59 @@ +import BattleScene from "#app/battle-scene.js"; +import { ExpNotification } from "#app/enums/exp-notification.js"; +import { EvolutionPhase } from "#app/phases/evolution-phase.js"; +import { PlayerPokemon } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { LevelAchv } from "#app/system/achv.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; +import { LearnMovePhase } from "./learn-move-phase"; + +export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { + private lastLevel: integer; + private level: integer; + + constructor(scene: BattleScene, partyMemberIndex: integer, lastLevel: integer, level: integer) { + super(scene, partyMemberIndex); + + this.lastLevel = lastLevel; + this.level = level; + this.scene = scene; + } + + start() { + super.start(); + + if (this.level > this.scene.gameData.gameStats.highestLevel) { + this.scene.gameData.gameStats.highestLevel = this.level; + } + + this.scene.validateAchvs(LevelAchv, new Utils.IntegerHolder(this.level)); + + const pokemon = this.getPokemon(); + const prevStats = pokemon.stats.slice(0); + pokemon.calculateStats(); + pokemon.updateInfo(); + if (this.scene.expParty === ExpNotification.DEFAULT) { + this.scene.playSound("level_up_fanfare"); + this.scene.ui.showText(i18next.t("battle:levelUp", { pokemonName: getPokemonNameWithAffix(this.getPokemon()), level: this.level }), null, () => this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()), null, true); + } else if (this.scene.expParty === ExpNotification.SKIP) { + this.end(); + } else { + // we still want to display the stats if activated + this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()); + } + if (this.lastLevel < 100) { // this feels like an unnecessary optimization + const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); + for (const lm of levelMoves) { + this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, lm[1])); + } + } + if (!pokemon.pauseEvolutions) { + const evolution = pokemon.getEvolution(); + if (evolution) { + this.scene.unshiftPhase(new EvolutionPhase(this.scene, pokemon as PlayerPokemon, evolution, this.lastLevel)); + } + } + } +} diff --git a/src/phases/login-phase.ts b/src/phases/login-phase.ts new file mode 100644 index 00000000000..b58cc9bca1f --- /dev/null +++ b/src/phases/login-phase.ts @@ -0,0 +1,116 @@ +import { updateUserInfo } from "#app/account.js"; +import BattleScene, { bypassLogin } from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { handleTutorial, Tutorial } from "#app/tutorial.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next, { t } from "i18next"; +import * as Utils from "#app/utils.js"; +import { SelectGenderPhase } from "./select-gender-phase"; +import { UnavailablePhase } from "./unavailable-phase"; + +export class LoginPhase extends Phase { + private showText: boolean; + + constructor(scene: BattleScene, showText?: boolean) { + super(scene); + + this.showText = showText === undefined || !!showText; + } + + start(): void { + super.start(); + + const hasSession = !!Utils.getCookie(Utils.sessionIdKey); + + this.scene.ui.setMode(Mode.LOADING, { buttonActions: [] }); + Utils.executeIf(bypassLogin || hasSession, updateUserInfo).then(response => { + const success = response ? response[0] : false; + const statusCode = response ? response[1] : null; + if (!success) { + if (!statusCode || statusCode === 400) { + if (this.showText) { + this.scene.ui.showText(i18next.t("menu:logInOrCreateAccount")); + } + + this.scene.playSound("menu_open"); + + const loadData = () => { + updateUserInfo().then(success => { + if (!success[0]) { + Utils.removeCookie(Utils.sessionIdKey); + this.scene.reset(true, true); + return; + } + this.scene.gameData.loadSystem().then(() => this.end()); + }); + }; + + this.scene.ui.setMode(Mode.LOGIN_FORM, { + buttonActions: [ + () => { + this.scene.ui.playSelect(); + loadData(); + }, () => { + this.scene.playSound("menu_open"); + this.scene.ui.setMode(Mode.REGISTRATION_FORM, { + buttonActions: [ + () => { + this.scene.ui.playSelect(); + updateUserInfo().then(success => { + if (!success[0]) { + Utils.removeCookie(Utils.sessionIdKey); + this.scene.reset(true, true); + return; + } + this.end(); + } ); + }, () => { + this.scene.unshiftPhase(new LoginPhase(this.scene, false)); + this.end(); + } + ] + }); + }, () => { + const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); + const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; + const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&prompt=none`; + window.open(discordUrl, "_self"); + }, () => { + const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); + const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; + const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&redirect_uri=${redirectUri}&response_type=code&scope=openid`; + window.open(googleUrl, "_self"); + } + ] + }); + } else if (statusCode === 401) { + Utils.removeCookie(Utils.sessionIdKey); + this.scene.reset(true, true); + } else { + this.scene.unshiftPhase(new UnavailablePhase(this.scene)); + super.end(); + } + return null; + } else { + this.scene.gameData.loadSystem().then(success => { + if (success || bypassLogin) { + this.end(); + } else { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(t("menu:failedToLoadSaveData")); + } + }); + } + }); + } + + end(): void { + this.scene.ui.setMode(Mode.MESSAGE); + + if (!this.scene.gameData.gender) { + this.scene.unshiftPhase(new SelectGenderPhase(this.scene)); + } + + handleTutorial(this.scene, Tutorial.Intro).then(() => super.end()); + } +} diff --git a/src/phases/message-phase.ts b/src/phases/message-phase.ts new file mode 100644 index 00000000000..46e907ae2ba --- /dev/null +++ b/src/phases/message-phase.ts @@ -0,0 +1,38 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; + +export class MessagePhase extends Phase { + private text: string; + private callbackDelay: integer | null; + private prompt: boolean | null; + private promptDelay: integer | null; + + constructor(scene: BattleScene, text: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) { + super(scene); + + this.text = text; + this.callbackDelay = callbackDelay!; // TODO: is this bang correct? + this.prompt = prompt!; // TODO: is this bang correct? + this.promptDelay = promptDelay!; // TODO: is this bang correct? + } + + start() { + super.start(); + + if (this.text.indexOf("$") > -1) { + const pageIndex = this.text.indexOf("$"); + this.scene.unshiftPhase(new MessagePhase(this.scene, this.text.slice(pageIndex + 1), this.callbackDelay, this.prompt, this.promptDelay)); + this.text = this.text.slice(0, pageIndex).trim(); + } + + this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt, this.promptDelay); + } + + end() { + if (this.scene.abilityBar.shown) { + this.scene.abilityBar.hide(); + } + + super.end(); + } +} diff --git a/src/phases/modifier-reward-phase.ts b/src/phases/modifier-reward-phase.ts new file mode 100644 index 00000000000..4d083a367a6 --- /dev/null +++ b/src/phases/modifier-reward-phase.ts @@ -0,0 +1,30 @@ +import BattleScene from "#app/battle-scene.js"; +import { ModifierType, ModifierTypeFunc, getModifierType } from "#app/modifier/modifier-type.js"; +import i18next from "i18next"; +import { BattlePhase } from "./battle-phase"; + +export class ModifierRewardPhase extends BattlePhase { + protected modifierType: ModifierType; + + constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc) { + super(scene); + + this.modifierType = getModifierType(modifierTypeFunc); + } + + start() { + super.start(); + + this.doReward().then(() => this.end()); + } + + doReward(): Promise { + return new Promise(resolve => { + const newModifier = this.modifierType.newModifier(); + this.scene.addModifier(newModifier).then(() => { + this.scene.playSound("item_fanfare"); + this.scene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => resolve(), null, true); + }); + }); + } +} diff --git a/src/phases/money-reward-phase.ts b/src/phases/money-reward-phase.ts new file mode 100644 index 00000000000..e0bff608972 --- /dev/null +++ b/src/phases/money-reward-phase.ts @@ -0,0 +1,34 @@ +import BattleScene from "#app/battle-scene.js"; +import { ArenaTagType } from "#app/enums/arena-tag-type.js"; +import { MoneyMultiplierModifier } from "#app/modifier/modifier.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; + +export class MoneyRewardPhase extends BattlePhase { + private moneyMultiplier: number; + + constructor(scene: BattleScene, moneyMultiplier: number) { + super(scene); + + this.moneyMultiplier = moneyMultiplier; + } + + start() { + const moneyAmount = new Utils.IntegerHolder(this.scene.getWaveMoneyAmount(this.moneyMultiplier)); + + this.scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + + if (this.scene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { + moneyAmount.value *= 2; + } + + this.scene.addMoney(moneyAmount.value); + + const userLocale = navigator.language || "en-US"; + const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale); + const message = i18next.t("battle:moneyWon", { moneyAmount: formattedMoneyAmount }); + + this.scene.ui.showText(message, null, () => this.end(), null, true); + } +} diff --git a/src/phases/move-anim-test-phase.ts b/src/phases/move-anim-test-phase.ts new file mode 100644 index 00000000000..7fb3c1d61e7 --- /dev/null +++ b/src/phases/move-anim-test-phase.ts @@ -0,0 +1,44 @@ +import BattleScene from "#app/battle-scene.js"; +import { initMoveAnim, loadMoveAnimAssets, MoveAnim } from "#app/data/battle-anims.js"; +import { allMoves, SelfStatusMove } from "#app/data/move.js"; +import { Moves } from "#app/enums/moves.js"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; + +export class MoveAnimTestPhase extends BattlePhase { + private moveQueue: Moves[]; + + constructor(scene: BattleScene, moveQueue?: Moves[]) { + super(scene); + + this.moveQueue = moveQueue || Utils.getEnumValues(Moves).slice(1); + } + + start() { + const moveQueue = this.moveQueue.slice(0); + this.playMoveAnim(moveQueue, true); + } + + playMoveAnim(moveQueue: Moves[], player: boolean) { + const moveId = player ? moveQueue[0] : moveQueue.shift(); + if (moveId === undefined) { + this.playMoveAnim(this.moveQueue.slice(0), true); + return; + } else if (player) { + console.log(Moves[moveId]); + } + + initMoveAnim(this.scene, moveId).then(() => { + loadMoveAnimAssets(this.scene, [moveId], true) + .then(() => { + new MoveAnim(moveId, player ? this.scene.getPlayerPokemon()! : this.scene.getEnemyPokemon()!, (player !== (allMoves[moveId] instanceof SelfStatusMove) ? this.scene.getEnemyPokemon()! : this.scene.getPlayerPokemon()!).getBattlerIndex()).play(this.scene, () => { // TODO: are the bangs correct here? + if (player) { + this.playMoveAnim(moveQueue, false); + } else { + this.playMoveAnim(moveQueue, true); + } + }); + }); + }); + } +} diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts new file mode 100644 index 00000000000..a5ac913cc5d --- /dev/null +++ b/src/phases/move-effect-phase.ts @@ -0,0 +1,447 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr, applyPostDefendAbAttrs, PostDefendAbAttr, applyPostAttackAbAttrs, PostAttackAbAttr, MaxMultiHitAbAttr, AlwaysHitAbAttr } from "#app/data/ability.js"; +import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag.js"; +import { MoveAnim } from "#app/data/battle-anims.js"; +import { BattlerTagLapseType, ProtectedTag, SemiInvulnerableTag } from "#app/data/battler-tags.js"; +import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr } from "#app/data/move.js"; +import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms.js"; +import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { Moves } from "#app/enums/moves.js"; +import Pokemon, { PokemonMove, MoveResult, HitResult } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { PokemonMultiHitModifier, FlinchChanceModifier, EnemyAttackStatusEffectChanceModifier, ContactHeldItemTransferChanceModifier, HitHealModifier } from "#app/modifier/modifier.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class MoveEffectPhase extends PokemonPhase { + public move: PokemonMove; + protected targets: BattlerIndex[]; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, targets: BattlerIndex[], move: PokemonMove) { + super(scene, battlerIndex); + this.move = move; + /** + * In double battles, if the right Pokemon selects a spread move and the left Pokemon dies + * with no party members available to switch in, then the right Pokemon takes the index + * of the left Pokemon and gets hit unless this is checked. + */ + if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) { + const i = targets.indexOf(battlerIndex); + targets.splice(i, i + 1); + } + this.targets = targets; + } + + start() { + super.start(); + + /** The Pokemon using this phase's invoked move */ + const user = this.getUserPokemon(); + /** All Pokemon targeted by this phase's invoked move */ + const targets = this.getTargets(); + + /** If the user was somehow removed from the field, end this phase */ + if (!user?.isOnField()) { + return super.end(); + } + + /** + * Does an effect from this move override other effects on this turn? + * e.g. Charging moves (Fly, etc.) on their first turn of use. + */ + const overridden = new Utils.BooleanHolder(false); + /** The {@linkcode Move} object from {@linkcode allMoves} invoked by this phase */ + const move = this.move.getMove(); + + // Assume single target for override + applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget() ?? null, move, overridden, this.move.virtual).then(() => { + // If other effects were overriden, stop this phase before they can be applied + if (overridden.value) { + return this.end(); + } + + user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); + + /** + * If this phase is for the first hit of the invoked move, + * resolve the move's total hit count. This block combines the + * effects of the move itself, Parental Bond, and Multi-Lens to do so. + */ + if (user.turnData.hitsLeft === undefined) { + const hitCount = new Utils.IntegerHolder(1); + // Assume single target for multi hit + applyMoveAttrs(MultiHitAttr, user, this.getTarget() ?? null, move, hitCount); + // If Parental Bond is applicable, double the hit count + applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, targets.length, hitCount, new Utils.IntegerHolder(0)); + // If Multi-Lens is applicable, multiply the hit count by 1 + the number of Multi-Lenses held by the user + if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) { + this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0)); + } + // Set the user's relevant turnData fields to reflect the final hit count + user.turnData.hitCount = hitCount.value; + user.turnData.hitsLeft = hitCount.value; + } + + /** + * Log to be entered into the user's move history once the move result is resolved. + * Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully + * used in the sense of "Does it have an effect on the user?". + */ + const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual }; + + /** + * Stores results of hit checks of the invoked move against all targets, organized by battler index. + * @see {@linkcode hitCheck} + */ + const targetHitChecks = Object.fromEntries(targets.map(p => [p.getBattlerIndex(), this.hitCheck(p)])); + const hasActiveTargets = targets.some(t => t.isActive(true)); + /** + * If no targets are left for the move to hit (FAIL), or the invoked move is single-target + * (and not random target) and failed the hit check against its target (MISS), log the move + * as FAILed or MISSed (depending on the conditions above) and end this phase. + */ + if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) { + this.stopMultiHit(); + if (hasActiveTargets) { + this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getTarget()? getPokemonNameWithAffix(this.getTarget()!) : "" })); + moveHistoryEntry.result = MoveResult.MISS; + applyMoveAttrs(MissEffectAttr, user, null, move); + } else { + this.scene.queueMessage(i18next.t("battle:attackFailed")); + moveHistoryEntry.result = MoveResult.FAIL; + } + user.pushMoveHistory(moveHistoryEntry); + return this.end(); + } + + /** All move effect attributes are chained together in this array to be applied asynchronously. */ + const applyAttrs: Promise[] = []; + + // Move animation only needs one target + new MoveAnim(move.id as Moves, user, this.getTarget()?.getBattlerIndex()!).play(this.scene, () => { // TODO: is the bang correct here? + /** Has the move successfully hit a target (for damage) yet? */ + let hasHit: boolean = false; + for (const target of targets) { + /** + * If the move missed a target, stop all future hits against that target + * and move on to the next target (if there is one). + */ + if (!targetHitChecks[target.getBattlerIndex()]) { + this.stopMultiHit(target); + this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + if (moveHistoryEntry.result === MoveResult.PENDING) { + moveHistoryEntry.result = MoveResult.MISS; + } + user.pushMoveHistory(moveHistoryEntry); + applyMoveAttrs(MissEffectAttr, user, null, move); + continue; + } + + /** The {@linkcode ArenaTagSide} to which the target belongs */ + const targetSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + /** Has the invoked move been cancelled by conditional protection (e.g Quick Guard)? */ + const hasConditionalProtectApplied = new Utils.BooleanHolder(false); + /** Does the applied conditional protection bypass Protect-ignoring effects? */ + const bypassIgnoreProtect = new Utils.BooleanHolder(false); + // If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects + if (!this.move.getMove().isAllyTarget()) { + this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect); + } + + /** Is the target protected by Protect, etc. or a relevant conditional protection effect? */ + const isProtected = (bypassIgnoreProtect.value || !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target)) + && (hasConditionalProtectApplied.value || target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))); + + /** Does this phase represent the invoked move's first strike? */ + const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount); + + // Only log the move's result on the first strike + if (firstHit) { + user.pushMoveHistory(moveHistoryEntry); + } + + /** + * Since all fail/miss checks have applied, the move is considered successfully applied. + * It's worth noting that if the move has no effect or is protected against, this assignment + * is overwritten and the move is logged as a FAIL. + */ + moveHistoryEntry.result = MoveResult.SUCCESS; + + /** + * Stores the result of applying the invoked move to the target. + * If the target is protected, the result is always `NO_EFFECT`. + * Otherwise, the hit result is based on type effectiveness, immunities, + * and other factors that may negate the attack or status application. + * + * Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated + * (for attack moves) and the target's HP is updated. However, this isn't + * made visible to the user until the resulting {@linkcode DamagePhase} + * is invoked. + */ + const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT; + + /** Does {@linkcode hitResult} indicate that damage was dealt to the target? */ + const dealsDamage = [ + HitResult.EFFECTIVE, + HitResult.SUPER_EFFECTIVE, + HitResult.NOT_VERY_EFFECTIVE, + HitResult.ONE_HIT_KO + ].includes(hitResult); + + /** Is this target the first one hit by the move on its current strike? */ + const firstTarget = dealsDamage && !hasHit; + if (firstTarget) { + hasHit = true; + } + + /** + * If the move has no effect on the target (i.e. the target is protected or immune), + * change the logged move result to FAIL. + */ + if (hitResult === HitResult.NO_EFFECT) { + moveHistoryEntry.result = MoveResult.FAIL; + } + + /** Does this phase represent the invoked move's last strike? */ + const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()); + + /** + * If the user can change forms by using the invoked move, + * it only changes forms after the move's last hit + * (see Relic Song's interaction with Parental Bond when used by Meloetta). + */ + if (lastHit) { + this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); + } + + /** + * Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs. + * These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger + * type requires different conditions to be met with respect to the move's hit result. + */ + applyAttrs.push(new Promise(resolve => { + // Apply all effects with PRE_MOVE triggers (if the target isn't immune to the move) + applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && hitResult !== HitResult.NO_EFFECT, + user, target, move).then(() => { + // All other effects require the move to not have failed or have been cancelled to trigger + if (hitResult !== HitResult.FAIL) { + /** Are the move's effects tied to the first turn of a charge move? */ + const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget() ?? null, move)); + /** + * If the invoked move's effects are meant to trigger during the move's "charge turn," + * ignore all effects after this point. + * Otherwise, apply all self-targeted POST_APPLY effects. + */ + Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY + && attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move)).then(() => { + // All effects past this point require the move to have hit the target + if (hitResult !== HitResult.NO_EFFECT) { + // Apply all non-self-targeted POST_APPLY effects + applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY + && !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => { + /** + * If the move hit, and the target doesn't have Shield Dust, + * apply the chance to flinch the target gained from King's Rock + */ + if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr)) { + const flinched = new Utils.BooleanHolder(false); + user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); + if (flinched.value) { + target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id); + } + } + // If the move was not protected against, apply all HIT effects + Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT + && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && (!attr.firstTargetOnly || firstTarget), user, target, this.move.getMove()).then(() => { + // Apply the target's post-defend ability effects (as long as the target is active or can otherwise apply them) + return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => { + // If the invoked move is an enemy attack, apply the enemy's status effect-inflicting tags and tokens + target.lapseTag(BattlerTagType.BEAK_BLAST_CHARGING); + if (move.category === MoveCategory.PHYSICAL && user.isPlayer() !== target.isPlayer()) { + target.lapseTag(BattlerTagType.SHELL_TRAP); + } + if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { + user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); + } + })).then(() => { + // Apply the user's post-attack ability effects + applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => { + /** + * If the invoked move is an attack, apply the user's chance to + * steal an item from the target granted by Grip Claw + */ + if (this.move.getMove() instanceof AttackMove) { + this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); + } + resolve(); + }); + }); + }) + ).then(() => resolve()); + }); + } else { + applyMoveAttrs(NoEffectAttr, user, null, move).then(() => resolve()); + } + }); + } else { + resolve(); + } + }); + })); + } + // Apply the move's POST_TARGET effects on the move's last hit, after all targeted effects have resolved + const postTarget = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()) ? + applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, user, null, move) : + null; + + if (!!postTarget) { + if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after + applyAttrs[applyAttrs.length - 1]?.then(() => postTarget); + } else { // Otherwise, push a new asynchronous move effect + applyAttrs.push(postTarget); + } + } + + // Wait for all move effects to finish applying, then end this phase + Promise.allSettled(applyAttrs).then(() => this.end()); + }); + }); + } + + end() { + const move = this.move.getMove(); + move.type = move.defaultType; + const user = this.getUserPokemon(); + /** + * If this phase isn't for the invoked move's last strike, + * unshift another MoveEffectPhase for the next strike. + * Otherwise, queue a message indicating the number of times the move has struck + * (if the move has struck more than once), then apply the heal from Shell Bell + * to the user. + */ + if (user) { + if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) { + this.scene.unshiftPhase(this.getNewHitPhase()); + } else { + // Queue message for number of hits made by multi-move + // If multi-hit attack only hits once, still want to render a message + const hitsTotal = user.turnData.hitCount! - Math.max(user.turnData.hitsLeft!, 0); // TODO: are those bangs correct? + if (hitsTotal > 1 || (user.turnData.hitsLeft && user.turnData.hitsLeft > 0)) { + // If there are multiple hits, or if there are hits of the multi-hit move left + this.scene.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); + } + this.scene.applyModifiers(HitHealModifier, this.player, user); + } + } + + super.end(); + } + + /** + * Resolves whether this phase's invoked move hits or misses the given target + * @param target {@linkcode Pokemon} the Pokemon targeted by the invoked move + * @returns `true` if the move does not miss the target; `false` otherwise + */ + hitCheck(target: Pokemon): boolean { + // Moves targeting the user and entry hazards can't miss + if ([MoveTarget.USER, MoveTarget.ENEMY_SIDE].includes(this.move.getMove().moveTarget)) { + return true; + } + + const user = this.getUserPokemon()!; // TODO: is this bang correct? + + // Hit check only calculated on first hit for multi-hit moves unless flag is set to check all hits. + // However, if an ability with the MaxMultiHitAbAttr, namely Skill Link, is present, act as a normal + // multi-hit move and proceed with all hits + if (user.turnData.hitsLeft < user.turnData.hitCount) { + if (!this.move.getMove().hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr(MaxMultiHitAbAttr)) { + return true; + } + } + + if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) { + return true; + } + + // If the user should ignore accuracy on a target, check who the user targeted last turn and see if they match + if (user.getTag(BattlerTagType.IGNORE_ACCURACY) && (user.getLastXMoves().find(() => true)?.targets || []).indexOf(target.getBattlerIndex()) !== -1) { + return true; + } + + if (target.getTag(BattlerTagType.ALWAYS_GET_HIT)) { + return true; + } + + const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); + if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) { + return false; + } + + const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user!, target); // TODO: is the bang correct here? + + if (moveAccuracy === -1) { + return true; + } + + const accuracyMultiplier = user.getAccuracyMultiplier(target, this.move.getMove()); + const rand = user.randSeedInt(100, 1); + + return rand <= moveAccuracy * (accuracyMultiplier!); // TODO: is this bang correct? + } + + /** Returns the {@linkcode Pokemon} using this phase's invoked move */ + getUserPokemon(): Pokemon | undefined { + if (this.battlerIndex > BattlerIndex.ENEMY_2) { + return this.scene.getPokemonById(this.battlerIndex) ?? undefined; + } + return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex]; + } + + /** Returns an array of all {@linkcode Pokemon} targeted by this phase's invoked move */ + getTargets(): Pokemon[] { + return this.scene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1); + } + + /** Returns the first target of this phase's invoked move */ + getTarget(): Pokemon | undefined { + return this.getTargets()[0]; + } + + /** + * Removes the given {@linkcode Pokemon} from this phase's target list + * @param target {@linkcode Pokemon} the Pokemon to be removed + */ + removeTarget(target: Pokemon): void { + const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex()); + if (targetIndex !== -1) { + this.targets.splice(this.targets.findIndex(ind => ind === target.getBattlerIndex()), 1); + } + } + + /** + * Prevents subsequent strikes of this phase's invoked move from occurring + * @param target {@linkcode Pokemon} if defined, only stop subsequent + * strikes against this Pokemon + */ + stopMultiHit(target?: Pokemon): void { + /** If given a specific target, remove the target from subsequent strikes */ + if (target) { + this.removeTarget(target); + } + /** + * If no target specified, or the specified target was the last of this move's + * targets, completely cancel all subsequent strikes. + */ + if (!target || this.targets.length === 0 ) { + this.getUserPokemon()!.turnData.hitCount = 1; // TODO: is the bang correct here? + this.getUserPokemon()!.turnData.hitsLeft = 1; // TODO: is the bang correct here? + } + } + + /** Returns a new MoveEffectPhase with the same properties as this phase */ + getNewHitPhase() { + return new MoveEffectPhase(this.scene, this.battlerIndex, this.targets, this.move); + } +} diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts new file mode 100644 index 00000000000..4bce2185aea --- /dev/null +++ b/src/phases/move-end-phase.ts @@ -0,0 +1,23 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { BattlerTagLapseType } from "#app/data/battler-tags.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class MoveEndPhase extends PokemonPhase { + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + if (pokemon.isActive(true)) { + pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE); + } + + this.scene.arena.setIgnoreAbilities(false); + + this.end(); + } +} diff --git a/src/phases/move-header-phase.ts b/src/phases/move-header-phase.ts new file mode 100644 index 00000000000..5166f287731 --- /dev/null +++ b/src/phases/move-header-phase.ts @@ -0,0 +1,30 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyMoveAttrs, MoveHeaderAttr } from "#app/data/move.js"; +import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; +import { BattlePhase } from "./battle-phase"; + +export class MoveHeaderPhase extends BattlePhase { + public pokemon: Pokemon; + public move: PokemonMove; + + constructor(scene: BattleScene, pokemon: Pokemon, move: PokemonMove) { + super(scene); + + this.pokemon = pokemon; + this.move = move; + } + + canMove(): boolean { + return this.pokemon.isActive(true) && this.move.isUsable(this.pokemon); + } + + start() { + super.start(); + + if (this.canMove()) { + applyMoveAttrs(MoveHeaderAttr, this.pokemon, null, this.move.getMove()).then(() => this.end()); + } else { + this.end(); + } + } +} diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts new file mode 100644 index 00000000000..b9656df856b --- /dev/null +++ b/src/phases/move-phase.ts @@ -0,0 +1,329 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { applyAbAttrs, RedirectMoveAbAttr, BlockRedirectAbAttr, IncreasePpAbAttr, applyPreAttackAbAttrs, PokemonTypeChangeAbAttr, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr } from "#app/data/ability.js"; +import { CommonAnim } from "#app/data/battle-anims.js"; +import { CenterOfAttentionTag, BattlerTagLapseType } from "#app/data/battler-tags.js"; +import { MoveFlags, BypassRedirectAttr, allMoves, CopyMoveAttr, applyMoveAttrs, BypassSleepAttr, HealStatusEffectAttr, ChargeAttr, PreMoveMessageAttr } from "#app/data/move.js"; +import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms.js"; +import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect.js"; +import { Type } from "#app/data/type.js"; +import { getTerrainBlockMessage } from "#app/data/weather.js"; +import { Abilities } from "#app/enums/abilities.js"; +import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { Moves } from "#app/enums/moves.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { MoveUsedEvent } from "#app/events/battle-scene.js"; +import Pokemon, { PokemonMove, MoveResult, TurnMove } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; +import { CommonAnimPhase } from "./common-anim-phase"; +import { MoveEffectPhase } from "./move-effect-phase"; +import { MoveEndPhase } from "./move-end-phase"; +import { ShowAbilityPhase } from "./show-ability-phase"; + +export class MovePhase extends BattlePhase { + public pokemon: Pokemon; + public move: PokemonMove; + public targets: BattlerIndex[]; + protected followUp: boolean; + protected ignorePp: boolean; + protected failed: boolean; + protected cancelled: boolean; + + constructor(scene: BattleScene, pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp?: boolean, ignorePp?: boolean) { + super(scene); + + this.pokemon = pokemon; + this.targets = targets; + this.move = move; + this.followUp = !!followUp; + this.ignorePp = !!ignorePp; + this.failed = false; + this.cancelled = false; + } + + canMove(): boolean { + return this.pokemon.isActive(true) && this.move.isUsable(this.pokemon, this.ignorePp) && !!this.targets.length; + } + + /**Signifies the current move should fail but still use PP */ + fail(): void { + this.failed = true; + } + + /**Signifies the current move should cancel and retain PP */ + cancel(): void { + this.cancelled = true; + } + + start() { + super.start(); + + console.log(Moves[this.move.moveId]); + + if (!this.canMove()) { + if (this.move.moveId && this.pokemon.summonData?.disabledMove === this.move.moveId) { + this.scene.queueMessage(`${this.move.getName()} is disabled!`); + } + if (this.pokemon.isActive(true) && this.move.ppUsed >= this.move.getMovePp()) { // if the move PP was reduced from Spite or otherwise, the move fails + this.fail(); + this.showMoveText(); + this.showFailedText(); + } + return this.end(); + } + + if (!this.followUp) { + if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) { + this.scene.arena.setIgnoreAbilities(); + } + } else { + this.pokemon.turnData.hitsLeft = 0; // TODO: is `0` correct? + this.pokemon.turnData.hitCount = 0; // TODO: is `0` correct? + } + + // Move redirection abilities (ie. Storm Drain) only support single target moves + const moveTarget = this.targets.length === 1 + ? new Utils.IntegerHolder(this.targets[0]) + : null; + if (moveTarget) { + const oldTarget = moveTarget.value; + this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget)); + this.pokemon.getOpponents().forEach(p => { + const redirectTag = p.getTag(CenterOfAttentionTag) as CenterOfAttentionTag; + if (redirectTag && (!redirectTag.powder || (!this.pokemon.isOfType(Type.GRASS) && !this.pokemon.hasAbility(Abilities.OVERCOAT)))) { + moveTarget.value = p.getBattlerIndex(); + } + }); + //Check if this move is immune to being redirected, and restore its target to the intended target if it is. + if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().hasAttr(BypassRedirectAttr))) { + //If an ability prevented this move from being redirected, display its ability pop up. + if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().hasAttr(BypassRedirectAttr)) && oldTarget !== moveTarget.value) { + this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr))); + } + moveTarget.value = oldTarget; + } + this.targets[0] = moveTarget.value; + } + + // Check for counterattack moves to switch target + if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) { + if (this.pokemon.turnData.attacksReceived.length) { + const attack = this.pokemon.turnData.attacksReceived[0]; + this.targets[0] = attack.sourceBattlerIndex; + + // account for metal burst and comeuppance hitting remaining targets in double battles + // counterattack will redirect to remaining ally if original attacker faints + if (this.scene.currentBattle.double && this.move.getMove().hasFlag(MoveFlags.REDIRECT_COUNTER)) { + if (this.scene.getField()[this.targets[0]].hp === 0) { + const opposingField = this.pokemon.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField(); + //@ts-ignore + this.targets[0] = opposingField.find(p => p.hp > 0)?.getBattlerIndex(); //TODO: fix ts-ignore + } + } + } + if (this.targets[0] === BattlerIndex.ATTACKER) { + this.fail(); // Marks the move as failed for later in doMove + this.showMoveText(); + this.showFailedText(); + } + } + + const targets = this.scene.getField(true).filter(p => { + if (this.targets.indexOf(p.getBattlerIndex()) > -1) { + return true; + } + return false; + }); + + const doMove = () => { + this.pokemon.turnData.acted = true; // Record that the move was attempted, even if it fails + + this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE); + + let ppUsed = 1; + // Filter all opponents to include only those this move is targeting + const targetedOpponents = this.pokemon.getOpponents().filter(o => this.targets.includes(o.getBattlerIndex())); + for (const opponent of targetedOpponents) { + if (this.move.ppUsed + ppUsed >= this.move.getMovePp()) { // If we're already at max PP usage, stop checking + break; + } + if (opponent.hasAbilityWithAttr(IncreasePpAbAttr)) { // Accounting for abilities like Pressure + ppUsed++; + } + } + + if (!this.followUp && this.canMove() && !this.cancelled) { + this.pokemon.lapseTags(BattlerTagLapseType.MOVE); + } + + const moveQueue = this.pokemon.getMoveQueue(); + if (this.cancelled || this.failed) { + if (this.failed) { + this.move.usePp(ppUsed); // Only use PP if the move failed + this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); + } + + // Record a failed move so Abilities like Truant don't trigger next turn and soft-lock + this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL }); + + this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); // Remove any tags from moves like Fly/Dive/etc. + moveQueue.shift(); // Remove the second turn of charge moves + return this.end(); + } + + this.scene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); + + if (this.move.moveId) { + this.showMoveText(); + } + + // This should only happen when there are no valid targets left on the field + if ((moveQueue.length && moveQueue[0].move === Moves.NONE) || !targets.length) { + this.showFailedText(); + this.cancel(); + + // Record a failed move so Abilities like Truant don't trigger next turn and soft-lock + this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL }); + + this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); // Remove any tags from moves like Fly/Dive/etc. + + moveQueue.shift(); + return this.end(); + } + + if (!moveQueue.length || !moveQueue.shift()?.ignorePP) { // using .shift here clears out two turn moves once they've been used + this.move.usePp(ppUsed); + this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); + } + + if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { + this.scene.currentBattle.lastMove = this.move.moveId; + } + + // Assume conditions affecting targets only apply to moves with a single target + let success = this.move.getMove().applyConditions(this.pokemon, targets[0], this.move.getMove()); + const cancelled = new Utils.BooleanHolder(false); + let failedText = this.move.getMove().getFailedText(this.pokemon, targets[0], this.move.getMove(), cancelled); + if (success && this.scene.arena.isMoveWeatherCancelled(this.move.getMove())) { + success = false; + } else if (success && this.scene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, this.move.getMove())) { + success = false; + if (failedText === null) { + failedText = getTerrainBlockMessage(targets[0], this.scene.arena.terrain?.terrainType!); // TODO: is this bang correct? + } + } + + /** + * Trigger pokemon type change before playing the move animation + * Will still change the user's type when using Roar, Whirlwind, Trick-or-Treat, and Forest's Curse, + * regardless of whether the move successfully executes or not. + */ + if (success || [Moves.ROAR, Moves.WHIRLWIND, Moves.TRICK_OR_TREAT, Moves.FORESTS_CURSE].includes(this.move.moveId)) { + applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); + } + + if (success) { + this.scene.unshiftPhase(this.getEffectPhase()); + } else { + this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual }); + if (!cancelled.value) { + this.showFailedText(failedText); + } + } + // Checks if Dancer ability is triggered + if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) { + // Pokemon with Dancer can be on either side of the battle so we check in both cases + this.scene.getPlayerField().forEach(pokemon => { + applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); + }); + this.scene.getEnemyField().forEach(pokemon => { + applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); + }); + } + this.end(); + }; + + if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) { + this.pokemon.status.incrementTurn(); + let activated = false; + let healed = false; + + switch (this.pokemon.status.effect) { + case StatusEffect.PARALYSIS: + if (!this.pokemon.randSeedInt(4)) { + activated = true; + this.cancelled = true; + } + break; + case StatusEffect.SLEEP: + applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); + healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; + activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); + this.cancelled = activated; + break; + case StatusEffect.FREEZE: + healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); + activated = !healed; + this.cancelled = activated; + break; + } + + if (activated) { + this.scene.queueMessage(getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); + this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.POISON + (this.pokemon.status.effect - 1))); + doMove(); + } else { + if (healed) { + this.scene.queueMessage(getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); + this.pokemon.resetStatus(); + this.pokemon.updateInfo(); + } + doMove(); + } + } else { + doMove(); + } + } + + getEffectPhase(): MoveEffectPhase { + return new MoveEffectPhase(this.scene, this.pokemon.getBattlerIndex(), this.targets, this.move); + } + + showMoveText(): void { + if (this.move.getMove().hasAttr(ChargeAttr)) { + const lastMove = this.pokemon.getLastXMoves() as TurnMove[]; + if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) { + this.scene.queueMessage(i18next.t("battle:useMove", { + pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), + moveName: this.move.getName() + }), 500); + return; + } + } + + if (this.pokemon.getTag(BattlerTagType.RECHARGING || BattlerTagType.INTERRUPTED)) { + return; + } + + this.scene.queueMessage(i18next.t("battle:useMove", { + pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), + moveName: this.move.getName() + }), 500); + applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents().find(() => true)!, this.move.getMove()); //TODO: is the bang correct here? + } + + showFailedText(failedText: string | null = null): void { + this.scene.queueMessage(failedText || i18next.t("battle:attackFailed")); + } + + end() { + if (!this.followUp && this.canMove()) { + this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex())); + } + + super.end(); + } +} diff --git a/src/phases/new-battle-phase.ts b/src/phases/new-battle-phase.ts new file mode 100644 index 00000000000..5a422c9e6c7 --- /dev/null +++ b/src/phases/new-battle-phase.ts @@ -0,0 +1,11 @@ +import { BattlePhase } from "./battle-phase"; + +export class NewBattlePhase extends BattlePhase { + start() { + super.start(); + + this.scene.newBattle(); + + this.end(); + } +} diff --git a/src/phases/new-biome-encounter-phase.ts b/src/phases/new-biome-encounter-phase.ts new file mode 100644 index 00000000000..c447e78f7b1 --- /dev/null +++ b/src/phases/new-biome-encounter-phase.ts @@ -0,0 +1,38 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyAbAttrs, PostBiomeChangeAbAttr } from "#app/data/ability.js"; +import { getRandomWeatherType } from "#app/data/weather.js"; +import { NextEncounterPhase } from "./next-encounter-phase"; + +export class NewBiomeEncounterPhase extends NextEncounterPhase { + constructor(scene: BattleScene) { + super(scene); + } + + doEncounter(): void { + this.scene.playBgm(undefined, true); + + for (const pokemon of this.scene.getParty()) { + if (pokemon) { + pokemon.resetBattleData(); + } + } + + this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false); + + for (const pokemon of this.scene.getParty().filter(p => p.isOnField())) { + applyAbAttrs(PostBiomeChangeAbAttr, pokemon, null); + } + + const enemyField = this.scene.getEnemyField(); + this.scene.tweens.add({ + targets: [this.scene.arenaEnemy, enemyField].flat(), + x: "+=300", + duration: 2000, + onComplete: () => { + if (!this.tryOverrideForBattleSpec()) { + this.doEncounterCommon(); + } + } + }); + } +} diff --git a/src/phases/next-encounter-phase.ts b/src/phases/next-encounter-phase.ts new file mode 100644 index 00000000000..89987534fc0 --- /dev/null +++ b/src/phases/next-encounter-phase.ts @@ -0,0 +1,46 @@ +import BattleScene from "#app/battle-scene.js"; +import { EncounterPhase } from "./encounter-phase"; + +export class NextEncounterPhase extends EncounterPhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + } + + doEncounter(): void { + this.scene.playBgm(undefined, true); + + for (const pokemon of this.scene.getParty()) { + if (pokemon) { + pokemon.resetBattleData(); + } + } + + this.scene.arenaNextEnemy.setBiome(this.scene.arena.biomeType); + this.scene.arenaNextEnemy.setVisible(true); + + const enemyField = this.scene.getEnemyField(); + this.scene.tweens.add({ + targets: [this.scene.arenaEnemy, this.scene.arenaNextEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.lastEnemyTrainer].flat(), + x: "+=300", + duration: 2000, + onComplete: () => { + this.scene.arenaEnemy.setBiome(this.scene.arena.biomeType); + this.scene.arenaEnemy.setX(this.scene.arenaNextEnemy.x); + this.scene.arenaEnemy.setAlpha(1); + this.scene.arenaNextEnemy.setX(this.scene.arenaNextEnemy.x - 300); + this.scene.arenaNextEnemy.setVisible(false); + if (this.scene.lastEnemyTrainer) { + this.scene.lastEnemyTrainer.destroy(); + } + + if (!this.tryOverrideForBattleSpec()) { + this.doEncounterCommon(); + } + } + }); + } +} diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts new file mode 100644 index 00000000000..ac6e66a2e9f --- /dev/null +++ b/src/phases/obtain-status-effect-phase.ts @@ -0,0 +1,48 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims.js"; +import { getStatusEffectObtainText, getStatusEffectOverlapText } from "#app/data/status-effect.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import Pokemon from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { PokemonPhase } from "./pokemon-phase"; +import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase"; + +export class ObtainStatusEffectPhase extends PokemonPhase { + private statusEffect: StatusEffect | undefined; + private cureTurn: integer | null; + private sourceText: string | null; + private sourcePokemon: Pokemon | null; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, cureTurn?: integer | null, sourceText?: string, sourcePokemon?: Pokemon) { + super(scene, battlerIndex); + + this.statusEffect = statusEffect; + this.cureTurn = cureTurn!; // TODO: is this bang correct? + this.sourceText = sourceText!; // TODO: is this bang correct? + this.sourcePokemon = sourcePokemon!; // For tracking which Pokemon caused the status effect // TODO: is this bang correct? + } + + start() { + const pokemon = this.getPokemon(); + if (!pokemon?.status) { + if (pokemon?.trySetStatus(this.statusEffect, false, this.sourcePokemon)) { + if (this.cureTurn) { + pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct? + } + pokemon.updateInfo(true); + new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, () => { + this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined)); + if (pokemon.status?.isPostTurn()) { + this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex)); + } + this.end(); + }); + return; + } + } else if (pokemon.status.effect === this.statusEffect) { + this.scene.queueMessage(getStatusEffectOverlapText(this.statusEffect, getPokemonNameWithAffix(pokemon))); + } + this.end(); + } +} diff --git a/src/phases/outdated-phase.ts b/src/phases/outdated-phase.ts new file mode 100644 index 00000000000..72d1bb3671d --- /dev/null +++ b/src/phases/outdated-phase.ts @@ -0,0 +1,13 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { Mode } from "#app/ui/ui.js"; + +export class OutdatedPhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start(): void { + this.scene.ui.setMode(Mode.OUTDATED); + } +} diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts new file mode 100644 index 00000000000..d9179826a19 --- /dev/null +++ b/src/phases/party-heal-phase.ts @@ -0,0 +1,40 @@ +import BattleScene from "#app/battle-scene.js"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; + +export class PartyHealPhase extends BattlePhase { + private resumeBgm: boolean; + + constructor(scene: BattleScene, resumeBgm: boolean) { + super(scene); + + this.resumeBgm = resumeBgm; + } + + start() { + super.start(); + + const bgmPlaying = this.scene.isBgmPlaying(); + if (bgmPlaying) { + this.scene.fadeOutBgm(1000, false); + } + this.scene.ui.fadeOut(1000).then(() => { + for (const pokemon of this.scene.getParty()) { + pokemon.hp = pokemon.getMaxHp(); + pokemon.resetStatus(); + for (const move of pokemon.moveset) { + move!.ppUsed = 0; // TODO: is this bang correct? + } + pokemon.updateInfo(true); + } + const healSong = this.scene.playSoundWithoutBgm("heal"); + this.scene.time.delayedCall(Utils.fixedInt(healSong.totalDuration * 1000), () => { + healSong.destroy(); + if (this.resumeBgm && bgmPlaying) { + this.scene.playBgm(); + } + this.scene.ui.fadeIn(500).then(() => this.end()); + }); + }); + } +} diff --git a/src/phases/party-member-pokemon-phase.ts b/src/phases/party-member-pokemon-phase.ts new file mode 100644 index 00000000000..1f27826884e --- /dev/null +++ b/src/phases/party-member-pokemon-phase.ts @@ -0,0 +1,27 @@ +import BattleScene from "#app/battle-scene.js"; +import Pokemon from "#app/field/pokemon.js"; +import { FieldPhase } from "./field-phase"; + +export abstract class PartyMemberPokemonPhase extends FieldPhase { + protected partyMemberIndex: integer; + protected fieldIndex: integer; + protected player: boolean; + + constructor(scene: BattleScene, partyMemberIndex: integer, player: boolean) { + super(scene); + + this.partyMemberIndex = partyMemberIndex; + this.fieldIndex = partyMemberIndex < this.scene.currentBattle.getBattlerCount() + ? partyMemberIndex + : -1; + this.player = player; + } + + getParty(): Pokemon[] { + return this.player ? this.scene.getParty() : this.scene.getEnemyParty(); + } + + getPokemon(): Pokemon { + return this.getParty()[this.partyMemberIndex]; + } +} diff --git a/src/phases/party-status-cure-phase.ts b/src/phases/party-status-cure-phase.ts new file mode 100644 index 00000000000..a11aa01b63a --- /dev/null +++ b/src/phases/party-status-cure-phase.ts @@ -0,0 +1,48 @@ +import BattleScene from "#app/battle-scene.js"; +import { Abilities } from "#app/enums/abilities.js"; +import Pokemon from "#app/field/pokemon.js"; +import { BattlePhase } from "./battle-phase"; +import { ShowAbilityPhase } from "./show-ability-phase"; + +/** + * Cures the party of all non-volatile status conditions, shows a message + * @param {BattleScene} scene The current scene + * @param {Pokemon} user The user of the move that cures the party + * @param {string} message The message that should be displayed + * @param {Abilities} abilityCondition Pokemon with this ability will not be affected ie. Soundproof + */ +export class PartyStatusCurePhase extends BattlePhase { + private user: Pokemon; + private message: string; + private abilityCondition: Abilities; + + constructor(scene: BattleScene, user: Pokemon, message: string, abilityCondition: Abilities) { + super(scene); + + this.user = user; + this.message = message; + this.abilityCondition = abilityCondition; + } + + start() { + super.start(); + for (const pokemon of this.scene.getParty()) { + if (!pokemon.isOnField() || pokemon === this.user) { + pokemon.resetStatus(false); + pokemon.updateInfo(true); + } else { + if (!pokemon.hasAbility(this.abilityCondition)) { + pokemon.resetStatus(); + pokemon.updateInfo(true); + } else { + // Manually show ability bar, since we're not hooked into the targeting system + pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, pokemon.getPassiveAbility()?.id === this.abilityCondition)); + } + } + } + if (this.message) { + this.scene.queueMessage(this.message); + } + this.end(); + } +} diff --git a/src/phases/player-party-member-pokemon-phase.ts b/src/phases/player-party-member-pokemon-phase.ts new file mode 100644 index 00000000000..4b1600b33d2 --- /dev/null +++ b/src/phases/player-party-member-pokemon-phase.ts @@ -0,0 +1,13 @@ +import BattleScene from "#app/battle-scene.js"; +import { PlayerPokemon } from "#app/field/pokemon.js"; +import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; + +export abstract class PlayerPartyMemberPokemonPhase extends PartyMemberPokemonPhase { + constructor(scene: BattleScene, partyMemberIndex: integer) { + super(scene, partyMemberIndex, true); + } + + getPlayerPokemon(): PlayerPokemon { + return super.getPokemon() as PlayerPokemon; + } +} diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts new file mode 100644 index 00000000000..6db8aeb4fca --- /dev/null +++ b/src/phases/pokemon-heal-phase.ts @@ -0,0 +1,104 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { CommonAnim } from "#app/data/battle-anims.js"; +import { getStatusEffectHealText } from "#app/data/status-effect.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { HitResult, DamageResult } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { HealingBoosterModifier } from "#app/modifier/modifier.js"; +import { HealAchv } from "#app/system/achv.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { CommonAnimPhase } from "./common-anim-phase"; + +export class PokemonHealPhase extends CommonAnimPhase { + private hpHealed: integer; + private message: string | null; + private showFullHpMessage: boolean; + private skipAnim: boolean; + private revive: boolean; + private healStatus: boolean; + private preventFullHeal: boolean; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, hpHealed: integer, message: string | null, showFullHpMessage: boolean, skipAnim: boolean = false, revive: boolean = false, healStatus: boolean = false, preventFullHeal: boolean = false) { + super(scene, battlerIndex, undefined, CommonAnim.HEALTH_UP); + + this.hpHealed = hpHealed; + this.message = message; + this.showFullHpMessage = showFullHpMessage; + this.skipAnim = skipAnim; + this.revive = revive; + this.healStatus = healStatus; + this.preventFullHeal = preventFullHeal; + } + + start() { + if (!this.skipAnim && (this.revive || this.getPokemon().hp) && !this.getPokemon().isFullHp()) { + super.start(); + } else { + this.end(); + } + } + + end() { + const pokemon = this.getPokemon(); + + if (!pokemon.isOnField() || (!this.revive && !pokemon.isActive())) { + super.end(); + return; + } + + const hasMessage = !!this.message; + const healOrDamage = (!pokemon.isFullHp() || this.hpHealed < 0); + let lastStatusEffect = StatusEffect.NONE; + + if (healOrDamage) { + const hpRestoreMultiplier = new Utils.IntegerHolder(1); + if (!this.revive) { + this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); + } + const healAmount = new Utils.NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value)); + if (healAmount.value < 0) { + pokemon.damageAndUpdate(healAmount.value * -1, HitResult.HEAL as DamageResult); + healAmount.value = 0; + } + // Prevent healing to full if specified (in case of healing tokens so Sturdy doesn't cause a softlock) + if (this.preventFullHeal && pokemon.hp + healAmount.value >= pokemon.getMaxHp()) { + healAmount.value = (pokemon.getMaxHp() - pokemon.hp) - 1; + } + healAmount.value = pokemon.heal(healAmount.value); + if (healAmount.value) { + this.scene.damageNumberHandler.add(pokemon, healAmount.value, HitResult.HEAL); + } + if (pokemon.isPlayer()) { + this.scene.validateAchvs(HealAchv, healAmount); + if (healAmount.value > this.scene.gameData.gameStats.highestHeal) { + this.scene.gameData.gameStats.highestHeal = healAmount.value; + } + } + if (this.healStatus && !this.revive && pokemon.status) { + lastStatusEffect = pokemon.status.effect; + pokemon.resetStatus(); + } + pokemon.updateInfo().then(() => super.end()); + } else if (this.healStatus && !this.revive && pokemon.status) { + lastStatusEffect = pokemon.status.effect; + pokemon.resetStatus(); + pokemon.updateInfo().then(() => super.end()); + } else if (this.showFullHpMessage) { + this.message = i18next.t("battle:hpIsFull", { pokemonName: getPokemonNameWithAffix(pokemon) }); + } + + if (this.message) { + this.scene.queueMessage(this.message); + } + + if (this.healStatus && lastStatusEffect && !hasMessage) { + this.scene.queueMessage(getStatusEffectHealText(lastStatusEffect, getPokemonNameWithAffix(pokemon))); + } + + if (!healOrDamage && !lastStatusEffect) { + super.end(); + } + } +} diff --git a/src/phases/pokemon-phase.ts b/src/phases/pokemon-phase.ts new file mode 100644 index 00000000000..871ee57d7a5 --- /dev/null +++ b/src/phases/pokemon-phase.ts @@ -0,0 +1,29 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import Pokemon from "#app/field/pokemon.js"; +import { FieldPhase } from "./field-phase"; + +export abstract class PokemonPhase extends FieldPhase { + protected battlerIndex: BattlerIndex | integer; + public player: boolean; + public fieldIndex: integer; + + constructor(scene: BattleScene, battlerIndex?: BattlerIndex | integer) { + super(scene); + + if (battlerIndex === undefined) { + battlerIndex = scene.getField().find(p => p?.isActive())!.getBattlerIndex(); // TODO: is the bang correct here? + } + + this.battlerIndex = battlerIndex; + this.player = battlerIndex < 2; + this.fieldIndex = battlerIndex % 2; + } + + getPokemon(): Pokemon { + if (this.battlerIndex > BattlerIndex.ENEMY_2) { + return this.scene.getPokemonById(this.battlerIndex)!; //TODO: is this bang correct? + } + return this.scene.getField()[this.battlerIndex]!; //TODO: is this bang correct? + } +} diff --git a/src/phases/post-game-over-phase.ts b/src/phases/post-game-over-phase.ts new file mode 100644 index 00000000000..02413b41a23 --- /dev/null +++ b/src/phases/post-game-over-phase.ts @@ -0,0 +1,46 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { EndCardPhase } from "./end-card-phase"; +import { TitlePhase } from "./title-phase"; + +export class PostGameOverPhase extends Phase { + private endCardPhase: EndCardPhase | null; + + constructor(scene: BattleScene, endCardPhase?: EndCardPhase) { + super(scene); + + this.endCardPhase = endCardPhase!; // TODO: is this bang correct? + } + + start() { + super.start(); + + const saveAndReset = () => { + this.scene.gameData.saveAll(this.scene, true, true, true).then(success => { + if (!success) { + return this.scene.reset(true); + } + this.scene.gameData.tryClearSession(this.scene, this.scene.sessionSlotId).then((success: boolean | [boolean, boolean]) => { + if (!success[0]) { + return this.scene.reset(true); + } + this.scene.reset(); + this.scene.unshiftPhase(new TitlePhase(this.scene)); + this.end(); + }); + }); + }; + + if (this.endCardPhase) { + this.scene.ui.fadeOut(500).then(() => { + this.scene.ui.getMessageHandler().bg.setVisible(true); + + this.endCardPhase?.endCard.destroy(); + this.endCardPhase?.text.destroy(); + saveAndReset(); + }); + } else { + saveAndReset(); + } + } +} diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts new file mode 100644 index 00000000000..e671bf30ed1 --- /dev/null +++ b/src/phases/post-summon-phase.ts @@ -0,0 +1,24 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { applyPostSummonAbAttrs, PostSummonAbAttr } from "#app/data/ability.js"; +import { ArenaTrapTag } from "#app/data/arena-tag.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class PostSummonPhase extends PokemonPhase { + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + + if (pokemon.status?.effect === StatusEffect.TOXIC) { + pokemon.status.turnCount = 0; + } + this.scene.arena.applyTags(ArenaTrapTag, pokemon); + applyPostSummonAbAttrs(PostSummonAbAttr, pokemon).then(() => this.end()); + } +} diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts new file mode 100644 index 00000000000..8b533f2e90a --- /dev/null +++ b/src/phases/post-turn-status-effect-phase.ts @@ -0,0 +1,61 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { applyAbAttrs, BlockNonDirectDamageAbAttr, BlockStatusDamageAbAttr, ReduceBurnDamageAbAttr } from "#app/data/ability.js"; +import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims.js"; +import { getStatusEffectActivationText } from "#app/data/status-effect.js"; +import { BattleSpec } from "#app/enums/battle-spec.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import * as Utils from "#app/utils.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class PostTurnStatusEffectPhase extends PokemonPhase { + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); + } + + start() { + const pokemon = this.getPokemon(); + if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) { + pokemon.status.incrementTurn(); + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + applyAbAttrs(BlockStatusDamageAbAttr, pokemon, cancelled); + + if (!cancelled.value) { + this.scene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + const damage = new Utils.NumberHolder(0); + switch (pokemon.status.effect) { + case StatusEffect.POISON: + damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); + break; + case StatusEffect.TOXIC: + damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); + break; + case StatusEffect.BURN: + damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); + applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, damage); + break; + } + if (damage.value) { + // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... + this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true)); + pokemon.updateInfo(); + } + new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end()); + } else { + this.end(); + } + } else { + this.end(); + } + } + + override end() { + if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + this.scene.initFinalBossPhaseTwo(this.getPokemon()); + } else { + super.end(); + } + } +} diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts new file mode 100644 index 00000000000..3d30d36907e --- /dev/null +++ b/src/phases/quiet-form-change-phase.ts @@ -0,0 +1,133 @@ +import BattleScene from "#app/battle-scene.js"; +import { SemiInvulnerableTag } from "#app/data/battler-tags.js"; +import { SpeciesFormChange, getSpeciesFormChangeMessage } from "#app/data/pokemon-forms.js"; +import { getTypeRgb } from "#app/data/type.js"; +import { BattleSpec } from "#app/enums/battle-spec.js"; +import Pokemon, { EnemyPokemon } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { BattlePhase } from "./battle-phase"; +import { MovePhase } from "./move-phase"; +import { PokemonHealPhase } from "./pokemon-heal-phase"; + +export class QuietFormChangePhase extends BattlePhase { + protected pokemon: Pokemon; + protected formChange: SpeciesFormChange; + + constructor(scene: BattleScene, pokemon: Pokemon, formChange: SpeciesFormChange) { + super(scene); + this.pokemon = pokemon; + this.formChange = formChange; + } + + start(): void { + super.start(); + + if (this.pokemon.formIndex === this.pokemon.species.forms.findIndex(f => f.formKey === this.formChange.formKey)) { + return this.end(); + } + + const preName = getPokemonNameWithAffix(this.pokemon); + + if (!this.pokemon.isOnField() || this.pokemon.getTag(SemiInvulnerableTag)) { + this.pokemon.changeForm(this.formChange).then(() => { + this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); + }); + return; + } + + const getPokemonSprite = () => { + const sprite = this.scene.addPokemonSprite(this.pokemon, this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y, "pkmn__sub"); + sprite.setOrigin(0.5, 1); + sprite.play(this.pokemon.getBattleSpriteKey()).stop(); + sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); + [ "spriteColors", "fusionSpriteColors" ].map(k => { + if (this.pokemon.summonData?.speciesForm) { + k += "Base"; + } + sprite.pipelineData[k] = this.pokemon.getSprite().pipelineData[k]; + }); + this.scene.field.add(sprite); + return sprite; + }; + + const [ pokemonTintSprite, pokemonFormTintSprite ] = [ getPokemonSprite(), getPokemonSprite() ]; + + this.pokemon.getSprite().on("animationupdate", (_anim, frame) => { + if (frame.textureKey === pokemonTintSprite.texture.key) { + pokemonTintSprite.setFrame(frame.textureFrame); + } else { + pokemonFormTintSprite.setFrame(frame.textureFrame); + } + }); + + pokemonTintSprite.setAlpha(0); + pokemonTintSprite.setTintFill(0xFFFFFF); + pokemonFormTintSprite.setVisible(false); + pokemonFormTintSprite.setTintFill(0xFFFFFF); + + this.scene.playSound("PRSFX- Transform"); + + this.scene.tweens.add({ + targets: pokemonTintSprite, + alpha: 1, + duration: 1000, + ease: "Cubic.easeIn", + onComplete: () => { + this.pokemon.setVisible(false); + this.pokemon.changeForm(this.formChange).then(() => { + pokemonFormTintSprite.setScale(0.01); + pokemonFormTintSprite.play(this.pokemon.getBattleSpriteKey()).stop(); + pokemonFormTintSprite.setVisible(true); + this.scene.tweens.add({ + targets: pokemonTintSprite, + delay: 250, + scale: 0.01, + ease: "Cubic.easeInOut", + duration: 500, + onComplete: () => pokemonTintSprite.destroy() + }); + this.scene.tweens.add({ + targets: pokemonFormTintSprite, + delay: 250, + scale: this.pokemon.getSpriteScale(), + ease: "Cubic.easeInOut", + duration: 500, + onComplete: () => { + this.pokemon.setVisible(true); + this.scene.tweens.add({ + targets: pokemonFormTintSprite, + delay: 250, + alpha: 0, + ease: "Cubic.easeOut", + duration: 1000, + onComplete: () => { + pokemonTintSprite.setVisible(false); + this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); + } + }); + } + }); + }); + } + }); + } + + end(): void { + if (this.pokemon.scene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon instanceof EnemyPokemon) { + this.scene.playBgm(); + this.scene.unshiftPhase(new PokemonHealPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getMaxHp(), null, false, false, false, true)); + this.pokemon.findAndRemoveTags(() => true); + this.pokemon.bossSegments = 5; + this.pokemon.bossSegmentIndex = 4; + this.pokemon.initBattleInfo(); + this.pokemon.cry(); + + const movePhase = this.scene.findPhase(p => p instanceof MovePhase && p.pokemon === this.pokemon) as MovePhase; + if (movePhase) { + movePhase.cancel(); + } + } + + super.end(); + } +} diff --git a/src/phases/reload-session-phase.ts b/src/phases/reload-session-phase.ts new file mode 100644 index 00000000000..a61c52323bf --- /dev/null +++ b/src/phases/reload-session-phase.ts @@ -0,0 +1,39 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { Mode } from "#app/ui/ui.js"; +import * as Utils from "#app/utils.js"; + +export class ReloadSessionPhase extends Phase { + private systemDataStr: string | null; + + constructor(scene: BattleScene, systemDataStr?: string) { + super(scene); + + this.systemDataStr = systemDataStr ?? null; + } + + start(): void { + this.scene.ui.setMode(Mode.SESSION_RELOAD); + + let delayElapsed = false; + let loaded = false; + + this.scene.time.delayedCall(Utils.fixedInt(1500), () => { + if (loaded) { + this.end(); + } else { + delayElapsed = true; + } + }); + + this.scene.gameData.clearLocalData(); + + (this.systemDataStr ? this.scene.gameData.initSystem(this.systemDataStr) : this.scene.gameData.loadSystem()).then(() => { + if (delayElapsed) { + this.end(); + } else { + loaded = true; + } + }); + } +} diff --git a/src/phases/return-phase.ts b/src/phases/return-phase.ts new file mode 100644 index 00000000000..e1753670ad4 --- /dev/null +++ b/src/phases/return-phase.ts @@ -0,0 +1,26 @@ +import BattleScene from "#app/battle-scene.js"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms.js"; +import { SwitchSummonPhase } from "./switch-summon-phase"; + +export class ReturnPhase extends SwitchSummonPhase { + constructor(scene: BattleScene, fieldIndex: integer) { + super(scene, fieldIndex, -1, true, false); + } + + switchAndSummon(): void { + this.end(); + } + + summon(): void { } + + onEnd(): void { + const pokemon = this.getPokemon(); + + pokemon.resetTurnData(); + pokemon.resetSummonData(); + + this.scene.updateFieldScale(); + + this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger); + } +} diff --git a/src/phases/ribbon-modifier-reward-phase.ts b/src/phases/ribbon-modifier-reward-phase.ts new file mode 100644 index 00000000000..4a80325b7e7 --- /dev/null +++ b/src/phases/ribbon-modifier-reward-phase.ts @@ -0,0 +1,33 @@ +import BattleScene from "#app/battle-scene.js"; +import PokemonSpecies from "#app/data/pokemon-species.js"; +import { ModifierTypeFunc } from "#app/modifier/modifier-type.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { ModifierRewardPhase } from "./modifier-reward-phase"; + +export class RibbonModifierRewardPhase extends ModifierRewardPhase { + private species: PokemonSpecies; + + constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc, species: PokemonSpecies) { + super(scene, modifierTypeFunc); + + this.species = species; + } + + doReward(): Promise { + return new Promise(resolve => { + const newModifier = this.modifierType.newModifier(); + this.scene.addModifier(newModifier).then(() => { + this.scene.playSound("level_up_fanfare"); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:beatModeFirstTime", { + speciesName: this.species.name, + gameMode: this.scene.gameMode.getName(), + newModifier: newModifier?.type.name + }), null, () => { + resolve(); + }, null, true, 1500); + }); + }); + } +} diff --git a/src/phases/scan-ivs-phase.ts b/src/phases/scan-ivs-phase.ts new file mode 100644 index 00000000000..f5e1a814612 --- /dev/null +++ b/src/phases/scan-ivs-phase.ts @@ -0,0 +1,69 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims.js"; +import { Stat } from "#app/enums/stat.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { getTextColor, TextStyle } from "#app/ui/text.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { PokemonPhase } from "./pokemon-phase"; + +export class ScanIvsPhase extends PokemonPhase { + private shownIvs: integer; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, shownIvs: integer) { + super(scene, battlerIndex); + + this.shownIvs = shownIvs; + } + + start() { + super.start(); + + if (!this.shownIvs) { + return this.end(); + } + + const pokemon = this.getPokemon(); + + let enemyIvs: number[] = []; + let statsContainer: Phaser.GameObjects.Sprite[] = []; + let statsContainerLabels: Phaser.GameObjects.Sprite[] = []; + const enemyField = this.scene.getEnemyField(); + const uiTheme = (this.scene as BattleScene).uiTheme; // Assuming uiTheme is accessible + for (let e = 0; e < enemyField.length; e++) { + enemyIvs = enemyField[e].ivs; + const currentIvs = this.scene.gameData.dexData[enemyField[e].species.getRootSpeciesId()].ivs; // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists + const ivsToShow = this.scene.ui.getMessageHandler().getTopIvs(enemyIvs, this.shownIvs); + statsContainer = enemyField[e].getBattleInfo().getStatsValueContainer().list as Phaser.GameObjects.Sprite[]; + statsContainerLabels = statsContainer.filter(m => m.name.indexOf("icon_stat_label") >= 0); + for (let s = 0; s < statsContainerLabels.length; s++) { + const ivStat = Stat[statsContainerLabels[s].frame.name]; + if (enemyIvs[ivStat] > currentIvs[ivStat] && ivsToShow.indexOf(Number(ivStat)) >= 0) { + const hexColour = enemyIvs[ivStat] === 31 ? getTextColor(TextStyle.PERFECT_IV, false, uiTheme) : getTextColor(TextStyle.SUMMARY_GREEN, false, uiTheme); + const hexTextColour = Phaser.Display.Color.HexStringToColor(hexColour).color; + statsContainerLabels[s].setTint(hexTextColour); + } + statsContainerLabels[s].setVisible(true); + } + } + + if (!this.scene.hideIvs) { + this.scene.ui.showText(i18next.t("battle:ivScannerUseQuestion", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { + this.scene.ui.setMode(Mode.CONFIRM, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.clearText(); + new CommonBattleAnim(CommonAnim.LOCK_ON, pokemon, pokemon).play(this.scene, () => { + this.scene.ui.getMessageHandler().promptIvs(pokemon.id, pokemon.ivs, this.shownIvs).then(() => this.end()); + }); + }, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.clearText(); + this.end(); + }); + }); + } else { + this.end(); + } + } +} diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts new file mode 100644 index 00000000000..68c2cd29f26 --- /dev/null +++ b/src/phases/select-biome-phase.ts @@ -0,0 +1,84 @@ +import BattleScene from "#app/battle-scene.js"; +import { biomeLinks, getBiomeName } from "#app/data/biomes.js"; +import { Biome } from "#app/enums/biome.js"; +import { MoneyInterestModifier, MapModifier } from "#app/modifier/modifier.js"; +import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import { BattlePhase } from "./battle-phase"; +import * as Utils from "#app/utils.js"; +import { PartyHealPhase } from "./party-heal-phase"; +import { SwitchBiomePhase } from "./switch-biome-phase"; + +export class SelectBiomePhase extends BattlePhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + const currentBiome = this.scene.arena.biomeType; + + const setNextBiome = (nextBiome: Biome) => { + if (this.scene.currentBattle.waveIndex % 10 === 1) { + this.scene.applyModifiers(MoneyInterestModifier, true, this.scene); + this.scene.unshiftPhase(new PartyHealPhase(this.scene, false)); + } + this.scene.unshiftPhase(new SwitchBiomePhase(this.scene, nextBiome)); + this.end(); + }; + + if ((this.scene.gameMode.isClassic && this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex + 9)) + || (this.scene.gameMode.isDaily && this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) + || (this.scene.gameMode.hasShortBiomes && !(this.scene.currentBattle.waveIndex % 50))) { + setNextBiome(Biome.END); + } else if (this.scene.gameMode.hasRandomBiomes) { + setNextBiome(this.generateNextBiome()); + } else if (Array.isArray(biomeLinks[currentBiome])) { + let biomes: Biome[] = []; + this.scene.executeWithSeedOffset(() => { + biomes = (biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) + .filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1])) + .map(b => !Array.isArray(b) ? b : b[0]); + }, this.scene.currentBattle.waveIndex); + if (biomes.length > 1 && this.scene.findModifier(m => m instanceof MapModifier)) { + let biomeChoices: Biome[] = []; + this.scene.executeWithSeedOffset(() => { + biomeChoices = (!Array.isArray(biomeLinks[currentBiome]) + ? [biomeLinks[currentBiome] as Biome] + : biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) + .filter((b, i) => !Array.isArray(b) || !Utils.randSeedInt(b[1])) + .map(b => Array.isArray(b) ? b[0] : b); + }, this.scene.currentBattle.waveIndex); + const biomeSelectItems = biomeChoices.map(b => { + const ret: OptionSelectItem = { + label: getBiomeName(b), + handler: () => { + this.scene.ui.setMode(Mode.MESSAGE); + setNextBiome(b); + return true; + } + }; + return ret; + }); + this.scene.ui.setMode(Mode.OPTION_SELECT, { + options: biomeSelectItems, + delay: 1000 + }); + } else { + setNextBiome(biomes[Utils.randSeedInt(biomes.length)]); + } + } else if (biomeLinks.hasOwnProperty(currentBiome)) { + setNextBiome(biomeLinks[currentBiome] as Biome); + } else { + setNextBiome(this.generateNextBiome()); + } + } + + generateNextBiome(): Biome { + if (!(this.scene.currentBattle.waveIndex % 50)) { + return Biome.END; + } + return this.scene.generateRandomBiome(this.scene.currentBattle.waveIndex); + } +} diff --git a/src/phases/select-challenge-phase.ts b/src/phases/select-challenge-phase.ts new file mode 100644 index 00000000000..eaf830e0059 --- /dev/null +++ b/src/phases/select-challenge-phase.ts @@ -0,0 +1,17 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { Mode } from "#app/ui/ui.js"; + +export class SelectChallengePhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.playBgm("menu"); + + this.scene.ui.setMode(Mode.CHALLENGE_SELECT); + } +} diff --git a/src/phases/select-gender-phase.ts b/src/phases/select-gender-phase.ts new file mode 100644 index 00000000000..3fc6916e233 --- /dev/null +++ b/src/phases/select-gender-phase.ts @@ -0,0 +1,46 @@ +import BattleScene from "#app/battle-scene.js"; +import { PlayerGender } from "#app/enums/player-gender.js"; +import { Phase } from "#app/phase.js"; +import { SettingKeys } from "#app/system/settings/settings.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; + +export class SelectGenderPhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start(): void { + super.start(); + + this.scene.ui.showText(i18next.t("menu:boyOrGirl"), null, () => { + this.scene.ui.setMode(Mode.OPTION_SELECT, { + options: [ + { + label: i18next.t("settings:boy"), + handler: () => { + this.scene.gameData.gender = PlayerGender.MALE; + this.scene.gameData.saveSetting(SettingKeys.Player_Gender, 0); + this.scene.gameData.saveSystem().then(() => this.end()); + return true; + } + }, + { + label: i18next.t("settings:girl"), + handler: () => { + this.scene.gameData.gender = PlayerGender.FEMALE; + this.scene.gameData.saveSetting(SettingKeys.Player_Gender, 1); + this.scene.gameData.saveSystem().then(() => this.end()); + return true; + } + } + ] + }); + }); + } + + end(): void { + this.scene.ui.setMode(Mode.MESSAGE); + super.end(); + } +} diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts new file mode 100644 index 00000000000..67ae904fb58 --- /dev/null +++ b/src/phases/select-modifier-phase.ts @@ -0,0 +1,234 @@ +import BattleScene from "#app/battle-scene.js"; +import { ModifierTier } from "#app/modifier/modifier-tier.js"; +import { regenerateModifierPoolThresholds, ModifierTypeOption, ModifierType, getPlayerShopModifierTypeOptionsForWave, PokemonModifierType, FusePokemonModifierType, PokemonMoveModifierType, TmModifierType, RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions } from "#app/modifier/modifier-type.js"; +import { ExtraModifierModifier, Modifier, PokemonHeldItemModifier } from "#app/modifier/modifier.js"; +import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler.js"; +import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; +import Overrides from "#app/overrides"; + +export class SelectModifierPhase extends BattlePhase { + private rerollCount: integer; + private modifierTiers: ModifierTier[]; + + constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[]) { + super(scene); + + this.rerollCount = rerollCount; + this.modifierTiers = modifierTiers!; // TODO: is this bang correct? + } + + start() { + super.start(); + + if (!this.rerollCount) { + this.updateSeed(); + } else { + this.scene.reroll = false; + } + + const party = this.scene.getParty(); + regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); + const modifierCount = new Utils.IntegerHolder(3); + if (this.isPlayer()) { + this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); + } + const typeOptions: ModifierTypeOption[] = this.getModifierTypeOptions(modifierCount.value); + + const modifierSelectCallback = (rowCursor: integer, cursor: integer) => { + if (rowCursor < 0 || cursor < 0) { + this.scene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { + this.scene.ui.setOverlayMode(Mode.CONFIRM, () => { + this.scene.ui.revertMode(); + this.scene.ui.setMode(Mode.MESSAGE); + super.end(); + }, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers))); + }); + return false; + } + let modifierType: ModifierType; + let cost: integer; + switch (rowCursor) { + case 0: + switch (cursor) { + case 0: + const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers); + if (this.scene.money < rerollCost) { + this.scene.ui.playError(); + return false; + } else { + this.scene.reroll = true; + this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + this.scene.money -= rerollCost; + this.scene.updateMoneyText(); + this.scene.animateMoneyChanged(false); + } + this.scene.playSound("buy"); + } + break; + case 1: + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { + if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { + const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + && m.isTransferrable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; + const itemModifier = itemModifiers[itemIndex]; + this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); + } else { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + } + }, PartyUiHandler.FilterItemMaxStacks); + break; + case 2: + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + }); + break; + case 3: + this.scene.lockModifierTiers = !this.scene.lockModifierTiers; + const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; + uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + uiHandler.updateLockRaritiesText(); + uiHandler.updateRerollCostText(); + return false; + } + return true; + case 1: + if (typeOptions[cursor].type) { + modifierType = typeOptions[cursor].type; + } + break; + default: + const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); + const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; + if (shopOption.type) { + modifierType = shopOption.type; + } + cost = shopOption.cost; + break; + } + + if (cost! && (this.scene.money < cost) && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { // TODO: is the bang on cost correct? + this.scene.ui.playError(); + return false; + } + + const applyModifier = (modifier: Modifier, playSound: boolean = false) => { + const result = this.scene.addModifier(modifier, false, playSound); + if (cost) { + result.then(success => { + if (success) { + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + this.scene.money -= cost; + this.scene.updateMoneyText(); + this.scene.animateMoneyChanged(false); + } + this.scene.playSound("buy"); + (this.scene.ui.getHandler() as ModifierSelectUiHandler).updateCostText(); + } else { + this.scene.ui.playError(); + } + }); + } else { + const doEnd = () => { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.MESSAGE); + super.end(); + }; + if (result instanceof Promise) { + result.then(() => doEnd()); + } else { + doEnd(); + } + } + }; + + if (modifierType! instanceof PokemonModifierType) { //TODO: is the bang correct? + if (modifierType instanceof FusePokemonModifierType) { + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.SPLICE, -1, (fromSlotIndex: integer, spliceSlotIndex: integer) => { + if (spliceSlotIndex !== undefined && fromSlotIndex < 6 && spliceSlotIndex < 6 && fromSlotIndex !== spliceSlotIndex) { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { + const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct? + applyModifier(modifier, true); + }); + } else { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + } + }, modifierType.selectFilter); + } else { + const pokemonModifierType = modifierType as PokemonModifierType; + const isMoveModifier = modifierType instanceof PokemonMoveModifierType; + const isTmModifier = modifierType instanceof TmModifierType; + const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType; + const isPpRestoreModifier = (modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType); + const partyUiMode = isMoveModifier ? PartyUiMode.MOVE_MODIFIER + : isTmModifier ? PartyUiMode.TM_MODIFIER + : isRememberMoveModifier ? PartyUiMode.REMEMBER_MOVE_MODIFIER + : PartyUiMode.MODIFIER; + const tmMoveId = isTmModifier + ? (modifierType as TmModifierType).moveId + : undefined; + this.scene.ui.setModeWithoutClear(Mode.PARTY, partyUiMode, -1, (slotIndex: integer, option: PartyOption) => { + if (slotIndex < 6) { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { + const modifier = !isMoveModifier + ? !isRememberMoveModifier + ? modifierType.newModifier(party[slotIndex]) + : modifierType.newModifier(party[slotIndex], option as integer) + : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); + applyModifier(modifier!, true); // TODO: is the bang correct? + }); + } else { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + } + }, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined, tmMoveId, isPpRestoreModifier); + } + } else { + applyModifier(modifierType!.newModifier()!); // TODO: is the bang correct? + } + + return !cost!;// TODO: is the bang correct? + }; + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + } + + updateSeed(): void { + this.scene.resetSeed(); + } + + isPlayer(): boolean { + return true; + } + + getRerollCost(typeOptions: ModifierTypeOption[], lockRarities: boolean): integer { + let baseValue = 0; + if (Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + return baseValue; + } else if (lockRarities) { + const tierValues = [50, 125, 300, 750, 2000]; + for (const opt of typeOptions) { + baseValue += tierValues[opt.type.tier ?? 0]; + } + } else { + baseValue = 250; + } + return Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount), Number.MAX_SAFE_INTEGER); + } + + getPoolType(): ModifierPoolType { + return ModifierPoolType.PLAYER; + } + + getModifierTypeOptions(modifierCount: integer): ModifierTypeOption[] { + return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined); + } + + addModifier(modifier: Modifier): Promise { + return this.scene.addModifier(modifier, false, true); + } +} diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts new file mode 100644 index 00000000000..ad972a49225 --- /dev/null +++ b/src/phases/select-starter-phase.ts @@ -0,0 +1,112 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyChallenges, ChallengeType } from "#app/data/challenge.js"; +import { Gender } from "#app/data/gender.js"; +import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms.js"; +import { getPokemonSpecies } from "#app/data/pokemon-species.js"; +import { Species } from "#app/enums/species.js"; +import { PlayerPokemon } from "#app/field/pokemon.js"; +import { overrideModifiers, overrideHeldItems } from "#app/modifier/modifier.js"; +import { Phase } from "#app/phase.js"; +import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler.js"; +import { Starter } from "#app/ui/starter-select-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; +import { TitlePhase } from "./title-phase"; +import Overrides from "#app/overrides"; + +export class SelectStarterPhase extends Phase { + + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.playBgm("menu"); + + this.scene.ui.setMode(Mode.STARTER_SELECT, (starters: Starter[]) => { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { + if (slotId === -1) { + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + return this.end(); + } + this.scene.sessionSlotId = slotId; + this.initBattle(starters); + }); + }); + } + + /** + * Initialize starters before starting the first battle + * @param starters {@linkcode Pokemon} with which to start the first battle + */ + initBattle(starters: Starter[]) { + const party = this.scene.getParty(); + const loadPokemonAssets: Promise[] = []; + starters.forEach((starter: Starter, i: integer) => { + if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { + starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species); + } + const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); + let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); + if ( + starter.species.speciesId in Overrides.STARTER_FORM_OVERRIDES && + starter.species.forms[Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!] + ) { + starterFormIndex = Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!; + } + + let starterGender = starter.species.malePercent !== null + ? !starterProps.female ? Gender.MALE : Gender.FEMALE + : Gender.GENDERLESS; + if (Overrides.GENDER_OVERRIDE !== null) { + starterGender = Overrides.GENDER_OVERRIDE; + } + const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0); + const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature); + starter.moveset && starterPokemon.tryPopulateMoveset(starter.moveset); + if (starter.passive) { + starterPokemon.passive = true; + } + starterPokemon.luck = this.scene.gameData.getDexAttrLuck(this.scene.gameData.dexData[starter.species.speciesId].caughtAttr); + if (starter.pokerus) { + starterPokemon.pokerus = true; + } + + if (starter.nickname) { + starterPokemon.nickname = starter.nickname; + } + + if (this.scene.gameMode.isSplicedOnly) { + starterPokemon.generateFusionSpecies(true); + } + starterPokemon.setVisible(false); + applyChallenges(this.scene.gameMode, ChallengeType.STARTER_MODIFY, starterPokemon); + party.push(starterPokemon); + loadPokemonAssets.push(starterPokemon.loadAssets()); + }); + overrideModifiers(this.scene); + overrideHeldItems(this.scene, party[0]); + Promise.all(loadPokemonAssets).then(() => { + SoundFade.fadeOut(this.scene, this.scene.sound.get("menu"), 500, true); + this.scene.time.delayedCall(500, () => this.scene.playBgm()); + if (this.scene.gameMode.isClassic) { + this.scene.gameData.gameStats.classicSessionsPlayed++; + } else { + this.scene.gameData.gameStats.endlessSessionsPlayed++; + } + this.scene.newBattle(); + this.scene.arena.init(); + this.scene.sessionPlayTime = 0; + this.scene.lastSavePlayTime = 0; + // Ensures Keldeo (or any future Pokemon that have this type of form change) starts in the correct form + this.scene.getParty().forEach((p: PlayerPokemon) => { + this.scene.triggerPokemonFormChange(p, SpeciesFormChangeMoveLearnedTrigger); + }); + this.end(); + }); + } +} diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts new file mode 100644 index 00000000000..fe72335e312 --- /dev/null +++ b/src/phases/select-target-phase.ts @@ -0,0 +1,32 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { Command } from "#app/ui/command-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import { CommandPhase } from "./command-phase"; +import { PokemonPhase } from "./pokemon-phase"; + +export class SelectTargetPhase extends PokemonPhase { + constructor(scene: BattleScene, fieldIndex: integer) { + super(scene, fieldIndex); + } + + start() { + super.start(); + + const turnCommand = this.scene.currentBattle.turnCommands[this.fieldIndex]; + const move = turnCommand?.move?.move; + this.scene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (targets: BattlerIndex[]) => { + this.scene.ui.setMode(Mode.MESSAGE); + if (targets.length < 1) { + this.scene.currentBattle.turnCommands[this.fieldIndex] = null; + this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex)); + } else { + turnCommand!.targets = targets; //TODO: is the bang correct here? + } + if (turnCommand?.command === Command.BALL && this.fieldIndex) { + this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; //TODO: is the bang correct here? + } + this.end(); + }); + } +} diff --git a/src/phases/shiny-sparkle-phase.ts b/src/phases/shiny-sparkle-phase.ts new file mode 100644 index 00000000000..4cd2b68f881 --- /dev/null +++ b/src/phases/shiny-sparkle-phase.ts @@ -0,0 +1,16 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class ShinySparklePhase extends PokemonPhase { + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); + } + + start() { + super.start(); + + this.getPokemon().sparkle(); + this.scene.time.delayedCall(1000, () => this.end()); + } +} diff --git a/src/phases/show-ability-phase.ts b/src/phases/show-ability-phase.ts new file mode 100644 index 00000000000..ee0b98f7886 --- /dev/null +++ b/src/phases/show-ability-phase.ts @@ -0,0 +1,29 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class ShowAbilityPhase extends PokemonPhase { + private passive: boolean; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, passive: boolean = false) { + super(scene, battlerIndex); + + this.passive = passive; + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + + if (pokemon) { + this.scene.abilityBar.showAbility(pokemon, this.passive); + + if (pokemon?.battleData) { + pokemon.battleData.abilityRevealed = true; + } + } + + this.end(); + } +} diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts new file mode 100644 index 00000000000..9920472e801 --- /dev/null +++ b/src/phases/show-party-exp-bar-phase.ts @@ -0,0 +1,56 @@ +import BattleScene from "#app/battle-scene.js"; +import { ExpNotification } from "#app/enums/exp-notification.js"; +import { ExpBoosterModifier } from "#app/modifier/modifier.js"; +import * as Utils from "#app/utils.js"; +import { HidePartyExpBarPhase } from "./hide-party-exp-bar-phase"; +import { LevelUpPhase } from "./level-up-phase"; +import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; + +export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { + private expValue: number; + + constructor(scene: BattleScene, partyMemberIndex: integer, expValue: number) { + super(scene, partyMemberIndex); + + this.expValue = expValue; + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + const exp = new Utils.NumberHolder(this.expValue); + this.scene.applyModifiers(ExpBoosterModifier, true, exp); + exp.value = Math.floor(exp.value); + + const lastLevel = pokemon.level; + pokemon.addExp(exp.value); + const newLevel = pokemon.level; + if (newLevel > lastLevel) { + this.scene.unshiftPhase(new LevelUpPhase(this.scene, this.partyMemberIndex, lastLevel, newLevel)); + } + this.scene.unshiftPhase(new HidePartyExpBarPhase(this.scene)); + pokemon.updateInfo(); + + if (this.scene.expParty === ExpNotification.SKIP) { + this.end(); + } else if (this.scene.expParty === ExpNotification.ONLY_LEVEL_UP) { + if (newLevel > lastLevel) { // this means if we level up + // instead of displaying the exp gain in the small frame, we display the new level + // we use the same method for mode 0 & 1, by giving a parameter saying to display the exp or the level + this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, this.scene.expParty === ExpNotification.ONLY_LEVEL_UP, newLevel).then(() => { + setTimeout(() => this.end(), 800 / Math.pow(2, this.scene.expGainsSpeed)); + }); + } else { + this.end(); + } + } else if (this.scene.expGainsSpeed < 3) { + this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, false, newLevel).then(() => { + setTimeout(() => this.end(), 500 / Math.pow(2, this.scene.expGainsSpeed)); + }); + } else { + this.end(); + } + + } +} diff --git a/src/phases/show-trainer-phase.ts b/src/phases/show-trainer-phase.ts new file mode 100644 index 00000000000..8a869f582d8 --- /dev/null +++ b/src/phases/show-trainer-phase.ts @@ -0,0 +1,24 @@ +import BattleScene from "#app/battle-scene.js"; +import { PlayerGender } from "#app/enums/player-gender.js"; +import { BattlePhase } from "./battle-phase"; + +export class ShowTrainerPhase extends BattlePhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.trainer.setVisible(true); + + this.scene.trainer.setTexture(`trainer_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); + + this.scene.tweens.add({ + targets: this.scene.trainer, + x: 106, + duration: 1000, + onComplete: () => this.end() + }); + } +} diff --git a/src/phases/stat-change-phase.ts b/src/phases/stat-change-phase.ts new file mode 100644 index 00000000000..3469cc62942 --- /dev/null +++ b/src/phases/stat-change-phase.ts @@ -0,0 +1,234 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { applyPreStatChangeAbAttrs, ProtectStatAbAttr, applyAbAttrs, StatChangeMultiplierAbAttr, StatChangeCopyAbAttr, applyPostStatChangeAbAttrs, PostStatChangeAbAttr } from "#app/data/ability.js"; +import { MistTag, ArenaTagSide } from "#app/data/arena-tag.js"; +import { BattleStat, getBattleStatName, getBattleStatLevelChangeDescription } from "#app/data/battle-stat.js"; +import Pokemon from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { PokemonResetNegativeStatStageModifier } from "#app/modifier/modifier.js"; +import { handleTutorial, Tutorial } from "#app/tutorial.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export type StatChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void; + +export class StatChangePhase extends PokemonPhase { + private stats: BattleStat[]; + private selfTarget: boolean; + private levels: integer; + private showMessage: boolean; + private ignoreAbilities: boolean; + private canBeCopied: boolean; + private onChange: StatChangeCallback | null; + + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: integer, showMessage: boolean = true, ignoreAbilities: boolean = false, canBeCopied: boolean = true, onChange: StatChangeCallback | null = null) { + super(scene, battlerIndex); + + this.selfTarget = selfTarget; + this.stats = stats; + this.levels = levels; + this.showMessage = showMessage; + this.ignoreAbilities = ignoreAbilities; + this.canBeCopied = canBeCopied; + this.onChange = onChange; + } + + start() { + const pokemon = this.getPokemon(); + + let random = false; + + if (this.stats.length === 1 && this.stats[0] === BattleStat.RAND) { + this.stats[0] = this.getRandomStat(); + random = true; + } + + this.aggregateStatChanges(random); + + if (!pokemon.isActive(true)) { + return this.end(); + } + + const filteredStats = this.stats.map(s => s !== BattleStat.RAND ? s : this.getRandomStat()).filter(stat => { + const cancelled = new Utils.BooleanHolder(false); + + if (!this.selfTarget && this.levels < 0) { + this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled); + } + + if (!cancelled.value && !this.selfTarget && this.levels < 0) { + applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled); + } + + return !cancelled.value; + }); + + const levels = new Utils.IntegerHolder(this.levels); + + if (!this.ignoreAbilities) { + applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, levels); + } + + const battleStats = this.getPokemon().summonData.battleStats; + const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats![stat] + levels.value, 6) : Math.max(battleStats![stat] + levels.value, -6)) - battleStats![stat]); + + this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels); + + const end = () => { + if (this.showMessage) { + const messages = this.getStatChangeMessages(filteredStats, levels.value, relLevels); + for (const message of messages) { + this.scene.queueMessage(message); + } + } + + for (const stat of filteredStats) { + pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6); + } + + if (levels.value > 0 && this.canBeCopied) { + for (const opponent of pokemon.getOpponents()) { + applyAbAttrs(StatChangeCopyAbAttr, opponent, null, this.stats, levels.value); + } + } + + applyPostStatChangeAbAttrs(PostStatChangeAbAttr, pokemon, filteredStats, this.levels, this.selfTarget); + + // Look for any other stat change phases; if this is the last one, do White Herb check + const existingPhase = this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex); + if (!(existingPhase instanceof StatChangePhase)) { + // Apply White Herb if needed + const whiteHerb = this.scene.applyModifier(PokemonResetNegativeStatStageModifier, this.player, pokemon) as PokemonResetNegativeStatStageModifier; + // If the White Herb was applied, consume it + if (whiteHerb) { + --whiteHerb.stackCount; + if (whiteHerb.stackCount <= 0) { + this.scene.removeModifier(whiteHerb); + } + this.scene.updateModifiers(this.player); + } + } + + pokemon.updateInfo(); + + handleTutorial(this.scene, Tutorial.Stat_Change).then(() => super.end()); + }; + + if (relLevels.filter(l => l).length && this.scene.moveAnimations) { + pokemon.enableMask(); + const pokemonMaskSprite = pokemon.maskSprite; + + const tileX = (this.player ? 106 : 236) * pokemon.getSpriteScale() * this.scene.field.scale; + const tileY = ((this.player ? 148 : 84) + (levels.value >= 1 ? 160 : 0)) * pokemon.getSpriteScale() * this.scene.field.scale; + const tileWidth = 156 * this.scene.field.scale * pokemon.getSpriteScale(); + const tileHeight = 316 * this.scene.field.scale * pokemon.getSpriteScale(); + + // On increase, show the red sprite located at ATK + // On decrease, show the blue sprite located at SPD + const spriteColor = levels.value >= 1 ? BattleStat[BattleStat.ATK].toLowerCase() : BattleStat[BattleStat.SPD].toLowerCase(); + const statSprite = this.scene.add.tileSprite(tileX, tileY, tileWidth, tileHeight, "battle_stats", spriteColor); + statSprite.setPipeline(this.scene.fieldSpritePipeline); + statSprite.setAlpha(0); + statSprite.setScale(6); + statSprite.setOrigin(0.5, 1); + + this.scene.playSound(`stat_${levels.value >= 1 ? "up" : "down"}`); + + statSprite.setMask(new Phaser.Display.Masks.BitmapMask(this.scene, pokemonMaskSprite ?? undefined)); + + this.scene.tweens.add({ + targets: statSprite, + duration: 250, + alpha: 0.8375, + onComplete: () => { + this.scene.tweens.add({ + targets: statSprite, + delay: 1000, + duration: 250, + alpha: 0 + }); + } + }); + + this.scene.tweens.add({ + targets: statSprite, + duration: 1500, + y: `${levels.value >= 1 ? "-" : "+"}=${160 * 6}` + }); + + this.scene.time.delayedCall(1750, () => { + pokemon.disableMask(); + end(); + }); + } else { + end(); + } + } + + getRandomStat(): BattleStat { + const allStats = Utils.getEnumValues(BattleStat); + return this.getPokemon() ? allStats[this.getPokemon()!.randSeedInt(BattleStat.SPD + 1)] : BattleStat.ATK; // TODO: return default ATK on random? idk... + } + + aggregateStatChanges(random: boolean = false): void { + const isAccEva = [BattleStat.ACC, BattleStat.EVA].some(s => this.stats.includes(s)); + let existingPhase: StatChangePhase; + if (this.stats.length === 1) { + while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.stats.length === 1 + && (p.stats[0] === this.stats[0] || (random && p.stats[0] === BattleStat.RAND)) + && p.selfTarget === this.selfTarget && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) { + if (existingPhase.stats[0] === BattleStat.RAND) { + existingPhase.stats[0] = this.getRandomStat(); + if (existingPhase.stats[0] !== this.stats[0]) { + continue; + } + } + this.levels += existingPhase.levels; + + if (!this.scene.tryRemovePhase(p => p === existingPhase)) { + break; + } + } + } + while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget + && ([BattleStat.ACC, BattleStat.EVA].some(s => p.stats.includes(s)) === isAccEva) + && p.levels === this.levels && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) { + this.stats.push(...existingPhase.stats); + if (!this.scene.tryRemovePhase(p => p === existingPhase)) { + break; + } + } + } + + getStatChangeMessages(stats: BattleStat[], levels: integer, relLevels: integer[]): string[] { + const messages: string[] = []; + + const relLevelStatIndexes = {}; + for (let rl = 0; rl < relLevels.length; rl++) { + const relLevel = relLevels[rl]; + if (!relLevelStatIndexes[relLevel]) { + relLevelStatIndexes[relLevel] = []; + } + relLevelStatIndexes[relLevel].push(rl); + } + + Object.keys(relLevelStatIndexes).forEach(rl => { + const relLevelStats = stats.filter((_, i) => relLevelStatIndexes[rl].includes(i)); + let statsFragment = ""; + + if (relLevelStats.length > 1) { + statsFragment = relLevelStats.length >= 5 + ? i18next.t("battle:stats") + : `${relLevelStats.slice(0, -1).map(s => getBattleStatName(s)).join(", ")}${relLevelStats.length > 2 ? "," : ""} ${i18next.t("battle:statsAnd")} ${getBattleStatName(relLevelStats[relLevelStats.length - 1])}`; + messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1,relLevelStats.length)); + } else { + statsFragment = getBattleStatName(relLevelStats[0]); + messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1,relLevelStats.length)); + } + }); + + return messages; + } +} diff --git a/src/phases/summon-missing-phase.ts b/src/phases/summon-missing-phase.ts new file mode 100644 index 00000000000..bb9607285ad --- /dev/null +++ b/src/phases/summon-missing-phase.ts @@ -0,0 +1,15 @@ +import BattleScene from "#app/battle-scene.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import i18next from "i18next"; +import { SummonPhase } from "./summon-phase"; + +export class SummonMissingPhase extends SummonPhase { + constructor(scene: BattleScene, fieldIndex: integer) { + super(scene, fieldIndex); + } + + preSummon(): void { + this.scene.ui.showText(i18next.t("battle:sendOutPokemon", { pokemonName: getPokemonNameWithAffix(this.getPokemon()) })); + this.scene.time.delayedCall(250, () => this.summon()); + } +} diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts new file mode 100644 index 00000000000..50424170ea7 --- /dev/null +++ b/src/phases/summon-phase.ts @@ -0,0 +1,194 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattleType } from "#app/battle.js"; +import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball.js"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms.js"; +import { TrainerSlot } from "#app/data/trainer-config.js"; +import { PlayerGender } from "#app/enums/player-gender.js"; +import { addPokeballOpenParticles } from "#app/field/anims.js"; +import Pokemon, { FieldPosition } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import i18next from "i18next"; +import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; +import { PostSummonPhase } from "./post-summon-phase"; +import { GameOverPhase } from "./game-over-phase"; +import { ShinySparklePhase } from "./shiny-sparkle-phase"; + +export class SummonPhase extends PartyMemberPokemonPhase { + private loaded: boolean; + + constructor(scene: BattleScene, fieldIndex: integer, player: boolean = true, loaded: boolean = false) { + super(scene, fieldIndex, player); + + this.loaded = loaded; + } + + start() { + super.start(); + + this.preSummon(); + } + + /** + * Sends out a Pokemon before the battle begins and shows the appropriate messages + */ + preSummon(): void { + const partyMember = this.getPokemon(); + // If the Pokemon about to be sent out is fainted or illegal under a challenge, switch to the first non-fainted legal Pokemon + if (!partyMember.isAllowedInBattle()) { + console.warn("The Pokemon about to be sent out is fainted or illegal under a challenge. Attempting to resolve..."); + + // First check if they're somehow still in play, if so remove them. + if (partyMember.isOnField()) { + partyMember.leaveField(); + } + + const party = this.getParty(); + + // Find the first non-fainted Pokemon index above the current one + const legalIndex = party.findIndex((p, i) => i > this.partyMemberIndex && p.isAllowedInBattle()); + if (legalIndex === -1) { + console.error("Party Details:\n", party); + console.error("All available Pokemon were fainted or illegal!"); + this.scene.clearPhaseQueue(); + this.scene.unshiftPhase(new GameOverPhase(this.scene)); + this.end(); + return; + } + + // Swaps the fainted Pokemon and the first non-fainted legal Pokemon in the party + [party[this.partyMemberIndex], party[legalIndex]] = [party[legalIndex], party[this.partyMemberIndex]]; + console.warn("Swapped %s %O with %s %O", getPokemonNameWithAffix(partyMember), partyMember, getPokemonNameWithAffix(party[0]), party[0]); + } + + if (this.player) { + this.scene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(this.getPokemon()) })); + if (this.player) { + this.scene.pbTray.hide(); + } + this.scene.trainer.setTexture(`trainer_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); + this.scene.time.delayedCall(562, () => { + this.scene.trainer.setFrame("2"); + this.scene.time.delayedCall(64, () => { + this.scene.trainer.setFrame("3"); + }); + }); + this.scene.tweens.add({ + targets: this.scene.trainer, + x: -36, + duration: 1000, + onComplete: () => this.scene.trainer.setVisible(false) + }); + this.scene.time.delayedCall(750, () => this.summon()); + } else { + const trainerName = this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); + const pokemonName = this.getPokemon().getNameToRender(); + const message = i18next.t("battle:trainerSendOut", { trainerName, pokemonName }); + + this.scene.pbTrayEnemy.hide(); + this.scene.ui.showText(message, null, () => this.summon()); + } + } + + summon(): void { + const pokemon = this.getPokemon(); + + const pokeball = this.scene.addFieldSprite(this.player ? 36 : 248, this.player ? 80 : 44, "pb", getPokeballAtlasKey(pokemon.pokeball)); + pokeball.setVisible(false); + pokeball.setOrigin(0.5, 0.625); + this.scene.field.add(pokeball); + + if (this.fieldIndex === 1) { + pokemon.setFieldPosition(FieldPosition.RIGHT, 0); + } else { + const availablePartyMembers = this.getParty().filter(p => p.isAllowedInBattle()).length; + pokemon.setFieldPosition(!this.scene.currentBattle.double || availablePartyMembers === 1 ? FieldPosition.CENTER : FieldPosition.LEFT); + } + + const fpOffset = pokemon.getFieldPositionOffset(); + + pokeball.setVisible(true); + + this.scene.tweens.add({ + targets: pokeball, + duration: 650, + x: (this.player ? 100 : 236) + fpOffset[0] + }); + + this.scene.tweens.add({ + targets: pokeball, + duration: 150, + ease: "Cubic.easeOut", + y: (this.player ? 70 : 34) + fpOffset[1], + onComplete: () => { + this.scene.tweens.add({ + targets: pokeball, + duration: 500, + ease: "Cubic.easeIn", + angle: 1440, + y: (this.player ? 132 : 86) + fpOffset[1], + onComplete: () => { + this.scene.playSound("pb_rel"); + pokeball.destroy(); + this.scene.add.existing(pokemon); + this.scene.field.add(pokemon); + if (!this.player) { + const playerPokemon = this.scene.getPlayerPokemon() as Pokemon; + if (playerPokemon?.visible) { + this.scene.field.moveBelow(pokemon, playerPokemon); + } + this.scene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); + } + addPokeballOpenParticles(this.scene, pokemon.x, pokemon.y - 16, pokemon.pokeball); + this.scene.updateModifiers(this.player); + this.scene.updateFieldScale(); + pokemon.showInfo(); + pokemon.playAnim(); + pokemon.setVisible(true); + pokemon.getSprite().setVisible(true); + pokemon.setScale(0.5); + pokemon.tint(getPokeballTintColor(pokemon.pokeball)); + pokemon.untint(250, "Sine.easeIn"); + this.scene.updateFieldScale(); + this.scene.tweens.add({ + targets: pokemon, + duration: 250, + ease: "Sine.easeIn", + scale: pokemon.getSpriteScale(), + onComplete: () => { + pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); + pokemon.getSprite().clearTint(); + pokemon.resetSummonData(); + this.scene.time.delayedCall(1000, () => this.end()); + } + }); + } + }); + } + }); + } + + onEnd(): void { + const pokemon = this.getPokemon(); + + if (pokemon.isShiny()) { + this.scene.unshiftPhase(new ShinySparklePhase(this.scene, pokemon.getBattlerIndex())); + } + + pokemon.resetTurnData(); + + if (!this.loaded || this.scene.currentBattle.battleType === BattleType.TRAINER || (this.scene.currentBattle.waveIndex % 10) === 1) { + this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); + this.queuePostSummon(); + } + } + + queuePostSummon(): void { + this.scene.pushPhase(new PostSummonPhase(this.scene, this.getPokemon().getBattlerIndex())); + } + + end() { + this.onEnd(); + + super.end(); + } +} diff --git a/src/phases/switch-biome-phase.ts b/src/phases/switch-biome-phase.ts new file mode 100644 index 00000000000..f20cd59b240 --- /dev/null +++ b/src/phases/switch-biome-phase.ts @@ -0,0 +1,65 @@ +import BattleScene from "#app/battle-scene.js"; +import { Biome } from "#app/enums/biome.js"; +import { getBiomeKey } from "#app/field/arena.js"; +import { BattlePhase } from "./battle-phase"; + +export class SwitchBiomePhase extends BattlePhase { + private nextBiome: Biome; + + constructor(scene: BattleScene, nextBiome: Biome) { + super(scene); + + this.nextBiome = nextBiome; + } + + start() { + super.start(); + + if (this.nextBiome === undefined) { + return this.end(); + } + + this.scene.tweens.add({ + targets: [this.scene.arenaEnemy, this.scene.lastEnemyTrainer], + x: "+=300", + duration: 2000, + onComplete: () => { + this.scene.arenaEnemy.setX(this.scene.arenaEnemy.x - 600); + + this.scene.newArena(this.nextBiome); + + const biomeKey = getBiomeKey(this.nextBiome); + const bgTexture = `${biomeKey}_bg`; + this.scene.arenaBgTransition.setTexture(bgTexture); + this.scene.arenaBgTransition.setAlpha(0); + this.scene.arenaBgTransition.setVisible(true); + this.scene.arenaPlayerTransition.setBiome(this.nextBiome); + this.scene.arenaPlayerTransition.setAlpha(0); + this.scene.arenaPlayerTransition.setVisible(true); + + this.scene.tweens.add({ + targets: [this.scene.arenaPlayer, this.scene.arenaBgTransition, this.scene.arenaPlayerTransition], + duration: 1000, + delay: 1000, + ease: "Sine.easeInOut", + alpha: (target: any) => target === this.scene.arenaPlayer ? 0 : 1, + onComplete: () => { + this.scene.arenaBg.setTexture(bgTexture); + this.scene.arenaPlayer.setBiome(this.nextBiome); + this.scene.arenaPlayer.setAlpha(1); + this.scene.arenaEnemy.setBiome(this.nextBiome); + this.scene.arenaEnemy.setAlpha(1); + this.scene.arenaNextEnemy.setBiome(this.nextBiome); + this.scene.arenaBgTransition.setVisible(false); + this.scene.arenaPlayerTransition.setVisible(false); + if (this.scene.lastEnemyTrainer) { + this.scene.lastEnemyTrainer.destroy(); + } + + this.end(); + } + }); + } + }); + } +} diff --git a/src/phases/switch-phase.ts b/src/phases/switch-phase.ts new file mode 100644 index 00000000000..93b0943febf --- /dev/null +++ b/src/phases/switch-phase.ts @@ -0,0 +1,65 @@ +import BattleScene from "#app/battle-scene.js"; +import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import { BattlePhase } from "./battle-phase"; +import { SwitchSummonPhase } from "./switch-summon-phase"; + +/** + * Opens the party selector UI and transitions into a {@linkcode SwitchSummonPhase} + * for the player (if a switch would be valid for the current battle state). + */ +export class SwitchPhase extends BattlePhase { + protected fieldIndex: integer; + private isModal: boolean; + private doReturn: boolean; + + /** + * Creates a new SwitchPhase + * @param scene {@linkcode BattleScene} Current battle scene + * @param fieldIndex Field index to switch out + * @param isModal Indicates if the switch should be forced (true) or is + * optional (false). + * @param doReturn Indicates if the party member on the field should be + * recalled to ball or has already left the field. Passed to {@linkcode SwitchSummonPhase}. + */ + constructor(scene: BattleScene, fieldIndex: integer, isModal: boolean, doReturn: boolean) { + super(scene); + + this.fieldIndex = fieldIndex; + this.isModal = isModal; + this.doReturn = doReturn; + } + + start() { + super.start(); + + // Skip modal switch if impossible (no remaining party members that aren't in battle) + if (this.isModal && !this.scene.getParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length) { + return super.end(); + } + + // Skip if the fainted party member has been revived already. doReturn is + // only passed as `false` from FaintPhase (as opposed to other usages such + // as ForceSwitchOutAttr or CheckSwitchPhase), so we only want to check this + // if the mon should have already been returned but is still alive and well + // on the field. see also; battle.test.ts + if (this.isModal && !this.doReturn && !this.scene.getParty()[this.fieldIndex].isFainted()) { + return super.end(); + } + + // Check if there is any space still in field + if (this.isModal && this.scene.getPlayerField().filter(p => p.isAllowedInBattle() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) { + return super.end(); + } + + // Override field index to 0 in case of double battle where 2/3 remaining legal party members fainted at once + const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? this.fieldIndex : 0; + + this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { + if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { + this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON)); + } + this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); + }, PartyUiHandler.FilterNonFainted); + } +} diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts new file mode 100644 index 00000000000..3e401925cea --- /dev/null +++ b/src/phases/switch-summon-phase.ts @@ -0,0 +1,168 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr } from "#app/data/ability.js"; +import { allMoves, ForceSwitchOutAttr } from "#app/data/move.js"; +import { getPokeballTintColor } from "#app/data/pokeball.js"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms.js"; +import { TrainerSlot } from "#app/data/trainer-config.js"; +import Pokemon from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { SwitchEffectTransferModifier } from "#app/modifier/modifier.js"; +import { Command } from "#app/ui/command-ui-handler.js"; +import i18next from "i18next"; +import { PostSummonPhase } from "./post-summon-phase"; +import { SummonPhase } from "./summon-phase"; + +export class SwitchSummonPhase extends SummonPhase { + private slotIndex: integer; + private doReturn: boolean; + private batonPass: boolean; + + private lastPokemon: Pokemon; + + /** + * Constructor for creating a new SwitchSummonPhase + * @param scene {@linkcode BattleScene} the scene the phase is associated with + * @param fieldIndex integer representing position on the battle field + * @param slotIndex integer for the index of pokemon (in party of 6) to switch into + * @param doReturn boolean whether to render "comeback" dialogue + * @param batonPass boolean if the switch is from baton pass + * @param player boolean if the switch is from the player + */ + constructor(scene: BattleScene, fieldIndex: integer, slotIndex: integer, doReturn: boolean, batonPass: boolean, player?: boolean) { + super(scene, fieldIndex, player !== undefined ? player : true); + + this.slotIndex = slotIndex; + this.doReturn = doReturn; + this.batonPass = batonPass; + } + + start(): void { + super.start(); + } + + preSummon(): void { + if (!this.player) { + if (this.slotIndex === -1) { + //@ts-ignore + this.slotIndex = this.scene.currentBattle.trainer?.getNextSummonIndex(!this.fieldIndex ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); // TODO: what would be the default trainer-slot fallback? + } + if (this.slotIndex > -1) { + this.showEnemyTrainer(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); + this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty()); + } + } + + if (!this.doReturn || (this.slotIndex !== -1 && !(this.player ? this.scene.getParty() : this.scene.getEnemyParty())[this.slotIndex])) { + if (this.player) { + return this.switchAndSummon(); + } else { + this.scene.time.delayedCall(750, () => this.switchAndSummon()); + return; + } + } + + const pokemon = this.getPokemon(); + + if (!this.batonPass) { + (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); + } + + this.scene.ui.showText(this.player ? + i18next.t("battle:playerComeBack", { pokemonName: getPokemonNameWithAffix(pokemon) }) : + i18next.t("battle:trainerComeBack", { + trainerName: this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER), + pokemonName: getPokemonNameWithAffix(pokemon) + }) + ); + this.scene.playSound("pb_rel"); + pokemon.hideInfo(); + pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, "Sine.easeIn"); + this.scene.tweens.add({ + targets: pokemon, + duration: 250, + ease: "Sine.easeIn", + scale: 0.5, + onComplete: () => { + pokemon.leaveField(!this.batonPass, false); + this.scene.time.delayedCall(750, () => this.switchAndSummon()); + } + }); + } + + switchAndSummon() { + const party = this.player ? this.getParty() : this.scene.getEnemyParty(); + const switchedInPokemon = party[this.slotIndex]; + this.lastPokemon = this.getPokemon(); + applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon); + if (this.batonPass && switchedInPokemon) { + (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedInPokemon.id)); + if (!this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { + const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier + && (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as SwitchEffectTransferModifier; + if (batonPassModifier && !this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { + this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedInPokemon, false); + } + } + } + if (switchedInPokemon) { + party[this.slotIndex] = this.lastPokemon; + party[this.fieldIndex] = switchedInPokemon; + const showTextAndSummon = () => { + this.scene.ui.showText(this.player ? + i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(switchedInPokemon) }) : + i18next.t("battle:trainerGo", { + trainerName: this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER), + pokemonName: this.getPokemon().getNameToRender() + }) + ); + // Ensure improperly persisted summon data (such as tags) is cleared upon switching + if (!this.batonPass) { + switchedInPokemon.resetBattleData(); + switchedInPokemon.resetSummonData(); + } + this.summon(); + }; + if (this.player) { + showTextAndSummon(); + } else { + this.scene.time.delayedCall(1500, () => { + this.hideEnemyTrainer(); + this.scene.pbTrayEnemy.hide(); + showTextAndSummon(); + }); + } + } else { + this.end(); + } + } + + onEnd(): void { + super.onEnd(); + + const pokemon = this.getPokemon(); + + const moveId = this.lastPokemon?.scene.currentBattle.lastMove; + const lastUsedMove = moveId ? allMoves[moveId] : undefined; + + const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command; + const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted(); + + // Compensate for turn spent summoning + // Or compensate for force switch move if switched out pokemon is not fainted + if (currentCommand === Command.POKEMON || lastPokemonIsForceSwitchedAndNotFainted) { + pokemon.battleSummonData.turnCount--; + } + + if (this.batonPass && pokemon) { + pokemon.transferSummon(this.lastPokemon); + } + + this.lastPokemon?.resetSummonData(); + + this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); + } + + queuePostSummon(): void { + this.scene.unshiftPhase(new PostSummonPhase(this.scene, this.getPokemon().getBattlerIndex())); + } +} diff --git a/src/phases/test-message-phase.ts b/src/phases/test-message-phase.ts new file mode 100644 index 00000000000..14fed24ef4b --- /dev/null +++ b/src/phases/test-message-phase.ts @@ -0,0 +1,8 @@ +import BattleScene from "#app/battle-scene.js"; +import { MessagePhase } from "./message-phase"; + +export class TestMessagePhase extends MessagePhase { + constructor(scene: BattleScene, message: string) { + super(scene, message, null, true); + } +} diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts new file mode 100644 index 00000000000..c74dca97f5c --- /dev/null +++ b/src/phases/title-phase.ts @@ -0,0 +1,303 @@ +import { loggedInUser } from "#app/account.js"; +import BattleScene from "#app/battle-scene.js"; +import { BattleType } from "#app/battle.js"; +import { getDailyRunStarters, fetchDailyRunSeed } from "#app/data/daily-run.js"; +import { Gender } from "#app/data/gender.js"; +import { getBiomeKey } from "#app/field/arena.js"; +import { GameModes, GameMode, getGameMode } from "#app/game-mode.js"; +import { regenerateModifierPoolThresholds, ModifierPoolType, modifierTypes, getDailyRunStarterModifiers } from "#app/modifier/modifier-type.js"; +import { Phase } from "#app/phase.js"; +import { SessionSaveData } from "#app/system/game-data.js"; +import { Unlockables } from "#app/system/unlockables.js"; +import { vouchers } from "#app/system/voucher.js"; +import { OptionSelectItem, OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler.js"; +import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { Modifier } from "#app/modifier/modifier.js"; +import { CheckSwitchPhase } from "./check-switch-phase"; +import { EncounterPhase } from "./encounter-phase"; +import { SelectChallengePhase } from "./select-challenge-phase"; +import { SelectStarterPhase } from "./select-starter-phase"; +import { SummonPhase } from "./summon-phase"; + + +export class TitlePhase extends Phase { + private loaded: boolean; + private lastSessionData: SessionSaveData; + public gameMode: GameModes; + + constructor(scene: BattleScene) { + super(scene); + + this.loaded = false; + } + + start(): void { + super.start(); + + this.scene.ui.clearText(); + this.scene.ui.fadeIn(250); + + this.scene.playBgm("title", true); + + this.scene.gameData.getSession(loggedInUser?.lastSessionSlot ?? -1).then(sessionData => { + if (sessionData) { + this.lastSessionData = sessionData; + const biomeKey = getBiomeKey(sessionData.arena.biome); + const bgTexture = `${biomeKey}_bg`; + this.scene.arenaBg.setTexture(bgTexture); + } + this.showOptions(); + }).catch(err => { + console.error(err); + this.showOptions(); + }); + } + + showOptions(): void { + const options: OptionSelectItem[] = []; + if (loggedInUser && loggedInUser.lastSessionSlot > -1) { + options.push({ + label: i18next.t("continue", {ns: "menu"}), + handler: () => { + this.loadSaveSlot(this.lastSessionData || !loggedInUser ? -1 : loggedInUser.lastSessionSlot); + return true; + } + }); + } + options.push({ + label: i18next.t("menu:newGame"), + handler: () => { + const setModeAndEnd = (gameMode: GameModes) => { + this.gameMode = gameMode; + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.clearText(); + this.end(); + }; + if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { + const options: OptionSelectItem[] = [ + { + label: GameMode.getModeName(GameModes.CLASSIC), + handler: () => { + setModeAndEnd(GameModes.CLASSIC); + return true; + } + }, + { + label: GameMode.getModeName(GameModes.CHALLENGE), + handler: () => { + setModeAndEnd(GameModes.CHALLENGE); + return true; + } + }, + { + label: GameMode.getModeName(GameModes.ENDLESS), + handler: () => { + setModeAndEnd(GameModes.ENDLESS); + return true; + } + } + ]; + if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { + options.push({ + label: GameMode.getModeName(GameModes.SPLICED_ENDLESS), + handler: () => { + setModeAndEnd(GameModes.SPLICED_ENDLESS); + return true; + } + }); + } + options.push({ + label: i18next.t("menu:cancel"), + handler: () => { + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + return true; + } + }); + this.scene.ui.showText(i18next.t("menu:selectGameMode"), null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); + } else { + this.gameMode = GameModes.CLASSIC; + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.clearText(); + this.end(); + } + return true; + } + }, + { + label: i18next.t("menu:loadGame"), + handler: () => { + this.scene.ui.setOverlayMode(Mode.SAVE_SLOT, SaveSlotUiMode.LOAD, + (slotId: integer) => { + if (slotId === -1) { + return this.showOptions(); + } + this.loadSaveSlot(slotId); + }); + return true; + } + }, + { + label: i18next.t("menu:dailyRun"), + handler: () => { + this.initDailyRun(); + return true; + }, + keepOpen: true + }, + { + label: i18next.t("menu:settings"), + handler: () => { + this.scene.ui.setOverlayMode(Mode.SETTINGS); + return true; + }, + keepOpen: true + }); + const config: OptionSelectConfig = { + options: options, + noCancel: true, + yOffset: 47 + }; + this.scene.ui.setMode(Mode.TITLE, config); + } + + loadSaveSlot(slotId: integer): void { + this.scene.sessionSlotId = slotId > -1 || !loggedInUser ? slotId : loggedInUser.lastSessionSlot; + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.resetModeChain(); + this.scene.gameData.loadSession(this.scene, slotId, slotId === -1 ? this.lastSessionData : undefined).then((success: boolean) => { + if (success) { + this.loaded = true; + this.scene.ui.showText(i18next.t("menu:sessionSuccess"), null, () => this.end()); + } else { + this.end(); + } + }).catch(err => { + console.error(err); + this.scene.ui.showText(i18next.t("menu:failedToLoadSession"), null); + }); + } + + initDailyRun(): void { + this.scene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { + this.scene.clearPhaseQueue(); + if (slotId === -1) { + this.scene.pushPhase(new TitlePhase(this.scene)); + return super.end(); + } + this.scene.sessionSlotId = slotId; + + const generateDaily = (seed: string) => { + this.scene.gameMode = getGameMode(GameModes.DAILY); + + this.scene.setSeed(seed); + this.scene.resetSeed(1); + + this.scene.money = this.scene.gameMode.getStartingMoney(); + + const starters = getDailyRunStarters(this.scene, seed); + const startingLevel = this.scene.gameMode.getStartingLevel(); + + const party = this.scene.getParty(); + const loadPokemonAssets: Promise[] = []; + for (const starter of starters) { + const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); + const starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); + const starterGender = starter.species.malePercent !== null + ? !starterProps.female ? Gender.MALE : Gender.FEMALE + : Gender.GENDERLESS; + const starterPokemon = this.scene.addPlayerPokemon(starter.species, startingLevel, starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, undefined, starter.nature); + starterPokemon.setVisible(false); + party.push(starterPokemon); + loadPokemonAssets.push(starterPokemon.loadAssets()); + } + + regenerateModifierPoolThresholds(party, ModifierPoolType.DAILY_STARTER); + + const modifiers: Modifier[] = Array(3).fill(null).map(() => modifierTypes.EXP_SHARE().withIdFromFunc(modifierTypes.EXP_SHARE).newModifier()) + .concat(Array(3).fill(null).map(() => modifierTypes.GOLDEN_EXP_CHARM().withIdFromFunc(modifierTypes.GOLDEN_EXP_CHARM).newModifier())) + .concat(getDailyRunStarterModifiers(party)) + .filter((m) => m !== null); + + for (const m of modifiers) { + this.scene.addModifier(m, true, false, false, true); + } + this.scene.updateModifiers(true, true); + + Promise.all(loadPokemonAssets).then(() => { + this.scene.time.delayedCall(500, () => this.scene.playBgm()); + this.scene.gameData.gameStats.dailyRunSessionsPlayed++; + this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene)); + this.scene.newBattle(); + this.scene.arena.init(); + this.scene.sessionPlayTime = 0; + this.scene.lastSavePlayTime = 0; + this.end(); + }); + }; + + // If Online, calls seed fetch from db to generate daily run. If Offline, generates a daily run based on current date. + if (!Utils.isLocal) { + fetchDailyRunSeed().then(seed => { + if (seed) { + generateDaily(seed); + } else { + throw new Error("Daily run seed is null!"); + } + }).catch(err => { + console.error("Failed to load daily run:\n", err); + }); + } else { + generateDaily(btoa(new Date().toISOString().substring(0, 10))); + } + }); + } + + end(): void { + if (!this.loaded && !this.scene.gameMode.isDaily) { + this.scene.arena.preloadBgm(); + this.scene.gameMode = getGameMode(this.gameMode); + if (this.gameMode === GameModes.CHALLENGE) { + this.scene.pushPhase(new SelectChallengePhase(this.scene)); + } else { + this.scene.pushPhase(new SelectStarterPhase(this.scene)); + } + this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene)); + } else { + this.scene.playBgm(); + } + + this.scene.pushPhase(new EncounterPhase(this.scene, this.loaded)); + + if (this.loaded) { + const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length; + + this.scene.pushPhase(new SummonPhase(this.scene, 0, true, true)); + if (this.scene.currentBattle.double && availablePartyMembers > 1) { + this.scene.pushPhase(new SummonPhase(this.scene, 1, true, true)); + } + + if (this.scene.currentBattle.battleType !== BattleType.TRAINER && (this.scene.currentBattle.waveIndex > 1 || !this.scene.gameMode.isDaily)) { + const minPartySize = this.scene.currentBattle.double ? 2 : 1; + if (availablePartyMembers > minPartySize) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); + if (this.scene.currentBattle.double) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + } + } + } + } + + for (const achv of Object.keys(this.scene.gameData.achvUnlocks)) { + if (vouchers.hasOwnProperty(achv)) { + this.scene.validateVoucher(vouchers[achv]); + } + } + + super.end(); + } +} diff --git a/src/phases/toggle-double-position-phase.ts b/src/phases/toggle-double-position-phase.ts new file mode 100644 index 00000000000..fe3d0482483 --- /dev/null +++ b/src/phases/toggle-double-position-phase.ts @@ -0,0 +1,31 @@ +import BattleScene from "#app/battle-scene.js"; +import { FieldPosition } from "#app/field/pokemon.js"; +import { BattlePhase } from "./battle-phase"; + +export class ToggleDoublePositionPhase extends BattlePhase { + private double: boolean; + + constructor(scene: BattleScene, double: boolean) { + super(scene); + + this.double = double; + } + + start() { + super.start(); + + const playerPokemon = this.scene.getPlayerField().find(p => p.isActive(true)); + if (playerPokemon) { + playerPokemon.setFieldPosition(this.double && this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? FieldPosition.LEFT : FieldPosition.CENTER, 500).then(() => { + if (playerPokemon.getFieldIndex() === 1) { + const party = this.scene.getParty(); + party[1] = party[0]; + party[0] = playerPokemon; + } + this.end(); + }); + } else { + this.end(); + } + } +} diff --git a/src/phases/trainer-message-test-phase.ts b/src/phases/trainer-message-test-phase.ts new file mode 100644 index 00000000000..4ea451660c3 --- /dev/null +++ b/src/phases/trainer-message-test-phase.ts @@ -0,0 +1,41 @@ +import BattleScene from "#app/battle-scene.js"; +import { trainerConfigs } from "#app/data/trainer-config.js"; +import { TrainerType } from "#app/enums/trainer-type.js"; +import { BattlePhase } from "./battle-phase"; +import { TestMessagePhase } from "./test-message-phase"; + +export class TrainerMessageTestPhase extends BattlePhase { + private trainerTypes: TrainerType[]; + + constructor(scene: BattleScene, ...trainerTypes: TrainerType[]) { + super(scene); + + this.trainerTypes = trainerTypes; + } + + start() { + super.start(); + + const testMessages: string[] = []; + + for (const t of Object.keys(trainerConfigs)) { + const type = parseInt(t); + if (this.trainerTypes.length && !this.trainerTypes.find(tt => tt === type as TrainerType)) { + continue; + } + const config = trainerConfigs[type]; + [config.encounterMessages, config.femaleEncounterMessages, config.victoryMessages, config.femaleVictoryMessages, config.defeatMessages, config.femaleDefeatMessages] + .map(messages => { + if (messages?.length) { + testMessages.push(...messages); + } + }); + } + + for (const message of testMessages) { + this.scene.pushPhase(new TestMessagePhase(this.scene, message)); + } + + this.end(); + } +} diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts new file mode 100644 index 00000000000..7b8ee05de44 --- /dev/null +++ b/src/phases/trainer-victory-phase.ts @@ -0,0 +1,65 @@ +import BattleScene from "#app/battle-scene.js"; +import { getCharVariantFromDialogue } from "#app/data/dialogue.js"; +import { TrainerSlot } from "#app/data/trainer-config.js"; +import { TrainerType } from "#app/enums/trainer-type.js"; +import { modifierTypes } from "#app/modifier/modifier-type.js"; +import { vouchers } from "#app/system/voucher.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; +import { ModifierRewardPhase } from "./modifier-reward-phase"; +import { MoneyRewardPhase } from "./money-reward-phase"; + +export class TrainerVictoryPhase extends BattlePhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + this.scene.disableMenu = true; + + this.scene.playBgm(this.scene.currentBattle.trainer?.config.victoryBgm); + + this.scene.unshiftPhase(new MoneyRewardPhase(this.scene, this.scene.currentBattle.trainer?.config.moneyMultiplier!)); // TODO: is this bang correct? + + const modifierRewardFuncs = this.scene.currentBattle.trainer?.config.modifierRewardFuncs!; // TODO: is this bang correct? + for (const modifierRewardFunc of modifierRewardFuncs) { + this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, modifierRewardFunc)); + } + + const trainerType = this.scene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? + if (vouchers.hasOwnProperty(TrainerType[trainerType])) { + if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer?.config.isBoss) { + this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, [modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][vouchers[TrainerType[trainerType]].voucherType])); + } + } + + this.scene.ui.showText(i18next.t("battle:trainerDefeated", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }), null, () => { + const victoryMessages = this.scene.currentBattle.trainer?.getVictoryMessages()!; // TODO: is this bang correct? + let message: string; + this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(victoryMessages), this.scene.currentBattle.waveIndex); + message = message!; // tell TS compiler it's defined now + + const showMessage = () => { + const originalFunc = showMessageOrEnd; + showMessageOrEnd = () => this.scene.ui.showDialogue(message, this.scene.currentBattle.trainer?.getName(), null, originalFunc); + + showMessageOrEnd(); + }; + let showMessageOrEnd = () => this.end(); + if (victoryMessages?.length) { + if (this.scene.currentBattle.trainer?.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { + const originalFunc = showMessageOrEnd; + showMessageOrEnd = () => this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => originalFunc())); + this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(this.scene.currentBattle.trainer?.getKey()!, getCharVariantFromDialogue(victoryMessages[0])).then(() => showMessage())); // TODO: is this bang correct? + } else { + showMessage(); + } + } else { + showMessageOrEnd(); + } + }, null, true); + + this.showEnemyTrainer(); + } +} diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts new file mode 100644 index 00000000000..62589e99b79 --- /dev/null +++ b/src/phases/turn-end-phase.ts @@ -0,0 +1,71 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyPostTurnAbAttrs, PostTurnAbAttr } from "#app/data/ability.js"; +import { BattlerTagLapseType } from "#app/data/battler-tags.js"; +import { allMoves } from "#app/data/move.js"; +import { TerrainType } from "#app/data/terrain.js"; +import { Moves } from "#app/enums/moves.js"; +import { WeatherType } from "#app/enums/weather-type.js"; +import { TurnEndEvent } from "#app/events/battle-scene.js"; +import Pokemon from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { TurnHealModifier, EnemyTurnHealModifier, EnemyStatusEffectHealChanceModifier, TurnStatusEffectModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier.js"; +import i18next from "i18next"; +import { FieldPhase } from "./field-phase"; +import { MessagePhase } from "./message-phase"; +import { PokemonHealPhase } from "./pokemon-heal-phase"; + +export class TurnEndPhase extends FieldPhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.currentBattle.incrementTurn(this.scene); + this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn)); + + const handlePokemon = (pokemon: Pokemon) => { + pokemon.lapseTags(BattlerTagLapseType.TURN_END); + + if (pokemon.summonData.disabledMove && !--pokemon.summonData.disabledTurns) { + this.scene.pushPhase(new MessagePhase(this.scene, i18next.t("battle:notDisabled", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: allMoves[pokemon.summonData.disabledMove].name }))); + pokemon.summonData.disabledMove = Moves.NONE; + } + + this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); + + if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { + this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), + Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true)); + } + + if (!pokemon.isPlayer()) { + this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon); + this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); + } + + applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); + + this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); + + this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); + + pokemon.battleSummonData.turnCount++; + }; + + this.executeForAll(handlePokemon); + + this.scene.arena.lapseTags(); + + if (this.scene.arena.weather && !this.scene.arena.weather.lapse()) { + this.scene.arena.trySetWeather(WeatherType.NONE, false); + } + + if (this.scene.arena.terrain && !this.scene.arena.terrain.lapse()) { + this.scene.arena.trySetTerrain(TerrainType.NONE, false); + } + + this.end(); + } +} diff --git a/src/phases/turn-init-phase.ts b/src/phases/turn-init-phase.ts new file mode 100644 index 00000000000..a999d57ca0f --- /dev/null +++ b/src/phases/turn-init-phase.ts @@ -0,0 +1,65 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { TurnInitEvent } from "#app/events/battle-scene.js"; +import { PlayerPokemon } from "#app/field/pokemon.js"; +import i18next from "i18next"; +import { FieldPhase } from "./field-phase"; +import { ToggleDoublePositionPhase } from "./toggle-double-position-phase"; +import { CommandPhase } from "./command-phase"; +import { EnemyCommandPhase } from "./enemy-command-phase"; +import { GameOverPhase } from "./game-over-phase"; +import { TurnStartPhase } from "./turn-start-phase"; + +export class TurnInitPhase extends FieldPhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.getPlayerField().forEach(p => { + // If this pokemon is in play and evolved into something illegal under the current challenge, force a switch + if (p.isOnField() && !p.isAllowedInBattle()) { + this.scene.queueMessage(i18next.t("challenges:illegalEvolution", { "pokemon": p.name }), null, true); + + const allowedPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); + + if (!allowedPokemon.length) { + // If there are no longer any legal pokemon in the party, game over. + this.scene.clearPhaseQueue(); + this.scene.unshiftPhase(new GameOverPhase(this.scene)); + } else if (allowedPokemon.length >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !allowedPokemon[0].isActive(true))) { + // If there is at least one pokemon in the back that is legal to switch in, force a switch. + p.switchOut(false); + } else { + // If there are no pokemon in the back but we're not game overing, just hide the pokemon. + // This should only happen in double battles. + p.leaveField(); + } + if (allowedPokemon.length === 1 && this.scene.currentBattle.double) { + this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + } + } + }); + + //this.scene.pushPhase(new MoveAnimTestPhase(this.scene)); + this.scene.eventTarget.dispatchEvent(new TurnInitEvent()); + + this.scene.getField().forEach((pokemon, i) => { + if (pokemon?.isActive()) { + if (pokemon.isPlayer()) { + this.scene.currentBattle.addParticipant(pokemon as PlayerPokemon); + } + + pokemon.resetTurnData(); + + this.scene.pushPhase(pokemon.isPlayer() ? new CommandPhase(this.scene, i) : new EnemyCommandPhase(this.scene, i - BattlerIndex.ENEMY)); + } + }); + + this.scene.pushPhase(new TurnStartPhase(this.scene)); + + this.end(); + } +} diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts new file mode 100644 index 00000000000..1320cb6235c --- /dev/null +++ b/src/phases/turn-start-phase.ts @@ -0,0 +1,172 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr, ChangeMovePriorityAbAttr } from "#app/data/ability.js"; +import { allMoves, applyMoveAttrs, IncrementMovePriorityAttr, MoveHeaderAttr } from "#app/data/move.js"; +import { Abilities } from "#app/enums/abilities.js"; +import { Stat } from "#app/enums/stat.js"; +import { PokemonMove } from "#app/field/pokemon.js"; +import { BypassSpeedChanceModifier } from "#app/modifier/modifier.js"; +import { Command } from "#app/ui/command-ui-handler.js"; +import * as Utils from "#app/utils.js"; +import { AttemptCapturePhase } from "./attempt-capture-phase"; +import { AttemptRunPhase } from "./attempt-run-phase"; +import { BerryPhase } from "./berry-phase"; +import { FieldPhase } from "./field-phase"; +import { MoveHeaderPhase } from "./move-header-phase"; +import { MovePhase } from "./move-phase"; +import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase"; +import { SwitchSummonPhase } from "./switch-summon-phase"; +import { TurnEndPhase } from "./turn-end-phase"; +import { WeatherEffectPhase } from "./weather-effect-phase"; + +export class TurnStartPhase extends FieldPhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + const field = this.scene.getField(); + const order = this.getOrder(); + + const battlerBypassSpeed = {}; + + this.scene.getField(true).filter(p => p.summonData).map(p => { + const bypassSpeed = new Utils.BooleanHolder(false); + const canCheckHeldItems = new Utils.BooleanHolder(true); + applyAbAttrs(BypassSpeedChanceAbAttr, p, null, bypassSpeed); + applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, bypassSpeed, canCheckHeldItems); + if (canCheckHeldItems.value) { + this.scene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); + } + battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; + }); + + const moveOrder = order.slice(0); + + moveOrder.sort((a, b) => { + const aCommand = this.scene.currentBattle.turnCommands[a]; + const bCommand = this.scene.currentBattle.turnCommands[b]; + + if (aCommand?.command !== bCommand?.command) { + if (aCommand?.command === Command.FIGHT) { + return 1; + } else if (bCommand?.command === Command.FIGHT) { + return -1; + } + } else if (aCommand?.command === Command.FIGHT) { + const aMove = allMoves[aCommand.move!.move];//TODO: is the bang correct here? + const bMove = allMoves[bCommand!.move!.move];//TODO: is the bang correct here? + + const aPriority = new Utils.IntegerHolder(aMove.priority); + const bPriority = new Utils.IntegerHolder(bMove.priority); + + applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here? + applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here? + + applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here? + applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here? + + if (aPriority.value !== bPriority.value) { + const bracketDifference = Math.ceil(aPriority.value) - Math.ceil(bPriority.value); + const hasSpeedDifference = battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value; + if (bracketDifference === 0 && hasSpeedDifference) { + return battlerBypassSpeed[a].value ? -1 : 1; + } + return aPriority.value < bPriority.value ? 1 : -1; + } + } + + if (battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) { + return battlerBypassSpeed[a].value ? -1 : 1; + } + + const aIndex = order.indexOf(a); + const bIndex = order.indexOf(b); + + return aIndex < bIndex ? -1 : aIndex > bIndex ? 1 : 0; + }); + + let orderIndex = 0; + + for (const o of moveOrder) { + + const pokemon = field[o]; + const turnCommand = this.scene.currentBattle.turnCommands[o]; + + if (turnCommand?.skip) { + continue; + } + + switch (turnCommand?.command) { + case Command.FIGHT: + const queuedMove = turnCommand.move; + pokemon.turnData.order = orderIndex++; + if (!queuedMove) { + continue; + } + const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); + if (move.getMove().hasAttr(MoveHeaderAttr)) { + this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); + } + if (pokemon.isPlayer()) { + if (turnCommand.cursor === -1) { + this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here? + } else { + const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here? + this.scene.pushPhase(playerPhase); + } + } else { + this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? + } + break; + case Command.BALL: + this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? + break; + case Command.POKEMON: + this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor!, true, turnCommand.args![0] as boolean, pokemon.isPlayer()));//TODO: is the bang correct here? + break; + case Command.RUN: + let runningPokemon = pokemon; + if (this.scene.currentBattle.double) { + const playerActivePokemon = field.filter(pokemon => { + if (!!pokemon) { + return pokemon.isPlayer() && pokemon.isActive(); + } else { + return; + } + }); + // if only one pokemon is alive, use that one + if (playerActivePokemon.length > 1) { + // find which active pokemon has faster speed + const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1]; + // check if either active pokemon has the ability "Run Away" + const hasRunAway = playerActivePokemon.find(p => p.hasAbility(Abilities.RUN_AWAY)); + runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; + } + } + this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex())); + break; + } + } + + + this.scene.pushPhase(new WeatherEffectPhase(this.scene)); + + for (const o of order) { + if (field[o].status && field[o].status.isPostTurn()) { + this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o)); + } + } + + this.scene.pushPhase(new BerryPhase(this.scene)); + this.scene.pushPhase(new TurnEndPhase(this.scene)); + + /** + * this.end() will call shiftPhase(), which dumps everything from PrependQueue (aka everything that is unshifted()) to the front + * of the queue and dequeues to start the next phase + * this is important since stuff like SwitchSummon, AttemptRun, AttemptCapture Phases break the "flow" and should take precedence + */ + this.end(); + } +} diff --git a/src/phases/unavailable-phase.ts b/src/phases/unavailable-phase.ts new file mode 100644 index 00000000000..4757af5e15d --- /dev/null +++ b/src/phases/unavailable-phase.ts @@ -0,0 +1,17 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { Mode } from "#app/ui/ui.js"; +import { LoginPhase } from "./login-phase"; + +export class UnavailablePhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start(): void { + this.scene.ui.setMode(Mode.UNAVAILABLE, () => { + this.scene.unshiftPhase(new LoginPhase(this.scene, true)); + this.end(); + }); + } +} diff --git a/src/phases/unlock-phase.ts b/src/phases/unlock-phase.ts new file mode 100644 index 00000000000..ce06e2445ac --- /dev/null +++ b/src/phases/unlock-phase.ts @@ -0,0 +1,27 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { Unlockables, getUnlockableName } from "#app/system/unlockables.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; + +export class UnlockPhase extends Phase { + private unlockable: Unlockables; + + constructor(scene: BattleScene, unlockable: Unlockables) { + super(scene); + + this.unlockable = unlockable; + } + + start(): void { + this.scene.time.delayedCall(2000, () => { + this.scene.gameData.unlocks[this.unlockable] = true; + this.scene.playSound("level_up_fanfare"); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:unlockedSomething", { unlockedThing: getUnlockableName(this.unlockable) }), null, () => { + this.scene.time.delayedCall(1500, () => this.scene.arenaBg.setVisible(true)); + this.end(); + }, null, true, 1500); + }); + } +} diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts new file mode 100644 index 00000000000..b7587de4dbb --- /dev/null +++ b/src/phases/victory-phase.ts @@ -0,0 +1,151 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex, BattleType } from "#app/battle.js"; +import { modifierTypes } from "#app/modifier/modifier-type.js"; +import { ExpShareModifier, ExpBalanceModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier.js"; +import * as Utils from "#app/utils.js"; +import Overrides from "#app/overrides"; +import { BattleEndPhase } from "./battle-end-phase"; +import { NewBattlePhase } from "./new-battle-phase"; +import { PokemonPhase } from "./pokemon-phase"; +import { AddEnemyBuffModifierPhase } from "./add-enemy-buff-modifier-phase"; +import { EggLapsePhase } from "./egg-lapse-phase"; +import { ExpPhase } from "./exp-phase"; +import { GameOverPhase } from "./game-over-phase"; +import { ModifierRewardPhase } from "./modifier-reward-phase"; +import { SelectModifierPhase } from "./select-modifier-phase"; +import { ShowPartyExpBarPhase } from "./show-party-exp-bar-phase"; +import { TrainerVictoryPhase } from "./trainer-victory-phase"; + +export class VictoryPhase extends PokemonPhase { + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); + } + + start() { + super.start(); + + this.scene.gameData.gameStats.pokemonDefeated++; + + const participantIds = this.scene.currentBattle.playerParticipantIds; + const party = this.scene.getParty(); + const expShareModifier = this.scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier; + const expBalanceModifier = this.scene.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier; + const multipleParticipantExpBonusModifier = this.scene.findModifier(m => m instanceof MultipleParticipantExpBonusModifier) as MultipleParticipantExpBonusModifier; + const nonFaintedPartyMembers = party.filter(p => p.hp); + const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < this.scene.getMaxExpLevel()); + const partyMemberExp: number[] = []; + + if (participantIds.size) { + let expValue = this.getPokemon().getExpValue(); + if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + expValue = Math.floor(expValue * 1.5); + } + for (const partyMember of nonFaintedPartyMembers) { + const pId = partyMember.id; + const participated = participantIds.has(pId); + if (participated) { + partyMember.addFriendship(2); + } + if (!expPartyMembers.includes(partyMember)) { + continue; + } + if (!participated && !expShareModifier) { + partyMemberExp.push(0); + continue; + } + let expMultiplier = 0; + if (participated) { + expMultiplier += (1 / participantIds.size); + if (participantIds.size > 1 && multipleParticipantExpBonusModifier) { + expMultiplier += multipleParticipantExpBonusModifier.getStackCount() * 0.2; + } + } else if (expShareModifier) { + expMultiplier += (expShareModifier.getStackCount() * 0.2) / participantIds.size; + } + if (partyMember.pokerus) { + expMultiplier *= 1.5; + } + if (Overrides.XP_MULTIPLIER_OVERRIDE !== null) { + expMultiplier = Overrides.XP_MULTIPLIER_OVERRIDE; + } + const pokemonExp = new Utils.NumberHolder(expValue * expMultiplier); + this.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp); + partyMemberExp.push(Math.floor(pokemonExp.value)); + } + + if (expBalanceModifier) { + let totalLevel = 0; + let totalExp = 0; + expPartyMembers.forEach((expPartyMember, epm) => { + totalExp += partyMemberExp[epm]; + totalLevel += expPartyMember.level; + }); + + const medianLevel = Math.floor(totalLevel / expPartyMembers.length); + + const recipientExpPartyMemberIndexes: number[] = []; + expPartyMembers.forEach((expPartyMember, epm) => { + if (expPartyMember.level <= medianLevel) { + recipientExpPartyMemberIndexes.push(epm); + } + }); + + const splitExp = Math.floor(totalExp / recipientExpPartyMemberIndexes.length); + + expPartyMembers.forEach((_partyMember, pm) => { + partyMemberExp[pm] = Phaser.Math.Linear(partyMemberExp[pm], recipientExpPartyMemberIndexes.indexOf(pm) > -1 ? splitExp : 0, 0.2 * expBalanceModifier.getStackCount()); + }); + } + + for (let pm = 0; pm < expPartyMembers.length; pm++) { + const exp = partyMemberExp[pm]; + + if (exp) { + const partyMemberIndex = party.indexOf(expPartyMembers[pm]); + this.scene.unshiftPhase(expPartyMembers[pm].isOnField() ? new ExpPhase(this.scene, partyMemberIndex, exp) : new ShowPartyExpBarPhase(this.scene, partyMemberIndex, exp)); + } + } + } + + if (!this.scene.getEnemyParty().find(p => this.scene.currentBattle.battleType ? !p?.isFainted(true) : p.isOnField())) { + this.scene.pushPhase(new BattleEndPhase(this.scene)); + if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + this.scene.pushPhase(new TrainerVictoryPhase(this.scene)); + } + if (this.scene.gameMode.isEndless || !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { + this.scene.pushPhase(new EggLapsePhase(this.scene)); + if (this.scene.currentBattle.waveIndex % 10) { + this.scene.pushPhase(new SelectModifierPhase(this.scene)); + } else if (this.scene.gameMode.isDaily) { + this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_CHARM)); + if (this.scene.currentBattle.waveIndex > 10 && !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { + this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.GOLDEN_POKEBALL)); + } + } else { + const superExpWave = !this.scene.gameMode.isEndless ? (this.scene.offsetGym ? 0 : 20) : 10; + if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex === 10) { + this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_SHARE)); + } + if (this.scene.currentBattle.waveIndex <= 750 && (this.scene.currentBattle.waveIndex <= 500 || (this.scene.currentBattle.waveIndex % 30) === superExpWave)) { + this.scene.pushPhase(new ModifierRewardPhase(this.scene, (this.scene.currentBattle.waveIndex % 30) !== superExpWave || this.scene.currentBattle.waveIndex > 250 ? modifierTypes.EXP_CHARM : modifierTypes.SUPER_EXP_CHARM)); + } + if (this.scene.currentBattle.waveIndex <= 150 && !(this.scene.currentBattle.waveIndex % 50)) { + this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.GOLDEN_POKEBALL)); + } + if (this.scene.gameMode.isEndless && !(this.scene.currentBattle.waveIndex % 50)) { + this.scene.pushPhase(new ModifierRewardPhase(this.scene, !(this.scene.currentBattle.waveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS)); + this.scene.pushPhase(new AddEnemyBuffModifierPhase(this.scene)); + } + } + this.scene.pushPhase(new NewBattlePhase(this.scene)); + } else { + this.scene.currentBattle.battleType = BattleType.CLEAR; + this.scene.score += this.scene.gameMode.getClearScoreBonus(); + this.scene.updateScoreText(); + this.scene.pushPhase(new GameOverPhase(this.scene, true)); + } + } + + this.end(); + } +} diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts new file mode 100644 index 00000000000..6f5fbc0fce3 --- /dev/null +++ b/src/phases/weather-effect-phase.ts @@ -0,0 +1,67 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyPreWeatherEffectAbAttrs, SuppressWeatherEffectAbAttr, PreWeatherDamageAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPostWeatherLapseAbAttrs, PostWeatherLapseAbAttr } from "#app/data/ability.js"; +import { CommonAnim } from "#app/data/battle-anims.js"; +import { Weather, getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather.js"; +import { WeatherType } from "#app/enums/weather-type.js"; +import Pokemon, { HitResult } from "#app/field/pokemon.js"; +import * as Utils from "#app/utils.js"; +import { CommonAnimPhase } from "./common-anim-phase"; + +export class WeatherEffectPhase extends CommonAnimPhase { + public weather: Weather | null; + + constructor(scene: BattleScene) { + super(scene, undefined, undefined, CommonAnim.SUNNY + ((scene?.arena?.weather?.weatherType || WeatherType.NONE) - 1)); + this.weather = scene?.arena?.weather; + } + + start() { + // Update weather state with any changes that occurred during the turn + this.weather = this.scene?.arena?.weather; + + if (!this.weather) { + this.end(); + return; + } + + this.setAnimation(CommonAnim.SUNNY + (this.weather.weatherType - 1)); + + if (this.weather.isDamaging()) { + + const cancelled = new Utils.BooleanHolder(false); + + this.executeForAll((pokemon: Pokemon) => applyPreWeatherEffectAbAttrs(SuppressWeatherEffectAbAttr, pokemon, this.weather, cancelled)); + + if (!cancelled.value) { + const inflictDamage = (pokemon: Pokemon) => { + const cancelled = new Utils.BooleanHolder(false); + + applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather , cancelled); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + + if (cancelled.value) { + return; + } + + const damage = Math.ceil(pokemon.getMaxHp() / 16); + + this.scene.queueMessage(getWeatherDamageMessage(this.weather?.weatherType!, pokemon)!); // TODO: are those bangs correct? + pokemon.damageAndUpdate(damage, HitResult.EFFECTIVE, false, false, true); + }; + + this.executeForAll((pokemon: Pokemon) => { + const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length; + if (!immune) { + inflictDamage(pokemon); + } + }); + } + } + + this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType)!, null, () => { // TODO: is this bang correct? + this.executeForAll((pokemon: Pokemon) => applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather)); + + super.start(); + }); + } +} diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 40f24fc8326..a9acd80fdee 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -27,7 +27,6 @@ import { Tutorial } from "../tutorial"; import { speciesEggMoves } from "../data/egg-moves"; import { allMoves } from "../data/move"; import { TrainerVariant } from "../field/trainer"; -import { OutdatedPhase, ReloadSessionPhase } from "#app/phases"; import { Variant, variantData } from "#app/data/variant"; import {setSettingGamepad, SettingGamepad, settingGamepadDefaults} from "./settings/settings-gamepad"; import {setSettingKeyboard, SettingKeyboard} from "#app/system/settings/settings-keyboard"; @@ -43,6 +42,8 @@ import { Species } from "#enums/species"; import { applyChallenges, ChallengeType } from "#app/data/challenge.js"; import { WeatherType } from "#app/enums/weather-type.js"; import { TerrainType } from "#app/data/terrain.js"; +import { OutdatedPhase } from "#app/phases/outdated-phase.js"; +import { ReloadSessionPhase } from "#app/phases/reload-session-phase.js"; export const defaultStarterSpecies: Species[] = [ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 4d8097897e9..7263ae3a3de 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -8,13 +8,21 @@ import SettingsUiHandler from "#app/ui/settings/settings-ui-handler"; import { EaseType } from "#enums/ease-type"; import { MoneyFormat } from "#enums/money-format"; import { PlayerGender } from "#enums/player-gender"; +import { getIsInitialized, initI18n } from "#app/plugins/i18n.js"; + +function getTranslation(key: string): string { + if (!getIsInitialized()) { + initI18n(); + } + return i18next.t(key); +} const VOLUME_OPTIONS: SettingOption[] = new Array(11).fill(null).map((_, i) => i ? { value: (i * 10).toString(), label: (i * 10).toString(), } : { value: "Mute", - label: i18next.t("settings:mute") + label: getTranslation("settings:mute") }); const SHOP_OVERLAY_OPACITY_OPTIONS: SettingOption[] = new Array(9).fill(null).map((_, i) => { const value = ((i + 1) * 10).toString(); diff --git a/src/system/voucher.ts b/src/system/voucher.ts index 0c71e3c0286..2f94308d9c8 100644 --- a/src/system/voucher.ts +++ b/src/system/voucher.ts @@ -1,9 +1,10 @@ import BattleScene from "../battle-scene"; import i18next from "i18next"; -import { Achv, AchvTier, achvs, getAchievementDescription } from "./achv"; +import { AchvTier, achvs, getAchievementDescription } from "./achv"; import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import { ConditionFn } from "#app/@types/common.js"; +import { trainerConfigs } from "#app/data/trainer-config.js"; export enum VoucherType { REGULAR, @@ -88,42 +89,36 @@ export interface Vouchers { export const vouchers: Vouchers = {}; -const voucherAchvs: Achv[] = [ achvs.CLASSIC_VICTORY ]; - export function initVouchers() { - import("../data/trainer-config").then(tc => { - const trainerConfigs = tc.trainerConfigs; + for (const achv of [achvs.CLASSIC_VICTORY]) { + const voucherType = achv.score >= 150 + ? VoucherType.GOLDEN + : achv.score >= 100 + ? VoucherType.PREMIUM + : achv.score >= 75 + ? VoucherType.PLUS + : VoucherType.REGULAR; + vouchers[achv.id] = new Voucher(voucherType, getAchievementDescription(achv.localizationKey)); + } - for (const achv of voucherAchvs) { - const voucherType = achv.score >= 150 - ? VoucherType.GOLDEN - : achv.score >= 100 - ? VoucherType.PREMIUM - : achv.score >= 75 - ? VoucherType.PLUS - : VoucherType.REGULAR; - vouchers[achv.id] = new Voucher(voucherType, getAchievementDescription(achv.localizationKey)); - } + const bossTrainerTypes = Object.keys(trainerConfigs) + .filter(tt => trainerConfigs[tt].isBoss && trainerConfigs[tt].getDerivedType() !== TrainerType.RIVAL && trainerConfigs[tt].hasVoucher); - const bossTrainerTypes = Object.keys(trainerConfigs) - .filter(tt => trainerConfigs[tt].isBoss && trainerConfigs[tt].getDerivedType() !== TrainerType.RIVAL && trainerConfigs[tt].hasVoucher); - - for (const trainerType of bossTrainerTypes) { - const voucherType = trainerConfigs[trainerType].moneyMultiplier < 10 - ? VoucherType.PLUS - : VoucherType.PREMIUM; - const key = TrainerType[trainerType]; - const trainerName = trainerConfigs[trainerType].name; - const trainer = trainerConfigs[trainerType]; - const title = trainer.title ? ` (${trainer.title})` : ""; - vouchers[key] = new Voucher( - voucherType, - `${i18next.t("voucher:defeatTrainer", { trainerName })} ${title}`, - ); - } - const voucherKeys = Object.keys(vouchers); - for (const k of voucherKeys) { - vouchers[k].id = k; - } - }); + for (const trainerType of bossTrainerTypes) { + const voucherType = trainerConfigs[trainerType].moneyMultiplier < 10 + ? VoucherType.PLUS + : VoucherType.PREMIUM; + const key = TrainerType[trainerType]; + const trainerName = trainerConfigs[trainerType].name; + const trainer = trainerConfigs[trainerType]; + const title = trainer.title ? ` (${trainer.title})` : ""; + vouchers[key] = new Voucher( + voucherType, + `${i18next.t("voucher:defeatTrainer", { trainerName })} ${title}`, + ); + } + const voucherKeys = Object.keys(vouchers); + for (const k of voucherKeys) { + vouchers[k].id = k; + } } diff --git a/src/test/abilities/ability_timing.test.ts b/src/test/abilities/ability_timing.test.ts index 3906233a7bf..c117c62d45b 100644 --- a/src/test/abilities/ability_timing.test.ts +++ b/src/test/abilities/ability_timing.test.ts @@ -1,4 +1,3 @@ -import { CommandPhase, MessagePhase, TurnInitPhase } from "#app/phases"; import i18next, { initI18n } from "#app/plugins/i18n"; import GameManager from "#test/utils/gameManager"; import { Mode } from "#app/ui/ui"; @@ -8,6 +7,9 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Ability Timing", () => { diff --git a/src/test/abilities/aura_break.test.ts b/src/test/abilities/aura_break.test.ts index a34475cb1ad..bca400bc0e3 100644 --- a/src/test/abilities/aura_break.test.ts +++ b/src/test/abilities/aura_break.test.ts @@ -1,5 +1,4 @@ import { allMoves } from "#app/data/move.js"; -import { MoveEffectPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -8,6 +7,7 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; describe("Abilities - Aura Break", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/battery.test.ts b/src/test/abilities/battery.test.ts index 2345e63d987..766c1c30ecc 100644 --- a/src/test/abilities/battery.test.ts +++ b/src/test/abilities/battery.test.ts @@ -1,6 +1,5 @@ import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; -import { MoveEffectPhase, TurnEndPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -8,6 +7,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Battery", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/battle_bond.test.ts b/src/test/abilities/battle_bond.test.ts index 1a5c71b4c15..c28a00e821d 100644 --- a/src/test/abilities/battle_bond.test.ts +++ b/src/test/abilities/battle_bond.test.ts @@ -1,6 +1,6 @@ import { Status, StatusEffect } from "#app/data/status-effect.js"; -import { QuietFormChangePhase } from "#app/form-change-phase.js"; -import { TurnEndPhase } from "#app/phases.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/abilities/costar.test.ts b/src/test/abilities/costar.test.ts index ef3fb3a2ab0..9410ee55069 100644 --- a/src/test/abilities/costar.test.ts +++ b/src/test/abilities/costar.test.ts @@ -2,12 +2,13 @@ import { BattleStat } from "#app/data/battle-stat.js"; import { Abilities } from "#app/enums/abilities.js"; import { Moves } from "#app/enums/moves.js"; import { Species } from "#app/enums/species.js"; -import { CommandPhase, MessagePhase } from "#app/phases.js"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/disguise.test.ts b/src/test/abilities/disguise.test.ts index 8b1b959bea8..969375c397e 100644 --- a/src/test/abilities/disguise.test.ts +++ b/src/test/abilities/disguise.test.ts @@ -4,10 +4,14 @@ import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { StatusEffect } from "#app/data/status-effect.js"; -import { CommandPhase, MoveEffectPhase, MoveEndPhase, TurnEndPhase, TurnInitPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { SPLASH_ONLY } from "../utils/testUtils"; import { Mode } from "#app/ui/ui.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/dry_skin.test.ts b/src/test/abilities/dry_skin.test.ts index 20b85eab767..1e3860da985 100644 --- a/src/test/abilities/dry_skin.test.ts +++ b/src/test/abilities/dry_skin.test.ts @@ -1,5 +1,4 @@ import { Species } from "#app/enums/species.js"; -import { TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -7,6 +6,7 @@ import { Moves } from "#enums/moves"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Dry Skin", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/flash_fire.test.ts b/src/test/abilities/flash_fire.test.ts index b77b271b754..28c59903b68 100644 --- a/src/test/abilities/flash_fire.test.ts +++ b/src/test/abilities/flash_fire.test.ts @@ -5,11 +5,12 @@ import { Moves } from "#enums/moves"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; -import { MovePhase, TurnEndPhase } from "#app/phases"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { StatusEffect } from "#app/data/status-effect.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { BattlerIndex } from "#app/battle.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Flash Fire", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/gulp_missile.test.ts b/src/test/abilities/gulp_missile.test.ts index 52ae323839d..d033604fe00 100644 --- a/src/test/abilities/gulp_missile.test.ts +++ b/src/test/abilities/gulp_missile.test.ts @@ -1,10 +1,4 @@ import { BattlerTagType } from "#app/enums/battler-tag-type.js"; -import { - BerryPhase, - MoveEndPhase, - TurnEndPhase, - TurnStartPhase, -} from "#app/phases"; import GameManager from "#app/test/utils/gameManager"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -16,6 +10,10 @@ import { SPLASH_ONLY } from "../utils/testUtils"; import { BattleStat } from "#app/data/battle-stat.js"; import { StatusEffect } from "#app/enums/status-effect.js"; import Pokemon from "#app/field/pokemon.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; describe("Abilities - Gulp Missile", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/heatproof.test.ts b/src/test/abilities/heatproof.test.ts index 8249ba6996f..64a45c5023f 100644 --- a/src/test/abilities/heatproof.test.ts +++ b/src/test/abilities/heatproof.test.ts @@ -1,5 +1,5 @@ import { Species } from "#app/enums/species.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/hustle.test.ts b/src/test/abilities/hustle.test.ts index dde310fda2a..8f5547a5518 100644 --- a/src/test/abilities/hustle.test.ts +++ b/src/test/abilities/hustle.test.ts @@ -1,7 +1,6 @@ import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; import { Stat } from "#app/enums/stat.js"; -import { DamagePhase, MoveEffectPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -9,6 +8,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; describe("Abilities - Hustle", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/ice_face.test.ts b/src/test/abilities/ice_face.test.ts index cdf8d5928ee..905e0dfdaf7 100644 --- a/src/test/abilities/ice_face.test.ts +++ b/src/test/abilities/ice_face.test.ts @@ -1,5 +1,3 @@ -import { QuietFormChangePhase } from "#app/form-change-phase"; -import { MoveEffectPhase, MoveEndPhase, TurnEndPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -8,6 +6,11 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Abilities - Ice Face", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/intimidate.test.ts b/src/test/abilities/intimidate.test.ts index 2b4c1041bfe..842b33108a3 100644 --- a/src/test/abilities/intimidate.test.ts +++ b/src/test/abilities/intimidate.test.ts @@ -7,11 +7,16 @@ import { generateStarter, getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; import { Status, StatusEffect } from "#app/data/status-effect"; import { GameModes, getGameMode } from "#app/game-mode"; -import { CommandPhase, DamagePhase, EncounterPhase, EnemyCommandPhase, SelectStarterPhase, TurnInitPhase } from "#app/phases"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Abilities - Intimidate", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/intrepid_sword.test.ts b/src/test/abilities/intrepid_sword.test.ts index dcc91421165..c1c05b59997 100644 --- a/src/test/abilities/intrepid_sword.test.ts +++ b/src/test/abilities/intrepid_sword.test.ts @@ -1,10 +1,10 @@ import { BattleStat } from "#app/data/battle-stat"; -import { CommandPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { Abilities } from "#enums/abilities"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; describe("Abilities - Intrepid Sword", () => { diff --git a/src/test/abilities/libero.test.ts b/src/test/abilities/libero.test.ts index 2581eac068d..d35cb8b6e2d 100644 --- a/src/test/abilities/libero.test.ts +++ b/src/test/abilities/libero.test.ts @@ -2,7 +2,6 @@ import { allMoves } from "#app/data/move.js"; import { Type } from "#app/data/type.js"; import { Weather, WeatherType } from "#app/data/weather.js"; import { PlayerPokemon } from "#app/field/pokemon.js"; -import { TurnEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Biome } from "#enums/biome"; @@ -13,6 +12,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vi import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/magic_guard.test.ts b/src/test/abilities/magic_guard.test.ts index c86d65ca453..c7404f83a54 100644 --- a/src/test/abilities/magic_guard.test.ts +++ b/src/test/abilities/magic_guard.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; import { Species } from "#enums/species"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { Moves } from "#enums/moves"; import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagSide, getArenaTag } from "#app/data/arena-tag"; diff --git a/src/test/abilities/moxie.test.ts b/src/test/abilities/moxie.test.ts index f99068dea41..6550dcab526 100644 --- a/src/test/abilities/moxie.test.ts +++ b/src/test/abilities/moxie.test.ts @@ -1,6 +1,5 @@ import { BattleStat } from "#app/data/battle-stat"; import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, EnemyCommandPhase, VictoryPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -10,6 +9,9 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { VictoryPhase } from "#app/phases/victory-phase.js"; describe("Abilities - Moxie", () => { diff --git a/src/test/abilities/mycelium_might.test.ts b/src/test/abilities/mycelium_might.test.ts index d519eb67626..2fcdc28b279 100644 --- a/src/test/abilities/mycelium_might.test.ts +++ b/src/test/abilities/mycelium_might.test.ts @@ -1,4 +1,5 @@ -import { MovePhase, TurnEndPhase } from "#app/phases"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/parental_bond.test.ts b/src/test/abilities/parental_bond.test.ts index 182f780763c..ef0ad7785d2 100644 --- a/src/test/abilities/parental_bond.test.ts +++ b/src/test/abilities/parental_bond.test.ts @@ -2,7 +2,6 @@ import { BattleStat } from "#app/data/battle-stat.js"; import { StatusEffect } from "#app/data/status-effect.js"; import { Type } from "#app/data/type.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; -import { BerryPhase, CommandPhase, DamagePhase, MoveEffectPhase, MoveEndPhase, TurnEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -11,6 +10,12 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/pastel_veil.test.ts b/src/test/abilities/pastel_veil.test.ts index e3d52a720b3..cb6be666d5f 100644 --- a/src/test/abilities/pastel_veil.test.ts +++ b/src/test/abilities/pastel_veil.test.ts @@ -2,13 +2,14 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; import { Species } from "#enums/species"; -import { CommandPhase, TurnEndPhase } from "#app/phases"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { StatusEffect } from "#app/data/status-effect.js"; import { allAbilities } from "#app/data/ability.js"; import { Abilities } from "#app/enums/abilities.js"; import { BattlerIndex } from "#app/battle.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Pastel Veil", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/power_construct.test.ts b/src/test/abilities/power_construct.test.ts index dd8fd836e51..e6a319d229f 100644 --- a/src/test/abilities/power_construct.test.ts +++ b/src/test/abilities/power_construct.test.ts @@ -1,6 +1,6 @@ import { Status, StatusEffect } from "#app/data/status-effect.js"; -import { QuietFormChangePhase } from "#app/form-change-phase.js"; -import { TurnEndPhase } from "#app/phases.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/abilities/power_spot.test.ts b/src/test/abilities/power_spot.test.ts index 368f8a48110..467fc677ac0 100644 --- a/src/test/abilities/power_spot.test.ts +++ b/src/test/abilities/power_spot.test.ts @@ -1,6 +1,5 @@ import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; -import { MoveEffectPhase, TurnEndPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -8,6 +7,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Power Spot", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/protean.test.ts b/src/test/abilities/protean.test.ts index 78768ce32db..ed63613945a 100644 --- a/src/test/abilities/protean.test.ts +++ b/src/test/abilities/protean.test.ts @@ -2,7 +2,6 @@ import { allMoves } from "#app/data/move.js"; import { Type } from "#app/data/type.js"; import { Weather, WeatherType } from "#app/data/weather.js"; import { PlayerPokemon } from "#app/field/pokemon.js"; -import { TurnEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Biome } from "#enums/biome"; @@ -13,6 +12,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vi import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/quick_draw.test.ts b/src/test/abilities/quick_draw.test.ts index 75bb9ec6a0a..6e3416b0724 100644 --- a/src/test/abilities/quick_draw.test.ts +++ b/src/test/abilities/quick_draw.test.ts @@ -1,5 +1,4 @@ import { allAbilities, BypassSpeedChanceAbAttr } from "#app/data/ability"; -import { FaintPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -7,6 +6,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; +import { FaintPhase } from "#app/phases/faint-phase.js"; describe("Abilities - Quick Draw", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/sand_veil.test.ts b/src/test/abilities/sand_veil.test.ts index 6aab362634a..010878db68d 100644 --- a/src/test/abilities/sand_veil.test.ts +++ b/src/test/abilities/sand_veil.test.ts @@ -1,7 +1,6 @@ import { BattleStatMultiplierAbAttr, allAbilities } from "#app/data/ability.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { WeatherType } from "#app/data/weather.js"; -import { CommandPhase, MoveEffectPhase, MoveEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -9,6 +8,9 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/sap_sipper.test.ts b/src/test/abilities/sap_sipper.test.ts index 6fbe57978e9..dfb4ab7e976 100644 --- a/src/test/abilities/sap_sipper.test.ts +++ b/src/test/abilities/sap_sipper.test.ts @@ -1,6 +1,5 @@ import { BattleStat } from "#app/data/battle-stat.js"; import { TerrainType } from "#app/data/terrain.js"; -import { MoveEndPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -9,6 +8,8 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; // See also: TypeImmunityAbAttr describe("Abilities - Sap Sipper", () => { diff --git a/src/test/abilities/schooling.test.ts b/src/test/abilities/schooling.test.ts index e55b7795006..62a7e98bc76 100644 --- a/src/test/abilities/schooling.test.ts +++ b/src/test/abilities/schooling.test.ts @@ -1,6 +1,6 @@ import { Status, StatusEffect } from "#app/data/status-effect.js"; -import { QuietFormChangePhase } from "#app/form-change-phase.js"; -import { TurnEndPhase } from "#app/phases.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/abilities/screen_cleaner.test.ts b/src/test/abilities/screen_cleaner.test.ts index a73f56dd3eb..403efcce1c0 100644 --- a/src/test/abilities/screen_cleaner.test.ts +++ b/src/test/abilities/screen_cleaner.test.ts @@ -1,5 +1,4 @@ import { ArenaTagType } from "#app/enums/arena-tag-type.js"; -import { PostSummonPhase, TurnEndPhase, } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -7,6 +6,8 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { PostSummonPhase } from "#app/phases/post-summon-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Screen Cleaner", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/serene_grace.test.ts b/src/test/abilities/serene_grace.test.ts index d46587e45c7..5e4841f005a 100644 --- a/src/test/abilities/serene_grace.test.ts +++ b/src/test/abilities/serene_grace.test.ts @@ -1,6 +1,5 @@ import { applyAbAttrs, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability"; import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, MoveEffectPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -12,6 +11,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { BattlerIndex } from "#app/battle.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; describe("Abilities - Serene Grace", () => { diff --git a/src/test/abilities/sheer_force.test.ts b/src/test/abilities/sheer_force.test.ts index 50a0f0b63fb..33b34124cc4 100644 --- a/src/test/abilities/sheer_force.test.ts +++ b/src/test/abilities/sheer_force.test.ts @@ -1,6 +1,7 @@ import { applyAbAttrs, applyPostDefendAbAttrs, applyPreAttackAbAttrs, MoveEffectChanceMultiplierAbAttr, MovePowerBoostAbAttr, PostDefendTypeChangeAbAttr } from "#app/data/ability"; import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, MoveEffectPhase } from "#app/phases"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; diff --git a/src/test/abilities/shield_dust.test.ts b/src/test/abilities/shield_dust.test.ts index f1534551e92..b40689a180a 100644 --- a/src/test/abilities/shield_dust.test.ts +++ b/src/test/abilities/shield_dust.test.ts @@ -1,6 +1,7 @@ import { applyAbAttrs, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability"; import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, MoveEffectPhase } from "#app/phases"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; diff --git a/src/test/abilities/shields_down.test.ts b/src/test/abilities/shields_down.test.ts index 4d85e8aa47c..e07c12ebb63 100644 --- a/src/test/abilities/shields_down.test.ts +++ b/src/test/abilities/shields_down.test.ts @@ -1,6 +1,6 @@ import { Status, StatusEffect } from "#app/data/status-effect.js"; -import { QuietFormChangePhase } from "#app/form-change-phase.js"; -import { TurnEndPhase } from "#app/phases.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/abilities/stall.test.ts b/src/test/abilities/stall.test.ts index 44519064300..5410d2e953e 100644 --- a/src/test/abilities/stall.test.ts +++ b/src/test/abilities/stall.test.ts @@ -1,4 +1,3 @@ -import { MovePhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -6,6 +5,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { MovePhase } from "#app/phases/move-phase.js"; describe("Abilities - Stall", () => { diff --git a/src/test/abilities/steely_spirit.test.ts b/src/test/abilities/steely_spirit.test.ts index 5d5514bc3a1..3ca1a55ebee 100644 --- a/src/test/abilities/steely_spirit.test.ts +++ b/src/test/abilities/steely_spirit.test.ts @@ -1,7 +1,6 @@ import { allAbilities } from "#app/data/ability.js"; import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; -import { MoveEffectPhase, SelectTargetPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -9,6 +8,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; describe("Abilities - Steely Spirit", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/sturdy.test.ts b/src/test/abilities/sturdy.test.ts index 4caa7b0bd14..602b2c04eb1 100644 --- a/src/test/abilities/sturdy.test.ts +++ b/src/test/abilities/sturdy.test.ts @@ -1,5 +1,4 @@ import { EnemyPokemon } from "#app/field/pokemon.js"; -import { DamagePhase, MoveEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -7,6 +6,8 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/sweet_veil.test.ts b/src/test/abilities/sweet_veil.test.ts index d650455664f..8ab384ae59e 100644 --- a/src/test/abilities/sweet_veil.test.ts +++ b/src/test/abilities/sweet_veil.test.ts @@ -2,13 +2,15 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; import { Species } from "#enums/species"; -import { CommandPhase, MovePhase, TurnEndPhase } from "#app/phases"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { Abilities } from "#app/enums/abilities.js"; import { BattlerIndex } from "#app/battle.js"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Sweet Veil", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/unseen_fist.test.ts b/src/test/abilities/unseen_fist.test.ts index a6cad8b03ce..7d47d73bb16 100644 --- a/src/test/abilities/unseen_fist.test.ts +++ b/src/test/abilities/unseen_fist.test.ts @@ -1,4 +1,3 @@ -import { TurnEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -6,6 +5,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/volt_absorb.test.ts b/src/test/abilities/volt_absorb.test.ts index 985459e133b..0e3d5c9792f 100644 --- a/src/test/abilities/volt_absorb.test.ts +++ b/src/test/abilities/volt_absorb.test.ts @@ -1,5 +1,5 @@ import { BattleStat } from "#app/data/battle-stat.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/wind_power.test.ts b/src/test/abilities/wind_power.test.ts index 670544a89ef..24f01cceebc 100644 --- a/src/test/abilities/wind_power.test.ts +++ b/src/test/abilities/wind_power.test.ts @@ -1,5 +1,5 @@ import { BattlerTagType } from "#app/enums/battler-tag-type.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/wind_rider.test.ts b/src/test/abilities/wind_rider.test.ts index e27349efe41..92c38507e4f 100644 --- a/src/test/abilities/wind_rider.test.ts +++ b/src/test/abilities/wind_rider.test.ts @@ -1,5 +1,5 @@ import { BattleStat } from "#app/data/battle-stat.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/wonder_skin.test.ts b/src/test/abilities/wonder_skin.test.ts index a2815152df6..d6e2b2443c4 100644 --- a/src/test/abilities/wonder_skin.test.ts +++ b/src/test/abilities/wonder_skin.test.ts @@ -1,6 +1,6 @@ import { allAbilities } from "#app/data/ability.js"; import { allMoves } from "#app/data/move.js"; -import { MoveEffectPhase } from "#app/phases"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/zen_mode.test.ts b/src/test/abilities/zen_mode.test.ts index 1bc7a6af4ce..72fdc5442c5 100644 --- a/src/test/abilities/zen_mode.test.ts +++ b/src/test/abilities/zen_mode.test.ts @@ -1,7 +1,5 @@ import { Stat } from "#app/data/pokemon-stat"; import { Status, StatusEffect } from "#app/data/status-effect.js"; -import { QuietFormChangePhase } from "#app/form-change-phase"; -import { CommandPhase, DamagePhase, EnemyCommandPhase, MessagePhase, PostSummonPhase, SwitchPhase, SwitchSummonPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -12,6 +10,17 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import { BattlerIndex } from "#app/battle.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; +import { PostSummonPhase } from "#app/phases/post-summon-phase.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { SwitchPhase } from "#app/phases/switch-phase.js"; +import { SwitchSummonPhase } from "#app/phases/switch-summon-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/zero_to_hero.test.ts b/src/test/abilities/zero_to_hero.test.ts index 7924b30eb76..ee6c07096a8 100644 --- a/src/test/abilities/zero_to_hero.test.ts +++ b/src/test/abilities/zero_to_hero.test.ts @@ -1,6 +1,6 @@ import { Status, StatusEffect } from "#app/data/status-effect.js"; -import { QuietFormChangePhase } from "#app/form-change-phase.js"; -import { TurnEndPhase } from "#app/phases.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/arena/arena_gravity.test.ts b/src/test/arena/arena_gravity.test.ts index 66d6994fb80..68c31258454 100644 --- a/src/test/arena/arena_gravity.test.ts +++ b/src/test/arena/arena_gravity.test.ts @@ -1,13 +1,14 @@ import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; -import { MoveEffectPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Arena - Gravity", () => { let phaserGame: Phaser.Game; diff --git a/src/test/arena/weather_fog.test.ts b/src/test/arena/weather_fog.test.ts index e5718b73a3c..350007ae943 100644 --- a/src/test/arena/weather_fog.test.ts +++ b/src/test/arena/weather_fog.test.ts @@ -1,7 +1,7 @@ import { allMoves } from "#app/data/move.js"; import { WeatherType } from "#app/data/weather.js"; import { Abilities } from "#app/enums/abilities.js"; -import { MoveEffectPhase } from "#app/phases"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; diff --git a/src/test/arena/weather_strong_winds.test.ts b/src/test/arena/weather_strong_winds.test.ts index d9f626cfb83..79fba30c019 100644 --- a/src/test/arena/weather_strong_winds.test.ts +++ b/src/test/arena/weather_strong_winds.test.ts @@ -1,5 +1,4 @@ import { allMoves } from "#app/data/move.js"; -import { TurnStartPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -7,6 +6,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; describe("Weather - Strong Winds", () => { let phaserGame: Phaser.Game; diff --git a/src/test/battle/battle-order.test.ts b/src/test/battle/battle-order.test.ts index 6aa919186b4..208b921b843 100644 --- a/src/test/battle/battle-order.test.ts +++ b/src/test/battle/battle-order.test.ts @@ -1,5 +1,4 @@ import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, EnemyCommandPhase, SelectTargetPhase, TurnStartPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -11,6 +10,10 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; describe("Battle order", () => { diff --git a/src/test/battle/battle.test.ts b/src/test/battle/battle.test.ts index a4713f90506..43d8ddce4b0 100644 --- a/src/test/battle/battle.test.ts +++ b/src/test/battle/battle.test.ts @@ -2,21 +2,6 @@ import { allSpecies } from "#app/data/pokemon-species"; import { TempBattleStat } from "#app/data/temp-battle-stat.js"; import { GameModes } from "#app/game-mode"; import { getGameMode } from "#app/game-mode.js"; -import { - BattleEndPhase, - CommandPhase, DamagePhase, - EncounterPhase, - EnemyCommandPhase, - LoginPhase, - NextEncounterPhase, - SelectGenderPhase, - SelectModifierPhase, - SelectStarterPhase, - SummonPhase, - SwitchPhase, - TitlePhase, - TurnInitPhase, VictoryPhase, -} from "#app/phases"; import GameManager from "#app/test/utils/gameManager"; import { generateStarter, getMovePosition, } from "#app/test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -28,6 +13,21 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "../utils/testUtils"; +import { BattleEndPhase } from "#app/phases/battle-end-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { LoginPhase } from "#app/phases/login-phase.js"; +import { NextEncounterPhase } from "#app/phases/next-encounter-phase.js"; +import { SelectGenderPhase } from "#app/phases/select-gender-phase.js"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; +import { SummonPhase } from "#app/phases/summon-phase.js"; +import { SwitchPhase } from "#app/phases/switch-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; +import { VictoryPhase } from "#app/phases/victory-phase.js"; describe("Test Battle Phase", () => { let phaserGame: Phaser.Game; diff --git a/src/test/battle/double_battle.test.ts b/src/test/battle/double_battle.test.ts index 76b7defe33d..d2ee3812b3e 100644 --- a/src/test/battle/double_battle.test.ts +++ b/src/test/battle/double_battle.test.ts @@ -1,4 +1,3 @@ -import { BattleEndPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition, } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -7,6 +6,8 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; import { Status, StatusEffect } from "#app/data/status-effect.js"; +import { BattleEndPhase } from "#app/phases/battle-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Double Battles", () => { let phaserGame: Phaser.Game; diff --git a/src/test/battle/special_battle.test.ts b/src/test/battle/special_battle.test.ts index 6130df703f5..9b0fd1b3ab1 100644 --- a/src/test/battle/special_battle.test.ts +++ b/src/test/battle/special_battle.test.ts @@ -1,4 +1,4 @@ -import { CommandPhase } from "#app/phases"; +import { CommandPhase } from "#app/phases/command-phase.js"; import GameManager from "#test/utils/gameManager"; import { Mode } from "#app/ui/ui"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/battlerTags/octolock.test.ts b/src/test/battlerTags/octolock.test.ts index 369a84e21fa..a69b45cdfd2 100644 --- a/src/test/battlerTags/octolock.test.ts +++ b/src/test/battlerTags/octolock.test.ts @@ -2,9 +2,9 @@ import { describe, expect, it, vi } from "vitest"; import Pokemon from "#app/field/pokemon.js"; import BattleScene from "#app/battle-scene.js"; import { BattlerTag, BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags.js"; -import { StatChangePhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; vi.mock("#app/battle-scene.js"); diff --git a/src/test/battlerTags/stockpiling.test.ts b/src/test/battlerTags/stockpiling.test.ts index 005f1e1593c..1a39d11e1bd 100644 --- a/src/test/battlerTags/stockpiling.test.ts +++ b/src/test/battlerTags/stockpiling.test.ts @@ -2,9 +2,9 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import Pokemon, { PokemonSummonData } from "#app/field/pokemon.js"; import BattleScene from "#app/battle-scene.js"; import { StockpilingTag } from "#app/data/battler-tags.js"; -import { StatChangePhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; import * as messages from "#app/messages.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; beforeEach(() => { vi.spyOn(messages, "getPokemonNameWithAffix").mockImplementation(() => ""); diff --git a/src/test/items/grip_claw.test.ts b/src/test/items/grip_claw.test.ts index 40ef81fed73..ecf144c96c5 100644 --- a/src/test/items/grip_claw.test.ts +++ b/src/test/items/grip_claw.test.ts @@ -4,11 +4,13 @@ import { Abilities } from "#app/enums/abilities.js"; import { BerryType } from "#app/enums/berry-type.js"; import { Moves } from "#app/enums/moves.js"; import { Species } from "#app/enums/species.js"; -import { CommandPhase, MoveEndPhase, SelectTargetPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; const TIMEOUT = 20 * 1000; // 20 seconds diff --git a/src/test/items/leek.test.ts b/src/test/items/leek.test.ts index 4abc470c6f0..1e46bda9f0f 100644 --- a/src/test/items/leek.test.ts +++ b/src/test/items/leek.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { CritBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; -import { MoveEffectPhase } from "#app/phases"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import * as Utils from "#app/utils"; import { Moves } from "#enums/moves"; diff --git a/src/test/items/leftovers.test.ts b/src/test/items/leftovers.test.ts index e791c4426a1..1a1c95ad9e6 100644 --- a/src/test/items/leftovers.test.ts +++ b/src/test/items/leftovers.test.ts @@ -1,4 +1,3 @@ -import { DamagePhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -6,6 +5,8 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Items - Leftovers", () => { diff --git a/src/test/items/lock_capsule.test.ts b/src/test/items/lock_capsule.test.ts index 32103a6d780..0909e51ea2c 100644 --- a/src/test/items/lock_capsule.test.ts +++ b/src/test/items/lock_capsule.test.ts @@ -4,8 +4,8 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { Abilities } from "#app/enums/abilities.js"; import { Moves } from "#app/enums/moves.js"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { SelectModifierPhase } from "#app/phases.js"; import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type.js"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase.js"; describe("Items - Lock Capsule", () => { let phaserGame: Phaser.Game; diff --git a/src/test/items/scope_lens.test.ts b/src/test/items/scope_lens.test.ts index 4efc7ab9d05..fa605ca7129 100644 --- a/src/test/items/scope_lens.test.ts +++ b/src/test/items/scope_lens.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { CritBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; -import { MoveEffectPhase } from "#app/phases"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import * as Utils from "#app/utils"; import { Moves } from "#enums/moves"; diff --git a/src/test/items/toxic_orb.test.ts b/src/test/items/toxic_orb.test.ts index 69f55cb2bbc..dc54a5a1c36 100644 --- a/src/test/items/toxic_orb.test.ts +++ b/src/test/items/toxic_orb.test.ts @@ -1,5 +1,4 @@ import { StatusEffect } from "#app/data/status-effect"; -import { CommandPhase, EnemyCommandPhase, MessagePhase, TurnEndPhase } from "#app/phases"; import i18next, { initI18n } from "#app/plugins/i18n"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; @@ -10,6 +9,10 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Items - Toxic orb", () => { diff --git a/src/test/moves/astonish.test.ts b/src/test/moves/astonish.test.ts index 358e4a9bec3..21a82f09d33 100644 --- a/src/test/moves/astonish.test.ts +++ b/src/test/moves/astonish.test.ts @@ -1,6 +1,5 @@ import { allMoves } from "#app/data/move.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; -import { BerryPhase, CommandPhase, MoveEndPhase, TurnEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -8,6 +7,10 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/aurora_veil.test.ts b/src/test/moves/aurora_veil.test.ts index a10c9b6b60a..5429efec2bf 100644 --- a/src/test/moves/aurora_veil.test.ts +++ b/src/test/moves/aurora_veil.test.ts @@ -4,7 +4,7 @@ import { WeatherType } from "#app/data/weather.js"; import { Abilities } from "#app/enums/abilities.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; import Pokemon from "#app/field/pokemon.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { NumberHolder } from "#app/utils.js"; diff --git a/src/test/moves/baton_pass.test.ts b/src/test/moves/baton_pass.test.ts index 9f0cb3619b2..790eddbf45c 100644 --- a/src/test/moves/baton_pass.test.ts +++ b/src/test/moves/baton_pass.test.ts @@ -1,5 +1,4 @@ import { BattleStat } from "#app/data/battle-stat.js"; -import { PostSummonPhase, TurnEndPhase } from "#app/phases.js"; import GameManager from "#app/test/utils/gameManager"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -7,6 +6,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "../utils/testUtils"; +import { PostSummonPhase } from "#app/phases/post-summon-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Baton Pass", () => { diff --git a/src/test/moves/beak_blast.test.ts b/src/test/moves/beak_blast.test.ts index 61a022ac9eb..8938b4c7af8 100644 --- a/src/test/moves/beak_blast.test.ts +++ b/src/test/moves/beak_blast.test.ts @@ -5,9 +5,11 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#test/utils/gameManagerUtils"; -import { BerryPhase, MovePhase, TurnEndPhase } from "#app/phases"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { StatusEffect } from "#app/enums/status-effect.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/beat_up.test.ts b/src/test/moves/beat_up.test.ts index a5e4b3cbd34..a0f168ea30f 100644 --- a/src/test/moves/beat_up.test.ts +++ b/src/test/moves/beat_up.test.ts @@ -5,8 +5,8 @@ import { Species } from "#app/enums/species.js"; import { Moves } from "#app/enums/moves.js"; import { Abilities } from "#app/enums/abilities.js"; import { getMovePosition } from "#test/utils/gameManagerUtils"; -import { MoveEffectPhase } from "#app/phases.js"; import { StatusEffect } from "#app/enums/status-effect.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; const TIMEOUT = 20 * 1000; // 20 sec timeout diff --git a/src/test/moves/belly_drum.test.ts b/src/test/moves/belly_drum.test.ts index 74afc910faf..e579a4587ad 100644 --- a/src/test/moves/belly_drum.test.ts +++ b/src/test/moves/belly_drum.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/ceaseless_edge.test.ts b/src/test/moves/ceaseless_edge.test.ts index c5ce8375102..c8291a99b59 100644 --- a/src/test/moves/ceaseless_edge.test.ts +++ b/src/test/moves/ceaseless_edge.test.ts @@ -2,13 +2,14 @@ import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { allMoves } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; -import { MoveEffectPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/clangorous_soul.test.ts b/src/test/moves/clangorous_soul.test.ts index 5493466ba56..5b2e8b6e06d 100644 --- a/src/test/moves/clangorous_soul.test.ts +++ b/src/test/moves/clangorous_soul.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/crafty_shield.test.ts b/src/test/moves/crafty_shield.test.ts index de2829aacf6..c3e50bc52c2 100644 --- a/src/test/moves/crafty_shield.test.ts +++ b/src/test/moves/crafty_shield.test.ts @@ -5,9 +5,10 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { BerryPhase, CommandPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/double_team.test.ts b/src/test/moves/double_team.test.ts index 2153b856517..1c89d5b6350 100644 --- a/src/test/moves/double_team.test.ts +++ b/src/test/moves/double_team.test.ts @@ -1,6 +1,6 @@ import { BattleStat } from "#app/data/battle-stat.js"; import { Abilities } from "#app/enums/abilities.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; diff --git a/src/test/moves/dragon_rage.test.ts b/src/test/moves/dragon_rage.test.ts index 6ec7521f678..8a27f4006f4 100644 --- a/src/test/moves/dragon_rage.test.ts +++ b/src/test/moves/dragon_rage.test.ts @@ -3,7 +3,7 @@ import { Type } from "#app/data/type"; import { Species } from "#app/enums/species.js"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { modifierTypes } from "#app/modifier/modifier-type"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/dragon_tail.test.ts b/src/test/moves/dragon_tail.test.ts index 7374451e643..28c47a83454 100644 --- a/src/test/moves/dragon_tail.test.ts +++ b/src/test/moves/dragon_tail.test.ts @@ -1,6 +1,5 @@ import { allMoves } from "#app/data/move.js"; import { SPLASH_ONLY } from "../utils/testUtils"; -import { BattleEndPhase, BerryPhase, TurnEndPhase} from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -9,6 +8,9 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vi import GameManager from "../utils/gameManager"; import { getMovePosition } from "../utils/gameManagerUtils"; import { BattlerIndex } from "#app/battle.js"; +import { BattleEndPhase } from "#app/phases/battle-end-phase.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/dynamax_cannon.test.ts b/src/test/moves/dynamax_cannon.test.ts index 57846c1aef7..5e81241ef46 100644 --- a/src/test/moves/dynamax_cannon.test.ts +++ b/src/test/moves/dynamax_cannon.test.ts @@ -1,12 +1,14 @@ import { BattlerIndex } from "#app/battle"; import { allMoves } from "#app/data/move"; -import { DamagePhase, MoveEffectPhase, TurnStartPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; describe("Moves - Dynamax Cannon", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/fillet_away.test.ts b/src/test/moves/fillet_away.test.ts index 6965ced46d9..fcad704ef29 100644 --- a/src/test/moves/fillet_away.test.ts +++ b/src/test/moves/fillet_away.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/fissure.test.ts b/src/test/moves/fissure.test.ts index 979bc40646c..65d692a5cc1 100644 --- a/src/test/moves/fissure.test.ts +++ b/src/test/moves/fissure.test.ts @@ -1,7 +1,6 @@ import { BattleStat } from "#app/data/battle-stat"; import { Species } from "#app/enums/species.js"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; -import { DamagePhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -9,6 +8,8 @@ import { Moves } from "#enums/moves"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Fissure", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/flame_burst.test.ts b/src/test/moves/flame_burst.test.ts index 0f9e311ca86..d6679f921df 100644 --- a/src/test/moves/flame_burst.test.ts +++ b/src/test/moves/flame_burst.test.ts @@ -2,12 +2,13 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; import { Species } from "#enums/species"; -import { SelectTargetPhase, TurnEndPhase } from "#app/phases"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#app/enums/abilities.js"; import { allAbilities } from "#app/data/ability.js"; import Pokemon from "#app/field/pokemon.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Flame Burst", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/flower_shield.test.ts b/src/test/moves/flower_shield.test.ts index 7ca5fb8bc62..9001e8ceacb 100644 --- a/src/test/moves/flower_shield.test.ts +++ b/src/test/moves/flower_shield.test.ts @@ -2,7 +2,7 @@ import { BattleStat } from "#app/data/battle-stat.js"; import { SemiInvulnerableTag } from "#app/data/battler-tags.js"; import { Type } from "#app/data/type.js"; import { Biome } from "#app/enums/biome.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/focus_punch.test.ts b/src/test/moves/focus_punch.test.ts index f5cf85ffae0..385234f0b71 100644 --- a/src/test/moves/focus_punch.test.ts +++ b/src/test/moves/focus_punch.test.ts @@ -5,8 +5,12 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#test/utils/gameManagerUtils"; -import { BerryPhase, MessagePhase, MoveHeaderPhase, SwitchSummonPhase, TurnStartPhase } from "#app/phases"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; +import { MoveHeaderPhase } from "#app/phases/move-header-phase.js"; +import { SwitchSummonPhase } from "#app/phases/switch-summon-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/follow_me.test.ts b/src/test/moves/follow_me.test.ts index 420dd7e0762..a0fff9afbf8 100644 --- a/src/test/moves/follow_me.test.ts +++ b/src/test/moves/follow_me.test.ts @@ -1,13 +1,15 @@ import { BattlerIndex } from "#app/battle.js"; import { Stat } from "#app/data/pokemon-stat"; import { Abilities } from "#app/enums/abilities.js"; -import { CommandPhase, SelectTargetPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/foresight.test.ts b/src/test/moves/foresight.test.ts index 3fef1569eba..91d3e3c37e0 100644 --- a/src/test/moves/foresight.test.ts +++ b/src/test/moves/foresight.test.ts @@ -5,7 +5,7 @@ import { Species } from "#app/enums/species.js"; import { SPLASH_ONLY } from "../utils/testUtils"; import { Moves } from "#app/enums/moves.js"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { MoveEffectPhase } from "#app/phases.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; describe("Moves - Foresight", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/freezy_frost.test.ts b/src/test/moves/freezy_frost.test.ts index 3ccd31bd29e..b4c30279c21 100644 --- a/src/test/moves/freezy_frost.test.ts +++ b/src/test/moves/freezy_frost.test.ts @@ -1,5 +1,4 @@ import { BattleStat } from "#app/data/battle-stat"; -import { MoveEndPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -9,6 +8,8 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; import { allMoves } from "#app/data/move.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Freezy Frost", () => { describe("integration tests", () => { diff --git a/src/test/moves/fusion_flare.test.ts b/src/test/moves/fusion_flare.test.ts index 9ae42e7977f..aa38357ddd3 100644 --- a/src/test/moves/fusion_flare.test.ts +++ b/src/test/moves/fusion_flare.test.ts @@ -1,11 +1,11 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; -import { TurnStartPhase } from "#app/phases"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { StatusEffect } from "#app/data/status-effect"; import { Species } from "#enums/species"; import { Moves } from "#enums/moves"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; describe("Moves - Fusion Flare", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/fusion_flare_bolt.test.ts b/src/test/moves/fusion_flare_bolt.test.ts index c2214d5442b..1b95062ee81 100644 --- a/src/test/moves/fusion_flare_bolt.test.ts +++ b/src/test/moves/fusion_flare_bolt.test.ts @@ -1,13 +1,16 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; -import { MoveEffectPhase, MovePhase, MoveEndPhase, DamagePhase } from "#app/phases"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Stat } from "#app/data/pokemon-stat"; import { allMoves } from "#app/data/move"; import { BattlerIndex } from "#app/battle"; import { Species } from "#enums/species"; import { Moves } from "#enums/moves"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; describe("Moves - Fusion Flare and Fusion Bolt", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/glaive_rush.test.ts b/src/test/moves/glaive_rush.test.ts index b9c9d2199d3..f97ba1f0367 100644 --- a/src/test/moves/glaive_rush.test.ts +++ b/src/test/moves/glaive_rush.test.ts @@ -1,12 +1,13 @@ import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; -import { DamagePhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Glaive Rush", () => { diff --git a/src/test/moves/growth.test.ts b/src/test/moves/growth.test.ts index bfa3cc54896..0c60bb723f4 100644 --- a/src/test/moves/growth.test.ts +++ b/src/test/moves/growth.test.ts @@ -1,6 +1,5 @@ import { BattleStat } from "#app/data/battle-stat"; import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, EnemyCommandPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -10,6 +9,9 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Growth", () => { diff --git a/src/test/moves/hard_press.test.ts b/src/test/moves/hard_press.test.ts index baf63a1ad23..255b9f1f4b1 100644 --- a/src/test/moves/hard_press.test.ts +++ b/src/test/moves/hard_press.test.ts @@ -1,5 +1,5 @@ import { allMoves } from "#app/data/move.js"; -import { MoveEffectPhase } from "#app/phases"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/haze.test.ts b/src/test/moves/haze.test.ts index 092575b8000..d5e3efcbd9d 100644 --- a/src/test/moves/haze.test.ts +++ b/src/test/moves/haze.test.ts @@ -1,5 +1,4 @@ import { BattleStat } from "#app/data/battle-stat"; -import { MoveEndPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -8,6 +7,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Haze", () => { describe("integration tests", () => { diff --git a/src/test/moves/hyper_beam.test.ts b/src/test/moves/hyper_beam.test.ts index f33ce4f5478..ac8075081fb 100644 --- a/src/test/moves/hyper_beam.test.ts +++ b/src/test/moves/hyper_beam.test.ts @@ -3,11 +3,12 @@ import { Abilities } from "#app/enums/abilities.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { Moves } from "#app/enums/moves.js"; import { Species } from "#app/enums/species.js"; -import { BerryPhase, TurnEndPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; // 20 sec timeout for all tests diff --git a/src/test/moves/light_screen.test.ts b/src/test/moves/light_screen.test.ts index 9de1f8c492b..4577ffc574a 100644 --- a/src/test/moves/light_screen.test.ts +++ b/src/test/moves/light_screen.test.ts @@ -3,7 +3,7 @@ import Move, { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; import Pokemon from "#app/field/pokemon.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { NumberHolder } from "#app/utils.js"; diff --git a/src/test/moves/lucky_chant.test.ts b/src/test/moves/lucky_chant.test.ts index 1232ce9ffc3..643a5eddb00 100644 --- a/src/test/moves/lucky_chant.test.ts +++ b/src/test/moves/lucky_chant.test.ts @@ -4,8 +4,9 @@ import { getMovePosition } from "../utils/gameManagerUtils"; import { Moves } from "#app/enums/moves.js"; import { Species } from "#app/enums/species.js"; import { Abilities } from "#app/enums/abilities.js"; -import { BerryPhase, TurnEndPhase } from "#app/phases.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/magnet_rise.test.ts b/src/test/moves/magnet_rise.test.ts index 9b3c6c457e2..4ab32b5d048 100644 --- a/src/test/moves/magnet_rise.test.ts +++ b/src/test/moves/magnet_rise.test.ts @@ -1,9 +1,10 @@ -import { CommandPhase, TurnEndPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Magnet Rise", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/make_it_rain.test.ts b/src/test/moves/make_it_rain.test.ts index a4440401c4b..5b0a8c6d62a 100644 --- a/src/test/moves/make_it_rain.test.ts +++ b/src/test/moves/make_it_rain.test.ts @@ -1,5 +1,4 @@ import { BattleStat } from "#app/data/battle-stat.js"; -import { MoveEndPhase, StatChangePhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -8,6 +7,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/mat_block.test.ts b/src/test/moves/mat_block.test.ts index 3a4d23d1497..27a55cab289 100644 --- a/src/test/moves/mat_block.test.ts +++ b/src/test/moves/mat_block.test.ts @@ -5,8 +5,10 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { BerryPhase, CommandPhase, TurnEndPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/miracle_eye.test.ts b/src/test/moves/miracle_eye.test.ts index 45de8b7e4fb..3e1e151e7d4 100644 --- a/src/test/moves/miracle_eye.test.ts +++ b/src/test/moves/miracle_eye.test.ts @@ -5,8 +5,8 @@ import { Species } from "#app/enums/species.js"; import { SPLASH_ONLY } from "../utils/testUtils"; import { Moves } from "#app/enums/moves.js"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { MoveEffectPhase } from "#app/phases.js"; import { BattlerIndex } from "#app/battle.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; describe("Moves - Miracle Eye", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/multi_target.test.ts b/src/test/moves/multi_target.test.ts index 4cb2dfb764d..6e8a7c99e9b 100644 --- a/src/test/moves/multi_target.test.ts +++ b/src/test/moves/multi_target.test.ts @@ -1,7 +1,7 @@ import { getMoveTargets } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; import { Species } from "#app/enums/species.js"; -import { TurnEndPhase } from "#app/phases.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { Moves } from "#enums/moves"; import Phaser from "phaser"; diff --git a/src/test/moves/octolock.test.ts b/src/test/moves/octolock.test.ts index 8988109f431..fcd68446eff 100644 --- a/src/test/moves/octolock.test.ts +++ b/src/test/moves/octolock.test.ts @@ -1,6 +1,5 @@ import { BattleStat } from "#app/data/battle-stat"; import { TrappedTag } from "#app/data/battler-tags.js"; -import { CommandPhase, MoveEndPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -9,6 +8,9 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Octolock", () => { describe("integration tests", () => { diff --git a/src/test/moves/parting_shot.test.ts b/src/test/moves/parting_shot.test.ts index b8b0faba4ce..32995d2d563 100644 --- a/src/test/moves/parting_shot.test.ts +++ b/src/test/moves/parting_shot.test.ts @@ -1,5 +1,4 @@ import { SPLASH_ONLY } from "../utils/testUtils"; -import { BerryPhase, MessagePhase, TurnInitPhase, FaintPhase } from "#app/phases"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -8,6 +7,10 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test, it } from "vi import GameManager from "../utils/gameManager"; import { getMovePosition } from "../utils/gameManagerUtils"; import { BattleStat } from "#app/data/battle-stat"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { FaintPhase } from "#app/phases/faint-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/protect.test.ts b/src/test/moves/protect.test.ts index 34e208e0914..4d97ef5ce82 100644 --- a/src/test/moves/protect.test.ts +++ b/src/test/moves/protect.test.ts @@ -5,10 +5,10 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { BerryPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { allMoves } from "#app/data/move.js"; import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/purify.test.ts b/src/test/moves/purify.test.ts index 7959927d63f..3020e4b47ac 100644 --- a/src/test/moves/purify.test.ts +++ b/src/test/moves/purify.test.ts @@ -1,6 +1,5 @@ import { Status, StatusEffect } from "#app/data/status-effect.js"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon.js"; -import { MoveEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -8,6 +7,7 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import { BattlerIndex } from "#app/battle.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/quick_guard.test.ts b/src/test/moves/quick_guard.test.ts index 58165f3d916..8bf647f2027 100644 --- a/src/test/moves/quick_guard.test.ts +++ b/src/test/moves/quick_guard.test.ts @@ -5,8 +5,9 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { BerryPhase, CommandPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/rage_powder.test.ts b/src/test/moves/rage_powder.test.ts index 92cdcc9b4f7..17b687feead 100644 --- a/src/test/moves/rage_powder.test.ts +++ b/src/test/moves/rage_powder.test.ts @@ -1,5 +1,4 @@ import { BattlerIndex } from "#app/battle.js"; -import { CommandPhase, SelectTargetPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -7,6 +6,9 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/reflect.test.ts b/src/test/moves/reflect.test.ts index f5ea489a75e..79dd4f8202b 100644 --- a/src/test/moves/reflect.test.ts +++ b/src/test/moves/reflect.test.ts @@ -3,7 +3,7 @@ import Move, { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; import Pokemon from "#app/field/pokemon.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { NumberHolder } from "#app/utils.js"; diff --git a/src/test/moves/rollout.test.ts b/src/test/moves/rollout.test.ts index 728fe1ecd45..1fc208c6724 100644 --- a/src/test/moves/rollout.test.ts +++ b/src/test/moves/rollout.test.ts @@ -1,5 +1,5 @@ import { allMoves } from "#app/data/move.js"; -import { CommandPhase } from "#app/phases"; +import { CommandPhase } from "#app/phases/command-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/roost.test.ts b/src/test/moves/roost.test.ts index a9036dcb478..c40bb18cdb1 100644 --- a/src/test/moves/roost.test.ts +++ b/src/test/moves/roost.test.ts @@ -2,11 +2,12 @@ import { Abilities } from "#app/enums/abilities.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { Moves } from "#app/enums/moves.js"; import { Species } from "#app/enums/species.js"; -import { MoveEffectPhase, TurnEndPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/shell_trap.test.ts b/src/test/moves/shell_trap.test.ts index b027541c252..c600b1ee1cc 100644 --- a/src/test/moves/shell_trap.test.ts +++ b/src/test/moves/shell_trap.test.ts @@ -6,9 +6,11 @@ import { Species } from "#app/enums/species.js"; import { allMoves } from "#app/data/move.js"; import { BattlerIndex } from "#app/battle.js"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { BerryPhase, MoveEndPhase, MovePhase } from "#app/phases.js"; import { SPLASH_ONLY } from "../utils/testUtils"; import { MoveResult } from "#app/field/pokemon.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/spikes.test.ts b/src/test/moves/spikes.test.ts index bbbb3347580..ae3c676b893 100644 --- a/src/test/moves/spikes.test.ts +++ b/src/test/moves/spikes.test.ts @@ -1,4 +1,4 @@ -import { CommandPhase } from "#app/phases"; +import { CommandPhase } from "#app/phases/command-phase.js"; import GameManager from "#test/utils/gameManager"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/src/test/moves/spit_up.test.ts b/src/test/moves/spit_up.test.ts index ec0a53028ff..51d84a5e151 100644 --- a/src/test/moves/spit_up.test.ts +++ b/src/test/moves/spit_up.test.ts @@ -3,7 +3,6 @@ import { StockpilingTag } from "#app/data/battler-tags.js"; import { allMoves } from "#app/data/move.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { MoveResult, TurnMove } from "#app/field/pokemon.js"; -import { MovePhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; @@ -11,6 +10,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Spit Up", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/spotlight.test.ts b/src/test/moves/spotlight.test.ts index 0893ba975d7..40ab78471ae 100644 --- a/src/test/moves/spotlight.test.ts +++ b/src/test/moves/spotlight.test.ts @@ -1,12 +1,14 @@ import { BattlerIndex } from "#app/battle.js"; import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, SelectTargetPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/stockpile.test.ts b/src/test/moves/stockpile.test.ts index 375eeab3c95..0b208e20f81 100644 --- a/src/test/moves/stockpile.test.ts +++ b/src/test/moves/stockpile.test.ts @@ -1,7 +1,6 @@ import { BattleStat } from "#app/data/battle-stat"; import { StockpilingTag } from "#app/data/battler-tags.js"; import { MoveResult, TurnMove } from "#app/field/pokemon.js"; -import { CommandPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -10,6 +9,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Stockpile", () => { describe("integration tests", () => { diff --git a/src/test/moves/swallow.test.ts b/src/test/moves/swallow.test.ts index aed30445fd2..6a054393acc 100644 --- a/src/test/moves/swallow.test.ts +++ b/src/test/moves/swallow.test.ts @@ -2,7 +2,6 @@ import { BattleStat } from "#app/data/battle-stat"; import { StockpilingTag } from "#app/data/battler-tags.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { MoveResult, TurnMove } from "#app/field/pokemon.js"; -import { MovePhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; @@ -10,6 +9,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Swallow", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/tackle.test.ts b/src/test/moves/tackle.test.ts index 512b23ae363..f442645baa9 100644 --- a/src/test/moves/tackle.test.ts +++ b/src/test/moves/tackle.test.ts @@ -1,5 +1,4 @@ import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, EnemyCommandPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -8,6 +7,9 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Tackle", () => { diff --git a/src/test/moves/tail_whip.test.ts b/src/test/moves/tail_whip.test.ts index 7630b31f7de..ba4a7459094 100644 --- a/src/test/moves/tail_whip.test.ts +++ b/src/test/moves/tail_whip.test.ts @@ -1,5 +1,4 @@ import { BattleStat } from "#app/data/battle-stat"; -import { CommandPhase, EnemyCommandPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -9,6 +8,9 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Tail whip", () => { diff --git a/src/test/moves/tailwind.test.ts b/src/test/moves/tailwind.test.ts index e32e10a4290..115a97f3be4 100644 --- a/src/test/moves/tailwind.test.ts +++ b/src/test/moves/tailwind.test.ts @@ -1,7 +1,7 @@ import { ArenaTagSide } from "#app/data/arena-tag.js"; import { Stat } from "#app/data/pokemon-stat.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; diff --git a/src/test/moves/thousand_arrows.test.ts b/src/test/moves/thousand_arrows.test.ts index 84a71ee5256..d72f3ed3fac 100644 --- a/src/test/moves/thousand_arrows.test.ts +++ b/src/test/moves/thousand_arrows.test.ts @@ -1,12 +1,13 @@ import { Abilities } from "#app/enums/abilities.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; -import { BerryPhase, MoveEffectPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/tidy_up.test.ts b/src/test/moves/tidy_up.test.ts index e35a438c562..64a63df08df 100644 --- a/src/test/moves/tidy_up.test.ts +++ b/src/test/moves/tidy_up.test.ts @@ -1,6 +1,5 @@ import { BattleStat } from "#app/data/battle-stat.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; -import { MoveEndPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -9,6 +8,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Tidy Up", () => { diff --git a/src/test/moves/u_turn.test.ts b/src/test/moves/u_turn.test.ts index 2c12a4da43b..b93f997c487 100644 --- a/src/test/moves/u_turn.test.ts +++ b/src/test/moves/u_turn.test.ts @@ -1,5 +1,4 @@ import { Abilities } from "#app/enums/abilities.js"; -import { SwitchPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#app/test/utils/gameManager"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -8,6 +7,8 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { StatusEffect } from "#app/enums/status-effect.js"; import { SPLASH_ONLY } from "../utils/testUtils"; +import { SwitchPhase } from "#app/phases/switch-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - U-turn", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/wide_guard.test.ts b/src/test/moves/wide_guard.test.ts index 94f382022c2..1f22428de4b 100644 --- a/src/test/moves/wide_guard.test.ts +++ b/src/test/moves/wide_guard.test.ts @@ -5,8 +5,9 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { BerryPhase, CommandPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/phases/phases.test.ts b/src/test/phases/phases.test.ts index c61eb1d41b8..2ed1e48c706 100644 --- a/src/test/phases/phases.test.ts +++ b/src/test/phases/phases.test.ts @@ -1,9 +1,11 @@ import BattleScene from "#app/battle-scene.js"; -import { LoginPhase, TitlePhase, UnavailablePhase } from "#app/phases.js"; import { Mode } from "#app/ui/ui.js"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; +import { LoginPhase } from "#app/phases/login-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; +import { UnavailablePhase } from "#app/phases/unavailable-phase.js"; describe("Phases", () => { let phaserGame: Phaser.Game; diff --git a/src/test/ui/starter-select.test.ts b/src/test/ui/starter-select.test.ts index 020b26b7f66..dbbdb1999b9 100644 --- a/src/test/ui/starter-select.test.ts +++ b/src/test/ui/starter-select.test.ts @@ -1,7 +1,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; -import { EncounterPhase, SelectStarterPhase, TitlePhase } from "#app/phases"; import { Mode } from "#app/ui/ui"; import { GameModes } from "#app/game-mode"; import StarterSelectUiHandler from "#app/ui/starter-select-ui-handler"; @@ -14,6 +13,9 @@ import { Nature} from "#app/data/nature"; import { Button } from "#enums/buttons"; import { Abilities } from "#enums/abilities"; import { Species } from "#enums/species"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; describe("UI - Starter select", () => { diff --git a/src/test/ui/transfer-item.test.ts b/src/test/ui/transfer-item.test.ts index 9315971e484..21aed9b5b87 100644 --- a/src/test/ui/transfer-item.test.ts +++ b/src/test/ui/transfer-item.test.ts @@ -2,7 +2,6 @@ import { BerryType } from "#app/enums/berry-type"; import { Button } from "#app/enums/buttons"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; -import { BattleEndPhase, SelectModifierPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; @@ -11,6 +10,8 @@ import Phaser from "phaser"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { BattleEndPhase } from "#app/phases/battle-end-phase.js"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase.js"; describe("UI - Transfer Items", () => { diff --git a/src/test/ui/type-hints.test.ts b/src/test/ui/type-hints.test.ts index eb0191812e8..f93260f15b7 100644 --- a/src/test/ui/type-hints.test.ts +++ b/src/test/ui/type-hints.test.ts @@ -1,7 +1,6 @@ import { Button } from "#app/enums/buttons.js"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; -import { CommandPhase } from "#app/phases"; import FightUiHandler from "#app/ui/fight-ui-handler.js"; import { Mode } from "#app/ui/ui.js"; import GameManager from "#test/utils/gameManager"; @@ -9,6 +8,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import MockText from "../utils/mocks/mocksContainer/mockText"; import { SPLASH_ONLY } from "../utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; describe("UI - Type Hints", () => { let phaserGame: Phaser.Game; diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index 6333179e3b2..d60cbd62836 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -1,7 +1,6 @@ import GameWrapper from "#test/utils/gameWrapper"; import { Mode } from "#app/ui/ui"; import { generateStarter, waitUntil } from "#test/utils/gameManagerUtils"; -import { CommandPhase, EncounterPhase, FaintPhase, LoginPhase, MovePhase, NewBattlePhase, SelectStarterPhase, SelectTargetPhase, TitlePhase, TurnEndPhase, TurnInitPhase, TurnStartPhase } from "#app/phases"; import BattleScene from "#app/battle-scene.js"; import PhaseInterceptor from "#test/utils/phaseInterceptor"; import TextInterceptor from "#test/utils/TextInterceptor"; @@ -31,6 +30,18 @@ import { vi } from "vitest"; import { ClassicModeHelper } from "./helpers/classicModeHelper"; import { DailyModeHelper } from "./helpers/dailyModeHelper"; import { SettingsHelper } from "./helpers/settingsHelper"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { FaintPhase } from "#app/phases/faint-phase.js"; +import { LoginPhase } from "#app/phases/login-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { NewBattlePhase } from "#app/phases/new-battle-phase.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; /** * Class to manage the game state and transitions between phases. diff --git a/src/test/utils/helpers/classicModeHelper.ts b/src/test/utils/helpers/classicModeHelper.ts index e6854d5bc79..cf59dd81183 100644 --- a/src/test/utils/helpers/classicModeHelper.ts +++ b/src/test/utils/helpers/classicModeHelper.ts @@ -1,7 +1,8 @@ import { Species } from "#app/enums/species.js"; import { GameModes, getGameMode } from "#app/game-mode.js"; import overrides from "#app/overrides.js"; -import { EncounterPhase, SelectStarterPhase } from "#app/phases.js"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; import { Mode } from "#app/ui/ui.js"; import { generateStarter } from "../gameManagerUtils"; import { GameManagerHelper } from "./gameManagerHelper"; diff --git a/src/test/utils/helpers/dailyModeHelper.ts b/src/test/utils/helpers/dailyModeHelper.ts index c83a2e587d9..a143e212fcb 100644 --- a/src/test/utils/helpers/dailyModeHelper.ts +++ b/src/test/utils/helpers/dailyModeHelper.ts @@ -1,6 +1,7 @@ import { Button } from "#app/enums/buttons.js"; import overrides from "#app/overrides.js"; -import { EncounterPhase, TitlePhase } from "#app/phases.js"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; import SaveSlotSelectUiHandler from "#app/ui/save-slot-select-ui-handler.js"; import { Mode } from "#app/ui/ui.js"; import { GameManagerHelper } from "./gameManagerHelper"; diff --git a/src/test/utils/helpers/moveHelper.ts b/src/test/utils/helpers/moveHelper.ts index 9438952aa92..3179e63a6d0 100644 --- a/src/test/utils/helpers/moveHelper.ts +++ b/src/test/utils/helpers/moveHelper.ts @@ -1,6 +1,6 @@ import { vi } from "vitest"; -import { MoveEffectPhase } from "#app/phases.js"; import { GameManagerHelper } from "./gameManagerHelper"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; /** * Helper to handle a Pokemon's move diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index 5a8b4ae01b2..2304d726757 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -1,44 +1,42 @@ -import { - BattleEndPhase, - BerryPhase, - CheckSwitchPhase, - CommandPhase, - DamagePhase, - EggLapsePhase, - EncounterPhase, - EnemyCommandPhase, - FaintPhase, - LoginPhase, - MessagePhase, - MoveEffectPhase, - MoveEndPhase, - MovePhase, - NewBattlePhase, - NextEncounterPhase, - PartyHealPhase, - PostSummonPhase, - SelectGenderPhase, - SelectModifierPhase, - SelectStarterPhase, - SelectTargetPhase, - ShinySparklePhase, - ShowAbilityPhase, - StatChangePhase, - SummonPhase, - SwitchPhase, - SwitchSummonPhase, - TitlePhase, - ToggleDoublePositionPhase, - TurnEndPhase, - TurnInitPhase, - TurnStartPhase, - UnavailablePhase, - VictoryPhase -} from "#app/phases"; import UI, { Mode } from "#app/ui/ui"; import { Phase } from "#app/phase"; import ErrorInterceptor from "#app/test/utils/errorInterceptor"; -import { QuietFormChangePhase } from "#app/form-change-phase"; +import { BattleEndPhase } from "#app/phases/battle-end-phase.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CheckSwitchPhase } from "#app/phases/check-switch-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { EggLapsePhase } from "#app/phases/egg-lapse-phase.js"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { FaintPhase } from "#app/phases/faint-phase.js"; +import { LoginPhase } from "#app/phases/login-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { NewBattlePhase } from "#app/phases/new-battle-phase.js"; +import { NextEncounterPhase } from "#app/phases/next-encounter-phase.js"; +import { PostSummonPhase } from "#app/phases/post-summon-phase.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { SelectGenderPhase } from "#app/phases/select-gender-phase.js"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase.js"; +import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; +import { SummonPhase } from "#app/phases/summon-phase.js"; +import { SwitchPhase } from "#app/phases/switch-phase.js"; +import { SwitchSummonPhase } from "#app/phases/switch-summon-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; +import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; +import { UnavailablePhase } from "#app/phases/unavailable-phase.js"; +import { VictoryPhase } from "#app/phases/victory-phase.js"; +import { PartyHealPhase } from "#app/phases/party-heal-phase.js"; export default class PhaseInterceptor { public scene; diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts index d8b3e5e3ee8..04691886d9c 100644 --- a/src/ui/ball-ui-handler.ts +++ b/src/ui/ball-ui-handler.ts @@ -1,4 +1,3 @@ -import { CommandPhase } from "../phases"; import BattleScene from "../battle-scene"; import { getPokeballName } from "../data/pokeball"; import { addTextObject, getTextStyleOptions, TextStyle } from "./text"; @@ -7,6 +6,7 @@ import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import { addWindow } from "./ui-theme"; import {Button} from "#enums/buttons"; +import { CommandPhase } from "#app/phases/command-phase.js"; export default class BallUiHandler extends UiHandler { private pokeballSelectContainer: Phaser.GameObjects.Container; diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts index 12211fa71cc..73c47da41fe 100644 --- a/src/ui/challenges-select-ui-handler.ts +++ b/src/ui/challenges-select-ui-handler.ts @@ -5,12 +5,13 @@ import UiHandler from "./ui-handler"; import { addWindow } from "./ui-theme"; import {Button} from "#enums/buttons"; import i18next from "i18next"; -import { SelectStarterPhase, TitlePhase } from "#app/phases.js"; import { Challenge } from "#app/data/challenge.js"; import * as Utils from "../utils"; import { Challenges } from "#app/enums/challenges.js"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { Color, ShadowColor } from "#app/enums/color.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; /** * Handles all the UI for choosing optional challenges. diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 11814a25240..b22ea5d20fc 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -1,4 +1,3 @@ -import { CommandPhase } from "../phases"; import BattleScene from "../battle-scene"; import { addTextObject, TextStyle } from "./text"; import PartyUiHandler, { PartyUiMode } from "./party-ui-handler"; @@ -7,6 +6,7 @@ import UiHandler from "./ui-handler"; import i18next from "i18next"; import {Button} from "#enums/buttons"; import { getPokemonNameWithAffix } from "#app/messages.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; export enum Command { FIGHT = 0, diff --git a/src/ui/egg-hatch-scene-handler.ts b/src/ui/egg-hatch-scene-handler.ts index f567861e0b7..733873b974e 100644 --- a/src/ui/egg-hatch-scene-handler.ts +++ b/src/ui/egg-hatch-scene-handler.ts @@ -1,8 +1,8 @@ import BattleScene from "../battle-scene"; -import { EggHatchPhase } from "../egg-hatch-phase"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import {Button} from "#enums/buttons"; +import { EggHatchPhase } from "#app/phases/egg-hatch-phase.js"; export default class EggHatchSceneHandler extends UiHandler { public eggHatchContainer: Phaser.GameObjects.Container; diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 4ade6ca5d20..71d137fbfd7 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -5,11 +5,11 @@ import { Command } from "./command-ui-handler"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import * as Utils from "../utils"; -import { CommandPhase } from "../phases"; import { MoveCategory } from "#app/data/move.js"; import i18next from "i18next"; import {Button} from "#enums/buttons"; import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; export default class FightUiHandler extends UiHandler { public static readonly MOVES_CONTAINER_NAME = "moves"; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 5e1ca7ccbb0..66c777944d1 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1,4 +1,3 @@ -import { CommandPhase, SelectModifierPhase } from "../phases"; import BattleScene from "../battle-scene"; import Pokemon, { MoveResult, PlayerPokemon, PokemonMove } from "../field/pokemon"; import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "./text"; @@ -23,6 +22,8 @@ import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { getPokemonNameWithAffix } from "#app/messages.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase.js"; const defaultMessage = i18next.t("partyUiHandler:choosePokemon"); diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 9f2df1f2329..67e870838a2 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -16,7 +16,6 @@ import { LevelMoves, pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "../ import PokemonSpecies, { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import { Type } from "../data/type"; import { GameModes } from "../game-mode"; -import { SelectChallengePhase, TitlePhase } from "../phases"; import { AbilityAttr, DexAttr, DexAttrProps, DexEntry, StarterMoveset, StarterAttributes, StarterPreferences, StarterPrefs } from "../system/game-data"; import { Tutorial, handleTutorial } from "../tutorial"; import * as Utils from "../utils"; @@ -44,6 +43,8 @@ import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType } import { StarterContainer } from "./starter-container"; import { DropDownColumn, FilterBar } from "./filter-bar"; import { ScrollBar } from "./scroll-bar"; +import { SelectChallengePhase } from "#app/phases/select-challenge-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; export type StarterSelectCallback = (starters: Starter[]) => void;