From 90d32b886c772107b79945c513a0b603bcd824a5 Mon Sep 17 00:00:00 2001 From: Xavion3 Date: Mon, 17 Feb 2025 08:20:50 +1100 Subject: [PATCH] [Feature] Tera Rework (#5233) * Commit old stashed changes * Complete basic implementation of Tera * Fix effectiveness test * Make tera retain until forced recall or faint, regain on biome change * Experimental sparkle fix * Fix champion teras * Attempted fix for double battles tera UI bug * Fix the fix * Fix linting and test issues * Fix more tests * Change int type * Implement tera for ME trainers * Cleanup species inclusivity check * Make tera instant recharge if terapagos in party * Make useless tera shards not generate * Implement stellar tera damage boost * Improve tera selection UI * Tidy up animation and localisation * Improve tera button sprite * Fix Lance tera * Make tera instant recharge during E4 in classic modes. * Fix formatting in the tera common animation The animation was also not playing due to `frameTimedEvents` being missing as well. * Make tera effect start after animation * Implement save migration * Update version number for migration code --------- Co-authored-by: Madmadness65 Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> --- public/battle-anims/common-terastallize.json | 774 ++++++++++++++++++ public/images/battle_anims/terastallize.png | Bin 0 -> 2092 bytes public/images/ui/button_tera.json | 158 ++++ public/images/ui/button_tera.png | Bin 0 -> 6700 bytes public/images/ui/legacy/button_tera.json | 158 ++++ public/images/ui/legacy/button_tera.png | Bin 0 -> 6700 bytes src/battle-scene.ts | 25 +- src/battle.ts | 2 + src/data/ability.ts | 48 +- src/data/battle-anims.ts | 1 + src/data/battler-tags.ts | 2 +- src/data/move.ts | 66 +- .../global-trade-system-encounter.ts | 4 +- .../the-expert-pokemon-breeder-encounter.ts | 9 +- .../utils/encounter-phase-utils.ts | 10 + .../encounter-transformation-sequence.ts | 2 +- src/data/pokemon-forms.ts | 45 +- src/data/trainer-config.ts | 252 +++--- src/field/arena.ts | 2 + src/field/pokemon-sprite-sparkle-handler.ts | 3 + src/field/pokemon.ts | 109 ++- src/field/trainer.ts | 20 +- src/loading-scene.ts | 1 + src/modifier/modifier-type.ts | 79 +- src/modifier/modifier.ts | 104 +-- src/phases/command-phase.ts | 3 + src/phases/enemy-command-phase.ts | 4 + src/phases/evolution-phase.ts | 2 +- src/phases/faint-phase.ts | 2 + src/phases/move-effect-phase.ts | 6 + src/phases/party-heal-phase.ts | 1 + src/phases/quiet-form-change-phase.ts | 8 +- src/phases/tera-phase.ts | 51 ++ src/phases/turn-start-phase.ts | 15 + src/pipelines/sprite.ts | 2 +- src/system/arena-data.ts | 2 + src/system/game-data.ts | 2 + src/system/pokemon-data.ts | 9 + .../version_migration/version_converter.ts | 15 + .../version_migration/versions/v1_7_0.ts | 49 ++ src/test/abilities/libero.test.ts | 4 +- src/test/abilities/protean.test.ts | 4 +- src/test/moves/effectiveness.test.ts | 9 +- src/test/moves/freeze_dry.test.ts | 10 +- src/test/moves/tar_shot.test.ts | 7 +- src/test/moves/tera_blast.test.ts | 27 +- src/test/moves/tera_starstorm.test.ts | 11 +- src/ui/battle-info.ts | 6 +- src/ui/command-ui-handler.ts | 64 +- src/ui/fight-ui-handler.ts | 4 +- src/ui/run-info-ui-handler.ts | 23 - src/ui/summary-ui-handler.ts | 3 +- 52 files changed, 1784 insertions(+), 433 deletions(-) create mode 100644 public/battle-anims/common-terastallize.json create mode 100644 public/images/battle_anims/terastallize.png create mode 100644 public/images/ui/button_tera.json create mode 100644 public/images/ui/button_tera.png create mode 100644 public/images/ui/legacy/button_tera.json create mode 100644 public/images/ui/legacy/button_tera.png create mode 100644 src/phases/tera-phase.ts create mode 100644 src/system/version_migration/versions/v1_7_0.ts diff --git a/public/battle-anims/common-terastallize.json b/public/battle-anims/common-terastallize.json new file mode 100644 index 00000000000..3843464dbde --- /dev/null +++ b/public/battle-anims/common-terastallize.json @@ -0,0 +1,774 @@ +{ + "graphic": "terastallize", + "frames": [ + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 0, + "opacity": 150, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 0, + "opacity": 225, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 70, + "zoomY": 70, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 255, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 70, + "zoomY": 70, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 255, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 90, + "zoomY": 90, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 255, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 90, + "zoomY": 90, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 255, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 255, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 255, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 255, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 200, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "tone": [ + 100, + 100, + 100, + 0 + ], + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 100, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "tone": [ + 100, + 100, + 100, + 0 + ], + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 100, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "tone": [ + 100, + 100, + 100, + 0 + ], + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 60, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "tone": [ + 100, + 100, + 100, + 0 + ], + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 60, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "tone": [ + 100, + 100, + 100, + 0 + ], + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + }, + { + "x": 0, + "y": -20, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 60, + "priority": 1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "tone": [ + 100, + 100, + 100, + 0 + ], + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "tone": [ + 255, + 255, + 255, + 255 + ], + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "tone": [ + 255, + 255, + 255, + 0 + ], + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "tone": [ + 255, + 255, + 255, + 255 + ], + "locked": true, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + } + ] + ], + "frameTimedEvents": {}, + "position": 4, + "hue": 0 +} \ No newline at end of file diff --git a/public/images/battle_anims/terastallize.png b/public/images/battle_anims/terastallize.png new file mode 100644 index 0000000000000000000000000000000000000000..78fbb7ec33506e0318de050424d429e8bc428314 GIT binary patch literal 2092 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 literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/button_tera.json b/public/images/ui/legacy/button_tera.json new file mode 100644 index 00000000000..7b64db66ae6 --- /dev/null +++ b/public/images/ui/legacy/button_tera.json @@ -0,0 +1,158 @@ +{ "frames": { + "unknown": { + "frame": { "x": 0, "y": 0, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "bug": { + "frame": { "x": 18, "y": 0, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "dark": { + "frame": { "x": 36, "y": 0, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "dragon": { + "frame": { "x": 54, "y": 0, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "electric": { + "frame": { "x": 72, "y": 0, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "fairy": { + "frame": { "x": 0, "y": 21, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "fighting": { + "frame": { "x": 18, "y": 21, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "fire": { + "frame": { "x": 36, "y": 21, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "flying": { + "frame": { "x": 54, "y": 21, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "ghost": { + "frame": { "x": 72, "y": 21, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "grass": { + "frame": { "x": 0, "y": 42, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "ground": { + "frame": { "x": 18, "y": 42, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "ice": { + "frame": { "x": 36, "y": 42, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "normal": { + "frame": { "x": 54, "y": 42, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "poison": { + "frame": { "x": 72, "y": 42, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "psychic": { + "frame": { "x": 0, "y": 63, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "rock": { + "frame": { "x": 18, "y": 63, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "steel": { + "frame": { "x": 36, "y": 63, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "water": { + "frame": { "x": 54, "y": 63, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + }, + "stellar": { + "frame": { "x": 72, "y": 63, "w": 18, "h": 21 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 }, + "sourceSize": { "w": 20, "h": 23 } + } + }, + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.7-dev", + "image": "button_tera.png", + "format": "RGBA8888", + "size": { "w": 90, "h": 84 }, + "scale": "1", + "frameTags": [ + ], + "layers": [ + { "name": "Sprite Sheet", "opacity": 255, "blendMode": "normal" } + ], + "slices": [ + ] + } +} diff --git a/public/images/ui/legacy/button_tera.png b/public/images/ui/legacy/button_tera.png new file mode 100644 index 0000000000000000000000000000000000000000..c9672bafa39be1077386d05e922bdc2683dc53d1 GIT binary patch literal 6700 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 literal 0 HcmV?d00001 diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 3f285c274af..4033f44eacb 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -9,7 +9,7 @@ import type { Constructor } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "#app/utils"; import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier"; -import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, ModifierBar, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier } from "./modifier/modifier"; +import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, ModifierBar, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier } from "./modifier/modifier"; import { PokeballType } from "#enums/pokeball"; import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims"; import type { Phase } from "#app/phase"; @@ -1373,7 +1373,11 @@ export default class BattleScene extends SceneBase { for (const pokemon of this.getPlayerParty()) { pokemon.resetBattleData(); + pokemon.resetTera(); applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); + if (pokemon.hasSpecies(Species.TERAPAGOS) || (this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190)) { + this.arena.playerTerasUsed = 0; + } } if (!this.trainer.visible) { @@ -1658,7 +1662,7 @@ export default class BattleScene extends SceneBase { } initPokemonSprite(sprite: Phaser.GameObjects.Sprite, pokemon?: Pokemon, hasShadow: boolean = false, ignoreOverride: boolean = false): Phaser.GameObjects.Sprite { - sprite.setPipeline(this.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: hasShadow, ignoreOverride: ignoreOverride, teraColor: pokemon ? getTypeRgb(pokemon.getTeraType()) : undefined }); + sprite.setPipeline(this.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: hasShadow, ignoreOverride: ignoreOverride, teraColor: pokemon ? getTypeRgb(pokemon.getTeraType()) : undefined, isTerastallized: pokemon ? pokemon.isTerastallized : false }); this.spriteSparkleHandler.add(sprite); return sprite; } @@ -2589,11 +2593,8 @@ export default class BattleScene extends SceneBase { const modifiersToRemove: PersistentModifier[] = []; const modifierPromises: Promise[] = []; if (modifier instanceof PersistentModifier) { - if (modifier instanceof TerastallizeModifier) { - modifiersToRemove.push(...(this.findModifiers(m => m instanceof TerastallizeModifier && m.pokemonId === modifier.pokemonId))); - } if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) { - if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { + if (modifier instanceof PokemonFormChangeItemModifier) { const pokemon = this.getPokemonById(modifier.pokemonId); if (pokemon) { success = modifier.apply(pokemon, true); @@ -2670,11 +2671,8 @@ export default class BattleScene extends SceneBase { addEnemyModifier(modifier: PersistentModifier, ignoreUpdate?: boolean, instant?: boolean): Promise { return new Promise(resolve => { const modifiersToRemove: PersistentModifier[] = []; - if (modifier instanceof TerastallizeModifier) { - modifiersToRemove.push(...(this.findModifiers(m => m instanceof TerastallizeModifier && m.pokemonId === modifier.pokemonId, false))); - } if ((modifier as PersistentModifier).add(this.enemyModifiers, false)) { - if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { + if (modifier instanceof PokemonFormChangeItemModifier) { const pokemon = this.getPokemonById(modifier.pokemonId); if (pokemon) { modifier.apply(pokemon, true); @@ -2798,6 +2796,8 @@ export default class BattleScene extends SceneBase { for (const modifier of modifiers) { this.addEnemyModifier(modifier, true, true); } + + this.currentBattle.trainer.genAI(party); } party.forEach((enemyPokemon: EnemyPokemon, i: number) => { @@ -2929,7 +2929,7 @@ export default class BattleScene extends SceneBase { const modifierIndex = modifiers.indexOf(modifier); if (modifierIndex > -1) { modifiers.splice(modifierIndex, 1); - if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { + if (modifier instanceof PokemonFormChangeItemModifier) { const pokemon = this.getPokemonById(modifier.pokemonId); if (pokemon) { modifier.apply(pokemon, false); @@ -3130,7 +3130,8 @@ export default class BattleScene extends SceneBase { name: p.name, form: p.getFormKey(), types: p.getTypes().map((type) => Type[type]), - teraType: p.getTeraType() !== Type.UNKNOWN ? Type[p.getTeraType()] : "", + teraType: Type[p.getTeraType()], + isTerastallized: p.isTerastallized, level: p.level, currentHP: p.hp, maxHP: p.getMaxHp(), diff --git a/src/battle.ts b/src/battle.ts index 7ede7b2982e..807ac215ea8 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -92,6 +92,7 @@ export default class Battle { public started: boolean = false; public enemySwitchCounter: number = 0; public turn: number = 0; + public preTurnCommands: TurnCommands; public turnCommands: TurnCommands; public playerParticipantIds: Set = new Set(); public battleScore: number = 0; @@ -180,6 +181,7 @@ export default class Battle { incrementTurn(): void { this.turn++; this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ])); + this.preTurnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ])); this.battleSeedState = null; } diff --git a/src/data/ability.ts b/src/data/ability.ts index e5b674d4fb4..6ba5f685acd 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -239,37 +239,25 @@ export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr { } } -export class PostBattleInitStatStageChangeAbAttr extends PostBattleInitAbAttr { +export class PostTeraFormChangeStatChangeAbAttr extends AbAttr { private stats: BattleStat[]; private stages: number; - private selfTarget: boolean; - constructor(stats: BattleStat[], stages: number, selfTarget?: boolean) { + constructor(stats: BattleStat[], stages: number) { super(); this.stats = stats; this.stages = stages; - this.selfTarget = !!selfTarget; } - applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise { const statStageChangePhases: StatStageChangePhase[] = []; if (!simulated) { - if (this.selfTarget) { - statStageChangePhases.push(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); - } else { - for (const opponent of pokemon.getOpponents()) { - statStageChangePhases.push(new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages)); - } - } + statStageChangePhases.push(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); for (const statStageChangePhase of statStageChangePhases) { - if (!this.selfTarget && !statStageChangePhase.getPokemon()?.summonData) { - globalScene.pushPhase(statStageChangePhase); - } else { // TODO: This causes the ability bar to be shown at the wrong time - globalScene.unshiftPhase(statStageChangePhase); - } + globalScene.unshiftPhase(statStageChangePhase); } } @@ -1307,7 +1295,7 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { if ( - !pokemon.isTerastallized() && + !pokemon.isTerastallized && move.id !== Moves.STRUGGLE && /** * Skip moves that call other moves because these moves generate a following move that will trigger this ability attribute @@ -4788,7 +4776,7 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { } override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, _args: any[]): boolean { - if (pokemon.isTerastallized()) { + if (pokemon.isTerastallized) { return false; } const currentTerrain = globalScene.arena.getTerrainType(); @@ -6201,7 +6189,7 @@ export function initAbilities() { .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) .attr(NoFusionAbilityAbAttr) - .condition((pokemon) => !pokemon.isTerastallized()), + .condition((pokemon) => !pokemon.isTerastallized), new Ability(Abilities.QUICK_DRAW, 8) .attr(BypassSpeedChanceAbAttr, 30), new Ability(Abilities.UNSEEN_FIST, 8) @@ -6353,29 +6341,25 @@ export function initAbilities() { new Ability(Abilities.TOXIC_CHAIN, 9) .attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC), new Ability(Abilities.EMBODY_ASPECT_TEAL, 9) - .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.SPD ], 1, true) + .attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.SPD ], 1) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) - .attr(NoTransformAbilityAbAttr) - .partial(), // Ogerpon tera interactions + .attr(NoTransformAbilityAbAttr), new Ability(Abilities.EMBODY_ASPECT_WELLSPRING, 9) - .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.SPDEF ], 1, true) + .attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.SPDEF ], 1) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) - .attr(NoTransformAbilityAbAttr) - .partial(), // Ogerpon tera interactions + .attr(NoTransformAbilityAbAttr), new Ability(Abilities.EMBODY_ASPECT_HEARTHFLAME, 9) - .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.ATK ], 1, true) + .attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.ATK ], 1) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) - .attr(NoTransformAbilityAbAttr) - .partial(), // Ogerpon tera interactions + .attr(NoTransformAbilityAbAttr), new Ability(Abilities.EMBODY_ASPECT_CORNERSTONE, 9) - .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.DEF ], 1, true) + .attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.DEF ], 1) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) - .attr(NoTransformAbilityAbAttr) - .partial(), // Ogerpon tera interactions + .attr(NoTransformAbilityAbAttr), new Ability(Abilities.TERA_SHIFT, 9) .attr(PostSummonFormChangeAbAttr, p => p.getFormKey() ? 0 : 1) .attr(UncopiableAbilityAbAttr) diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 99e9e82d4a6..a179f3a3e9b 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -56,6 +56,7 @@ export enum ChargeAnim { export enum CommonAnim { USE_ITEM = 2000, HEALTH_UP, + TERASTALLIZE, POISON = 2010, TOXIC, PARALYSIS, diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 91ab10aecfa..b97be756048 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2485,7 +2485,7 @@ export class TarShotTag extends BattlerTag { * @returns whether the tag is applied */ override canAdd(pokemon: Pokemon): boolean { - return !pokemon.isTerastallized(); + return !pokemon.isTerastallized; } override onAdd(pokemon: Pokemon): void { diff --git a/src/data/move.ts b/src/data/move.ts index cc22f582790..7e504e87667 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -791,7 +791,7 @@ export default class Move implements Localizable { applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, null, typeChangeMovePowerMultiplier); const sourceTeraType = source.getTeraType(); - if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { + if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { power.value = 60; } @@ -4634,7 +4634,7 @@ export class TeraMoveCategoryAttr extends VariableMoveCategoryAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const category = (args[0] as Utils.NumberHolder); - if (user.isTerastallized() && user.getEffectiveStat(Stat.ATK, target, move, true, true, false, false, true) > + if (user.isTerastallized && user.getEffectiveStat(Stat.ATK, target, move, true, true, false, false, true) > user.getEffectiveStat(Stat.SPATK, target, move, true, true, false, false, true)) { category.value = MoveCategory.PHYSICAL; return true; @@ -4662,7 +4662,7 @@ export class TeraBlastPowerAttr extends VariablePowerAttr { */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const power = args[0] as Utils.NumberHolder; - if (user.isTerastallized() && user.getTeraType() === Type.STELLAR) { + if (user.isTerastallized && user.getTeraType() === Type.STELLAR) { power.value = 100; return true; } @@ -4720,30 +4720,6 @@ export class VariableMoveTypeAttr extends MoveAttr { } } -/** - * Attribute used for Tera Starstorm that changes the move type to Stellar - * @extends VariableMoveTypeAttr - */ -export class TeraStarstormTypeAttr extends VariableMoveTypeAttr { - /** - * - * @param user the {@linkcode Pokemon} using the move - * @param target n/a - * @param move n/a - * @param args[0] {@linkcode Utils.NumberHolder} the move type - * @returns `true` if the move type is changed to {@linkcode Type.STELLAR}, `false` otherwise - */ - override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (user.isTerastallized() && (user.hasFusionSpecies(Species.TERAPAGOS) || user.species.speciesId === Species.TERAPAGOS)) { - const moveType = args[0] as Utils.NumberHolder; - - moveType.value = Type.STELLAR; - return true; - } - return false; - } -} - export class FormChangeItemTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; @@ -5009,7 +4985,7 @@ export class TeraBlastTypeAttr extends VariableMoveTypeAttr { return false; } - if (user.isTerastallized()) { + if (user.isTerastallized) { moveType.value = user.getTeraType(); // changes move type to tera type return true; } @@ -5018,6 +4994,30 @@ export class TeraBlastTypeAttr extends VariableMoveTypeAttr { } } +/** + * Attribute used for Tera Starstorm that changes the move type to Stellar + * @extends VariableMoveTypeAttr + */ +export class TeraStarstormTypeAttr extends VariableMoveTypeAttr { + /** + * + * @param user the {@linkcode Pokemon} using the move + * @param target n/a + * @param move n/a + * @param args[0] {@linkcode Utils.NumberHolder} the move type + * @returns `true` if the move type is changed to {@linkcode Type.STELLAR}, `false` otherwise + */ + override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (user.isTerastallized && user.hasSpecies(Species.TERAPAGOS)) { + const moveType = args[0] as Utils.NumberHolder; + + moveType.value = Type.STELLAR; + return true; + } + return false; + } +} + export class MatchUserTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; @@ -6345,7 +6345,7 @@ export class RemoveTypeAttr extends MoveEffectAttr { return false; } - if (user.isTerastallized() && user.getTeraType() === this.removedType) { // active tera types cannot be removed + if (user.isTerastallized && user.getTeraType() === this.removedType) { // active tera types cannot be removed return false; } @@ -6525,7 +6525,7 @@ export class ChangeTypeAttr extends MoveEffectAttr { } getCondition(): MoveConditionFunc { - return (user, target, move) => !target.isTerastallized() && !target.hasAbility(Abilities.MULTITYPE) && !target.hasAbility(Abilities.RKS_SYSTEM) && !(target.getTypes().length === 1 && target.getTypes()[0] === this.type); + return (user, target, move) => !target.isTerastallized && !target.hasAbility(Abilities.MULTITYPE) && !target.hasAbility(Abilities.RKS_SYSTEM) && !(target.getTypes().length === 1 && target.getTypes()[0] === this.type); } } @@ -6548,7 +6548,7 @@ export class AddTypeAttr extends MoveEffectAttr { } getCondition(): MoveConditionFunc { - return (user, target, move) => !target.isTerastallized() && !target.getTypes().includes(this.type); + return (user, target, move) => !target.isTerastallized && !target.getTypes().includes(this.type); } } @@ -11076,7 +11076,7 @@ export function initMoves() { .attr(TeraMoveCategoryAttr) .attr(TeraBlastTypeAttr) .attr(TeraBlastPowerAttr) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, { condition: (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR) }), + .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, { condition: (user, target, move) => user.isTerastallized && user.isOfType(Type.STELLAR) }), new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9) .attr(ProtectAttr, BattlerTagType.SILK_TRAP) .condition(failIfLastCondition), @@ -11271,7 +11271,7 @@ export function initMoves() { new AttackMove(Moves.TERA_STARSTORM, Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) .attr(TeraMoveCategoryAttr) .attr(TeraStarstormTypeAttr) - .attr(VariableTargetAttr, (user, target, move) => (user.hasFusionSpecies(Species.TERAPAGOS) || user.species.speciesId === Species.TERAPAGOS) && user.isTerastallized() ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER) + .attr(VariableTargetAttr, (user, target, move) => user.hasSpecies(Species.TERAPAGOS) && user.isTerastallized ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER) .partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */ new AttackMove(Moves.FICKLE_BEAM, Type.DRAGON, MoveCategory.SPECIAL, 80, 100, 5, 30, 0, 9) .attr(PreMoveMessageAttr, doublePowerChanceMessageFunc) diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 65bbab16603..f494aaf2c28 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -594,7 +594,7 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P console.error(`Failed to play animation for ${spriteKey}`, err); } - sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) }); + sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()), isTerastallized: tradedPokemon.isTerastallized }); sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey()); sprite.setPipelineData("shiny", tradedPokemon.shiny); @@ -615,7 +615,7 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P console.error(`Failed to play animation for ${spriteKey}`, err); } - sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) }); + sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()), isTerastallized: tradedPokemon.isTerastallized }); sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey()); sprite.setPipelineData("shiny", receivedPokemon.shiny); diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index 2f5843e39d2..a4e80c158bb 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -1,5 +1,5 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { generateModifierType, handleMysteryEncounterBattleFailed, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { handleMysteryEncounterBattleFailed, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { trainerConfigs } from "#app/data/trainer-config"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; @@ -23,7 +23,6 @@ import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import { Type } from "#enums/type"; import { getPokeballTintColor } from "#app/data/pokeball"; @@ -387,11 +386,7 @@ function getPartyConfig(): EnemyPartyConfig { nature: Nature.ADAMANT, moveSet: [ Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH ], ivs: [ 31, 31, 31, 31, 31, 31 ], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.TERA_SHARD, [ Type.STEEL ]) as PokemonHeldItemModifierType, - } - ] + tera: Type.STEEL, } ] }; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 351b969b1a8..d745da5ecb3 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -46,6 +46,7 @@ import type { Variant } from "#app/data/variant"; import { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { Type } from "#app/enums/type"; /** * Animates exclamation sprite over trainer's head at start of encounter @@ -98,6 +99,7 @@ export interface EnemyPokemonConfig { modifierConfigs?: HeldModifierConfig[]; tags?: BattlerTagType[]; dataSource?: PokemonData; + tera?: Type; aiType?: AiType; } @@ -329,6 +331,14 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): tags.forEach(tag => enemyPokemon.addTag(tag)); } + // Set tera + if (config.tera && config.tera !== Type.UNKNOWN) { + enemyPokemon.teraType = config.tera; + if (battle.trainer) { + battle.trainer.config.setInstantTera(e); + } + } + // mysteryEncounterBattleEffects will only be used IFF MYSTERY_ENCOUNTER_POST_SUMMON tag is applied if (config.mysteryEncounterBattleEffects) { enemyPokemon.mysteryEncounterBattleEffects = config.mysteryEncounterBattleEffects; diff --git a/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts b/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts index d4ae3496b0c..0cb2a695de8 100644 --- a/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts +++ b/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts @@ -61,7 +61,7 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon, console.error(`Failed to play animation for ${spriteKey}`, err); } - sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()) }); + sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()), isTerastallized: previousPokemon.isTerastallized }); sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey()); sprite.setPipelineData("shiny", previousPokemon.shiny); diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 0226aef79d1..46dfbfecae2 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -1,8 +1,7 @@ -import { PokemonFormChangeItemModifier, TerastallizeModifier } from "../modifier/modifier"; +import { PokemonFormChangeItemModifier } from "../modifier/modifier"; import type Pokemon from "../field/pokemon"; import { StatusEffect } from "#enums/status-effect"; import { MoveCategory, allMoves } from "./move"; -import { Type } from "#enums/type"; import type { Constructor, nil } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; @@ -399,23 +398,7 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger { * @extends SpeciesFormChangeTrigger */ export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger { - /** The Tera type that triggers the form change */ - private teraType: Type; - - constructor(teraType: Type) { - super(); - this.teraType = teraType; - this.description = i18next.t("pokemonEvolutions:Forms.tera", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) }); - } - - /** - * Checks if the associated Pokémon has the required Tera Shard that matches with the associated Tera type. - * @param {Pokemon} pokemon the Pokémon that is trying to do the form change - * @returns `true` if the Pokémon can change forms, `false` otherwise - */ - canChange(pokemon: Pokemon): boolean { - return !!globalScene.findModifier(m => m instanceof TerastallizeModifier && m.pokemonId === pokemon.id && m.teraType === this.teraType); - } + description = i18next.t("pokemonEvolutions:Forms.tera" ); } /** @@ -425,10 +408,6 @@ export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger { */ export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger { description = i18next.t("pokemonEvolutions:Forms.teraLapse"); - - canChange(pokemon: Pokemon): boolean { - return !!globalScene.findModifier(m => m instanceof TerastallizeModifier && m.pokemonId === pokemon.id); - } } /** @@ -992,19 +971,19 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.OGERPON, "teal-mask", "wellspring-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.WELLSPRING_MASK)), new SpeciesFormChange(Species.OGERPON, "teal-mask", "hearthflame-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.HEARTHFLAME_MASK)), new SpeciesFormChange(Species.OGERPON, "teal-mask", "cornerstone-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.CORNERSTONE_MASK)), - new SpeciesFormChange(Species.OGERPON, "teal-mask", "teal-mask-tera", new SpeciesFormChangeTeraTrigger(Type.GRASS)), - new SpeciesFormChange(Species.OGERPON, "teal-mask-tera", "teal-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.GRASS)), - new SpeciesFormChange(Species.OGERPON, "wellspring-mask", "wellspring-mask-tera", new SpeciesFormChangeTeraTrigger(Type.WATER)), - new SpeciesFormChange(Species.OGERPON, "wellspring-mask-tera", "wellspring-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.WATER)), - new SpeciesFormChange(Species.OGERPON, "hearthflame-mask", "hearthflame-mask-tera", new SpeciesFormChangeTeraTrigger(Type.FIRE)), - new SpeciesFormChange(Species.OGERPON, "hearthflame-mask-tera", "hearthflame-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.FIRE)), - new SpeciesFormChange(Species.OGERPON, "cornerstone-mask", "cornerstone-mask-tera", new SpeciesFormChangeTeraTrigger(Type.ROCK)), - new SpeciesFormChange(Species.OGERPON, "cornerstone-mask-tera", "cornerstone-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.ROCK)) + new SpeciesFormChange(Species.OGERPON, "teal-mask", "teal-mask-tera", new SpeciesFormChangeTeraTrigger(), true), + new SpeciesFormChange(Species.OGERPON, "teal-mask-tera", "teal-mask", new SpeciesFormChangeLapseTeraTrigger(), true), + new SpeciesFormChange(Species.OGERPON, "wellspring-mask", "wellspring-mask-tera", new SpeciesFormChangeTeraTrigger(), true), + new SpeciesFormChange(Species.OGERPON, "wellspring-mask-tera", "wellspring-mask", new SpeciesFormChangeLapseTeraTrigger(), true), + new SpeciesFormChange(Species.OGERPON, "hearthflame-mask", "hearthflame-mask-tera", new SpeciesFormChangeTeraTrigger(), true), + new SpeciesFormChange(Species.OGERPON, "hearthflame-mask-tera", "hearthflame-mask", new SpeciesFormChangeLapseTeraTrigger(), true), + new SpeciesFormChange(Species.OGERPON, "cornerstone-mask", "cornerstone-mask-tera", new SpeciesFormChangeTeraTrigger(), true), + new SpeciesFormChange(Species.OGERPON, "cornerstone-mask-tera", "cornerstone-mask", new SpeciesFormChangeLapseTeraTrigger(), true) ], [Species.TERAPAGOS]: [ new SpeciesFormChange(Species.TERAPAGOS, "", "terastal", new SpeciesFormChangeAbilityTrigger(), true), - new SpeciesFormChange(Species.TERAPAGOS, "terastal", "stellar", new SpeciesFormChangeTeraTrigger(Type.STELLAR)), - new SpeciesFormChange(Species.TERAPAGOS, "stellar", "terastal", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.STELLAR)) + new SpeciesFormChange(Species.TERAPAGOS, "terastal", "stellar", new SpeciesFormChangeTeraTrigger(), true), + new SpeciesFormChange(Species.TERAPAGOS, "stellar", "terastal", new SpeciesFormChangeLapseTeraTrigger(), true) ], [Species.GALAR_DARMANITAN]: [ new SpeciesFormChange(Species.GALAR_DARMANITAN, "", "zen", new SpeciesFormChangeAbilityTrigger(), true), diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index 564518845dc..9cbec400a87 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -175,11 +175,51 @@ export const trainerPartyTemplates = { type PartyTemplateFunc = () => TrainerPartyTemplate; type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon; type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[]; +type GenAIFunc = (party: EnemyPokemon[]) => void; export interface PartyMemberFuncs { [key: number]: PartyMemberFunc } +export enum TeraAIMode { + NO_TERA, + INSTANT_TERA, + SMART_TERA +} + +/** + * Stores data and helper functions about a trainers AI options. + */ +export class TrainerAI { + public teraMode: TeraAIMode = TeraAIMode.NO_TERA; + public instantTeras: number[]; + + /** + * @param canTerastallize Whether this trainer is allowed to tera + */ + constructor(teraMode: TeraAIMode = TeraAIMode.NO_TERA) { + this.teraMode = teraMode; + this.instantTeras = []; + } + + /** + * Checks if a trainer can tera + * @returns Whether this trainer can currently tera + */ + public canTerastallize() { + return this.teraMode !== TeraAIMode.NO_TERA; + } + + /** + * Sets a pokemon on this AI to just instantly tera on first move used + * @param index The index of the pokemon to instantly tera + */ + public setInstantTera(index: number) { + this.teraMode = TeraAIMode.INSTANT_TERA; + this.instantTeras.push(index); + } +} + export class TrainerConfig { public trainerType: TrainerType; public trainerTypeDouble: TrainerType; @@ -203,6 +243,7 @@ export class TrainerConfig { public doubleEncounterBgm: string; public victoryBgm: string; public genModifiersFunc: GenModifiersFunc; + public genAIFuncs: GenAIFunc[] = []; public modifierRewardFuncs: ModifierTypeFunc[] = []; public partyTemplates: TrainerPartyTemplate[]; public partyTemplateFunc: PartyTemplateFunc; @@ -212,6 +253,7 @@ export class TrainerConfig { public speciesFilter: PokemonSpeciesFilter; public specialtyTypes: Type[] = []; public hasVoucher: boolean = false; + public trainerAI: TrainerAI; public encounterMessages: string[] = []; public victoryMessages: string[] = []; @@ -227,6 +269,7 @@ export class TrainerConfig { constructor(trainerType: TrainerType, allowLegendaries?: boolean) { this.trainerType = trainerType; + this.trainerAI = new TrainerAI(); this.name = Utils.toReadableString(TrainerType[this.getDerivedType()]); this.battleBgm = "battle_trainer"; this.mixedBattleBgm = "battle_trainer"; @@ -550,6 +593,47 @@ export class TrainerConfig { return this; } + /** + * Sets random pokemon from the trainers team to instant tera. Uses their specialty types is they have one. + * @param count The amount of pokemon to have instant tera + * @returns this + */ + setRandomTeraModifiers(count: () => integer): TrainerConfig { + this.genAIFuncs.push((party: EnemyPokemon[]) => { + const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i); + for (let t = 0; t < Math.min(count(), party.length); t++) { + const randomIndex = Utils.randSeedItem(partyMemberIndexes); + partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1); + if (this.specialtyTypes?.length) { + party[randomIndex].teraType = Utils.randSeedItem(this.specialtyTypes); + } + this.trainerAI.setInstantTera(randomIndex); + } + }); + return this; + } + + /** + * Sets a specific pokemon to instant tera + * @param index The index within the team to have instant tera + * @returns this + */ + setInstantTera(index: number): TrainerConfig { + this.trainerAI.setInstantTera(index); + return this; + } + + // function getRandomTeraModifiers(party: EnemyPokemon[], count: integer, types?: Type[]): PersistentModifier[] { + // const ret: PersistentModifier[] = []; + // const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i); + // for (let t = 0; t < Math.min(count, party.length); t++) { + // const randomIndex = Utils.randSeedItem(partyMemberIndexes); + // partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1); + // ret.push(modifierTypes.TERA_SHARD().generateType([], [ Utils.randSeedItem(types ? types : party[randomIndex].getTypes()) ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier); // TODO: is the bang correct? + // } + // return ret; + // } + setEventModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig { this.eventRewardFuncs = modifierTypeFuncs.map(func => () => { const modifierTypeFunc = func(); @@ -851,10 +935,7 @@ export class TrainerConfig { this.setHasVoucher(true); this.setBattleBgm("battle_unova_gym"); this.setVictoryBgm("victory_gym"); - this.setGenModifiersFunc(party => { - const waveIndex = globalScene.currentBattle.waveIndex; - return getRandomTeraModifiers(party, waveIndex >= 100 ? 1 : 0, specialtyTypes.length ? specialtyTypes : undefined); - }); + this.setRandomTeraModifiers(() => globalScene.currentBattle.waveIndex >= 100 ? 1 : 0); return this; } @@ -910,7 +991,7 @@ export class TrainerConfig { this.setHasVoucher(true); this.setBattleBgm("battle_unova_elite"); this.setVictoryBgm("victory_gym"); - this.setGenModifiersFunc(party => getRandomTeraModifiers(party, 1, specialtyTypes.length ? specialtyTypes : undefined)); + this.setRandomTeraModifiers(() => 1); return this; } @@ -1197,16 +1278,6 @@ function getSpeciesFilterRandomPartyMemberFunc( }; } -function getRandomTeraModifiers(party: EnemyPokemon[], count: number, types?: Type[]): PersistentModifier[] { - const ret: PersistentModifier[] = []; - const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i); - for (let t = 0; t < Math.min(count, party.length); t++) { - const randomIndex = Utils.randSeedItem(partyMemberIndexes); - partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1); - ret.push(modifierTypes.TERA_SHARD().generateType([], [ Utils.randSeedItem(types ? types : party[randomIndex].getTypes()) ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier); // TODO: is the bang correct? - } - return ret; -} type SignatureSpecies = { [key in string]: (Species | Species[])[]; @@ -1747,10 +1818,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateName(); p.gender = Gender.MALE; })) - .setGenModifiersFunc(party => { - const teraPokemon = party[3]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(3), [TrainerType.RED]: new TrainerConfig(++t).initForChampion(true).setBattleBgm("battle_johto_champion").setMixedBattleBgm("battle_johto_champion").setHasDouble("red_blue_double").setDoubleTrainerType(TrainerType.BLUE).setDoubleTitle("champion_double") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PIKACHU ], TrainerSlot.TRAINER, true, p => { p.formIndex = 8; // G-Max Pikachu @@ -1774,10 +1842,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateName(); p.gender = Gender.MALE; })) - .setGenModifiersFunc(party => { - const teraPokemon = party[3]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(3), [TrainerType.LANCE_CHAMPION]: new TrainerConfig(++t).setName("Lance").initForChampion(true).setBattleBgm("battle_johto_champion").setMixedBattleBgm("battle_johto_champion") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GYARADOS, Species.KINGDRA ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.AERODACTYL ])) @@ -1787,16 +1852,15 @@ export const trainerConfigs: TrainerConfigs = { p.generateName(); })) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.CHARIZARD ])) - .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.TYRANITAR, Species.GARCHOMP, Species.KOMMO_O ])) + .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.TYRANITAR, Species.GARCHOMP, Species.KOMMO_O ], TrainerSlot.TRAINER, true, p => { + p.teraType = p.species.type1; + })) .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.DRAGONITE ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.gender = Gender.MALE; p.setBoss(true, 2); })) - .setGenModifiersFunc(party => { - const teraPokemon = party[4]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(4), [TrainerType.STEVEN]: new TrainerConfig(++t).initForChampion(true).setBattleBgm("battle_hoenn_champion_g5").setMixedBattleBgm("battle_hoenn_champion_g6").setHasDouble("steven_wallace_double").setDoubleTrainerType(TrainerType.WALLACE).setDoubleTitle("champion_double") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SKARMORY ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.CRADILY, Species.ARMALDO ])) @@ -1814,10 +1878,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.generateName(); })) - .setGenModifiersFunc(party => { - const teraPokemon = party[4]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(4), [TrainerType.WALLACE]: new TrainerConfig(++t).initForChampion(true).setBattleBgm("battle_hoenn_champion_g5").setMixedBattleBgm("battle_hoenn_champion_g6").setHasDouble("wallace_steven_double").setDoubleTrainerType(TrainerType.STEVEN).setDoubleTitle("champion_double") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PELIPPER ], TrainerSlot.TRAINER, true, p => { p.abilityIndex = 1; // Drizzle @@ -1840,10 +1901,7 @@ export const trainerConfigs: TrainerConfigs = { p.gender = Gender.FEMALE; p.setBoss(true, 2); })) - .setGenModifiersFunc(party => { - const teraPokemon = party[4]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(4), [TrainerType.CYNTHIA]: new TrainerConfig(++t).initForChampion(false).setBattleBgm("battle_sinnoh_champion").setMixedBattleBgm("battle_sinnoh_champion") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SPIRITOMB ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); @@ -1853,7 +1911,9 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.MASTER_BALL; })) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.MILOTIC, Species.ROSERADE, Species.HISUI_ARCANINE ])) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.MILOTIC, Species.ROSERADE, Species.HISUI_ARCANINE ], TrainerSlot.TRAINER, true, p => { + p.teraType = p.species.type1; + })) .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.TOGEKISS ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.setBoss(true, 2); @@ -1864,10 +1924,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateName(); p.gender = Gender.FEMALE; })) - .setGenModifiersFunc(party => { - const teraPokemon = party[3]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(3), [TrainerType.ALDER]: new TrainerConfig(++t).initForChampion(true).setHasDouble("alder_iris_double").setDoubleTrainerType(TrainerType.IRIS).setDoubleTitle("champion_double").setBattleBgm("battle_champion_alder").setMixedBattleBgm("battle_champion_alder") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BOUFFALANT, Species.BRAVIARY ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.HISUI_LILLIGANT, Species.HISUI_ZOROARK, Species.BASCULEGION ], TrainerSlot.TRAINER, true, p => { @@ -1882,16 +1939,15 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; })) - .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.CHANDELURE, Species.KROOKODILE, Species.REUNICLUS, Species.CONKELDURR ])) + .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.CHANDELURE, Species.KROOKODILE, Species.REUNICLUS, Species.CONKELDURR ], TrainerSlot.TRAINER, true, p => { + p.teraType = p.species.speciesId === Species.KROOKODILE ? Type.DARK : p.species.type1; + })) .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.VOLCARONA ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.gender = Gender.MALE; p.setBoss(true, 2); })) - .setGenModifiersFunc(party => { - const pokemon = party[4]; - return [ modifierTypes.TERA_SHARD().generateType([], [ pokemon.species.speciesId === Species.KROOKODILE ? pokemon.species.type2 : pokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(pokemon) as PersistentModifier ]; - }), + .setInstantTera(4), [TrainerType.IRIS]: new TrainerConfig(++t).initForChampion(false).setBattleBgm("battle_champion_iris").setMixedBattleBgm("battle_champion_iris").setHasDouble("iris_alder_double").setDoubleTrainerType(TrainerType.ALDER).setDoubleTitle("champion_double") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.DRUDDIGON ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ARCHEOPS ])) @@ -1899,7 +1955,9 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.MASTER_BALL; })) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.SALAMENCE, Species.HYDREIGON, Species.ARCHALUDON ])) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.SALAMENCE, Species.HYDREIGON, Species.ARCHALUDON ], TrainerSlot.TRAINER, true, p => { + p.teraType = Type.DRAGON; + })) .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.LAPRAS ], TrainerSlot.TRAINER, true, p => { p.formIndex = 1; // G-Max Lapras p.generateAndPopulateMoveset(); @@ -1911,10 +1969,7 @@ export const trainerConfigs: TrainerConfigs = { p.gender = Gender.FEMALE; p.setBoss(true, 2); })) - .setGenModifiersFunc(party => { - const teraPokemon = party[3]; - return [ modifierTypes.TERA_SHARD().generateType([], [ Type.DRAGON ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(3), [TrainerType.DIANTHA]: new TrainerConfig(++t).initForChampion(false).setMixedBattleBgm("battle_kalos_champion") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.HAWLUCHA ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); @@ -1927,6 +1982,7 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.TYRANTRUM, Species.AURORUS ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.abilityIndex = 2; // Rock Head Tyrantrum, Snow Warning Aurorus + p.teraType = p.species.speciesId === Species.TYRANTRUM ? Type.DRAGON : Type.ICE; })) .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.GOODRA ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); @@ -1938,10 +1994,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateName(); p.gender = Gender.FEMALE; })) - .setGenModifiersFunc(party => { - const teraPokemon = party[3]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type2 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(3), [TrainerType.KUKUI]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_champion_kukui") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LYCANROC ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); @@ -1965,11 +2018,9 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.INCINEROAR, Species.HISUI_DECIDUEYE ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.gender = Gender.MALE; + p.teraType = p.species.speciesId === Species.INCINEROAR ? Type.DARK : Type.FIGHTING; })) - .setGenModifiersFunc(party => { - const teraPokemon = party[5]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type2 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(5), [TrainerType.HAU]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_alola_champion") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ALOLA_RAICHU ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); @@ -1982,6 +2033,7 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.TAPU_LELE, Species.TAPU_BULU ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; + p.teraType = p.species.type1; })) .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.ZYGARDE ], TrainerSlot.TRAINER, true, p => { p.formIndex = 1; // Zygarde 10% forme, Aura Break @@ -1993,10 +2045,7 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); p.gender = p.species.speciesId === Species.PRIMARINA ? Gender.FEMALE : Gender.MALE; })) - .setGenModifiersFunc(party => { - const teraPokemon = party[3]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(3), [TrainerType.LEON]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_galar_champion") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.AEGISLASH ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); @@ -2017,10 +2066,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateName(); p.gender = Gender.MALE; })) - .setGenModifiersFunc(party => { - const teraPokemon = party[3]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(3), [TrainerType.MUSTARD]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_mustard") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CORVIKNIGHT ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); @@ -2033,6 +2079,7 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.GALAR_SLOWBRO, Species.GALAR_SLOWKING ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; + p.teraType = Type.PSYCHIC; })) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GALAR_DARMANITAN ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); @@ -2050,10 +2097,7 @@ export const trainerConfigs: TrainerConfigs = { p.gender = Gender.MALE; p.pokeball = PokeballType.ULTRA_BALL; })) - .setGenModifiersFunc(party => { - const teraPokemon = party[2]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type2 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(2), [TrainerType.GEETA]: new TrainerConfig(++t).initForChampion(false).setMixedBattleBgm("battle_champion_geeta") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GLIMMORA ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); @@ -2070,11 +2114,9 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KINGAMBIT ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.abilityIndex = 1; // Supreme Overlord + p.teraType = Type.FLYING; })) - .setGenModifiersFunc(party => { - const teraPokemon = party[5]; - return [ modifierTypes.TERA_SHARD().generateType([], [ Type.FLYING ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(5), [TrainerType.NEMONA]: new TrainerConfig(++t).initForChampion(false).setMixedBattleBgm("battle_champion_nemona") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LYCANROC ], TrainerSlot.TRAINER, true, p => { p.formIndex = 0; // Midday form @@ -2086,16 +2128,15 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.MASTER_BALL; })) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GHOLDENGO ])) - .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.ARMAROUGE, Species.CERULEDGE ])) + .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.ARMAROUGE, Species.CERULEDGE ], TrainerSlot.TRAINER, true, p => { + p.teraType = p.species.speciesId === Species.ARMAROUGE ? Type.PSYCHIC : Type.GHOST; + })) .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.gender = Gender.MALE; p.setBoss(true, 2); })) - .setGenModifiersFunc(party => { - const teraPokemon = party[4]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type2 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; - }), + .setInstantTera(4), [TrainerType.KIERAN]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_champion_kieran") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.POLIWRATH, Species.POLITOED ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); @@ -2117,7 +2158,7 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.ULTRA_BALL; })) .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.OGERPON ], TrainerSlot.TRAINER, true, p => { - p.formIndex = Utils.randSeedInt(4, 4); // Random Ogerpon Tera Mask + p.formIndex = Utils.randSeedInt(4); // Random Ogerpon Tera Mask p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; if (!p.moveset.some(move => !Utils.isNullOrUndefined(move) && move.moveId === Moves.IVY_CUDGEL)) { // Check if Ivy Cudgel is in the moveset, if not, replace the first move with Ivy Cudgel. @@ -2129,17 +2170,7 @@ export const trainerConfigs: TrainerConfigs = { p.gender = Gender.MALE; p.setBoss(true, 2); })) - .setGenModifiersFunc(party => { - const starter = party[4]; - let teraShardType: Type; - const pokemonType2 = starter.species.forms[starter.formIndex].type2; - if (starter.formIndex === 4 || Utils.isNullOrUndefined(pokemonType2)) { - teraShardType = starter.species.type1; - } else { - teraShardType = pokemonType2; - } - return [ modifierTypes.TERA_SHARD().generateType([], [ teraShardType ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ]; - }), + .setInstantTera(4), [TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL) .setModifierRewardFuncs(() => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.EXP_SHARE) @@ -2162,19 +2193,20 @@ export const trainerConfigs: TrainerConfigs = { .setSpeciesFilter(species => species.baseTotal >= 540), [TrainerType.RIVAL_4]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(1.75).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_2").setMixedBattleBgm("battle_rival_2").setPartyTemplates(trainerPartyTemplates.RIVAL_4) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, - (p => p.abilityIndex = 0))) + (p => { + p.abilityIndex = 0; + p.teraType = p.species.type1; + }))) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) .setSpeciesFilter(species => species.baseTotal >= 540) - .setGenModifiersFunc(party => { - const starter = party[0]; - return [ modifierTypes.TERA_SHARD().generateType([], [ starter.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ]; // TODO: is the bang correct? - }), + .setInstantTera(0), [TrainerType.RIVAL_5]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_5) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); p.abilityIndex = 0; + p.teraType = p.species.type1; })) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) @@ -2185,15 +2217,13 @@ export const trainerConfigs: TrainerConfigs = { p.shiny = true; p.variant = 1; })) - .setGenModifiersFunc(party => { - const starter = party[0]; - return [ modifierTypes.TERA_SHARD().generateType([], [ starter.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ]; //TODO: is the bang correct? - }), + .setInstantTera(0), [TrainerType.RIVAL_6]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm("final").setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_6) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 3); p.abilityIndex = 0; + p.teraType = p.species.type1; p.generateAndPopulateMoveset(); })) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true, @@ -2212,10 +2242,7 @@ export const trainerConfigs: TrainerConfigs = { p.formIndex = 1; // Mega Rayquaza p.generateName(); })) - .setGenModifiersFunc(party => { - const starter = party[0]; - return [ modifierTypes.TERA_SHARD().generateType([], [ starter.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ]; // TODO: is the bang correct? - }), + .setInstantTera(0), [TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", []).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN ], TrainerSlot.TRAINER, true, p => { @@ -2713,10 +2740,7 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.ULTRA_BALL; p.generateName(); })) - .setGenModifiersFunc(party => { - const teraPokemon = party[4]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct? - }), + .setInstantTera(4), [TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); @@ -2749,10 +2773,12 @@ export const trainerConfigs: TrainerConfigs = { p.generateName(); p.pokeball = PokeballType.ULTRA_BALL; })) - .setGenModifiersFunc(party => { - const teraPokemon = party[0]; - return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct? - }), + .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.ZAMAZENTA ], TrainerSlot.TRAINER, true, p => { + p.setBoss(true, 2); + p.generateAndPopulateMoveset(); + p.pokeball = PokeballType.MASTER_BALL; + })) + .setInstantTera(0), [TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer([], true) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLAYDOL ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 3); diff --git a/src/field/arena.ts b/src/field/arena.ts index 5ee065d71dc..60ee4b5b03c 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -44,6 +44,7 @@ export class Arena { public bgm: string; public ignoreAbilities: boolean; public ignoringEffectSource: BattlerIndex | null; + public playerTerasUsed: number; /** * Saves the number of times a party pokemon faints during a arena encounter. * {@linkcode globalScene.currentBattle.enemyFaints} is the corresponding faint counter for the enemy (this resets every wave). @@ -63,6 +64,7 @@ export class Arena { this.bgm = bgm; this.trainerPool = biomeTrainerPools[biome]; this.updatePoolsForTimeOfDay(); + this.playerTerasUsed = 0; this.playerFaints = playerFaints; } diff --git a/src/field/pokemon-sprite-sparkle-handler.ts b/src/field/pokemon-sprite-sparkle-handler.ts index 074933f0f00..d1803cc036e 100644 --- a/src/field/pokemon-sprite-sparkle-handler.ts +++ b/src/field/pokemon-sprite-sparkle-handler.ts @@ -27,6 +27,9 @@ export default class PokemonSpriteSparkleHandler { if (!s.visible || (s.parentContainer instanceof Pokemon && !s.parentContainer.parentContainer)) { continue; } + if (!(s.parentContainer instanceof Pokemon) || !(s.parentContainer as Pokemon).isTerastallized) { + continue; + } const pokemon = s.parentContainer instanceof Pokemon ? s.parentContainer as Pokemon : null; const parent = (pokemon || s).parentContainer; const texture = s.texture; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 5c6fbb34aea..dfce632ab06 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -52,7 +52,7 @@ import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; import { Type } from "#enums/type"; import { getLevelTotalExp } from "#app/data/exp"; import { Stat, type PermanentStat, type BattleStat, type EffectiveStat, PERMANENT_STATS, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat"; -import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier, PokemonMultiHitModifier } from "#app/modifier/modifier"; +import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier, PokemonMultiHitModifier } from "#app/modifier/modifier"; import { PokeballType } from "#enums/pokeball"; import { Gender } from "#app/data/gender"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; @@ -79,7 +79,7 @@ import { DexAttr } from "#app/system/game-data"; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; import { getNatureStatMultiplier } from "#app/data/nature"; import type { SpeciesFormChange } from "#app/data/pokemon-forms"; -import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms"; import { TerrainType } from "#app/data/terrain"; import type { TrainerSlot } from "#app/data/trainer-config"; import Overrides from "#app/overrides"; @@ -163,6 +163,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public pokerus: boolean; public switchOutStatus: boolean; public evoCounter: number; + public teraType: Type; + public isTerastallized: boolean; + public stellarTypesBoosted: Type[]; public fusionSpecies: PokemonSpecies | null; public fusionFormIndex: number; @@ -172,6 +175,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public fusionGender: Gender; public fusionLuck: number; public fusionCustomPokemonData: CustomPokemonData | null; + public fusionTeraType: Type; private summonDataPrimer: PokemonSummonData | null; @@ -269,8 +273,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.fusionGender = dataSource.fusionGender; this.fusionLuck = dataSource.fusionLuck; this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData; + this.fusionTeraType = dataSource.fusionTeraType; this.usedTMs = dataSource.usedTMs ?? []; this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData); + this.teraType = dataSource.teraType; + this.isTerastallized = dataSource.isTerastallized; + this.stellarTypesBoosted = dataSource.stellarTypesBoosted ?? []; } else { this.id = Utils.randSeedInt(4294967296); this.ivs = ivs || Utils.getIvsFromId(this.id); @@ -319,6 +327,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); this.fusionLuck = this.luck; + + this.teraType = Utils.randSeedItem(this.getTypes(false, false, true)); + this.isTerastallized = false; + this.stellarTypesBoosted = []; } this.generateName(); @@ -355,7 +367,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const getSprite = (hasShadow?: boolean) => { const ret = globalScene.addPokemonSprite(this, 0, 0, `pkmn__${this.isPlayer() ? "back__" : ""}sub`, undefined, true); ret.setOrigin(0.5, 1); - ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, teraColor: getTypeRgb(this.getTeraType()) }); + ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, teraColor: getTypeRgb(this.getTeraType()), isTerastallized: this.isTerastallized }); return ret; }; @@ -723,7 +735,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } updateSpritePipelineData(): void { - [ this.getSprite(), this.getTintSprite() ].filter(s => !!s).map(s => s.pipelineData["teraColor"] = getTypeRgb(this.getTeraType())); + [ this.getSprite(), this.getTintSprite() ].filter(s => !!s).map(s => { + s.pipelineData["teraColor"] = getTypeRgb(this.getTeraType()); + s.pipelineData["isTerastallized"] = this.isTerastallized; + }); this.updateInfo(true); } @@ -1208,6 +1223,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.fusionSpecies?.speciesId === species; } + /** + * Checks if the {@linkcode Pokemon} has is the specified {@linkcode Species} or is fused with it. + * @param species the pokemon {@linkcode Species} to check + * @returns `true` if the pokemon is the species or is fused with it, `false` otherwise + */ + hasSpecies(species: Species): boolean { + return this.species.speciesId === species || this.fusionSpecies?.speciesId === species; + } + abstract isBoss(): boolean; getMoveset(ignoreOverride?: boolean): (PokemonMove | null)[] { @@ -1284,9 +1308,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride: boolean = false): Type[] { const types: Type[] = []; - if (includeTeraType) { + if (includeTeraType && this.isTerastallized) { const teraType = this.getTeraType(); - if (teraType !== Type.UNKNOWN) { + if (this.isTerastallized && !(forDefend && teraType === Type.STELLAR)) { // Stellar tera uses its original types defensively types.push(teraType); if (forDefend) { return types; @@ -1590,23 +1614,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * @returns the pokemon's current tera {@linkcode Type}, or `Type.UNKNOWN` if the pokemon is not terastallized + * @returns the pokemon's current tera {@linkcode Type} */ - public getTeraType(): Type { - // I don't think this should be possible anymore, please report if you encounter this. --NightKev - if (globalScene === undefined) { - console.warn("Pokemon.getTeraType(): Global scene is not defined!"); - return Type.UNKNOWN; + getTeraType(): Type { + if (this.hasSpecies(Species.TERAPAGOS)) { + return Type.STELLAR; + } else if (this.hasSpecies(Species.OGERPON)) { + const ogerponForm = this.species.speciesId === Species.OGERPON ? this.formIndex : this.fusionFormIndex; + switch (ogerponForm) { + case 0: + case 4: + return Type.GRASS; + case 1: + case 5: + return Type.WATER; + case 2: + case 6: + return Type.FIRE; + case 3: + case 7: + return Type.ROCK; + } + } else if (this.hasSpecies(Species.SHEDINJA)) { + return Type.BUG; } - const teraModifier = globalScene.findModifier(m => - m instanceof TerastallizeModifier - && m.pokemonId === this.id - && m.getBattlesLeft() > 0, this.isPlayer()) as TerastallizeModifier; - return teraModifier?.teraType ?? Type.UNKNOWN; - } - - public isTerastallized(): boolean { - return this.getTeraType() !== Type.UNKNOWN; + return this.teraType; } public isGrounded(): boolean { @@ -1748,7 +1780,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ getAttackTypeEffectiveness(moveType: Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true, move?: Move): TypeDamageMultiplier { if (moveType === Type.STELLAR) { - return this.isTerastallized() ? 2 : 1; + return this.isTerastallized ? 2 : 1; } const types = this.getTypes(true, true); const arena = globalScene.arena; @@ -2785,11 +2817,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const matchesSourceType = sourceTypes.includes(moveType); /** A damage multiplier for when the attack is of the attacker's type and/or Tera type. */ const stabMultiplier = new Utils.NumberHolder(1); - if (matchesSourceType) { - stabMultiplier.value += 0.5; - } - applyMoveAttrs(CombinedPledgeStabBoostAttr, source, this, move, stabMultiplier); - if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === moveType) { + if (matchesSourceType && moveType !== Type.STELLAR) { stabMultiplier.value += 0.5; } @@ -2797,6 +2825,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier); } + applyMoveAttrs(CombinedPledgeStabBoostAttr, source, this, move, stabMultiplier); + + if (source.isTerastallized && sourceTeraType === moveType && moveType !== Type.STELLAR) { + stabMultiplier.value += 0.5; + } + + if (source.isTerastallized && source.teraType === Type.STELLAR && (!source.stellarTypesBoosted.includes(moveType) || source.hasSpecies(Species.TERAPAGOS))) { + if (matchesSourceType) { + stabMultiplier.value += 0.5; + } else { + stabMultiplier.value += 0.2; + } + } + stabMultiplier.value = Math.min(stabMultiplier.value, 2.25); /** Halves damage if the attacker is using a physical attack while burned */ @@ -3837,6 +3879,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } + resetTera(): void { + const wasTerastallized = this.isTerastallized; + this.isTerastallized = false; + this.stellarTypesBoosted = []; + if (wasTerastallized) { + this.updateSpritePipelineData(); + globalScene.triggerPokemonFormChange(this, SpeciesFormChangeLapseTeraTrigger); + } + } + resetTurnData(): void { this.turnData = new PokemonTurnData(); } @@ -4600,6 +4652,7 @@ export class PlayerPokemon extends Pokemon { newPokemon.fusionVariant = this.fusionVariant; newPokemon.fusionGender = this.fusionGender; newPokemon.fusionLuck = this.fusionLuck; + newPokemon.fusionTeraType = this.teraType; newPokemon.usedTMs = this.usedTMs; globalScene.getPlayerParty().push(newPokemon); @@ -4751,6 +4804,7 @@ export class EnemyPokemon extends Pokemon { public aiType: AiType; public bossSegments: number; public bossSegmentIndex: number; + public initialTeamIndex: number; /** To indicate if the instance was populated with a dataSource -> e.g. loaded & populated from session data */ public readonly isPopulatedFromDataSource: boolean; @@ -4760,6 +4814,7 @@ export class EnemyPokemon extends Pokemon { undefined, dataSource ? dataSource.nature : undefined, dataSource); this.trainerSlot = trainerSlot; + this.initialTeamIndex = globalScene.currentBattle?.enemyParty.length ?? 0; this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource if (boss) { this.setBoss(boss, dataSource?.bossSegments); diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 65bca641163..5bce08afae6 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -11,7 +11,8 @@ import { TrainerSlot, trainerConfigs, trainerPartyTemplates, - signatureSpecies + signatureSpecies, + TeraAIMode } from "#app/data/trainer-config"; import type { EnemyPokemon } from "#app/field/pokemon"; import * as Utils from "#app/utils"; @@ -36,6 +37,7 @@ export default class Trainer extends Phaser.GameObjects.Container { public partyTemplateIndex: number; public name: string; public partnerName: string; + public originalIndexes: { [key: number]: number } = {}; constructor(trainerType: TrainerType, variant: TrainerVariant, partyTemplateIndex?: number, name?: string, partnerName?: string, trainerConfigOverride?: TrainerConfig) { super(globalScene, -72, 80); @@ -546,6 +548,13 @@ export default class Trainer extends Phaser.GameObjects.Container { return []; } + genAI(party: EnemyPokemon[]) { + if (this.config.genAIFuncs) { + this.config.genAIFuncs.forEach(f => f(party)); + } + console.log("Generated AI funcs"); + } + loadAssets(): Promise { return this.config.loadAssets(this.variant); } @@ -667,4 +676,13 @@ export default class Trainer extends Phaser.GameObjects.Container { } }); } + + shouldTera(pokemon: EnemyPokemon): boolean { + if (this.config.trainerAI.teraMode === TeraAIMode.INSTANT_TERA) { + if (!pokemon.isTerastallized && this.config.trainerAI.instantTeras.includes(pokemon.initialTeamIndex)) { + return true; + } + } + return false; + } } diff --git a/src/loading-scene.ts b/src/loading-scene.ts index e8f817c1c39..fc685fc2332 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -103,6 +103,7 @@ export class LoadingScene extends SceneBase { this.loadImage("icon_tera", "ui"); this.loadImage("type_tera", "ui"); this.loadAtlas("type_bgs", "ui"); + this.loadAtlas("button_tera", "ui"); this.loadImage("mystery_egg", "ui"); this.loadImage("normal_memory", "ui"); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index da2ab49e9fc..35ed75d8c6e 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -11,7 +11,7 @@ import { Type } from "#enums/type"; import type { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerastallizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, TempExtraModifierModifier, CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier"; +import { AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerrastalizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, TempExtraModifierModifier, CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier"; import { ModifierTier } from "#app/modifier/modifier-tier"; import Overrides from "#app/overrides"; import { Unlockables } from "#app/system/unlockables"; @@ -19,7 +19,7 @@ import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import PartyUiHandler from "#app/ui/party-ui-handler"; import { getModifierTierTextTint } from "#app/ui/text"; -import { formatMoney, getEnumKeys, getEnumValues, isNullOrUndefined, NumberHolder, padInt, randSeedInt, randSeedItem } from "#app/utils"; +import { formatMoney, getEnumKeys, getEnumValues, isNullOrUndefined, NumberHolder, padInt, randSeedInt } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; @@ -275,6 +275,36 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { } } + +export class TerastallizeModifierType extends PokemonModifierType { + private teraType: Type; + + constructor(teraType: Type) { + super("", `${Type[teraType].toLowerCase()}_tera_shard`, (type, args) => new TerrastalizeModifier(type as TerastallizeModifierType, (args[0] as Pokemon).id, teraType), + (pokemon: PlayerPokemon) => { + if ([ pokemon.species.speciesId, pokemon.fusionSpecies?.speciesId ].filter(s => s === Species.TERAPAGOS || s === Species.OGERPON || s === Species.SHEDINJA).length > 0) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "tera_shard"); + + this.teraType = teraType; + } + + get name(): string { + return i18next.t("modifierType:ModifierType.TerastallizeModifierType.name", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) }); + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.TerastallizeModifierType.description", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) }); + } + + getPregenArgs(): any[] { + return [ this.teraType ]; + } +} + export class PokemonHpRestoreModifierType extends PokemonModifierType { protected restorePoints: number; protected restorePercent: number; @@ -1192,28 +1222,6 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { } } -export class TerastallizeModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType { - private teraType: Type; - - constructor(teraType: Type) { - super("", `${Type[teraType].toLowerCase()}_tera_shard`, (type, args) => new TerastallizeModifier(type as TerastallizeModifierType, (args[0] as Pokemon).id, teraType), "tera_shard"); - - this.teraType = teraType; - } - - get name(): string { - return i18next.t("modifierType:ModifierType.TerastallizeModifierType.name", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) }); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.TerastallizeModifierType.description", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) }); - } - - getPregenArgs(): any[] { - return [ this.teraType ]; - } -} - export class ContactHeldItemTransferChanceModifierType extends PokemonHeldItemModifierType { private chancePercent: number; @@ -1469,14 +1477,21 @@ export const modifierTypes = { if (!globalScene.getModifiers(TerastallizeAccessModifier).length) { return null; } - let type: Type; - if (!randSeedInt(3)) { - const partyMemberTypes = party.map(p => p.getTypes(false, false, true)).flat(); - type = randSeedItem(partyMemberTypes); - } else { - type = randSeedInt(64) ? randSeedInt(18) as Type : Type.STELLAR; + const teraTypes: Type[] = []; + party.forEach(p => { + if (!(p.hasSpecies(Species.TERAPAGOS) || p.hasSpecies(Species.OGERPON) || p.hasSpecies(Species.SHEDINJA))) { + teraTypes.push(p.teraType); + } + }); + let excludedType = Type.UNKNOWN; + if (teraTypes.length > 0 && teraTypes.filter(t => t === teraTypes[0]).length === teraTypes.length) { + excludedType = teraTypes[0]; } - return new TerastallizeModifierType(type); + let shardType = randSeedInt(64) ? randSeedInt(18) as Type : Type.STELLAR; + while (shardType === excludedType) { + shardType = randSeedInt(64) ? randSeedInt(18) as Type : Type.STELLAR; + } + return new TerastallizeModifierType(shardType); }), BERRY: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { @@ -1719,7 +1734,7 @@ const modifierPool: ModifierPool = { return Math.min(Math.ceil(highestPartyLevel / 20), 4); }, 4), new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), - new WeightedModifierType(modifierTypes.TERA_SHARD, 1), + new WeightedModifierType(modifierTypes.TERA_SHARD, (party: Pokemon[]) => party.filter(p => !(p.hasSpecies(Species.TERAPAGOS) || p.hasSpecies(Species.OGERPON) || p.hasSpecies(Species.SHEDINJA))).length > 0 ? 1 : 0), new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => { if (party.filter(p => !p.fusionSpecies).length > 1) { if (globalScene.gameMode.isSplicedOnly) { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index d955385ecee..fe61eadaccd 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -3,16 +3,16 @@ import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { getLevelTotalExp } from "#app/data/exp"; import { allMoves } from "#app/data/move"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; -import { type FormChangeItem, SpeciesFormChangeItemTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; +import { type FormChangeItem, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectHealText } from "#app/data/status-effect"; -import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { EvolutionPhase } from "#app/phases/evolution-phase"; import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase"; import { LevelUpPhase } from "#app/phases/level-up-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; -import { achvs } from "#app/system/achv"; import type { VoucherType } from "#app/system/voucher"; import { Command } from "#app/ui/command-ui-handler"; import { addTextObject, TextStyle } from "#app/ui/text"; @@ -25,7 +25,7 @@ import type { PokeballType } from "#enums/pokeball"; import { Species } from "#enums/species"; import { type PermanentStat, type TempBattleStat, BATTLE_STATS, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; -import { Type } from "#enums/type"; +import type { Type } from "#enums/type"; import i18next from "i18next"; import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type"; import { Color, ShadowColor } from "#enums/color"; @@ -786,72 +786,6 @@ export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModi } } -export class TerastallizeModifier extends LapsingPokemonHeldItemModifier { - public override type: TerastallizeModifierType; - public teraType: Type; - public isTransferable: boolean = false; - - constructor(type: TerastallizeModifierType, pokemonId: number, teraType: Type, battlesLeft?: number, stackCount?: number) { - super(type, pokemonId, battlesLeft || 10, stackCount); - - this.teraType = teraType; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof TerastallizeModifier && modifier.teraType === this.teraType) { - return true; - } - return false; - } - - clone(): TerastallizeModifier { - return new TerastallizeModifier(this.type, this.pokemonId, this.teraType, this.battlesLeft, this.stackCount); - } - - getArgs(): any[] { - return [ this.pokemonId, this.teraType, this.battlesLeft ]; - } - - /** - * Applies the {@linkcode TerastallizeModifier} to the specified {@linkcode Pokemon}. - * @param pokemon the {@linkcode Pokemon} to be terastallized - * @returns always `true` - */ - override apply(pokemon: Pokemon): boolean { - if (pokemon.isPlayer()) { - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeTeraTrigger); - globalScene.validateAchv(achvs.TERASTALLIZE); - if (this.teraType === Type.STELLAR) { - globalScene.validateAchv(achvs.STELLAR_TERASTALLIZE); - } - } - pokemon.updateSpritePipelineData(); - return true; - } - - /** - * Triggers {@linkcode LapsingPokemonHeldItemModifier.lapse} and if it returns `0` a form change is triggered. - * @param pokemon THe {@linkcode Pokemon} to be terastallized - * @returns the result of {@linkcode LapsingPokemonHeldItemModifier.lapse} - */ - public override lapse(pokemon: Pokemon): boolean { - const ret = super.lapse(pokemon); - if (!ret) { - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeLapseTeraTrigger); - pokemon.updateSpritePipelineData(); - } - return ret; - } - - getScoreMultiplier(): number { - return 1.25; - } - - getMaxHeldItemCount(pokemon: Pokemon): number { - return 1; - } -} - /** * Modifier used for held items, specifically vitamins like Carbos, Hp Up, etc., that * increase the value of a given {@linkcode PermanentStat}. @@ -2022,6 +1956,36 @@ export abstract class ConsumablePokemonModifier extends ConsumableModifier { } } +export class TerrastalizeModifier extends ConsumablePokemonModifier { + public override type: TerastallizeModifierType; + public teraType: Type; + + constructor(type: TerastallizeModifierType, pokemonId: number, teraType: Type) { + super(type, pokemonId); + + this.teraType = teraType; + } + + /** + * Checks if {@linkcode TerrastalizeModifier} should be applied + * @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item + * @returns `true` if the {@linkcode TerrastalizeModifier} should be applied + */ + override shouldApply(playerPokemon?: PlayerPokemon): boolean { + return super.shouldApply(playerPokemon) && [ playerPokemon?.species.speciesId, playerPokemon?.fusionSpecies?.speciesId ].filter(s => s === Species.TERAPAGOS || s === Species.OGERPON || s === Species.SHEDINJA).length === 0; + } + + /** + * Applies {@linkcode TerrastalizeModifier} + * @param pokemon The {@linkcode PlayerPokemon} that consumes the item + * @returns `true` if hp was restored + */ + override apply(pokemon: Pokemon): boolean { + pokemon.teraType = this.teraType; + return true; + } +} + export class PokemonHpRestoreModifier extends ConsumablePokemonModifier { private restorePoints: number; private restorePercent: number; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 632344be335..411022a84b4 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -118,6 +118,7 @@ export class CommandPhase extends FieldPhase { let success: boolean = false; switch (command) { + case Command.TERA: case Command.FIGHT: let useStruggle = false; const turnMove: TurnMove | undefined = (args.length === 2 ? (args[1] as TurnMove) : undefined); @@ -137,6 +138,7 @@ export class CommandPhase extends FieldPhase { } const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; + const preTurnCommand: TurnCommand = { command: command, targets: [ this.fieldIndex ], skip: command === Command.FIGHT }; const moveTargets: MoveTargetSet = turnMove === undefined ? getMoveTargets(playerPokemon, moveId) : { targets: turnMove.targets, multiple: turnMove.targets.length > 1 }; if (!moveId) { turnCommand.targets = [ this.fieldIndex ]; @@ -152,6 +154,7 @@ export class CommandPhase extends FieldPhase { } else { globalScene.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); } + globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand; globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; success = true; } else if (cursor < playerPokemon.getMoveset().length) { diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts index e76518bb71e..429674e7786 100644 --- a/src/phases/enemy-command-phase.ts +++ b/src/phases/enemy-command-phase.ts @@ -81,6 +81,10 @@ export class EnemyCommandPhase extends FieldPhase { /** Select a move to use (and a target to use it against, if applicable) */ const nextMove = enemyPokemon.getNextMove(); + if (trainer && trainer.shouldTera(enemyPokemon)) { + globalScene.currentBattle.preTurnCommands[this.fieldIndex + BattlerIndex.ENEMY] = { command: Command.TERA }; + } + globalScene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = { command: Command.FIGHT, move: nextMove, skip: this.skipTurn }; diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 12d2923ec36..ea857cac8ba 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -116,7 +116,7 @@ export class EvolutionPhase extends Phase { console.error(`Failed to play animation for ${spriteKey}`, err); } - sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); + sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()), isTerastallized: this.pokemon.isTerastallized }); sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); sprite.setPipelineData("shiny", this.pokemon.shiny); diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 340c5362087..f354bc8031e 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -108,6 +108,8 @@ export class FaintPhase extends PokemonPhase { globalScene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); + pokemon.resetTera(); + if (pokemon.turnData?.attacksReceived?.length) { const lastAttack = pokemon.turnData.attacksReceived[0]; applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, globalScene.getPokemonById(lastAttack.sourceId)!, new PokemonMove(lastAttack.move).getMove(), lastAttack.result); // TODO: is this bang correct? diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 35fe446fc43..65d8d95cde6 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -450,6 +450,12 @@ export class MoveEffectPhase extends PokemonPhase { target.lapseTag(BattlerTagType.SUBSTITUTE); } }); + + const moveType = user.getMoveType(move, true); + if (move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) { + user.stellarTypesBoosted.push(moveType); + } + this.end(); }); }); diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts index 2c1a6c33163..c87c5d00be5 100644 --- a/src/phases/party-heal-phase.ts +++ b/src/phases/party-heal-phase.ts @@ -36,5 +36,6 @@ export class PartyHealPhase extends BattlePhase { globalScene.ui.fadeIn(500).then(() => this.end()); }); }); + globalScene.arena.playerTerasUsed = 0; } } diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts index 185156a20c7..6cd1129d318 100644 --- a/src/phases/quiet-form-change-phase.ts +++ b/src/phases/quiet-form-change-phase.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; import { SemiInvulnerableTag } from "#app/data/battler-tags"; import type { SpeciesFormChange } from "#app/data/pokemon-forms"; -import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms"; +import { getSpeciesFormChangeMessage, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; import { getTypeRgb } from "#app/data/type"; import { BattleSpec } from "#app/enums/battle-spec"; import { BattlerTagType } from "#app/enums/battler-tag-type"; @@ -11,6 +11,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { BattlePhase } from "./battle-phase"; import { MovePhase } from "./move-phase"; import { PokemonHealPhase } from "./pokemon-heal-phase"; +import { applyAbAttrs, PostTeraFormChangeStatChangeAbAttr } from "#app/data/ability"; export class QuietFormChangePhase extends BattlePhase { protected pokemon: Pokemon; @@ -51,7 +52,7 @@ export class QuietFormChangePhase extends BattlePhase { } catch (err: unknown) { console.error(`Failed to play animation for ${spriteKey}`, err); } - sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); + sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()), isTerastallized: this.pokemon.isTerastallized }); [ "spriteColors", "fusionSpriteColors" ].map(k => { if (this.pokemon.summonData?.speciesForm) { k += "Base"; @@ -145,6 +146,9 @@ export class QuietFormChangePhase extends BattlePhase { movePhase.cancel(); } } + if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) { + applyAbAttrs(PostTeraFormChangeStatChangeAbAttr, this.pokemon, null); + } super.end(); } diff --git a/src/phases/tera-phase.ts b/src/phases/tera-phase.ts new file mode 100644 index 00000000000..f4b72d39192 --- /dev/null +++ b/src/phases/tera-phase.ts @@ -0,0 +1,51 @@ +import type Pokemon from "#app/field/pokemon"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { BattlePhase } from "./battle-phase"; +import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; +import { Type } from "#app/enums/type"; +import { achvs } from "#app/system/achv"; +import { SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; +import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; + +export class TeraPhase extends BattlePhase { + public pokemon: Pokemon; + + constructor(pokemon: Pokemon) { + super(); + + this.pokemon = pokemon; + } + + start() { + super.start(); + + console.log(this.pokemon.name, "terastallized to", Type[this.pokemon.teraType].toString()); + + globalScene.queueMessage(i18next.t("battle:pokemonTerastallized", { pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), type: i18next.t(`pokemonInfo:Type.${Type[this.pokemon.teraType]}`) })); + new CommonBattleAnim(CommonAnim.TERASTALLIZE, this.pokemon).play(false, () => { + this.end(); + }); + } + + + end() { + this.pokemon.isTerastallized = true; + this.pokemon.updateSpritePipelineData(); + + if (this.pokemon.isPlayer()) { + globalScene.arena.playerTerasUsed += 1; + } + + globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangeTeraTrigger); + + if (this.pokemon.isPlayer()) { + globalScene.validateAchv(achvs.TERASTALLIZE); + if (this.pokemon.teraType === Type.STELLAR) { + globalScene.validateAchv(achvs.STELLAR_TERASTALLIZE); + } + } + + super.end(); + } +} diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index c64d7ddf526..c6d145e1a4c 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -21,6 +21,7 @@ import { BattlerIndex } from "#app/battle"; import { TrickRoomTag } from "#app/data/arena-tag"; import { SwitchType } from "#enums/switch-type"; import { globalScene } from "#app/global-scene"; +import { TeraPhase } from "./tera-phase"; export class TurnStartPhase extends FieldPhase { constructor() { @@ -139,6 +140,20 @@ export class TurnStartPhase extends FieldPhase { let orderIndex = 0; + for (const o of this.getSpeedOrder()) { + const pokemon = field[o]; + const preTurnCommand = globalScene.currentBattle.preTurnCommands[o]; + + if (preTurnCommand?.skip) { + continue; + } + + switch (preTurnCommand?.command) { + case Command.TERA: + globalScene.pushPhase(new TeraPhase(pokemon)); + } + } + for (const o of moveOrder) { const pokemon = field[o]; diff --git a/src/pipelines/sprite.ts b/src/pipelines/sprite.ts index 67639d6450a..90c0e65d25c 100644 --- a/src/pipelines/sprite.ts +++ b/src/pipelines/sprite.ts @@ -351,7 +351,7 @@ export default class SpritePipeline extends FieldSpritePipeline { const data = sprite.pipelineData; const tone = data["tone"] as number[]; - const teraColor = data["teraColor"] as number[] ?? [ 0, 0, 0 ]; + const teraColor = (data["isTerastallized"] as boolean) ? (data["teraColor"] as number[] ?? [ 0, 0, 0 ]) : [ 0, 0, 0 ]; const hasShadow = data["hasShadow"] as boolean; const yShadowOffset = data["yShadowOffset"] as number; const ignoreFieldPos = data["ignoreFieldPos"] as boolean; diff --git a/src/system/arena-data.ts b/src/system/arena-data.ts index 98ab611ff3c..518acb55c89 100644 --- a/src/system/arena-data.ts +++ b/src/system/arena-data.ts @@ -10,12 +10,14 @@ export default class ArenaData { public weather: Weather | null; public terrain: Terrain | null; public tags: ArenaTag[]; + public playerTerasUsed: number; constructor(source: Arena | any) { const sourceArena = source instanceof Arena ? source as Arena : null; this.biome = sourceArena ? sourceArena.biomeType : source.biome; this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null; this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null; + this.playerTerasUsed = (sourceArena ? sourceArena.playerTerasUsed : source.playerTerasUsed) ?? 0; this.tags = []; if (source.tags) { diff --git a/src/system/game-data.ts b/src/system/game-data.ts index c16fab9db04..63d79d47fba 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1087,6 +1087,8 @@ export class GameData { globalScene.arena.terrain = sessionData.arena.terrain; globalScene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, globalScene.arena.terrain?.terrainType!, globalScene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct? + globalScene.arena.playerTerasUsed = sessionData.arena.playerTerasUsed; + globalScene.arena.tags = sessionData.arena.tags; if (globalScene.arena.tags) { for (const tag of globalScene.arena.tags) { diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 6459bb5033d..20507860e4e 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -13,6 +13,7 @@ import type { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import type { Species } from "#enums/species"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import type { Type } from "#app/enums/type"; export default class PokemonData { public id: number; @@ -45,6 +46,9 @@ export default class PokemonData { public pokerus: boolean; public usedTMs: Moves[]; public evoCounter: number; + public teraType: Type; + public isTerastallized: boolean; + public stellarTypesBoosted: Type[]; public fusionSpecies: Species; public fusionFormIndex: number; @@ -53,6 +57,7 @@ export default class PokemonData { public fusionVariant: Variant; public fusionGender: Gender; public fusionLuck: number; + public fusionTeraType: Type; public boss: boolean; public bossSegments?: number; @@ -103,6 +108,9 @@ export default class PokemonData { this.evoCounter = source.evoCounter ?? 0; } this.pokerus = !!source.pokerus; + this.teraType = source.teraType as Type; + this.isTerastallized = source.isTerastallized || false; + this.stellarTypesBoosted = source.stellarTypesBoosted || []; this.fusionSpecies = sourcePokemon ? sourcePokemon.fusionSpecies?.speciesId : source.fusionSpecies; this.fusionFormIndex = source.fusionFormIndex; @@ -112,6 +120,7 @@ export default class PokemonData { this.fusionGender = source.fusionGender; this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0); this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData); + this.fusionTeraType = (source.fusionTeraType ?? 0) as Type; this.usedTMs = source.usedTMs ?? []; this.customPokemonData = new CustomPokemonData(source.customPokemonData); diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts index aee84805143..95537461768 100644 --- a/src/system/version_migration/version_converter.ts +++ b/src/system/version_migration/version_converter.ts @@ -7,6 +7,9 @@ import * as v1_0_4 from "./versions/v1_0_4"; // --- v1.1.0 PATCHES --- // import * as v1_1_0 from "./versions/v1_1_0"; +// --- v1.7.0 PATCHES --- // +import * as v1_7_0 from "./versions/v1_7_0"; + const LATEST_VERSION = version.split(".").map(value => parseInt(value)); /** @@ -138,6 +141,10 @@ class SessionVersionConverter extends VersionConverter { console.log("Applying v1.1.0 session data migration!"); this.callMigrators(data, v1_1_0.sessionMigrators); } + if (curMinor < 7) { + console.log("Applying v1.7.0 session data migration!"); + this.callMigrators(data, v1_7_0.sessionMigrators); + } } console.log(`Session data successfully migrated to v${version}!`); @@ -164,6 +171,10 @@ class SystemVersionConverter extends VersionConverter { console.log("Applying v1.1.0 system data migraton!"); this.callMigrators(data, v1_1_0.systemMigrators); } + if (curMinor < 7) { + console.log("Applying v1.7.0 session data migration!"); + this.callMigrators(data, v1_7_0.systemMigrators); + } } console.log(`System data successfully migrated to v${version}!`); @@ -190,6 +201,10 @@ class SettingsVersionConverter extends VersionConverter { console.log("Applying v1.1.0 settings data migraton!"); this.callMigrators(data, v1_1_0.settingsMigrators); } + if (curMinor < 7) { + console.log("Applying v1.7.0 session data migration!"); + this.callMigrators(data, v1_7_0.settingsMigrators); + } } console.log(`System data successfully migrated to v${version}!`); diff --git a/src/system/version_migration/versions/v1_7_0.ts b/src/system/version_migration/versions/v1_7_0.ts new file mode 100644 index 00000000000..2acb9d8151a --- /dev/null +++ b/src/system/version_migration/versions/v1_7_0.ts @@ -0,0 +1,49 @@ +import { getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import type { SessionSaveData } from "#app/system/game-data"; +import * as Utils from "#app/utils"; + +export const systemMigrators = [] as const; + +export const settingsMigrators = [] as const; + +export const sessionMigrators = [ + function migrateTera(data: SessionSaveData) { + for (let i = 0; i < data.modifiers.length;) { + if (data.modifiers[i].className === "TerastallizeModifier") { + data.party.forEach((p) => { + if (p.id === data.modifiers[i].args[0]) { + p.teraType = data.modifiers[i].args[1]; + } + }); + data.modifiers.splice(i, 1); + } else { + i++; + } + } + + for (let i = 0; i < data.enemyModifiers.length;) { + if (data.enemyModifiers[i].className === "TerastallizeModifier") { + data.enemyParty.forEach((p) => { + if (p.id === data.enemyModifiers[i].args[0]) { + p.teraType = data.enemyModifiers[i].args[1]; + } + }); + data.enemyModifiers.splice(i, 1); + } else { + i++; + } + } + + data.party.forEach(p => { + if (Utils.isNullOrUndefined(p.teraType)) { + p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1; + } + }); + + data.enemyParty.forEach(p => { + if (Utils.isNullOrUndefined(p.teraType)) { + p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1; + } + }); + } +] as const; diff --git a/src/test/abilities/libero.test.ts b/src/test/abilities/libero.test.ts index 42627da51a3..f6e85979c69 100644 --- a/src/test/abilities/libero.test.ts +++ b/src/test/abilities/libero.test.ts @@ -11,7 +11,7 @@ import { Species } from "#enums/species"; import { WeatherType } from "#enums/weather-type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; describe("Abilities - Libero", () => { @@ -258,7 +258,7 @@ describe("Abilities - Libero", () => { const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); - vi.spyOn(leadPokemon, "isTerastallized").mockReturnValue(true); + leadPokemon.isTerastallized = true; game.move.select(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase); diff --git a/src/test/abilities/protean.test.ts b/src/test/abilities/protean.test.ts index 787834f8a9d..c7d04b9e1c8 100644 --- a/src/test/abilities/protean.test.ts +++ b/src/test/abilities/protean.test.ts @@ -11,7 +11,7 @@ import { Species } from "#enums/species"; import { WeatherType } from "#enums/weather-type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; describe("Abilities - Protean", () => { @@ -258,7 +258,7 @@ describe("Abilities - Protean", () => { const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); - vi.spyOn(leadPokemon, "isTerastallized").mockReturnValue(true); + leadPokemon.isTerastallized = true; game.move.select(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase); diff --git a/src/test/moves/effectiveness.test.ts b/src/test/moves/effectiveness.test.ts index c78416b1237..09c94c740cc 100644 --- a/src/test/moves/effectiveness.test.ts +++ b/src/test/moves/effectiveness.test.ts @@ -6,7 +6,6 @@ import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; import * as Messages from "#app/messages"; -import { TerastallizeModifier, overrideHeldItems } from "#app/modifier/modifier"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; @@ -15,15 +14,14 @@ function testMoveEffectiveness(game: GameManager, move: Moves, targetSpecies: Sp expected: number, targetAbility: Abilities = Abilities.BALL_FETCH, teraType?: Type): void { // Suppress getPokemonNameWithAffix because it calls on a null battle spec vi.spyOn(Messages, "getPokemonNameWithAffix").mockReturnValue(""); - game.override - .enemyAbility(targetAbility) - .enemyHeldItems([{ name:"TERA_SHARD", type: teraType }]); + game.override.enemyAbility(targetAbility); const user = game.scene.addPlayerPokemon(getPokemonSpecies(Species.SNORLAX), 5); const target = game.scene.addEnemyPokemon(getPokemonSpecies(targetSpecies), 5, TrainerSlot.NONE); if (teraType !== undefined) { - overrideHeldItems(target, false); + target.teraType = teraType; + target.isTerastallized = true; } expect(target.getMoveEffectiveness(user, allMoves[move])).toBe(expected); @@ -40,7 +38,6 @@ describe("Moves - Type Effectiveness", () => { type: Phaser.HEADLESS, }); game = new GameManager(phaserGame); - TerastallizeModifier.prototype.apply = (args) => true; game.override.ability(Abilities.BALL_FETCH); }); diff --git a/src/test/moves/freeze_dry.test.ts b/src/test/moves/freeze_dry.test.ts index 9206a103a35..f207e297191 100644 --- a/src/test/moves/freeze_dry.test.ts +++ b/src/test/moves/freeze_dry.test.ts @@ -117,11 +117,12 @@ describe("Moves - Freeze-Dry", () => { }); it("should deal 2x damage to steel type terastallized into water", async () => { - game.override.enemySpecies(Species.SKARMORY) - .enemyHeldItems([{ name: "TERA_SHARD", type: Type.WATER }]); + game.override.enemySpecies(Species.SKARMORY); await game.classicMode.startBattle(); const enemy = game.scene.getEnemyPokemon()!; + enemy.teraType = Type.WATER; + enemy.isTerastallized = true; vi.spyOn(enemy, "getMoveEffectiveness"); game.move.select(Moves.FREEZE_DRY); @@ -132,11 +133,12 @@ describe("Moves - Freeze-Dry", () => { }); it("should deal 0.5x damage to water type terastallized into fire", async () => { - game.override.enemySpecies(Species.PELIPPER) - .enemyHeldItems([{ name: "TERA_SHARD", type: Type.FIRE }]); + game.override.enemySpecies(Species.PELIPPER); await game.classicMode.startBattle(); const enemy = game.scene.getEnemyPokemon()!; + enemy.teraType = Type.FIRE; + enemy.isTerastallized = true; vi.spyOn(enemy, "getMoveEffectiveness"); game.move.select(Moves.FREEZE_DRY); diff --git a/src/test/moves/tar_shot.test.ts b/src/test/moves/tar_shot.test.ts index 5fb70abc19c..66f540e4f9f 100644 --- a/src/test/moves/tar_shot.test.ts +++ b/src/test/moves/tar_shot.test.ts @@ -83,10 +83,12 @@ describe("Moves - Tar Shot", () => { }); it("does not double the effectiveness of Fire-type moves against a Pokémon that is Terastallized", async () => { - game.override.enemyHeldItems([{ name: "TERA_SHARD", type: Type.GRASS }]).enemySpecies(Species.SPRIGATITO); + game.override.enemySpecies(Species.SPRIGATITO); await game.classicMode.startBattle([ Species.PIKACHU ]); const enemy = game.scene.getEnemyPokemon()!; + enemy.teraType = Type.GRASS; + enemy.isTerastallized = true; vi.spyOn(enemy, "getMoveEffectiveness"); @@ -119,7 +121,8 @@ describe("Moves - Tar Shot", () => { await game.toNextTurn(); - game.override.enemyHeldItems([{ name: "TERA_SHARD", type: Type.GRASS }]); + enemy.teraType = Type.GRASS; + enemy.isTerastallized = true; game.move.select(Moves.FIRE_PUNCH); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); diff --git a/src/test/moves/tera_blast.test.ts b/src/test/moves/tera_blast.test.ts index 34d171b47bb..08e401ef9d1 100644 --- a/src/test/moves/tera_blast.test.ts +++ b/src/test/moves/tera_blast.test.ts @@ -35,7 +35,6 @@ describe("Moves - Tera Blast", () => { .starterSpecies(Species.FEEBAS) .moveset([ Moves.TERA_BLAST ]) .ability(Abilities.BALL_FETCH) - .startingHeldItems([{ name: "TERA_SHARD", type: Type.FIRE }]) .enemySpecies(Species.MAGIKARP) .enemyMoveset(Moves.SPLASH) .enemyAbility(Abilities.STURDY) @@ -45,13 +44,15 @@ describe("Moves - Tera Blast", () => { }); it("changes type to match user's tera type", async () => { - game.override - .enemySpecies(Species.FURRET) - .startingHeldItems([{ name: "TERA_SHARD", type: Type.FIGHTING }]); + game.override.enemySpecies(Species.FURRET); await game.startBattle(); const enemyPokemon = game.scene.getEnemyPokemon()!; vi.spyOn(enemyPokemon, "apply"); + const playerPokemon = game.scene.getPlayerPokemon()!; + playerPokemon.teraType = Type.FIGHTING; + playerPokemon.isTerastallized = true; + game.move.select(Moves.TERA_BLAST); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); await game.phaseInterceptor.to("MoveEffectPhase"); @@ -60,10 +61,12 @@ describe("Moves - Tera Blast", () => { }, 20000); it("increases power if user is Stellar tera type", async () => { - game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]); - await game.startBattle(); + const playerPokemon = game.scene.getPlayerPokemon()!; + playerPokemon.teraType = Type.STELLAR; + playerPokemon.isTerastallized = true; + game.move.select(Moves.TERA_BLAST); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); await game.phaseInterceptor.to("MoveEffectPhase"); @@ -72,13 +75,15 @@ describe("Moves - Tera Blast", () => { }, 20000); it("is super effective against terastallized targets if user is Stellar tera type", async () => { - game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]); - await game.startBattle(); + const playerPokemon = game.scene.getPlayerPokemon()!; + playerPokemon.teraType = Type.STELLAR; + playerPokemon.isTerastallized = true; + const enemyPokemon = game.scene.getEnemyPokemon()!; vi.spyOn(enemyPokemon, "apply"); - vi.spyOn(enemyPokemon, "isTerastallized").mockReturnValue(true); + enemyPokemon.isTerastallized = true; game.move.select(Moves.TERA_BLAST); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); @@ -93,6 +98,7 @@ describe("Moves - Tera Blast", () => { const playerPokemon = game.scene.getPlayerPokemon()!; playerPokemon.stats[Stat.ATK] = 100; playerPokemon.stats[Stat.SPATK] = 1; + playerPokemon.isTerastallized = true; vi.spyOn(teraBlastAttr, "apply"); @@ -169,10 +175,11 @@ describe("Moves - Tera Blast", () => { it("causes stat drops if user is Stellar tera type", async () => { - game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]); await game.startBattle(); const playerPokemon = game.scene.getPlayerPokemon()!; + playerPokemon.teraType = Type.STELLAR; + playerPokemon.isTerastallized = true; game.move.select(Moves.TERA_BLAST); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); diff --git a/src/test/moves/tera_starstorm.test.ts b/src/test/moves/tera_starstorm.test.ts index 22dd5b3c4d1..1e934b88c86 100644 --- a/src/test/moves/tera_starstorm.test.ts +++ b/src/test/moves/tera_starstorm.test.ts @@ -29,8 +29,7 @@ describe("Moves - Tera Starstorm", () => { .enemyAbility(Abilities.BALL_FETCH) .enemyMoveset(Moves.SPLASH) .enemyLevel(30) - .enemySpecies(Species.MAGIKARP) - .startingHeldItems([{ name: "TERA_SHARD", type: Type.FIRE }]); + .enemySpecies(Species.MAGIKARP); }); it("changes type to Stellar when used by Terapagos in its Stellar Form", async () => { @@ -38,19 +37,22 @@ describe("Moves - Tera Starstorm", () => { await game.classicMode.startBattle([ Species.TERAPAGOS ]); const terapagos = game.scene.getPlayerPokemon()!; + terapagos.isTerastallized = true; vi.spyOn(terapagos, "getMoveType"); game.move.select(Moves.TERA_STARSTORM); await game.phaseInterceptor.to("TurnEndPhase"); - expect(terapagos.isTerastallized()).toBe(true); expect(terapagos.getMoveType).toHaveReturnedWith(Type.STELLAR); }); it("targets both opponents in a double battle when used by Terapagos in its Stellar Form", async () => { await game.classicMode.startBattle([ Species.MAGIKARP, Species.TERAPAGOS ]); + const terapagos = game.scene.getPlayerParty()[1]; + terapagos.isTerastallized = true; + game.move.select(Moves.TERA_STARSTORM, 0, BattlerIndex.ENEMY); game.move.select(Moves.TERA_STARSTORM, 1); @@ -82,6 +84,8 @@ describe("Moves - Tera Starstorm", () => { fusionedMon.fusionGender = magikarp.gender; fusionedMon.fusionLuck = magikarp.luck; + fusionedMon.isTerastallized = true; + vi.spyOn(fusionedMon, "getMoveType"); game.move.select(Moves.TERA_STARSTORM, 0); @@ -90,7 +94,6 @@ describe("Moves - Tera Starstorm", () => { // Fusion and terastallized expect(fusionedMon.isFusion()).toBe(true); - expect(fusionedMon.isTerastallized()).toBe(true); // Move effects should be applied expect(fusionedMon.getMoveType).toHaveReturnedWith(Type.STELLAR); expect(game.scene.getEnemyField().every(pokemon => pokemon.isFullHp())).toBe(false); diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index fa8767f5eb0..ab7f76daf0b 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -324,9 +324,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.lastTeraType = pokemon.getTeraType(); this.teraIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2); - this.teraIcon.setVisible(this.lastTeraType !== Type.UNKNOWN); + this.teraIcon.setVisible(pokemon.isTerastallized); this.teraIcon.on("pointerover", () => { - if (this.lastTeraType !== Type.UNKNOWN) { + if (pokemon.isTerastallized) { globalScene.ui.showTooltip("", i18next.t("fightUiHandler:teraHover", { type: i18next.t(`pokemonInfo:Type.${Type[this.lastTeraType]}`) })); } }); @@ -542,7 +542,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.genderText.setPositionRelative(this.nameText, this.nameText.displayWidth, 0); } - const teraType = pokemon.getTeraType(); + const teraType = pokemon.isTerastallized ? pokemon.getTeraType() : Type.UNKNOWN; const teraTypeUpdated = this.lastTeraType !== teraType; if (teraTypeUpdated) { diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index de75f29ff6f..f23cc78c9f7 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -7,18 +7,24 @@ import { Button } from "#enums/buttons"; import { getPokemonNameWithAffix } from "#app/messages"; import { CommandPhase } from "#app/phases/command-phase"; import { globalScene } from "#app/global-scene"; +import { TerastallizeAccessModifier } from "#app/modifier/modifier"; +import { Type } from "#app/enums/type"; +import { getTypeRgb } from "#app/data/type"; export enum Command { FIGHT = 0, BALL, POKEMON, - RUN + RUN, + TERA } export default class CommandUiHandler extends UiHandler { private commandsContainer: Phaser.GameObjects.Container; private cursorObj: Phaser.GameObjects.Image | null; + private teraButton: Phaser.GameObjects.Sprite; + protected fieldIndex: number = 0; protected cursor2: number = 0; @@ -40,6 +46,13 @@ export default class CommandUiHandler extends UiHandler { this.commandsContainer.setVisible(false); ui.add(this.commandsContainer); + this.teraButton = globalScene.add.sprite(-32, 15, "button_tera"); + this.teraButton.setName("terrastallize-button"); + this.teraButton.setScale(1.3); + this.teraButton.setFrame("fire"); + this.teraButton.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true, teraColor: getTypeRgb(Type.FIRE), isTerastallized: false }); + this.commandsContainer.add(this.teraButton); + for (let c = 0; c < commands.length; c++) { const commandText = addTextObject(c % 2 === 0 ? 0 : 55.8, c < 2 ? 0 : 16, commands[c], TextStyle.WINDOW); commandText.setName(commands[c]); @@ -62,11 +75,22 @@ export default class CommandUiHandler extends UiHandler { commandPhase = globalScene.getStandbyPhase() as CommandPhase; } + if (this.canTera()) { + this.teraButton.setVisible(true); + this.teraButton.setFrame(Type[globalScene.getField()[this.fieldIndex].getTeraType()].toLowerCase()); + } else { + this.teraButton.setVisible(false); + if (this.cursor === Command.TERA) { + this.setCursor(Command.FIGHT); + } + } + this.toggleTeraButton(); + const messageHandler = this.getUi().getMessageHandler(); messageHandler.bg.setVisible(true); messageHandler.commandWindow.setVisible(true); messageHandler.movesWindowContainer.setVisible(false); - messageHandler.message.setWordWrapWidth(1110); + messageHandler.message.setWordWrapWidth(this.canTera() ? 910 : 1110); messageHandler.showText(i18next.t("commandUiHandler:actionMessage", { pokemonName: getPokemonNameWithAffix(commandPhase.getPokemon()) }), 0); if (this.getCursor() === Command.POKEMON) { this.setCursor(Command.FIGHT); @@ -108,6 +132,10 @@ export default class CommandUiHandler extends UiHandler { (globalScene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0); success = true; break; + case Command.TERA: + ui.setMode(Mode.FIGHT, (globalScene.getCurrentPhase() as CommandPhase).getFieldIndex(), Command.TERA); + success = true; + break; } } else { (globalScene.getCurrentPhase() as CommandPhase).cancel(); @@ -115,23 +143,29 @@ export default class CommandUiHandler extends UiHandler { } else { switch (button) { case Button.UP: - if (cursor >= 2) { + if (cursor === Command.POKEMON || cursor === Command.RUN) { success = this.setCursor(cursor - 2); } break; case Button.DOWN: - if (cursor < 2) { + if (cursor === Command.FIGHT || cursor === Command.BALL) { success = this.setCursor(cursor + 2); } break; case Button.LEFT: - if (cursor % 2 === 1) { + if (cursor === Command.BALL || cursor === Command.RUN) { success = this.setCursor(cursor - 1); + } else if ((cursor === Command.FIGHT || cursor === Command.POKEMON) && this.canTera()) { + success = this.setCursor(Command.TERA); + this.toggleTeraButton(); } break; case Button.RIGHT: - if (cursor % 2 === 0) { + if (cursor === Command.FIGHT || cursor === Command.POKEMON) { success = this.setCursor(cursor + 1); + } else if (cursor === Command.TERA) { + success = this.setCursor(Command.FIGHT); + this.toggleTeraButton(); } break; } @@ -144,6 +178,17 @@ export default class CommandUiHandler extends UiHandler { return success; } + canTera(): boolean { + const hasTeraMod = !!globalScene.getModifiers(TerastallizeAccessModifier).length; + const currentTeras = globalScene.arena.playerTerasUsed; + const plannedTera = globalScene.currentBattle.preTurnCommands[0]?.command === Command.TERA && this.fieldIndex > 0 ? 1 : 0; + return hasTeraMod && (currentTeras + plannedTera) < 1; + } + + toggleTeraButton() { + this.teraButton.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true, teraColor: getTypeRgb(globalScene.getField()[this.fieldIndex].getTeraType()), isTerastallized: this.getCursor() === Command.TERA }); + } + getCursor(): number { return !this.fieldIndex ? this.cursor : this.cursor2; } @@ -163,7 +208,12 @@ export default class CommandUiHandler extends UiHandler { this.commandsContainer.add(this.cursorObj); } - this.cursorObj.setPosition(-5 + (cursor % 2 === 1 ? 56 : 0), 8 + (cursor >= 2 ? 16 : 0)); + if (cursor === Command.TERA) { + this.cursorObj.setVisible(false); + } else { + this.cursorObj.setPosition(-5 + (cursor % 2 === 1 ? 56 : 0), 8 + (cursor >= 2 ? 16 : 0)); + this.cursorObj.setVisible(true); + } return changed; } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 72fb90066e7..1c1dceb24a5 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -33,6 +33,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { private moveInfoOverlay : MoveInfoOverlay; protected fieldIndex: number = 0; + protected fromCommand: Command = Command.FIGHT; protected cursor2: number = 0; constructor() { @@ -114,6 +115,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { super.show(args); this.fieldIndex = args.length ? args[0] as number : 0; + this.fromCommand = args.length > 1 ? args[1] as Command : Command.FIGHT; const messageHandler = this.getUi().getMessageHandler(); messageHandler.bg.setVisible(false); @@ -140,7 +142,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { if (button === Button.CANCEL || button === Button.ACTION) { if (button === Button.ACTION) { - if ((globalScene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, cursor, false)) { + if ((globalScene.getCurrentPhase() as CommandPhase).handleCommand(this.fromCommand, cursor, false)) { success = true; } else { ui.playError(); diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 02bcba17a73..bf07374e21a 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -420,17 +420,6 @@ export default class RunInfoUiHandler extends UiHandler { private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) { // Loads and adds trainer sprites to the UI this.showTrainerSprites(enemyContainer); - // Determining which Terastallize Modifier belongs to which Pokemon - // Creates a dictionary {PokemonId: TeraShardType} - const teraPokemon = {}; - this.runInfo.enemyModifiers.forEach((m) => { - const modifier = m.toModifier(this.modifiersModule[m.className]); - if (modifier instanceof Modifier.TerastallizeModifier) { - const teraDetails = modifier?.getArgs(); - const pkmnId = teraDetails[0]; - teraPokemon[pkmnId] = teraDetails[1]; - } - }); // Creates the Pokemon icons + level information and adds it to enemyContainer // 2 Rows x 3 Columns @@ -444,18 +433,6 @@ export default class RunInfoUiHandler extends UiHandler { enemyData["player"] = true; const enemy = enemyData.toPokemon(); const enemyIcon = globalScene.addPokemonIcon(enemy, 0, 0, 0, 0); - // Applying Terastallizing Type tint to Pokemon icon - // If the Pokemon is a fusion, it has two sprites and so, the tint has to be applied to each icon separately - const enemySprite1 = enemyIcon.list[0] as Phaser.GameObjects.Sprite; - const enemySprite2 = (enemyIcon.list.length > 1) ? enemyIcon.list[1] as Phaser.GameObjects.Sprite : undefined; - if (teraPokemon[enemyData.id]) { - const teraTint = getTypeRgb(teraPokemon[enemyData.id]); - const teraColor = new Phaser.Display.Color(teraTint[0], teraTint[1], teraTint[2]); - enemySprite1.setTint(teraColor.color); - if (enemySprite2) { - enemySprite2.setTint(teraColor.color); - } - } enemyIcon.setPosition(39 * (e % 3) + 5, (35 * pokemonRowHeight)); const enemyLevel = addTextObject(43 * (e % 3), (27 * (pokemonRowHeight + 1)), `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, isBoss ? TextStyle.PARTY_RED : TextStyle.PARTY, { fontSize: "54px" }); enemyLevel.setShadow(0, 0, undefined); diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index cf5d40bc006..1526ae982e5 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -334,6 +334,7 @@ export default class SummaryUiHandler extends UiHandler { console.error(`Failed to play animation for ${spriteKey}`, err); } this.pokemonSprite.setPipelineData("teraColor", getTypeRgb(this.pokemon.getTeraType())); + this.pokemonSprite.setPipelineData("isTerastallized", this.pokemon.isTerastallized); this.pokemonSprite.setPipelineData("ignoreTimeTint", true); this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny); @@ -782,7 +783,7 @@ export default class SummaryUiHandler extends UiHandler { if (types.length > 1) { profileContainer.add(getTypeIcon(1, types[1])); } - if (this.pokemon?.isTerastallized()) { + if (this.pokemon?.isTerastallized) { profileContainer.add(getTypeIcon(types.length, this.pokemon.getTeraType(), true)); }