Merge branch 'beta' into hebrew-pr

This commit is contained in:
Lugiad 2025-01-21 15:42:54 +01:00 committed by GitHub
commit 80523937e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
449 changed files with 11496 additions and 14117 deletions

View File

@ -46,6 +46,7 @@ export default [
"computed-property-spacing": ["error", "never" ], // Enforces consistent spacing inside computed property brackets
"space-infix-ops": ["error", { "int32Hint": false }], // Enforces spacing around infix operators
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], // Disallows multiple empty lines
"@typescript-eslint/consistent-type-imports": "error", // Enforces type-only imports wherever possible
}
}
]

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 712 B

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 B

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -4,6 +4,8 @@
"ffee52": "37d6de",
"debd29": "078a8f",
"833100": "002112",
"830009": "23033b",
"189d87": "c2247b",
"ff7b73": "712f8f",
"de4141": "3f1375",
"ffbdbd": "a266b0",
@ -11,6 +13,7 @@
"107b6a": "9e1976",
"105241": "4f2800",
"83de7b": "a37707",
"2e5529": "38001c",
"5a9c39": "705207",
"20b49c": "de3592",
"fdfdfd": "fdfdfd",
@ -21,14 +24,17 @@
"ffee52": "f75ea8",
"debd29": "a30a66",
"833100": "0b2e01",
"830009": "154205",
"189d87": "f17f05",
"ff7b73": "9db042",
"de4141": "3c8227",
"ffbdbd": "e7e385",
"101010": "101010",
"107b6a": "d44300",
"105241": "030129",
"83de7b": "433d99",
"5a9c39": "19164f",
"105241": "381601",
"83de7b": "80ced9",
"2e5519": "011c38",
"5a9c39": "446b94",
"20b49c": "fa8405",
"fdfdfd": "fdfdfd",
"5ad5c5": "faa405"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,6 +1,7 @@
{
"1": {
"843100": "033b22",
"830009": "23033b",
"ff7b73": "712f8f",
"ffbdbd": "a266b0",
"debd29": "078a8f",
@ -13,11 +14,13 @@
"5a9c3a": "b34952",
"84de7b": "ff745e",
"5ad6c5": "f062a4",
"2e5519": "38001c",
"21b59c": "de3592",
"ffffff": "ffffff"
},
"2": {
"843100": "420514",
"830009": "154205",
"ff7b73": "9db042",
"ffbdbd": "e7e385",
"debd29": "a30a66",
@ -30,6 +33,7 @@
"5a9c3a": "446b94",
"84de7b": "80ced9",
"5ad6c5": "faa405",
"2e5519": "011c38",
"21b59c": "fa8405",
"ffffff": "ffffff"
}

View File

@ -4,6 +4,7 @@
"3f4447": "466698",
"de3431": "3fca9f",
"f8f8f8": "a1e9f0",
"f4f4f4": "d7eff4",
"7b282e": "0e3e81",
"6b1d1d": "206d74",
"4ebdd9": "41a7b0",
@ -11,7 +12,7 @@
"bfbfbf": "8cc7d4",
"ffb2bf": "b7e9ff",
"bf4c60": "4386df",
"fff0a6": "271f4c",
"fff0a6": "208698",
"3e7acc": "6b4592",
"18335c": "170738",
"f2798d": "8dcfff",
@ -25,6 +26,7 @@
"3f4447": "466698",
"de3431": "9ceec6",
"f8f8f8": "89d2b8",
"f4f4f4": "d7eff4",
"7b282e": "152a5c",
"6b1d1d": "356e8d",
"4ebdd9": "2f6e74",

View File

@ -835,7 +835,7 @@
"6713": [0, 1, 1],
"8901": [1, 1, 1],
"female": {
"3": [0, 2, 1],
"3": [0, 1, 1],
"19": [0, 1, 1],
"20": [0, 1, 1],
"25": [0, 1, 1],
@ -869,6 +869,7 @@
"198": [0, 1, 1],
"203": [0, 1, 1],
"207": [0, 1, 1],
"212": [1, 1, 1],
"215": [0, 1, 1],
"217": [1, 1, 1],
"229": [0, 1, 1],
@ -1778,6 +1779,7 @@
"198": [0, 1, 1],
"203": [0, 1, 1],
"207": [0, 1, 1],
"212": [1, 1, 1],
"215": [0, 1, 1],
"217": [1, 1, 1],
"229": [0, 1, 1],

View File

@ -1,6 +1,7 @@
{
"1": {
"833100": "180136",
"830009": "23033b",
"bd6a31": "012729",
"ffee52": "37d6de",
"debd29": "078a8f",
@ -8,15 +9,18 @@
"de4141": "3f1375",
"ff7b73": "712f8f",
"ffbdbd": "a266b0",
"5a9c39": "705207",
"105241": "4f2800",
"83de7b": "a37707",
"e8a3a3": "91579e",
"5a9c39": "b34952",
"105241": "190038",
"2e5519": "38001c",
"83de7b": "ff745e",
"107b6a": "b80479",
"20b49c": "de3592",
"fdfdfd": "fdfdfd"
},
"2": {
"833100": "0b2e01",
"830009": "154205",
"bd6a31": "420514",
"ffee52": "f75ea8",
"debd29": "a30a66",
@ -24,9 +28,11 @@
"de4141": "3c8227",
"ff7b73": "9db042",
"ffbdbd": "e7e385",
"5a9c39": "19164f",
"105241": "030129",
"83de7b": "433d99",
"e8a3a3": "ced76f",
"5a9c39": "446b94",
"105241": "381601",
"2e5519": "011c38",
"83de7b": "80ced9",
"107b6a": "d15d04",
"20b49c": "fa8405",
"fdfdfd": "fdfdfd"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,6 +1,7 @@
{
"1": {
"843100": "033b22",
"830009": "23033b",
"ffbdbd": "a266b0",
"ff7b73": "712f8f",
"debd29": "078a8f",
@ -11,6 +12,7 @@
"105242": "190038",
"107b6b": "c21f7e",
"5a9c3a": "b34952",
"2e5519": "38001c",
"5ad6c5": "f062a4",
"21b59c": "de3592",
"84de7b": "ff745e",
@ -18,6 +20,7 @@
},
"2": {
"843100": "420514",
"830009": "154205",
"ffbdbd": "e7e385",
"ff7b73": "9db042",
"debd29": "a30a66",
@ -25,7 +28,8 @@
"de4242": "3c8227",
"101010": "101010",
"ffef52": "f75ea8",
"105242": "001a33",
"105242": "381601",
"2e5519": "011c38",
"107b6b": "d15d04",
"5a9c3a": "446b94",
"5ad6c5": "faa405",

View File

@ -9,7 +9,7 @@
"de62a4": "ffc668",
"4a83a4": "387fa7",
"314a62": "244260",
"70bbb4": "f8d371",
"548e88": "2d60bb",
"a4295a": "cc762f"
},
"1": {
@ -22,7 +22,7 @@
"de62a4": "ffdf90",
"4a83a4": "a1c8db",
"314a62": "7396b4",
"70bbb4": "70bbb4",
"548e88": "a9c0c6",
"a4295a": "e28c27"
},
"2": {
@ -35,7 +35,7 @@
"de62a4": "e25038",
"4a83a4": "e6aa47",
"314a62": "b56f2a",
"70bbb4": "f8d371",
"548e88": "e0b544",
"a4295a": "a62a21"
}
}

View File

@ -0,0 +1,41 @@
{
"0": {
"632929": "215a2d",
"f76b6b": "8cce73",
"a52929": "2f794e",
"101010": "101010",
"d63a3a": "4a9c53",
"9494a5": "9494a5",
"ffffff": "ffffff",
"b5b5ce": "b5b5ce",
"3a3a4a": "3a3a4a",
"9c6b21": "9c6b21",
"dec510": "dec510"
},
"1": {
"632929": "2f2962",
"f76b6b": "639cf7",
"a52929": "29429c",
"101010": "101010",
"d63a3a": "4263ef",
"9494a5": "6262a4",
"ffffff": "ffffff",
"b5b5ce": "b5b5ce",
"3a3a4a": "3c3c50",
"9c6b21": "131387",
"dec510": "10bdde"
},
"2": {
"632929": "645117",
"f76b6b": "c59f29",
"a52929": "b88619",
"101010": "101010",
"d63a3a": "ffca2a",
"9494a5": "3c4543",
"ffffff": "ffffff",
"b5b5ce": "b5b5ce",
"3a3a4a": "282d2c",
"9c6b21": "9c6b21",
"dec510": "dec510"
}
}

View File

@ -1,6 +1,7 @@
{
"1": {
"843100": "033b22",
"830009": "23033b",
"ffbdbd": "a266b0",
"ffef52": "37d6de",
"debd29": "078a8f",
@ -10,6 +11,7 @@
"101010": "101010",
"105242": "190038",
"107b6b": "9e1976",
"2e5519": "38001c",
"5a9c3a": "b34952",
"5ad6c5": "f062a4",
"21b59c": "de3592",
@ -18,6 +20,7 @@
},
"2": {
"843100": "420514",
"830009": "154205",
"ffbdbd": "e7e385",
"ffef52": "f75ea8",
"debd29": "a30a66",
@ -27,6 +30,7 @@
"101010": "101010",
"105242": "381601",
"107b6b": "d15d04",
"2e5519": "011c38",
"5a9c3a": "446b94",
"5ad6c5": "faa405",
"21b59c": "fa8405",

View File

@ -4,6 +4,7 @@
"3f4447": "466698",
"de3431": "3fca9f",
"f8f8f8": "a1e9f0",
"f4f4f4": "d7effa",
"7b282e": "0e3e81",
"6b1d1d": "206d74",
"4ebdd9": "41a7b0",
@ -25,6 +26,7 @@
"3f4447": "466698",
"de3431": "9ceec6",
"f8f8f8": "89d2b8",
"f4f4f4": "d7effa",
"7b282e": "152a5c",
"6b1d1d": "356e8d",
"4ebdd9": "2f6e74",

View File

@ -8,7 +8,7 @@
"0f0f0f": "0f0f0f",
"314a62": "244260",
"621841": "71370f",
"70bbb4": "f8d371",
"548e88": "2d60bb",
"de62a4": "ffc668",
"a4295a": "cc762f"
},
@ -21,7 +21,7 @@
"0f0f0f": "0f0f0f",
"314a62": "7396b4",
"621841": "7b3c08",
"70bbb4": "70bbb4",
"548e88": "a9c0c6",
"de62a4": "ffdf90",
"a4295a": "e28c27"
},
@ -34,7 +34,7 @@
"0f0f0f": "0f0f0f",
"314a62": "b56f2a",
"621841": "5a0a05",
"70bbb4": "f8d371",
"548e88": "e0b544",
"de62a4": "e25038",
"a4295a": "a62a21"
}

View File

@ -0,0 +1,41 @@
{
"0": {
"632929": "215a2d",
"f76b6b": "8cce73",
"101010": "101010",
"3a3a4a": "3a3a4a",
"ffffff": "ffffff",
"d63a3a": "4a9c53",
"b5b5ce": "b5b5ce",
"9494a5": "9494a5",
"a52929": "2f794e",
"dec510": "dec510",
"9c6b21": "9c6b21"
},
"1": {
"632929": "2f2962",
"f76b6b": "639cf7",
"101010": "101010",
"3a3a4a": "3c3c50",
"ffffff": "ffffff",
"d63a3a": "4263ef",
"b5b5ce": "b5b5ce",
"9494a5": "6262a4",
"a52929": "29429c",
"dec510": "10bdde",
"9c6b21": "131387"
},
"2": {
"632929": "645117",
"f76b6b": "c59f29",
"101010": "101010",
"3a3a4a": "282d2c",
"ffffff": "ffffff",
"d63a3a": "ffca2a",
"b5b5ce": "b5b5ce",
"9494a5": "3c4543",
"a52929": "b88619",
"dec510": "dec510",
"9c6b21": "9c6b21"
}
}

View File

@ -1,19 +1,41 @@
{
"1": {
"843100": "033b22",
"830009": "23033b",
"ffbdbd": "a266b0",
"ffef52": "37d6de",
"debd29": "078a8f",
"ff7b73": "712f8f",
"bd6b31": "168a69",
"de4242": "3f1375",
"101010": "101010",
"105242": "190038",
"107b6b": "9e1976",
"2e5519": "38001c",
"5a9c3a": "b34952",
"5ad6c5": "f062a4",
"21b59c": "de3592",
"84de7b": "ff745e",
"ffffff": "ffffff"
},
"2": {
"843100": "420514",
"ff7b73": "9db042",
"830009": "154205",
"ffbdbd": "e7e385",
"ffef52": "f75ea8",
"debd29": "a30a66",
"ff7b73": "9db042",
"bd6b31": "852a41",
"de4242": "3c8227",
"101010": "101010",
"105242": "381601",
"107b6b": "d44300",
"107b6b": "d15d04",
"2e5519": "011c38",
"5a9c3a": "446b94",
"84de7b": "80ced9",
"5ad6c5": "faa405",
"21b59c": "fa8405",
"ffffff": "ffffff"
"84de7b": "80ced9",
"ffffff": "ffffff",
"2f561a": "011b34"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 985 B

@ -1 +1 @@
Subproject commit 2e03bc8f2736269bfa365faad587c3ec54a37621
Subproject commit e07ab625f2080afe36b61fad291b0ec5eff4000c

View File

@ -1,3 +1 @@
import BattleScene from "#app/battle-scene";
export type ConditionFn = (scene: BattleScene, args?: any[]) => boolean;
export type ConditionFn = (args?: any[]) => boolean;

View File

@ -1,4 +1,4 @@
import { TOptions } from "i18next";
import type { TOptions } from "i18next";
// Module declared to make referencing keys in the localization files type-safe.
declare module "i18next" {

View File

@ -1,13 +1,18 @@
import Phaser from "phaser";
import UI from "#app/ui/ui";
import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species";
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import type { PokemonSpeciesFilter } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import type { Constructor } from "#app/utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import * as Utils from "#app/utils";
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
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 { PokeballType } from "#enums/pokeball";
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims";
import { Phase } from "#app/phase";
import type { Phase } from "#app/phase";
import { initGameSpeed } from "#app/system/game-speed";
import { Arena, ArenaBase } from "#app/field/arena";
import { GameData } from "#app/system/game-data";
@ -17,26 +22,32 @@ import { MusicPreference } from "#app/system/settings/settings";
import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, ModifierPoolType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import AbilityBar from "#app/ui/ability-bar";
import { allAbilities, applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs, BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, PostBattleInitAbAttr, PostItemLostAbAttr } from "#app/data/ability";
import Battle, { BattleType, FixedBattleConfig } from "#app/battle";
import { GameMode, GameModes, getGameMode } from "#app/game-mode";
import type { FixedBattleConfig } from "#app/battle";
import Battle, { BattleType } from "#app/battle";
import type { GameMode } from "#app/game-mode";
import { GameModes, getGameMode } from "#app/game-mode";
import FieldSpritePipeline from "#app/pipelines/field-sprite";
import SpritePipeline from "#app/pipelines/sprite";
import PartyExpBar from "#app/ui/party-exp-bar";
import { trainerConfigs, TrainerSlot } from "#app/data/trainer-config";
import type { TrainerSlot } from "#app/data/trainer-config";
import { trainerConfigs } from "#app/data/trainer-config";
import Trainer, { TrainerVariant } from "#app/field/trainer";
import TrainerData from "#app/system/trainer-data";
import type TrainerData from "#app/system/trainer-data";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import PokeballTray from "#app/ui/pokeball-tray";
import InvertPostFX from "#app/pipelines/invert";
import { Achv, achvs, ModifierAchv, MoneyAchv } from "#app/system/achv";
import { Voucher, vouchers } from "#app/system/voucher";
import type { Achv } from "#app/system/achv";
import { achvs, ModifierAchv, MoneyAchv } from "#app/system/achv";
import type { Voucher } from "#app/system/voucher";
import { vouchers } from "#app/system/voucher";
import { Gender } from "#app/data/gender";
import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin";
import type UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin";
import { addUiThemeOverrides } from "#app/ui/ui-theme";
import PokemonData from "#app/system/pokemon-data";
import type PokemonData from "#app/system/pokemon-data";
import { Nature } from "#enums/nature";
import { FormChangeItem, pokemonFormChanges, SpeciesFormChange, SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger } from "#app/data/pokemon-forms";
import type { SpeciesFormChange, SpeciesFormChangeTrigger } from "#app/data/pokemon-forms";
import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger } from "#app/data/pokemon-forms";
import { FormChangePhase } from "#app/phases/form-change-phase";
import { getTypeRgb } from "#app/data/type";
import { Type } from "#enums/type";
@ -47,8 +58,9 @@ import PokemonInfoContainer from "#app/ui/pokemon-info-container";
import { biomeDepths, getBiomeName } from "#app/data/balance/biomes";
import { SceneBase } from "#app/scene-base";
import CandyBar from "#app/ui/candy-bar";
import { Variant, variantColorCache, variantData, VariantSet } from "#app/data/variant";
import { Localizable } from "#app/interfaces/locales";
import type { Variant, VariantSet } from "#app/data/variant";
import { variantColorCache, variantData } from "#app/data/variant";
import type { Localizable } from "#app/interfaces/locales";
import Overrides from "#app/overrides";
import { InputsController } from "#app/inputs-controller";
import { UiInputs } from "#app/ui-inputs";
@ -58,14 +70,14 @@ import { EaseType } from "#enums/ease-type";
import { BattleSpec } from "#enums/battle-spec";
import { BattleStyle } from "#enums/battle-style";
import { Biome } from "#enums/biome";
import { ExpNotification } from "#enums/exp-notification";
import type { ExpNotification } from "#enums/exp-notification";
import { MoneyFormat } from "#enums/money-format";
import { Moves } from "#enums/moves";
import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species";
import { UiTheme } from "#enums/ui-theme";
import { TimedEventManager } from "#app/timed-event-manager";
import { PokemonAnimType } from "#enums/pokemon-anim-type";
import type { PokemonAnimType } from "#enums/pokemon-anim-type";
import i18next from "i18next";
import { TrainerType } from "#enums/trainer-type";
import { battleSpecDialogue } from "#app/data/dialogue";
@ -92,7 +104,7 @@ import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
import { ExpPhase } from "#app/phases/exp-phase";
import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
@ -100,6 +112,7 @@ import { ExpGainsSpeed } from "#enums/exp-gains-speed";
import { BattlerTagType } from "#enums/battler-tag-type";
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters";
import { StatusEffect } from "#enums/status-effect";
import { initGlobalScene } from "#app/global-scene";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -330,6 +343,7 @@ export default class BattleScene extends SceneBase {
this.nextCommandPhaseQueue = [];
this.eventManager = new TimedEventManager();
this.updateGameInfo();
initGlobalScene(this);
}
loadPokemonAtlas(key: string, atlasPath: string, experimental?: boolean) {
@ -349,40 +363,41 @@ export default class BattleScene extends SceneBase {
/**
* Load the variant assets for the given sprite and stores them in {@linkcode variantColorCache}
*/
loadPokemonVariantAssets(spriteKey: string, fileRoot: string, variant?: Variant) {
public async loadPokemonVariantAssets(spriteKey: string, fileRoot: string, variant?: Variant): Promise<void> {
const useExpSprite = this.experimentalSprites && this.hasExpSprite(spriteKey);
if (useExpSprite) {
fileRoot = `exp/${fileRoot}`;
}
let variantConfig = variantData;
fileRoot.split("/").map(p => variantConfig ? variantConfig = variantConfig[p] : null);
fileRoot.split("/").map((p) => (variantConfig ? (variantConfig = variantConfig[p]) : null));
const variantSet = variantConfig as VariantSet;
if (variantSet && (variant !== undefined && variantSet[variant] === 1)) {
const populateVariantColors = (key: string): Promise<void> => {
return new Promise(resolve => {
if (variantColorCache.hasOwnProperty(key)) {
return resolve();
}
this.cachedFetch(`./images/pokemon/variant/${fileRoot}.json`).then(res => res.json()).then(c => {
variantColorCache[key] = c;
return new Promise<void>((resolve) => {
if (variantSet && variant !== undefined && variantSet[variant] === 1) {
if (variantColorCache.hasOwnProperty(spriteKey)) {
return resolve();
}
this.cachedFetch(`./images/pokemon/variant/${fileRoot}.json`)
.then((res) => res.json())
.then((c) => {
variantColorCache[spriteKey] = c;
resolve();
});
});
};
populateVariantColors(spriteKey);
}
} else {
resolve();
}
});
}
async preload() {
if (DEBUG_RNG) {
const scene = this;
const originalRealInRange = Phaser.Math.RND.realInRange;
Phaser.Math.RND.realInRange = function (min: number, max: number): number {
const ret = originalRealInRange.apply(this, [ min, max ]);
const args = [ "RNG", ++scene.rngCounter, ret / (max - min), `min: ${min} / max: ${max}` ];
args.push(`seed: ${scene.rngSeedOverride || scene.waveSeed || scene.seed}`);
if (scene.rngOffset) {
args.push(`offset: ${scene.rngOffset}`);
const args = [ "RNG", ++this.rngCounter, ret / (max - min), `min: ${min} / max: ${max}` ];
args.push(`seed: ${this.rngSeedOverride || this.waveSeed || this.seed}`);
if (this.rngOffset) {
args.push(`offset: ${this.rngOffset}`);
}
console.log(...args);
return ret;
@ -397,12 +412,12 @@ export default class BattleScene extends SceneBase {
create() {
this.scene.remove(LoadingScene.KEY);
initGameSpeed.apply(this);
this.inputController = new InputsController(this);
this.uiInputs = new UiInputs(this, this.inputController);
this.inputController = new InputsController();
this.uiInputs = new UiInputs(this.inputController);
this.gameData = new GameData(this);
this.gameData = new GameData();
addUiThemeOverrides(this);
addUiThemeOverrides();
this.load.setBaseURL();
@ -489,76 +504,76 @@ export default class BattleScene extends SceneBase {
this.modifiers = [];
this.enemyModifiers = [];
this.modifierBar = new ModifierBar(this);
this.modifierBar = new ModifierBar();
this.modifierBar.setName("modifier-bar");
this.add.existing(this.modifierBar);
uiContainer.add(this.modifierBar);
this.enemyModifierBar = new ModifierBar(this, true);
this.enemyModifierBar = new ModifierBar(true);
this.enemyModifierBar.setName("enemy-modifier-bar");
this.add.existing(this.enemyModifierBar);
uiContainer.add(this.enemyModifierBar);
this.charSprite = new CharSprite(this);
this.charSprite = new CharSprite();
this.charSprite.setName("sprite-char");
this.charSprite.setup();
this.fieldUI.add(this.charSprite);
this.pbTray = new PokeballTray(this, true);
this.pbTray = new PokeballTray(true);
this.pbTray.setName("pb-tray");
this.pbTray.setup();
this.pbTrayEnemy = new PokeballTray(this, false);
this.pbTrayEnemy = new PokeballTray(false);
this.pbTrayEnemy.setName("enemy-pb-tray");
this.pbTrayEnemy.setup();
this.fieldUI.add(this.pbTray);
this.fieldUI.add(this.pbTrayEnemy);
this.abilityBar = new AbilityBar(this);
this.abilityBar = new AbilityBar();
this.abilityBar.setName("ability-bar");
this.abilityBar.setup();
this.fieldUI.add(this.abilityBar);
this.partyExpBar = new PartyExpBar(this);
this.partyExpBar = new PartyExpBar();
this.partyExpBar.setName("party-exp-bar");
this.partyExpBar.setup();
this.fieldUI.add(this.partyExpBar);
this.candyBar = new CandyBar(this);
this.candyBar = new CandyBar();
this.candyBar.setName("candy-bar");
this.candyBar.setup();
this.fieldUI.add(this.candyBar);
this.biomeWaveText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, startingWave.toString(), TextStyle.BATTLE_INFO);
this.biomeWaveText = addTextObject((this.game.canvas.width / 6) - 2, 0, startingWave.toString(), TextStyle.BATTLE_INFO);
this.biomeWaveText.setName("text-biome-wave");
this.biomeWaveText.setOrigin(1, 0.5);
this.fieldUI.add(this.biomeWaveText);
this.moneyText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, "", TextStyle.MONEY);
this.moneyText = addTextObject((this.game.canvas.width / 6) - 2, 0, "", TextStyle.MONEY);
this.moneyText.setName("text-money");
this.moneyText.setOrigin(1, 0.5);
this.fieldUI.add(this.moneyText);
this.scoreText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" });
this.scoreText = addTextObject((this.game.canvas.width / 6) - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" });
this.scoreText.setName("text-score");
this.scoreText.setOrigin(1, 0.5);
this.fieldUI.add(this.scoreText);
this.luckText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" });
this.luckText = addTextObject((this.game.canvas.width / 6) - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" });
this.luckText.setName("text-luck");
this.luckText.setOrigin(1, 0.5);
this.luckText.setVisible(false);
this.fieldUI.add(this.luckText);
this.luckLabelText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, i18next.t("common:luckIndicator"), TextStyle.PARTY, { fontSize: "54px" });
this.luckLabelText = addTextObject((this.game.canvas.width / 6) - 2, 0, i18next.t("common:luckIndicator"), TextStyle.PARTY, { fontSize: "54px" });
this.luckLabelText.setName("text-luck-label");
this.luckLabelText.setOrigin(1, 0.5);
this.luckLabelText.setVisible(false);
this.fieldUI.add(this.luckLabelText);
this.arenaFlyout = new ArenaFlyout(this);
this.arenaFlyout = new ArenaFlyout();
this.fieldUI.add(this.arenaFlyout);
this.fieldUI.moveBelow<Phaser.GameObjects.GameObject>(this.arenaFlyout, this.fieldOverlay);
@ -567,9 +582,9 @@ export default class BattleScene extends SceneBase {
this.damageNumberHandler = new DamageNumberHandler();
this.spriteSparkleHandler = new PokemonSpriteSparkleHandler();
this.spriteSparkleHandler.setup(this);
this.spriteSparkleHandler.setup();
this.pokemonInfoContainer = new PokemonInfoContainer(this, (this.game.canvas.width / 6) + 52, -(this.game.canvas.height / 6) + 66);
this.pokemonInfoContainer = new PokemonInfoContainer((this.game.canvas.width / 6) + 52, -(this.game.canvas.height / 6) + 66);
this.pokemonInfoContainer.setup();
this.fieldUI.add(this.pokemonInfoContainer);
@ -578,13 +593,13 @@ export default class BattleScene extends SceneBase {
const loadPokemonAssets = [];
this.arenaPlayer = new ArenaBase(this, true);
this.arenaPlayer = new ArenaBase(true);
this.arenaPlayer.setName("arena-player");
this.arenaPlayerTransition = new ArenaBase(this, true);
this.arenaPlayerTransition = new ArenaBase(true);
this.arenaPlayerTransition.setName("arena-player-transition");
this.arenaEnemy = new ArenaBase(this, false);
this.arenaEnemy = new ArenaBase(false);
this.arenaEnemy.setName("arena-enemy");
this.arenaNextEnemy = new ArenaBase(this, false);
this.arenaNextEnemy = new ArenaBase(false);
this.arenaNextEnemy.setName("arena-next-enemy");
this.arenaBgTransition.setVisible(false);
@ -625,7 +640,7 @@ export default class BattleScene extends SceneBase {
this.reset(false, false, true);
const ui = new UI(this);
const ui = new UI();
this.uiContainer.add(ui);
this.ui = ui;
@ -636,12 +651,12 @@ export default class BattleScene extends SceneBase {
Promise.all([
Promise.all(loadPokemonAssets),
initCommonAnims(this).then(() => loadCommonAnimAssets(this, true)),
Promise.all([ Moves.TACKLE, Moves.TAIL_WHIP, Moves.FOCUS_ENERGY, Moves.STRUGGLE ].map(m => initMoveAnim(this, m))).then(() => loadMoveAnimAssets(this, defaultMoves, true)),
initCommonAnims().then(() => loadCommonAnimAssets(true)),
Promise.all([ Moves.TACKLE, Moves.TAIL_WHIP, Moves.FOCUS_ENERGY, Moves.STRUGGLE ].map(m => initMoveAnim(m))).then(() => loadMoveAnimAssets(defaultMoves, true)),
this.initStarterColors()
]).then(() => {
this.pushPhase(new LoginPhase(this));
this.pushPhase(new TitlePhase(this));
this.pushPhase(new LoginPhase());
this.pushPhase(new TitlePhase());
this.shiftPhase();
});
@ -911,7 +926,7 @@ export default class BattleScene extends SceneBase {
}
addPlayerPokemon(species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void): PlayerPokemon {
const pokemon = new PlayerPokemon(this, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
const pokemon = new PlayerPokemon(species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
if (postProcess) {
postProcess(pokemon);
}
@ -929,7 +944,7 @@ export default class BattleScene extends SceneBase {
boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1;
}
const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, shinyLock, dataSource);
const pokemon = new EnemyPokemon(species, level, trainerSlot, boss, shinyLock, dataSource);
if (Overrides.OPP_FUSION_OVERRIDE) {
pokemon.generateFusionSpecies();
}
@ -1064,7 +1079,7 @@ export default class BattleScene extends SceneBase {
/**
* Generates a random number using the current battle's seed
*
* This calls {@linkcode Battle.randSeedInt}(`scene`, {@linkcode range}, {@linkcode min}) in `src/battle.ts`
* This calls {@linkcode Battle.randSeedInt}({@linkcode range}, {@linkcode min}) in `src/battle.ts`
* which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts`
*
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
@ -1072,12 +1087,12 @@ export default class BattleScene extends SceneBase {
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
*/
randBattleSeedInt(range: integer, min: integer = 0): integer {
return this.currentBattle?.randSeedInt(this, range, min);
return this.currentBattle?.randSeedInt(range, min);
}
reset(clearScene: boolean = false, clearData: boolean = false, reloadI18n: boolean = false): void {
if (clearData) {
this.gameData = new GameData(this);
this.gameData = new GameData();
}
this.gameMode = getGameMode(GameModes.CLASSIC);
@ -1178,6 +1193,9 @@ export default class BattleScene extends SceneBase {
onComplete: () => {
this.clearPhaseQueue();
this.ui.freeUIData();
this.uiContainer.remove(this.ui, true);
this.uiContainer.destroy();
this.children.removeAll(true);
this.game.domContainer.innerHTML = "";
this.launchBattle();
@ -1210,7 +1228,7 @@ export default class BattleScene extends SceneBase {
battleConfig = this.gameMode.getFixedBattle(newWaveIndex);
newDouble = battleConfig.double;
newBattleType = battleConfig.battleType;
this.executeWithSeedOffset(() => newTrainer = battleConfig?.getTrainer(this), (battleConfig.seedOffsetWaveIndex || newWaveIndex) << 8);
this.executeWithSeedOffset(() => newTrainer = battleConfig?.getTrainer(), (battleConfig.seedOffsetWaveIndex || newWaveIndex) << 8);
if (newTrainer) {
this.field.add(newTrainer);
}
@ -1236,7 +1254,7 @@ export default class BattleScene extends SceneBase {
}
}
const variant = doubleTrainer ? TrainerVariant.DOUBLE : (Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT);
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, variant);
newTrainer = trainerData !== undefined ? trainerData.toTrainer() : new Trainer(trainerType, variant);
this.field.add(newTrainer);
}
@ -1314,7 +1332,7 @@ export default class BattleScene extends SceneBase {
this.executeWithSeedOffset(() => {
this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble);
}, newWaveIndex << 3, this.waveSeed);
this.currentBattle.incrementTurn(this);
this.currentBattle.incrementTurn();
if (newBattleType === BattleType.MYSTERY_ENCOUNTER) {
// Will generate the actual Mystery Encounter during NextEncounterPhase, to ensure it uses proper biome
@ -1342,7 +1360,7 @@ export default class BattleScene extends SceneBase {
playerField.forEach((pokemon, p) => {
if (pokemon.isOnField()) {
this.pushPhase(new ReturnPhase(this, p));
this.pushPhase(new ReturnPhase(p));
}
});
@ -1352,7 +1370,7 @@ export default class BattleScene extends SceneBase {
}
if (!this.trainer.visible) {
this.pushPhase(new ShowTrainerPhase(this));
this.pushPhase(new ShowTrainerPhase());
}
}
@ -1361,14 +1379,14 @@ export default class BattleScene extends SceneBase {
}
if (!this.gameMode.hasRandomBiomes && !isNewBiome) {
this.pushPhase(new NextEncounterPhase(this));
this.pushPhase(new NextEncounterPhase());
} else {
this.pushPhase(new SelectBiomePhase(this));
this.pushPhase(new NewBiomeEncounterPhase(this));
this.pushPhase(new SelectBiomePhase());
this.pushPhase(new NewBiomeEncounterPhase());
const newMaxExpLevel = this.getMaxExpLevel();
if (newMaxExpLevel > maxExpLevel) {
this.pushPhase(new LevelCapPhase(this));
this.pushPhase(new LevelCapPhase());
}
}
}
@ -1377,7 +1395,7 @@ export default class BattleScene extends SceneBase {
}
newArena(biome: Biome): Arena {
this.arena = new Arena(this, biome, Biome[biome].toLowerCase());
this.arena = new Arena(biome, Biome[biome].toLowerCase());
this.eventTarget.dispatchEvent(new NewArenaEvent());
this.arenaBg.pipelineData = { terrainColorRatio: this.arena.getBgTerrainColorRatioForBiome() };
@ -1424,6 +1442,8 @@ export default class BattleScene extends SceneBase {
return 0;
}
const isEggPhase: boolean = [ "EggLapsePhase", "EggHatchPhase" ].includes(this.getCurrentPhase()?.constructor.name ?? "");
switch (species.speciesId) {
case Species.UNOWN:
case Species.SHELLOS:
@ -1455,7 +1475,7 @@ export default class BattleScene extends SceneBase {
}
return Utils.randSeedInt(8);
case Species.EEVEE:
if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) {
if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30 && !isEggPhase) {
return 0; // No Partner Eevee for Wave 12 Preschoolers
}
return Utils.randSeedInt(2);
@ -1483,7 +1503,7 @@ export default class BattleScene extends SceneBase {
return 0;
case Species.GIMMIGHOUL:
// Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs
if (this.gameMode.hasMysteryEncounters) {
if (this.gameMode.hasMysteryEncounters && !isEggPhase) {
return 1; // Wandering form
} else {
return Utils.randSeedInt(species.forms.length);
@ -1791,7 +1811,7 @@ export default class BattleScene extends SceneBase {
}
updateUIPositions(): void {
const enemyModifierCount = this.enemyModifiers.filter(m => m.isIconVisible(this)).length;
const enemyModifierCount = this.enemyModifiers.filter(m => m.isIconVisible()).length;
const biomeWaveTextHeight = this.biomeWaveText.getBottomLeft().y - this.biomeWaveText.getTopLeft().y;
this.biomeWaveText.setY(
-(this.game.canvas.height / 6) + (enemyModifierCount ? enemyModifierCount <= 12 ? 15 : 24 : 0) + (biomeWaveTextHeight / 2)
@ -1850,7 +1870,7 @@ export default class BattleScene extends SceneBase {
generateRandomBiome(waveIndex: integer): Biome {
const relWave = waveIndex % 250;
const biomes = Utils.getEnumValues(Biome).slice(1, Utils.getEnumValues(Biome).filter(b => b >= 40).length * -1);
const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END);
const maxDepth = biomeDepths[Biome.END][0] - 2;
const depthWeights = new Array(maxDepth + 1).fill(null)
.map((_, i: integer) => ((1 - Math.min(Math.abs((i / (maxDepth - 1)) - (relWave / 250)) + 0.25, 1)) / 0.75) * 250);
@ -1863,9 +1883,9 @@ export default class BattleScene extends SceneBase {
const randInt = Utils.randSeedInt(totalWeight);
for (const biome of biomes) {
if (randInt < biomeThresholds[biome]) {
return biome;
for (let i = 0; i < biomes.length; i++) {
if (randInt < biomeThresholds[i]) {
return biomes[i];
}
}
@ -1878,7 +1898,7 @@ export default class BattleScene extends SceneBase {
playBgm(bgmName?: string, fadeOut?: boolean): void {
if (bgmName === undefined) {
bgmName = this.currentBattle?.getBgmOverride(this) || this.arena?.bgm;
bgmName = this.currentBattle?.getBgmOverride() || this.arena?.bgm;
}
if (this.bgm && bgmName === this.bgm.key) {
if (!this.bgm.isPlaying) {
@ -2497,7 +2517,7 @@ export default class BattleScene extends SceneBase {
* @param defer boolean for which queue to add it to, false -> add to PhaseQueuePrepend, true -> nextCommandPhaseQueue
*/
queueMessage(message: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null, defer?: boolean | null) {
const phase = new MessagePhase(this, message, callbackDelay, prompt, promptDelay);
const phase = new MessagePhase(message, callbackDelay, prompt, promptDelay);
if (!defer) {
// adds to the end of PhaseQueuePrepend
this.unshiftPhase(phase);
@ -2515,7 +2535,7 @@ export default class BattleScene extends SceneBase {
this.phaseQueue.push(...this.nextCommandPhaseQueue);
this.nextCommandPhaseQueue.splice(0, this.nextCommandPhaseQueue.length);
}
this.phaseQueue.push(new TurnInitPhase(this));
this.phaseQueue.push(new TurnInitPhase());
}
addMoney(amount: integer): void {
@ -2546,7 +2566,7 @@ export default class BattleScene extends SceneBase {
if (modifier instanceof TerastallizeModifier) {
modifiersToRemove.push(...(this.findModifiers(m => m instanceof TerastallizeModifier && m.pokemonId === modifier.pokemonId)));
}
if ((modifier as PersistentModifier).add(this.modifiers, !!virtual, this)) {
if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) {
if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) {
const pokemon = this.getPokemonById(modifier.pokemonId);
if (pokemon) {
@ -2627,7 +2647,7 @@ export default class BattleScene extends SceneBase {
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, this)) {
if ((modifier as PersistentModifier).add(this.enemyModifiers, false)) {
if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) {
const pokemon = this.getPokemonById(modifier.pokemonId);
if (pokemon) {
@ -2662,7 +2682,7 @@ export default class BattleScene extends SceneBase {
*/
tryTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, playSound: boolean, transferQuantity: number = 1, instant?: boolean, ignoreUpdate?: boolean, itemLost: boolean = true): Promise<boolean> {
return new Promise(resolve => {
const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null;
const source = itemModifier.pokemonId ? itemModifier.getPokemon() : null;
const cancelled = new Utils.BooleanHolder(false);
Utils.executeIf(!!source && source.isPlayer() !== target.isPlayer(), () => applyAbAttrs(BlockItemTheftAbAttr, source! /* checked in condition*/, cancelled)).then(() => {
if (cancelled.value) {
@ -2670,11 +2690,11 @@ export default class BattleScene extends SceneBase {
}
const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier;
newItemModifier.pokemonId = target.id;
const matchingModifier = target.scene.findModifier(m => m instanceof PokemonHeldItemModifier
const matchingModifier = this.findModifier(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).matchType(itemModifier) && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier;
let removeOld = true;
if (matchingModifier) {
const maxStackCount = matchingModifier.getMaxStackCount(target.scene);
const maxStackCount = matchingModifier.getMaxStackCount();
if (matchingModifier.stackCount >= maxStackCount) {
return resolve(false);
}
@ -2791,7 +2811,7 @@ export default class BattleScene extends SceneBase {
count = Math.max(count, Math.floor(chances / 2));
}
getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance)
.map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this));
.map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false));
}
return true;
});
@ -2815,7 +2835,7 @@ export default class BattleScene extends SceneBase {
* @param pokemon - If specified, only removes held items from that {@linkcode Pokemon}
*/
clearEnemyHeldItemModifiers(pokemon?: Pokemon): void {
const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier && (!pokemon || m.getPokemon(this) === pokemon));
const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier && (!pokemon || m.getPokemon() === pokemon));
for (const m of modifiersToRemove) {
this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1);
}
@ -2864,9 +2884,7 @@ export default class BattleScene extends SceneBase {
updatePartyForModifiers(party: Pokemon[], instant?: boolean): Promise<void> {
return new Promise(resolve => {
Promise.allSettled(party.map(p => {
if (p.scene) {
p.calculateStats();
}
p.calculateStats();
return p.updateInfo(instant);
})).then(() => resolve());
});
@ -2929,15 +2947,14 @@ export default class BattleScene extends SceneBase {
/**
* Apply all modifiers that match `modifierType` in a random order
* @param scene {@linkcode BattleScene} used to randomize the order of modifiers
* @param modifierType The type of modifier to apply; must extend {@linkcode PersistentModifier}
* @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true`
* @param ...args The list of arguments needed to invoke `modifierType.apply`
* @returns the list of all modifiers that matched `modifierType` and were applied.
*/
applyShuffledModifiers<T extends PersistentModifier>(scene: BattleScene, modifierType: Constructor<T>, player: boolean = true, ...args: Parameters<T["apply"]>): T[] {
applyShuffledModifiers<T extends PersistentModifier>(modifierType: Constructor<T>, player: boolean = true, ...args: Parameters<T["apply"]>): T[] {
let modifiers = (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType && m.shouldApply(...args));
scene.executeWithSeedOffset(() => {
this.executeWithSeedOffset(() => {
const shuffleModifiers = mods => {
if (mods.length < 1) {
return mods;
@ -2946,7 +2963,7 @@ export default class BattleScene extends SceneBase {
return [ mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand)) ];
};
modifiers = shuffleModifiers(modifiers);
}, scene.currentBattle.turn << 4, scene.waveSeed);
}, this.currentBattle.turn << 4, this.waveSeed);
return this.applyModifiersInternal(modifiers, player, args);
}
@ -3016,9 +3033,9 @@ export default class BattleScene extends SceneBase {
if (matchingFormChange) {
let phase: Phase;
if (pokemon instanceof PlayerPokemon && !matchingFormChange.quiet) {
phase = new FormChangePhase(this, pokemon, matchingFormChange, modal);
phase = new FormChangePhase(pokemon, matchingFormChange, modal);
} else {
phase = new QuietFormChangePhase(this, pokemon, matchingFormChange);
phase = new QuietFormChangePhase(pokemon, matchingFormChange);
}
if (pokemon instanceof PlayerPokemon && !matchingFormChange.quiet && modal) {
this.overridePhase(phase);
@ -3035,7 +3052,7 @@ export default class BattleScene extends SceneBase {
}
triggerPokemonBattleAnim(pokemon: Pokemon, battleAnimType: PokemonAnimType, fieldAssets?: Phaser.GameObjects.Sprite[], delayed: boolean = false): boolean {
const phase: Phase = new PokemonAnimPhase(this, battleAnimType, pokemon, fieldAssets);
const phase: Phase = new PokemonAnimPhase(battleAnimType, pokemon, fieldAssets);
if (delayed) {
this.pushPhase(phase);
} else {
@ -3053,7 +3070,7 @@ export default class BattleScene extends SceneBase {
validateAchv(achv: Achv, args?: unknown[]): boolean {
if ((!this.gameData.achvUnlocks.hasOwnProperty(achv.id) || Overrides.ACHIEVEMENTS_REUNLOCK_OVERRIDE)
&& achv.validate(this, args)) {
&& achv.validate(args)) {
this.gameData.achvUnlocks[achv.id] = new Date().getTime();
this.ui.achvBar.showAchv(achv);
if (vouchers.hasOwnProperty(achv.id)) {
@ -3066,7 +3083,7 @@ export default class BattleScene extends SceneBase {
}
validateVoucher(voucher: Voucher, args?: unknown[]): boolean {
if (!this.gameData.voucherUnlocks.hasOwnProperty(voucher.id) && voucher.validate(this, args)) {
if (!this.gameData.voucherUnlocks.hasOwnProperty(voucher.id) && voucher.validate(args)) {
this.gameData.voucherUnlocks[voucher.id] = new Date().getTime();
this.ui.achvBar.showAchv(voucher);
this.gameData.voucherCounts[voucher.voucherType]++;
@ -3139,9 +3156,9 @@ export default class BattleScene extends SceneBase {
this.currentBattle.double = true;
const availablePartyMembers = this.getPlayerParty().filter((p) => p.isAllowedInBattle());
if (availablePartyMembers.length > 1) {
this.pushPhase(new ToggleDoublePositionPhase(this, true));
this.pushPhase(new ToggleDoublePositionPhase(true));
if (!availablePartyMembers[1].isOnField()) {
this.pushPhase(new SummonPhase(this, 1));
this.pushPhase(new SummonPhase(1));
}
}
@ -3186,7 +3203,7 @@ export default class BattleScene extends SceneBase {
if (participated && pokemonDefeated) {
partyMember.addFriendship(FRIENDSHIP_GAIN_FROM_BATTLE);
const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier);
if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount(this)) {
if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount()) {
machoBraceModifier.stackCount++;
this.updateModifiers(true, true);
partyMember.updateInfo();
@ -3248,7 +3265,7 @@ export default class BattleScene extends SceneBase {
if (exp) {
const partyMemberIndex = party.indexOf(expPartyMembers[pm]);
this.unshiftPhase(expPartyMembers[pm].isOnField() ? new ExpPhase(this, partyMemberIndex, exp) : new ShowPartyExpBarPhase(this, partyMemberIndex, exp));
this.unshiftPhase(expPartyMembers[pm].isOnField() ? new ExpPhase(partyMemberIndex, exp) : new ShowPartyExpBarPhase(partyMemberIndex, exp));
}
}
}
@ -3340,7 +3357,7 @@ export default class BattleScene extends SceneBase {
if (encounter) {
encounter = new MysteryEncounter(encounter);
encounter.populateDialogueTokensFromRequirements(this);
encounter.populateDialogueTokensFromRequirements();
return encounter;
}
@ -3368,8 +3385,9 @@ export default class BattleScene extends SceneBase {
}
let availableEncounters: MysteryEncounter[] = [];
// New encounter should never be the same as the most recent encounter
const previousEncounter = this.mysteryEncounterSaveData.encounteredEvents.length > 0 ? this.mysteryEncounterSaveData.encounteredEvents[this.mysteryEncounterSaveData.encounteredEvents.length - 1].type : null;
const previousEncounter = this.mysteryEncounterSaveData.encounteredEvents.length > 0 ?
this.mysteryEncounterSaveData.encounteredEvents[this.mysteryEncounterSaveData.encounteredEvents.length - 1].type
: null;
const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType) ?? [];
// If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available
while (availableEncounters.length === 0 && tier !== null) {
@ -3379,27 +3397,27 @@ export default class BattleScene extends SceneBase {
if (!encounterCandidate) {
return false;
}
if (encounterCandidate.encounterTier !== tier) { // Encounter is in tier
if (encounterCandidate.encounterTier !== tier) {
return false;
}
const disallowedGameModes = encounterCandidate.disallowedGameModes;
if (disallowedGameModes && disallowedGameModes.length > 0
&& disallowedGameModes.includes(this.gameMode.modeId)) { // Encounter is enabled for game mode
&& disallowedGameModes.includes(this.gameMode.modeId)) {
return false;
}
if (this.gameMode.modeId === GameModes.CHALLENGE) { // Encounter is enabled for challenges
if (this.gameMode.modeId === GameModes.CHALLENGE) {
const disallowedChallenges = encounterCandidate.disallowedChallenges;
if (disallowedChallenges && disallowedChallenges.length > 0 && this.gameMode.challenges.some(challenge => disallowedChallenges.includes(challenge.id))) {
return false;
}
}
if (!encounterCandidate.meetsRequirements(this)) { // Meets encounter requirements
if (!encounterCandidate.meetsRequirements()) {
return false;
}
if (previousEncounter !== null && encounterType === previousEncounter) { // Previous encounter was not this one
if (previousEncounter !== null && encounterType === previousEncounter) {
return false;
}
if (this.mysteryEncounterSaveData.encounteredEvents.length > 0 && // Encounter has not exceeded max allowed encounters
if (this.mysteryEncounterSaveData.encounteredEvents.length > 0 &&
(encounterCandidate.maxAllowedEncounters && encounterCandidate.maxAllowedEncounters > 0)
&& this.mysteryEncounterSaveData.encounteredEvents.filter(e => e.type === encounterType).length >= encounterCandidate.maxAllowedEncounters) {
return false;
@ -3427,7 +3445,7 @@ export default class BattleScene extends SceneBase {
encounter = availableEncounters[Utils.randSeedInt(availableEncounters.length)];
// New encounter object to not dirty flags
encounter = new MysteryEncounter(encounter);
encounter.populateDialogueTokensFromRequirements(this);
encounter.populateDialogueTokensFromRequirements();
return encounter;
}
}

View File

@ -1,26 +1,27 @@
import BattleScene from "./battle-scene";
import { Command } from "./ui/command-ui-handler";
import { globalScene } from "#app/global-scene";
import type { Command } from "./ui/command-ui-handler";
import * as Utils from "./utils";
import Trainer, { TrainerVariant } from "./field/trainer";
import { GameMode } from "./game-mode";
import type { GameMode } from "./game-mode";
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
import { PokeballType } from "#enums/pokeball";
import type { PokeballType } from "#enums/pokeball";
import { trainerConfigs } from "#app/data/trainer-config";
import { SpeciesFormKey } from "#enums/species-form-key";
import Pokemon, { EnemyPokemon, PlayerPokemon, QueuedMove } from "#app/field/pokemon";
import type { EnemyPokemon, PlayerPokemon, TurnMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { ArenaTagType } from "#enums/arena-tag-type";
import { BattleSpec } from "#enums/battle-spec";
import { Moves } from "#enums/moves";
import type { Moves } from "#enums/moves";
import { PlayerGender } from "#enums/player-gender";
import { MusicPreference } from "#app/system/settings/settings";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import i18next from "#app/plugins/i18n";
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { CustomModifierSettings } from "#app/modifier/modifier-type";
import type { CustomModifierSettings } from "#app/modifier/modifier-type";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
export enum ClassicFixedBossWaves {
// TODO: other fixed wave battles should be added here
@ -44,12 +45,12 @@ export enum BattlerIndex {
}
export interface TurnCommand {
command: Command;
cursor?: number;
move?: QueuedMove;
targets?: BattlerIndex[];
skip?: boolean;
args?: any[];
command: Command;
cursor?: number;
move?: TurnMove;
targets?: BattlerIndex[];
skip?: boolean;
args?: any[];
}
export interface FaintLogEntry {
@ -154,7 +155,7 @@ export default class Battle {
return this.double ? 2 : 1;
}
incrementTurn(scene: BattleScene): void {
incrementTurn(): void {
this.turn++;
this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ]));
this.battleSeedState = null;
@ -169,7 +170,7 @@ export default class Battle {
}
addPostBattleLoot(enemyPokemon: EnemyPokemon): void {
this.postBattleLoot.push(...enemyPokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, false).map(i => {
this.postBattleLoot.push(...globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, false).map(i => {
const ret = i as PokemonHeldItemModifier;
//@ts-ignore - this is awful to fix/change
ret.pokemonId = null;
@ -177,43 +178,43 @@ export default class Battle {
}));
}
pickUpScatteredMoney(scene: BattleScene): void {
const moneyAmount = new Utils.IntegerHolder(scene.currentBattle.moneyScattered);
scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount);
pickUpScatteredMoney(): void {
const moneyAmount = new Utils.IntegerHolder(globalScene.currentBattle.moneyScattered);
globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount);
if (scene.arena.getTag(ArenaTagType.HAPPY_HOUR)) {
if (globalScene.arena.getTag(ArenaTagType.HAPPY_HOUR)) {
moneyAmount.value *= 2;
}
scene.addMoney(moneyAmount.value);
globalScene.addMoney(moneyAmount.value);
const userLocale = navigator.language || "en-US";
const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale);
const message = i18next.t("battle:moneyPickedUp", { moneyAmount: formattedMoneyAmount });
scene.queueMessage(message, undefined, true);
globalScene.queueMessage(message, undefined, true);
scene.currentBattle.moneyScattered = 0;
globalScene.currentBattle.moneyScattered = 0;
}
addBattleScore(scene: BattleScene): void {
let partyMemberTurnMultiplier = scene.getEnemyParty().length / 2 + 0.5;
addBattleScore(): void {
let partyMemberTurnMultiplier = globalScene.getEnemyParty().length / 2 + 0.5;
if (this.double) {
partyMemberTurnMultiplier /= 1.5;
}
for (const p of scene.getEnemyParty()) {
for (const p of globalScene.getEnemyParty()) {
if (p.isBoss()) {
partyMemberTurnMultiplier *= (p.bossSegments / 1.5) / scene.getEnemyParty().length;
partyMemberTurnMultiplier *= (p.bossSegments / 1.5) / globalScene.getEnemyParty().length;
}
}
const turnMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")(1 - Math.min(this.turn - 2, 10 * partyMemberTurnMultiplier) / (10 * partyMemberTurnMultiplier));
const finalBattleScore = Math.ceil(this.battleScore * turnMultiplier);
scene.score += finalBattleScore;
globalScene.score += finalBattleScore;
console.log(`Battle Score: ${finalBattleScore} (${this.turn - 1} Turns x${Math.floor(turnMultiplier * 100) / 100})`);
console.log(`Total Score: ${scene.score}`);
scene.updateScoreText();
console.log(`Total Score: ${globalScene.score}`);
globalScene.updateScoreText();
}
getBgmOverride(scene: BattleScene): string | null {
getBgmOverride(): string | null {
if (this.isBattleMysteryEncounter() && this.mysteryEncounter?.encounterMode === MysteryEncounterMode.DEFAULT) {
// Music is overridden for MEs during ME onInit()
// Should not use any BGM overrides before swapping from DEFAULT mode
@ -222,7 +223,7 @@ export default class Battle {
if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) {
return `encounter_${this.trainer?.getEncounterBgm()}`;
}
if (scene.musicPreference === MusicPreference.GENFIVE) {
if (globalScene.musicPreference === MusicPreference.GENFIVE) {
return this.trainer?.getBattleBgm() ?? null;
} else {
return this.trainer?.getMixedBattleBgm() ?? null;
@ -230,7 +231,7 @@ export default class Battle {
} else if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) {
return "end_summit";
}
const wildOpponents = scene.getEnemyParty();
const wildOpponents = globalScene.getEnemyParty();
for (const pokemon of wildOpponents) {
if (this.battleSpec === BattleSpec.FINAL_BOSS) {
if (pokemon.species.getFormSpriteKey(pokemon.formIndex) === SpeciesFormKey.ETERNAMAX) {
@ -239,7 +240,7 @@ export default class Battle {
return "battle_final_encounter";
}
if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) {
if (scene.musicPreference === MusicPreference.GENFIVE) {
if (globalScene.musicPreference === MusicPreference.GENFIVE) {
switch (pokemon.species.speciesId) {
case Species.REGIROCK:
case Species.REGICE:
@ -256,7 +257,7 @@ export default class Battle {
}
return "battle_legendary_unova";
}
} else if (scene.musicPreference === MusicPreference.ALLGENS) {
} else if (globalScene.musicPreference === MusicPreference.ALLGENS) {
switch (pokemon.species.speciesId) {
case Species.ARTICUNO:
case Species.ZAPDOS:
@ -396,7 +397,7 @@ export default class Battle {
}
}
if (scene.gameMode.isClassic && this.waveIndex <= 4) {
if (globalScene.gameMode.isClassic && this.waveIndex <= 4) {
return "battle_wild";
}
@ -409,12 +410,12 @@ export default class Battle {
* @param min The minimum integer to pick, default `0`
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
*/
randSeedInt(scene: BattleScene, range: number, min: number = 0): number {
randSeedInt(range: number, min: number = 0): number {
if (range <= 1) {
return min;
}
const tempRngCounter = scene.rngCounter;
const tempSeedOverride = scene.rngSeedOverride;
const tempRngCounter = globalScene.rngCounter;
const tempSeedOverride = globalScene.rngSeedOverride;
const state = Phaser.Math.RND.state();
if (this.battleSeedState) {
Phaser.Math.RND.state(this.battleSeedState);
@ -422,13 +423,13 @@ export default class Battle {
Phaser.Math.RND.sow([ Utils.shiftCharCodes(this.battleSeed, this.turn << 6) ]);
console.log("Battle Seed:", this.battleSeed);
}
scene.rngCounter = this.rngCounter++;
scene.rngSeedOverride = this.battleSeed;
globalScene.rngCounter = this.rngCounter++;
globalScene.rngSeedOverride = this.battleSeed;
const ret = Utils.randSeedInt(range, min);
this.battleSeedState = Phaser.Math.RND.state();
Phaser.Math.RND.state(state);
scene.rngCounter = tempRngCounter;
scene.rngSeedOverride = tempSeedOverride;
globalScene.rngCounter = tempRngCounter;
globalScene.rngSeedOverride = tempSeedOverride;
return ret;
}
@ -441,16 +442,16 @@ export default class Battle {
}
export class FixedBattle extends Battle {
constructor(scene: BattleScene, waveIndex: number, config: FixedBattleConfig) {
super(scene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer(scene) : undefined, config.double);
constructor(waveIndex: number, config: FixedBattleConfig) {
super(globalScene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer() : undefined, config.double);
if (config.getEnemyParty) {
this.enemyParty = config.getEnemyParty(scene);
this.enemyParty = config.getEnemyParty();
}
}
}
type GetTrainerFunc = (scene: BattleScene) => Trainer;
type GetEnemyPartyFunc = (scene: BattleScene) => EnemyPokemon[];
type GetTrainerFunc = () => Trainer;
type GetEnemyPartyFunc = () => EnemyPokemon[];
export class FixedBattleConfig {
public battleType: BattleType;
@ -500,11 +501,11 @@ export class FixedBattleConfig {
* @returns the generated trainer
*/
function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], randomGender: boolean = false, seedOffset: number = 0): GetTrainerFunc {
return (scene: BattleScene) => {
return () => {
const rand = Utils.randSeedInt(trainerPool.length);
const trainerTypes: TrainerType[] = [];
scene.executeWithSeedOffset(() => {
globalScene.executeWithSeedOffset(() => {
for (const trainerPoolEntry of trainerPool) {
const trainerType = Array.isArray(trainerPoolEntry)
? Utils.randSeedItem(trainerPoolEntry)
@ -523,10 +524,10 @@ function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], rand
const isEvilTeamGrunt = evilTeamGrunts.includes(trainerTypes[rand]);
if (trainerConfigs[trainerTypes[rand]].hasDouble && isEvilTeamGrunt) {
return new Trainer(scene, trainerTypes[rand], (Utils.randInt(3) === 0) ? TrainerVariant.DOUBLE : trainerGender);
return new Trainer(trainerTypes[rand], (Utils.randInt(3) === 0) ? TrainerVariant.DOUBLE : trainerGender);
}
return new Trainer(scene, trainerTypes[rand], trainerGender);
return new Trainer(trainerTypes[rand], trainerGender);
};
}
@ -544,16 +545,16 @@ export interface FixedBattleConfigs {
*/
export const classicFixedBattles: FixedBattleConfigs = {
[5]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
.setGetTrainerFunc(() => new Trainer(TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
[8]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
[25]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_2, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_2, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }),
[35]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)),
[55]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_3, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_3, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }),
[62]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)),
@ -562,7 +563,7 @@ export const classicFixedBattles: FixedBattleConfigs = {
[66]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.ROOD ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], TrainerType.FABA, TrainerType.PLUMERIA, TrainerType.OLEANA, [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]], true)),
[95]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_4, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_4, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }),
[112]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)),
@ -572,7 +573,7 @@ export const classicFixedBattles: FixedBattleConfigs = {
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.MAXIE, TrainerType.ARCHIE, TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE, TrainerType.LUSAMINE, TrainerType.GUZMA, TrainerType.ROSE, TrainerType.PENNY ]))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }),
[145]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_5, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_5, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }),
[ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_2, TrainerType.MAXIE_2, TrainerType.ARCHIE_2, TrainerType.CYRUS_2, TrainerType.GHETSIS_2, TrainerType.LYSANDRE_2, TrainerType.LUSAMINE_2, TrainerType.GUZMA_2, TrainerType.ROSE_2, TrainerType.PENNY_2 ]))
@ -588,6 +589,6 @@ export const classicFixedBattles: FixedBattleConfigs = {
[190]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BLUE, [ TrainerType.RED, TrainerType.LANCE_CHAMPION ], [ TrainerType.STEVEN, TrainerType.WALLACE ], TrainerType.CYNTHIA, [ TrainerType.ALDER, TrainerType.IRIS ], TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, [ TrainerType.GEETA, TrainerType.NEMONA ], TrainerType.KIERAN ])),
[195]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_6, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_6, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false })
};

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,13 @@
import { Arena } from "#app/field/arena";
import BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import type { Arena } from "#app/field/arena";
import { Type } from "#enums/type";
import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils";
import { MoveCategory, allMoves, MoveTarget } from "#app/data/move";
import { getPokemonNameWithAffix } from "#app/messages";
import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { HitResult, PokemonMove } from "#app/field/pokemon";
import { StatusEffect } from "#enums/status-effect";
import { BattlerIndex } from "#app/battle";
import type { BattlerIndex } from "#app/battle";
import { BlockNonDirectDamageAbAttr, InfiltratorAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability";
import { Stat } from "#enums/stat";
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
@ -44,7 +45,7 @@ export abstract class ArenaTag {
onRemove(arena: Arena, quiet: boolean = false): void {
if (!quiet) {
arena.scene.queueMessage(i18next.t(`arenaTag:arenaOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, { moveName: this.getMoveName() }));
globalScene.queueMessage(i18next.t(`arenaTag:arenaOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, { moveName: this.getMoveName() }));
}
}
@ -74,27 +75,25 @@ export abstract class ArenaTag {
/**
* Helper function that retrieves the source Pokemon
* @param scene medium to retrieve the source Pokemon
* @returns The source {@linkcode Pokemon} or `null` if none is found
*/
public getSourcePokemon(scene: BattleScene): Pokemon | null {
return this.sourceId ? scene.getPokemonById(this.sourceId) : null;
public getSourcePokemon(): Pokemon | null {
return this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
}
/**
* Helper function that retrieves the Pokemon affected
* @param scene - medium to retrieve the involved Pokemon
* @returns list of PlayerPokemon or EnemyPokemon on the field
*/
public getAffectedPokemon(scene: BattleScene): Pokemon[] {
public getAffectedPokemon(): Pokemon[] {
switch (this.side) {
case ArenaTagSide.PLAYER:
return scene.getPlayerField() ?? [];
return globalScene.getPlayerField() ?? [];
case ArenaTagSide.ENEMY:
return scene.getEnemyField() ?? [];
return globalScene.getEnemyField() ?? [];
case ArenaTagSide.BOTH:
default:
return scene.getField(true) ?? [];
return globalScene.getField(true) ?? [];
}
}
}
@ -112,10 +111,10 @@ export class MistTag extends ArenaTag {
super.onAdd(arena);
if (this.sourceId) {
const source = arena.scene.getPokemonById(this.sourceId);
const source = globalScene.getPokemonById(this.sourceId);
if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:mistOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
globalScene.queueMessage(i18next.t("arenaTag:mistOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
} else if (!quiet) {
console.warn("Failed to get source for MistTag onAdd");
}
@ -146,7 +145,7 @@ export class MistTag extends ArenaTag {
cancelled.value = true;
if (!simulated) {
arena.scene.queueMessage(i18next.t("arenaTag:mistApply"));
globalScene.queueMessage(i18next.t("arenaTag:mistApply"));
}
return true;
@ -193,7 +192,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
if (bypassed.value) {
return false;
}
damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5;
damageMultiplier.value = globalScene.currentBattle.double ? 2732 / 4096 : 0.5;
return true;
}
return false;
@ -211,7 +210,7 @@ class ReflectTag extends WeakenMoveScreenTag {
onAdd(arena: Arena, quiet: boolean = false): void {
if (!quiet) {
arena.scene.queueMessage(i18next.t(`arenaTag:reflectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
globalScene.queueMessage(i18next.t(`arenaTag:reflectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
}
}
@ -227,7 +226,7 @@ class LightScreenTag extends WeakenMoveScreenTag {
onAdd(arena: Arena, quiet: boolean = false): void {
if (!quiet) {
arena.scene.queueMessage(i18next.t(`arenaTag:lightScreenOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
globalScene.queueMessage(i18next.t(`arenaTag:lightScreenOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
}
}
@ -243,7 +242,7 @@ class AuroraVeilTag extends WeakenMoveScreenTag {
onAdd(arena: Arena, quiet: boolean = false): void {
if (!quiet) {
arena.scene.queueMessage(i18next.t(`arenaTag:auroraVeilOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
globalScene.queueMessage(i18next.t(`arenaTag:auroraVeilOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
}
}
@ -268,7 +267,7 @@ export class ConditionalProtectTag extends ArenaTag {
}
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t(`arenaTag:conditionalProtectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, { moveName: super.getMoveName() }));
globalScene.queueMessage(i18next.t(`arenaTag:conditionalProtectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, { moveName: super.getMoveName() }));
}
// Removes default message for effect removal
@ -296,8 +295,8 @@ export class ConditionalProtectTag extends ArenaTag {
if (!simulated) {
attacker.stopMultiHit(defender);
new CommonBattleAnim(CommonAnim.PROTECT, defender).play(arena.scene);
arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(defender) }));
new CommonBattleAnim(CommonAnim.PROTECT, defender).play();
globalScene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(defender) }));
}
}
@ -318,7 +317,7 @@ export class ConditionalProtectTag extends ArenaTag {
*/
const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
const move = allMoves[moveId];
const effectPhase = arena.scene.getCurrentPhase();
const effectPhase = globalScene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase) {
const attacker = effectPhase.getUserPokemon();
@ -393,9 +392,9 @@ class MatBlockTag extends ConditionalProtectTag {
onAdd(arena: Arena) {
if (this.sourceId) {
const source = arena.scene.getPokemonById(this.sourceId);
const source = globalScene.getPokemonById(this.sourceId);
if (source) {
arena.scene.queueMessage(i18next.t("arenaTag:matBlockOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
globalScene.queueMessage(i18next.t("arenaTag:matBlockOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
} else {
console.warn("Failed to get source for MatBlockTag onAdd");
}
@ -448,15 +447,15 @@ export class NoCritTag extends ArenaTag {
/** Queues a message upon adding this effect to the field */
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t(`arenaTag:noCritOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : "Enemy"}`, {
globalScene.queueMessage(i18next.t(`arenaTag:noCritOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : "Enemy"}`, {
moveName: this.getMoveName()
}));
}
/** Queues a message upon removing this effect from the field */
onRemove(arena: Arena): void {
const source = arena.scene.getPokemonById(this.sourceId!); // TODO: is this bang correct?
arena.scene.queueMessage(i18next.t("arenaTag:noCritOnRemove", {
const source = globalScene.getPokemonById(this.sourceId!); // TODO: is this bang correct?
globalScene.queueMessage(i18next.t("arenaTag:noCritOnRemove", {
pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined),
moveName: this.getMoveName()
}));
@ -478,7 +477,7 @@ class WishTag extends ArenaTag {
onAdd(arena: Arena): void {
if (this.sourceId) {
const user = arena.scene.getPokemonById(this.sourceId);
const user = globalScene.getPokemonById(this.sourceId);
if (user) {
this.battlerIndex = user.getBattlerIndex();
this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) });
@ -490,10 +489,10 @@ class WishTag extends ArenaTag {
}
onRemove(arena: Arena): void {
const target = arena.scene.getField()[this.battlerIndex];
const target = globalScene.getField()[this.battlerIndex];
if (target?.isActive(true)) {
arena.scene.queueMessage(this.triggerMessage);
arena.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(), this.healHp, null, true, false));
globalScene.queueMessage(this.triggerMessage);
globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), this.healHp, null, true, false));
}
}
}
@ -546,11 +545,11 @@ class MudSportTag extends WeakenMoveTypeTag {
}
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:mudSportOnAdd"));
globalScene.queueMessage(i18next.t("arenaTag:mudSportOnAdd"));
}
onRemove(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:mudSportOnRemove"));
globalScene.queueMessage(i18next.t("arenaTag:mudSportOnRemove"));
}
}
@ -564,11 +563,11 @@ class WaterSportTag extends WeakenMoveTypeTag {
}
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:waterSportOnAdd"));
globalScene.queueMessage(i18next.t("arenaTag:waterSportOnAdd"));
}
onRemove(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:waterSportOnRemove"));
globalScene.queueMessage(i18next.t("arenaTag:waterSportOnRemove"));
}
}
@ -584,7 +583,7 @@ export class IonDelugeTag extends ArenaTag {
/** Queues an on-add message */
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:plasmaFistsOnAdd"));
globalScene.queueMessage(i18next.t("arenaTag:plasmaFistsOnAdd"));
}
onRemove(arena: Arena): void { } // Removes default on-remove message
@ -679,9 +678,9 @@ class SpikesTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena);
const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:spikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() }));
globalScene.queueMessage(i18next.t("arenaTag:spikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() }));
}
}
@ -698,7 +697,7 @@ class SpikesTag extends ArenaTrapTag {
const damageHpRatio = 1 / (10 - 2 * this.layers);
const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio);
pokemon.scene.queueMessage(i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
globalScene.queueMessage(i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.damageAndUpdate(damage, HitResult.OTHER);
if (pokemon.turnData) {
pokemon.turnData.damageTaken += damage;
@ -728,9 +727,9 @@ class ToxicSpikesTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena);
const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:toxicSpikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() }));
globalScene.queueMessage(i18next.t("arenaTag:toxicSpikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() }));
}
}
@ -747,8 +746,8 @@ class ToxicSpikesTag extends ArenaTrapTag {
}
if (pokemon.isOfType(Type.POISON)) {
this.neutralized = true;
if (pokemon.scene.arena.removeTag(this.tagType)) {
pokemon.scene.queueMessage(i18next.t("arenaTag:toxicSpikesActivateTrapPoison", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName() }));
if (globalScene.arena.removeTag(this.tagType)) {
globalScene.queueMessage(i18next.t("arenaTag:toxicSpikesActivateTrapPoison", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName() }));
return true;
}
} else if (!pokemon.status) {
@ -792,7 +791,7 @@ export class DelayedAttackTag extends ArenaTag {
const ret = super.lapse(arena);
if (!ret) {
arena.scene.unshiftPhase(new MoveEffectPhase(arena.scene, this.sourceId!, [ this.targetIndex ], new PokemonMove(this.sourceMove!, 0, 0, true))); // TODO: are those bangs correct?
globalScene.unshiftPhase(new MoveEffectPhase(this.sourceId!, [ this.targetIndex ], new PokemonMove(this.sourceMove!, 0, 0, true))); // TODO: are those bangs correct?
}
return ret;
@ -814,9 +813,9 @@ class StealthRockTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena);
const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:stealthRockOnAdd", { opponentDesc: source.getOpponentDescriptor() }));
globalScene.queueMessage(i18next.t("arenaTag:stealthRockOnAdd", { opponentDesc: source.getOpponentDescriptor() }));
}
}
@ -864,7 +863,7 @@ class StealthRockTag extends ArenaTrapTag {
return true;
}
const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio);
pokemon.scene.queueMessage(i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
globalScene.queueMessage(i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.damageAndUpdate(damage, HitResult.OTHER);
if (pokemon.turnData) {
pokemon.turnData.damageTaken += damage;
@ -893,9 +892,9 @@ class StickyWebTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena);
const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:stickyWebOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() }));
globalScene.queueMessage(i18next.t("arenaTag:stickyWebOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() }));
}
}
@ -909,9 +908,9 @@ class StickyWebTag extends ArenaTrapTag {
}
if (!cancelled.value) {
pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() }));
globalScene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() }));
const stages = new NumberHolder(-1);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value));
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value));
return true;
}
}
@ -945,14 +944,14 @@ export class TrickRoomTag extends ArenaTag {
}
onAdd(arena: Arena): void {
const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
if (source) {
arena.scene.queueMessage(i18next.t("arenaTag:trickRoomOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
globalScene.queueMessage(i18next.t("arenaTag:trickRoomOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
}
}
onRemove(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:trickRoomOnRemove"));
globalScene.queueMessage(i18next.t("arenaTag:trickRoomOnRemove"));
}
}
@ -967,8 +966,8 @@ export class GravityTag extends ArenaTag {
}
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:gravityOnAdd"));
arena.scene.getField(true).forEach((pokemon) => {
globalScene.queueMessage(i18next.t("arenaTag:gravityOnAdd"));
globalScene.getField(true).forEach((pokemon) => {
if (pokemon !== null) {
pokemon.removeTag(BattlerTagType.FLOATING);
pokemon.removeTag(BattlerTagType.TELEKINESIS);
@ -980,7 +979,7 @@ export class GravityTag extends ArenaTag {
}
onRemove(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:gravityOnRemove"));
globalScene.queueMessage(i18next.t("arenaTag:gravityOnRemove"));
}
}
@ -996,29 +995,29 @@ class TailwindTag extends ArenaTag {
onAdd(arena: Arena, quiet: boolean = false): void {
if (!quiet) {
arena.scene.queueMessage(i18next.t(`arenaTag:tailwindOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
globalScene.queueMessage(i18next.t(`arenaTag:tailwindOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
const source = arena.scene.getPokemonById(this.sourceId!); //TODO: this bang is questionable!
const party = (source?.isPlayer() ? source.scene.getPlayerField() : source?.scene.getEnemyField()) ?? [];
const source = globalScene.getPokemonById(this.sourceId!); //TODO: this bang is questionable!
const party = (source?.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField()) ?? [];
for (const pokemon of party) {
// Apply the CHARGED tag to party members with the WIND_POWER ability
if (pokemon.hasAbility(Abilities.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) {
pokemon.addTag(BattlerTagType.CHARGED);
pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName() }));
globalScene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName() }));
}
// Raise attack by one stage if party member has WIND_RIDER ability
if (pokemon.hasAbility(Abilities.WIND_RIDER)) {
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK ], 1, true));
globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.getBattlerIndex()));
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.ATK ], 1, true));
}
}
}
onRemove(arena: Arena, quiet: boolean = false): void {
if (!quiet) {
arena.scene.queueMessage(i18next.t(`arenaTag:tailwindOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
globalScene.queueMessage(i18next.t(`arenaTag:tailwindOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
}
}
@ -1033,11 +1032,11 @@ class HappyHourTag extends ArenaTag {
}
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:happyHourOnAdd"));
globalScene.queueMessage(i18next.t("arenaTag:happyHourOnAdd"));
}
onRemove(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:happyHourOnRemove"));
globalScene.queueMessage(i18next.t("arenaTag:happyHourOnRemove"));
}
}
@ -1047,11 +1046,11 @@ class SafeguardTag extends ArenaTag {
}
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t(`arenaTag:safeguardOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
globalScene.queueMessage(i18next.t(`arenaTag:safeguardOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
onRemove(arena: Arena): void {
arena.scene.queueMessage(i18next.t(`arenaTag:safeguardOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
globalScene.queueMessage(i18next.t(`arenaTag:safeguardOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
}
@ -1074,16 +1073,16 @@ class ImprisonTag extends ArenaTrapTag {
* This function applies the effects of Imprison to the opposing Pokemon already present on the field.
* @param arena
*/
override onAdd({ scene }: Arena) {
const source = this.getSourcePokemon(scene);
override onAdd() {
const source = this.getSourcePokemon();
if (source) {
const party = this.getAffectedPokemon(scene);
const party = this.getAffectedPokemon();
party?.forEach((p: Pokemon ) => {
if (p.isAllowedInBattle()) {
p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId);
}
});
scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
globalScene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
}
}
@ -1092,8 +1091,8 @@ class ImprisonTag extends ArenaTrapTag {
* @param _arena
* @returns `true` if the source of the tag is still active on the field | `false` if not
*/
override lapse({ scene }: Arena): boolean {
const source = this.getSourcePokemon(scene);
override lapse(): boolean {
const source = this.getSourcePokemon();
return source ? source.isActive(true) : false;
}
@ -1103,7 +1102,7 @@ class ImprisonTag extends ArenaTrapTag {
* @returns `true`
*/
override activateTrap(pokemon: Pokemon): boolean {
const source = this.getSourcePokemon(pokemon.scene);
const source = this.getSourcePokemon();
if (source && source.isActive(true) && pokemon.isAllowedInBattle()) {
pokemon.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId);
}
@ -1114,8 +1113,8 @@ class ImprisonTag extends ArenaTrapTag {
* When the arena tag is removed, it also attempts to remove any related Battler Tags if they haven't already been removed from the affected Pokemon
* @param arena
*/
override onRemove({ scene }: Arena): void {
const party = this.getAffectedPokemon(scene);
override onRemove(): void {
const party = this.getAffectedPokemon();
party?.forEach((p: Pokemon) => {
p.removeTag(BattlerTagType.IMPRISON);
});
@ -1136,19 +1135,19 @@ class FireGrassPledgeTag extends ArenaTag {
override onAdd(arena: Arena): void {
// "A sea of fire enveloped your/the opposing team!"
arena.scene.queueMessage(i18next.t(`arenaTag:fireGrassPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
globalScene.queueMessage(i18next.t(`arenaTag:fireGrassPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
override lapse(arena: Arena): boolean {
const field: Pokemon[] = (this.side === ArenaTagSide.PLAYER)
? arena.scene.getPlayerField()
: arena.scene.getEnemyField();
? globalScene.getPlayerField()
: globalScene.getEnemyField();
field.filter(pokemon => !pokemon.isOfType(Type.FIRE) && !pokemon.switchOutStatus).forEach(pokemon => {
// "{pokemonNameWithAffix} was hurt by the sea of fire!"
pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
globalScene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
// TODO: Replace this with a proper animation
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM));
globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM));
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8));
});
@ -1170,7 +1169,7 @@ class WaterFirePledgeTag extends ArenaTag {
override onAdd(arena: Arena): void {
// "A rainbow appeared in the sky on your/the opposing team's side!"
arena.scene.queueMessage(i18next.t(`arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
globalScene.queueMessage(i18next.t(`arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
/**
@ -1200,7 +1199,7 @@ class GrassWaterPledgeTag extends ArenaTag {
override onAdd(arena: Arena): void {
// "A swamp enveloped your/the opposing team!"
arena.scene.queueMessage(i18next.t(`arenaTag:grassWaterPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
globalScene.queueMessage(i18next.t(`arenaTag:grassWaterPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
}
@ -1217,7 +1216,7 @@ export class FairyLockTag extends ArenaTag {
}
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:fairyLockOnAdd"));
globalScene.queueMessage(i18next.t("arenaTag:fairyLockOnAdd"));
}
}

View File

@ -1,6 +1,7 @@
import { Type } from "#enums/type";
import * as Utils from "#app/utils";
import { pokemonEvolutions, SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import i18next from "i18next";
import { Biome } from "#enums/biome";
import { Species } from "#enums/species";

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene";
import { Gender } from "#app/data/gender";
import { PokeballType } from "#enums/pokeball";
import Pokemon from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { Type } from "#enums/type";
import * as Utils from "#app/utils";
import { WeatherType } from "#enums/weather-type";
@ -266,8 +267,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.ELECTRODE, 30, null, null)
],
[Species.CUBONE]: [
new SpeciesEvolution(Species.ALOLA_MAROWAK, 28, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.MAROWAK, 28, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY))
new SpeciesEvolution(Species.ALOLA_MAROWAK, 28, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.MAROWAK, 28, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY))
],
[Species.TYROGUE]: [
/**
@ -287,8 +288,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
)),
],
[Species.KOFFING]: [
new SpeciesEvolution(Species.GALAR_WEEZING, 35, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.WEEZING, 35, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY))
new SpeciesEvolution(Species.GALAR_WEEZING, 35, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.WEEZING, 35, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY))
],
[Species.RHYHORN]: [
new SpeciesEvolution(Species.RHYDON, 42, null, null)
@ -333,8 +334,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.QUILAVA, 14, null, null)
],
[Species.QUILAVA]: [
new SpeciesEvolution(Species.HISUI_TYPHLOSION, 36, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.TYPHLOSION, 36, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY))
new SpeciesEvolution(Species.HISUI_TYPHLOSION, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.TYPHLOSION, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY))
],
[Species.TOTODILE]: [
new SpeciesEvolution(Species.CROCONAW, 18, null, null)
@ -436,8 +437,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.LINOONE, 20, null, null)
],
[Species.WURMPLE]: [
new SpeciesEvolution(Species.SILCOON, 7, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)),
new SpeciesEvolution(Species.CASCOON, 7, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
new SpeciesEvolution(Species.SILCOON, 7, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)),
new SpeciesEvolution(Species.CASCOON, 7, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
],
[Species.SILCOON]: [
new SpeciesEvolution(Species.BEAUTIFLY, 10, null, null)
@ -478,7 +479,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
],
[Species.NINCADA]: [
new SpeciesEvolution(Species.NINJASK, 20, null, null),
new SpeciesEvolution(Species.SHEDINJA, 20, null, new SpeciesEvolutionCondition(p => p.scene.getPlayerParty().length < 6 && p.scene.pokeballCounts[PokeballType.POKEBALL] > 0))
new SpeciesEvolution(Species.SHEDINJA, 20, null, new SpeciesEvolutionCondition(p => globalScene.getPlayerParty().length < 6 && globalScene.pokeballCounts[PokeballType.POKEBALL] > 0))
],
[Species.WHISMUR]: [
new SpeciesEvolution(Species.LOUDRED, 20, null, null)
@ -660,7 +661,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.LUMINEON, 31, null, null)
],
[Species.MANTYKE]: [
new SpeciesEvolution(Species.MANTINE, 32, null, new SpeciesEvolutionCondition(p => !!p.scene.gameData.dexData[Species.REMORAID].caughtAttr), SpeciesWildEvolutionDelay.MEDIUM)
new SpeciesEvolution(Species.MANTINE, 32, null, new SpeciesEvolutionCondition(p => !!globalScene.gameData.dexData[Species.REMORAID].caughtAttr), SpeciesWildEvolutionDelay.MEDIUM)
],
[Species.SNOVER]: [
new SpeciesEvolution(Species.ABOMASNOW, 40, null, null)
@ -681,8 +682,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.DEWOTT, 17, null, null)
],
[Species.DEWOTT]: [
new SpeciesEvolution(Species.HISUI_SAMUROTT, 36, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.SAMUROTT, 36, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY))
new SpeciesEvolution(Species.HISUI_SAMUROTT, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.SAMUROTT, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY))
],
[Species.PATRAT]: [
new SpeciesEvolution(Species.WATCHOG, 20, null, null)
@ -832,8 +833,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.KINGAMBIT, 1, EvolutionItem.LEADERS_CREST, null, SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.RUFFLET]: [
new SpeciesEvolution(Species.HISUI_BRAVIARY, 54, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.BRAVIARY, 54, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY))
new SpeciesEvolution(Species.HISUI_BRAVIARY, 54, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.BRAVIARY, 54, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY))
],
[Species.VULLABY]: [
new SpeciesEvolution(Species.MANDIBUZZ, 54, null, null)
@ -890,7 +891,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.GOGOAT, 32, null, null)
],
[Species.PANCHAM]: [
new SpeciesEvolution(Species.PANGORO, 32, null, new SpeciesEvolutionCondition(p => !!p.scene.getPlayerParty().find(p => p.getTypes(false, false, true).indexOf(Type.DARK) > -1)), SpeciesWildEvolutionDelay.MEDIUM)
new SpeciesEvolution(Species.PANGORO, 32, null, new SpeciesEvolutionCondition(p => !!globalScene.getPlayerParty().find(p => p.getTypes(false, false, true).indexOf(Type.DARK) > -1)), SpeciesWildEvolutionDelay.MEDIUM)
],
[Species.ESPURR]: [
new SpeciesFormEvolution(Species.MEOWSTIC, "", "female", 25, null, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE)),
@ -912,21 +913,21 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.CLAWITZER, 37, null, null)
],
[Species.TYRUNT]: [
new SpeciesEvolution(Species.TYRANTRUM, 39, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY))
new SpeciesEvolution(Species.TYRANTRUM, 39, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY))
],
[Species.AMAURA]: [
new SpeciesEvolution(Species.AURORUS, 39, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
new SpeciesEvolution(Species.AURORUS, 39, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
],
[Species.GOOMY]: [
new SpeciesEvolution(Species.HISUI_SLIGGOO, 40, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.SLIGGOO, 40, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY))
new SpeciesEvolution(Species.HISUI_SLIGGOO, 40, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.SLIGGOO, 40, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY))
],
[Species.SLIGGOO]: [
new SpeciesEvolution(Species.GOODRA, 50, null, new SpeciesEvolutionCondition(p => [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ].indexOf(p.scene.arena.weather?.weatherType || WeatherType.NONE) > -1), SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(Species.GOODRA, 50, null, new SpeciesEvolutionCondition(p => [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ].indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1), SpeciesWildEvolutionDelay.LONG)
],
[Species.BERGMITE]: [
new SpeciesEvolution(Species.HISUI_AVALUGG, 37, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.AVALUGG, 37, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY))
new SpeciesEvolution(Species.HISUI_AVALUGG, 37, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.AVALUGG, 37, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY))
],
[Species.NOIBAT]: [
new SpeciesEvolution(Species.NOIVERN, 48, null, null)
@ -935,8 +936,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.DARTRIX, 17, null, null)
],
[Species.DARTRIX]: [
new SpeciesEvolution(Species.HISUI_DECIDUEYE, 36, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.DECIDUEYE, 34, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY))
new SpeciesEvolution(Species.HISUI_DECIDUEYE, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)),
new SpeciesEvolution(Species.DECIDUEYE, 34, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY))
],
[Species.LITTEN]: [
new SpeciesEvolution(Species.TORRACAT, 17, null, null)
@ -957,7 +958,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.TOUCANNON, 28, null, null)
],
[Species.YUNGOOS]: [
new SpeciesEvolution(Species.GUMSHOOS, 20, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY))
new SpeciesEvolution(Species.GUMSHOOS, 20, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY))
],
[Species.GRUBBIN]: [
new SpeciesEvolution(Species.CHARJABUG, 20, null, null)
@ -975,7 +976,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.ARAQUANID, 22, null, null)
],
[Species.FOMANTIS]: [
new SpeciesEvolution(Species.LURANTIS, 34, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY))
new SpeciesEvolution(Species.LURANTIS, 34, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY))
],
[Species.MORELULL]: [
new SpeciesEvolution(Species.SHIINOTIC, 24, null, null)
@ -1012,7 +1013,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.MELMETAL, 48, null, null)
],
[Species.ALOLA_RATTATA]: [
new SpeciesEvolution(Species.ALOLA_RATICATE, 20, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
new SpeciesEvolution(Species.ALOLA_RATICATE, 20, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
],
[Species.ALOLA_DIGLETT]: [
new SpeciesEvolution(Species.ALOLA_DUGTRIO, 26, null, null)
@ -1135,7 +1136,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.GALAR_LINOONE, 20, null, null)
],
[Species.GALAR_LINOONE]: [
new SpeciesEvolution(Species.OBSTAGOON, 35, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
new SpeciesEvolution(Species.OBSTAGOON, 35, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
],
[Species.GALAR_YAMASK]: [
new SpeciesEvolution(Species.RUNERIGUS, 34, null, null)
@ -1144,7 +1145,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.HISUI_ZOROARK, 30, null, null)
],
[Species.HISUI_SLIGGOO]: [
new SpeciesEvolution(Species.HISUI_GOODRA, 50, null, new SpeciesEvolutionCondition(p => [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ].indexOf(p.scene.arena.weather?.weatherType || WeatherType.NONE) > -1), SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(Species.HISUI_GOODRA, 50, null, new SpeciesEvolutionCondition(p => [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ].indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1), SpeciesWildEvolutionDelay.LONG)
],
[Species.SPRIGATITO]: [
new SpeciesEvolution(Species.FLORAGATO, 16, null, null)
@ -1183,7 +1184,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
[Species.TANDEMAUS]: [
new SpeciesFormEvolution(Species.MAUSHOLD, "", "three", 25, null, new SpeciesEvolutionCondition(p => {
let ret = false;
p.scene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id);
globalScene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id);
return ret;
})),
new SpeciesEvolution(Species.MAUSHOLD, 25, null, null)
@ -1243,7 +1244,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.GLIMMORA, 35, null, null)
],
[Species.GREAVARD]: [
new SpeciesEvolution(Species.HOUNDSTONE, 30, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
new SpeciesEvolution(Species.HOUNDSTONE, 30, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
],
[Species.FRIGIBAX]: [
new SpeciesEvolution(Species.ARCTIBAX, 35, null, null)
@ -1311,10 +1312,10 @@ export const pokemonEvolutions: PokemonEvolutions = {
[Species.EEVEE]: [
new SpeciesFormEvolution(Species.SYLVEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => !!p.getMoveset().find(m => m?.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.SYLVEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => !!p.getMoveset().find(m => m?.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ESPEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ESPEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.UMBREON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.UMBREON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ESPEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ESPEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.UMBREON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.UMBREON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.VAPOREON, "", "", 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.VAPOREON, "partner", "", 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.JOLTEON, "", "", 1, EvolutionItem.THUNDER_STONE, null, SpeciesWildEvolutionDelay.LONG),
@ -1351,17 +1352,17 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "three-segment", 32, null, new SpeciesEvolutionCondition(p => {
let ret = false;
if (p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0) {
p.scene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id);
globalScene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id);
}
return ret;
}), SpeciesWildEvolutionDelay.LONG),
new SpeciesEvolution(Species.DUDUNSPARCE, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0), SpeciesWildEvolutionDelay.LONG)
],
[Species.GLIGAR]: [
new SpeciesEvolution(Species.GLISCOR, 1, EvolutionItem.RAZOR_FANG, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor fang at night*/), SpeciesWildEvolutionDelay.VERY_LONG)
new SpeciesEvolution(Species.GLISCOR, 1, EvolutionItem.RAZOR_FANG, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor fang at night*/), SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.SNEASEL]: [
new SpeciesEvolution(Species.WEAVILE, 1, EvolutionItem.RAZOR_CLAW, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor claw at night*/), SpeciesWildEvolutionDelay.VERY_LONG)
new SpeciesEvolution(Species.WEAVILE, 1, EvolutionItem.RAZOR_CLAW, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor claw at night*/), SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.URSARING]: [
new SpeciesEvolution(Species.URSALUNA, 1, EvolutionItem.PEAT_BLOCK, null, SpeciesWildEvolutionDelay.VERY_LONG) //Ursaring does not evolve into Bloodmoon Ursaluna
@ -1391,8 +1392,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.SUDOWOODO, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0), SpeciesWildEvolutionDelay.MEDIUM)
],
[Species.MIME_JR]: [
new SpeciesEvolution(Species.GALAR_MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0 && (p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), SpeciesWildEvolutionDelay.MEDIUM),
new SpeciesEvolution(Species.MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0 && (p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)), SpeciesWildEvolutionDelay.MEDIUM)
new SpeciesEvolution(Species.GALAR_MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), SpeciesWildEvolutionDelay.MEDIUM),
new SpeciesEvolution(Species.MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)), SpeciesWildEvolutionDelay.MEDIUM)
],
[Species.PANSAGE]: [
new SpeciesEvolution(Species.SIMISAGE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG)
@ -1442,9 +1443,9 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.CRABOMINABLE, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG)
],
[Species.ROCKRUFF]: [
new SpeciesFormEvolution(Species.LYCANROC, "", "midday", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY) && (p.formIndex === 0))),
new SpeciesFormEvolution(Species.LYCANROC, "", "midday", 25, null, new SpeciesEvolutionCondition(p => (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY) && (p.formIndex === 0))),
new SpeciesFormEvolution(Species.LYCANROC, "own-tempo", "dusk", 25, null, new SpeciesEvolutionCondition(p => p.formIndex === 1)),
new SpeciesFormEvolution(Species.LYCANROC, "", "midnight", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT) && (p.formIndex === 0)))
new SpeciesFormEvolution(Species.LYCANROC, "", "midnight", 25, null, new SpeciesEvolutionCondition(p => (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT) && (p.formIndex === 0)))
],
[Species.STEENEE]: [
new SpeciesEvolution(Species.TSAREENA, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.STOMP).length > 0), SpeciesWildEvolutionDelay.LONG)
@ -1471,15 +1472,15 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesFormEvolution(Species.POLTEAGEIST, "antique", "antique", 1, EvolutionItem.CHIPPED_POT, null, SpeciesWildEvolutionDelay.LONG)
],
[Species.MILCERY]: [
new SpeciesFormEvolution(Species.ALCREMIE, "", "vanilla-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.TOWN || p.scene.arena.biomeType === Biome.PLAINS || p.scene.arena.biomeType === Biome.GRASS || p.scene.arena.biomeType === Biome.TALL_GRASS || p.scene.arena.biomeType === Biome.METROPOLIS), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "ruby-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.BADLANDS || p.scene.arena.biomeType === Biome.VOLCANO || p.scene.arena.biomeType === Biome.GRAVEYARD || p.scene.arena.biomeType === Biome.FACTORY || p.scene.arena.biomeType === Biome.SLUM), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "matcha-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.FOREST || p.scene.arena.biomeType === Biome.SWAMP || p.scene.arena.biomeType === Biome.MEADOW || p.scene.arena.biomeType === Biome.JUNGLE), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "mint-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.SEA || p.scene.arena.biomeType === Biome.BEACH || p.scene.arena.biomeType === Biome.LAKE || p.scene.arena.biomeType === Biome.SEABED), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "lemon-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.DESERT || p.scene.arena.biomeType === Biome.POWER_PLANT || p.scene.arena.biomeType === Biome.DOJO || p.scene.arena.biomeType === Biome.RUINS || p.scene.arena.biomeType === Biome.CONSTRUCTION_SITE), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "salted-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.MOUNTAIN || p.scene.arena.biomeType === Biome.CAVE || p.scene.arena.biomeType === Biome.ICE_CAVE || p.scene.arena.biomeType === Biome.FAIRY_CAVE || p.scene.arena.biomeType === Biome.SNOWY_FOREST), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "ruby-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.WASTELAND || p.scene.arena.biomeType === Biome.LABORATORY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "caramel-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.TEMPLE || p.scene.arena.biomeType === Biome.ISLAND), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "rainbow-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.ABYSS || p.scene.arena.biomeType === Biome.SPACE || p.scene.arena.biomeType === Biome.END), SpeciesWildEvolutionDelay.LONG)
new SpeciesFormEvolution(Species.ALCREMIE, "", "vanilla-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.TOWN || globalScene.arena.biomeType === Biome.PLAINS || globalScene.arena.biomeType === Biome.GRASS || globalScene.arena.biomeType === Biome.TALL_GRASS || globalScene.arena.biomeType === Biome.METROPOLIS), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "ruby-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.BADLANDS || globalScene.arena.biomeType === Biome.VOLCANO || globalScene.arena.biomeType === Biome.GRAVEYARD || globalScene.arena.biomeType === Biome.FACTORY || globalScene.arena.biomeType === Biome.SLUM), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "matcha-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.FOREST || globalScene.arena.biomeType === Biome.SWAMP || globalScene.arena.biomeType === Biome.MEADOW || globalScene.arena.biomeType === Biome.JUNGLE), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "mint-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.SEA || globalScene.arena.biomeType === Biome.BEACH || globalScene.arena.biomeType === Biome.LAKE || globalScene.arena.biomeType === Biome.SEABED), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "lemon-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.DESERT || globalScene.arena.biomeType === Biome.POWER_PLANT || globalScene.arena.biomeType === Biome.DOJO || globalScene.arena.biomeType === Biome.RUINS || globalScene.arena.biomeType === Biome.CONSTRUCTION_SITE), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "salted-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.MOUNTAIN || globalScene.arena.biomeType === Biome.CAVE || globalScene.arena.biomeType === Biome.ICE_CAVE || globalScene.arena.biomeType === Biome.FAIRY_CAVE || globalScene.arena.biomeType === Biome.SNOWY_FOREST), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "ruby-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.WASTELAND || globalScene.arena.biomeType === Biome.LABORATORY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "caramel-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.TEMPLE || globalScene.arena.biomeType === Biome.ISLAND), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ALCREMIE, "", "rainbow-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.ABYSS || globalScene.arena.biomeType === Biome.SPACE || globalScene.arena.biomeType === Biome.END), SpeciesWildEvolutionDelay.LONG)
],
[Species.DURALUDON]: [
new SpeciesFormEvolution(Species.ARCHALUDON, "", "", 1, EvolutionItem.METAL_ALLOY, null, SpeciesWildEvolutionDelay.VERY_LONG)
@ -1501,7 +1502,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.OVERQWIL, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.BARB_BARRAGE).length > 0), SpeciesWildEvolutionDelay.LONG)
],
[Species.HISUI_SNEASEL]: [
new SpeciesEvolution(Species.SNEASLER, 1, EvolutionItem.RAZOR_CLAW, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY /* Razor claw at day*/), SpeciesWildEvolutionDelay.VERY_LONG)
new SpeciesEvolution(Species.SNEASLER, 1, EvolutionItem.RAZOR_CLAW, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY /* Razor claw at day*/), SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.CHARCADET]: [
new SpeciesEvolution(Species.ARMAROUGE, 1, EvolutionItem.AUSPICIOUS_ARMOR, null, SpeciesWildEvolutionDelay.LONG),
@ -1581,10 +1582,10 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.CONKELDURR, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.KARRABLAST]: [
new SpeciesEvolution(Species.ESCAVALIER, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => !!p.scene.gameData.dexData[Species.SHELMET].caughtAttr), SpeciesWildEvolutionDelay.VERY_LONG)
new SpeciesEvolution(Species.ESCAVALIER, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => !!globalScene.gameData.dexData[Species.SHELMET].caughtAttr), SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.SHELMET]: [
new SpeciesEvolution(Species.ACCELGOR, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => !!p.scene.gameData.dexData[Species.KARRABLAST].caughtAttr), SpeciesWildEvolutionDelay.VERY_LONG)
new SpeciesEvolution(Species.ACCELGOR, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => !!globalScene.gameData.dexData[Species.KARRABLAST].caughtAttr), SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.SPRITZEE]: [
new SpeciesEvolution(Species.AROMATISSE, 1, EvolutionItem.SACHET, null, SpeciesWildEvolutionDelay.VERY_LONG)
@ -1627,13 +1628,13 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.MARILL, 1, null, new SpeciesFriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT)
],
[Species.BUDEW]: [
new SpeciesEvolution(Species.ROSELIA, 1, null, new SpeciesFriendshipEvolutionCondition(70, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.SHORT)
new SpeciesEvolution(Species.ROSELIA, 1, null, new SpeciesFriendshipEvolutionCondition(70, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.SHORT)
],
[Species.BUNEARY]: [
new SpeciesEvolution(Species.LOPUNNY, 1, null, new SpeciesFriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.MEDIUM)
],
[Species.CHINGLING]: [
new SpeciesEvolution(Species.CHIMECHO, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM)
new SpeciesEvolution(Species.CHIMECHO, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM)
],
[Species.HAPPINY]: [
new SpeciesEvolution(Species.CHANSEY, 1, null, new SpeciesFriendshipEvolutionCondition(160), SpeciesWildEvolutionDelay.SHORT)
@ -1642,7 +1643,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.SNORLAX, 1, null, new SpeciesFriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG)
],
[Species.RIOLU]: [
new SpeciesEvolution(Species.LUCARIO, 1, null, new SpeciesFriendshipEvolutionCondition(120, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(Species.LUCARIO, 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG)
],
[Species.WOOBAT]: [
new SpeciesEvolution(Species.SWOOBAT, 1, null, new SpeciesFriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.MEDIUM)
@ -1657,16 +1658,16 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.ALOLA_PERSIAN, 1, null, new SpeciesFriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG)
],
[Species.SNOM]: [
new SpeciesEvolution(Species.FROSMOTH, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM)
new SpeciesEvolution(Species.FROSMOTH, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM)
],
[Species.GIMMIGHOUL]: [
new SpeciesFormEvolution(Species.GHOLDENGO, "chest", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter
+ p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length
+ p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier
+ globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier
|| m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG),
new SpeciesFormEvolution(Species.GHOLDENGO, "roaming", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter
+ p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length
+ p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier
+ globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier
|| m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG)
]
};

View File

@ -51,9 +51,7 @@ export const speciesStarterCosts = {
[Species.SANDSHREW]: 2,
[Species.NIDORAN_F]: 3,
[Species.NIDORAN_M]: 3,
[Species.CLEFAIRY]: 3,
[Species.VULPIX]: 3,
[Species.JIGGLYPUFF]: 2,
[Species.ZUBAT]: 3,
[Species.ODDISH]: 3,
[Species.PARAS]: 2,
@ -84,22 +82,15 @@ export const speciesStarterCosts = {
[Species.VOLTORB]: 2,
[Species.EXEGGCUTE]: 3,
[Species.CUBONE]: 3,
[Species.HITMONLEE]: 4,
[Species.HITMONCHAN]: 4,
[Species.LICKITUNG]: 3,
[Species.KOFFING]: 2,
[Species.RHYHORN]: 4,
[Species.CHANSEY]: 3,
[Species.TANGELA]: 3,
[Species.KANGASKHAN]: 4,
[Species.HORSEA]: 3,
[Species.GOLDEEN]: 2,
[Species.STARYU]: 3,
[Species.MR_MIME]: 3,
[Species.SCYTHER]: 5,
[Species.JYNX]: 4,
[Species.ELECTABUZZ]: 4,
[Species.MAGMAR]: 4,
[Species.PINSIR]: 4,
[Species.TAUROS]: 4,
[Species.MAGIKARP]: 4,
@ -110,7 +101,6 @@ export const speciesStarterCosts = {
[Species.OMANYTE]: 3,
[Species.KABUTO]: 3,
[Species.AERODACTYL]: 5,
[Species.SNORLAX]: 5,
[Species.ARTICUNO]: 5,
[Species.ZAPDOS]: 6,
[Species.MOLTRES]: 6,
@ -132,8 +122,6 @@ export const speciesStarterCosts = {
[Species.TOGEPI]: 3,
[Species.NATU]: 2,
[Species.MAREEP]: 2,
[Species.MARILL]: 4,
[Species.SUDOWOODO]: 3,
[Species.HOPPIP]: 2,
[Species.AIPOM]: 2,
[Species.SUNKERN]: 1,
@ -142,7 +130,6 @@ export const speciesStarterCosts = {
[Species.MURKROW]: 3,
[Species.MISDREAVUS]: 2,
[Species.UNOWN]: 1,
[Species.WOBBUFFET]: 2,
[Species.GIRAFARIG]: 3,
[Species.PINECO]: 2,
[Species.DUNSPARCE]: 3,
@ -158,7 +145,6 @@ export const speciesStarterCosts = {
[Species.CORSOLA]: 2,
[Species.REMORAID]: 2,
[Species.DELIBIRD]: 2,
[Species.MANTINE]: 3,
[Species.SKARMORY]: 4,
[Species.HOUNDOUR]: 3,
[Species.PHANPY]: 3,
@ -206,7 +192,6 @@ export const speciesStarterCosts = {
[Species.MINUN]: 2,
[Species.VOLBEAT]: 2,
[Species.ILLUMISE]: 2,
[Species.ROSELIA]: 3,
[Species.GULPIN]: 1,
[Species.CARVANHA]: 3,
[Species.WAILMER]: 2,
@ -232,7 +217,6 @@ export const speciesStarterCosts = {
[Species.SHUPPET]: 2,
[Species.DUSKULL]: 3,
[Species.TROPIUS]: 3,
[Species.CHIMECHO]: 3,
[Species.ABSOL]: 4,
[Species.WYNAUT]: 2,
[Species.SNORUNT]: 2,
@ -543,7 +527,6 @@ export const speciesStarterCosts = {
[Species.GALAR_PONYTA]: 2,
[Species.GALAR_SLOWPOKE]: 3,
[Species.GALAR_FARFETCHD]: 3,
[Species.GALAR_MR_MIME]: 3,
[Species.GALAR_ARTICUNO]: 6,
[Species.GALAR_ZAPDOS]: 6,
[Species.GALAR_MOLTRES]: 6,

View File

@ -1,16 +1,14 @@
//import { battleAnimRawData } from "./battle-anim-raw-data";
import BattleScene from "../battle-scene";
import { globalScene } from "#app/global-scene";
import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move";
import Pokemon from "../field/pokemon";
import type Pokemon from "../field/pokemon";
import * as Utils from "../utils";
import { BattlerIndex } from "../battle";
import { Element } from "json-stable-stringify";
import type { BattlerIndex } from "../battle";
import type { Element } from "json-stable-stringify";
import { Moves } from "#enums/moves";
import { SubstituteTag } from "./battler-tags";
import { isNullOrUndefined } from "../utils";
import Phaser from "phaser";
import { EncounterAnim } from "#enums/encounter-anims";
//import fs from 'vite-plugin-fs/browser';
export enum AnimFrameTarget {
USER,
@ -308,7 +306,7 @@ abstract class AnimTimedEvent {
this.resourceName = resourceName;
}
abstract execute(scene: BattleScene, battleAnim: BattleAnim, priority?: number): integer;
abstract execute(battleAnim: BattleAnim, priority?: number): integer;
abstract getEventType(): string;
}
@ -326,15 +324,15 @@ class AnimTimedSoundEvent extends AnimTimedEvent {
}
}
execute(scene: BattleScene, battleAnim: BattleAnim, priority?: number): integer {
execute(battleAnim: BattleAnim, priority?: number): integer {
const soundConfig = { rate: (this.pitch * 0.01), volume: (this.volume * 0.01) };
if (this.resourceName) {
try {
scene.playSound(`battle_anims/${this.resourceName}`, soundConfig);
globalScene.playSound(`battle_anims/${this.resourceName}`, soundConfig);
} catch (err) {
console.error(err);
}
return Math.ceil((scene.sound.get(`battle_anims/${this.resourceName}`).totalDuration * 1000) / 33.33);
return Math.ceil((globalScene.sound.get(`battle_anims/${this.resourceName}`).totalDuration * 1000) / 33.33);
} else {
return Math.ceil((battleAnim.user!.cry(soundConfig).totalDuration * 1000) / 33.33); // TODO: is the bang behind user correct?
}
@ -388,7 +386,7 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
super(frameIndex, resourceName, source);
}
execute(scene: BattleScene, moveAnim: MoveAnim, priority?: number): integer {
execute(moveAnim: MoveAnim, priority?: number): integer {
const tweenProps = {};
if (this.bgX !== undefined) {
tweenProps["x"] = (this.bgX * 0.5) - 320;
@ -400,7 +398,7 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
tweenProps["alpha"] = (this.opacity || 0) / 255;
}
if (Object.keys(tweenProps).length) {
scene.tweens.add(Object.assign({
globalScene.tweens.add(Object.assign({
targets: moveAnim.bgSprite,
duration: Utils.getFrameMs(this.duration * 3)
}, tweenProps));
@ -418,25 +416,25 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
super(frameIndex, resourceName, source);
}
execute(scene: BattleScene, moveAnim: MoveAnim, priority?: number): integer {
execute(moveAnim: MoveAnim, priority?: number): integer {
if (moveAnim.bgSprite) {
moveAnim.bgSprite.destroy();
}
moveAnim.bgSprite = this.resourceName
? scene.add.tileSprite(this.bgX - 320, this.bgY - 284, 896, 576, this.resourceName)
: scene.add.rectangle(this.bgX - 320, this.bgY - 284, 896, 576, 0);
? globalScene.add.tileSprite(this.bgX - 320, this.bgY - 284, 896, 576, this.resourceName)
: globalScene.add.rectangle(this.bgX - 320, this.bgY - 284, 896, 576, 0);
moveAnim.bgSprite.setOrigin(0, 0);
moveAnim.bgSprite.setScale(1.25);
moveAnim.bgSprite.setAlpha(this.opacity / 255);
scene.field.add(moveAnim.bgSprite);
const fieldPokemon = scene.getEnemyPokemon(false) ?? scene.getPlayerPokemon(false);
globalScene.field.add(moveAnim.bgSprite);
const fieldPokemon = globalScene.getEnemyPokemon(false) ?? globalScene.getPlayerPokemon(false);
if (!isNullOrUndefined(priority)) {
scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority);
globalScene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority);
} else if (fieldPokemon?.isOnField()) {
scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon);
globalScene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon);
}
scene.tweens.add({
globalScene.tweens.add({
targets: moveAnim.bgSprite,
duration: Utils.getFrameMs(this.duration * 3)
});
@ -454,14 +452,14 @@ export const chargeAnims = new Map<ChargeAnim, AnimConfig | [AnimConfig, AnimCon
export const commonAnims = new Map<CommonAnim, AnimConfig>();
export const encounterAnims = new Map<EncounterAnim, AnimConfig>();
export function initCommonAnims(scene: BattleScene): Promise<void> {
export function initCommonAnims(): Promise<void> {
return new Promise(resolve => {
const commonAnimNames = Utils.getEnumKeys(CommonAnim);
const commonAnimIds = Utils.getEnumValues(CommonAnim);
const commonAnimFetches: Promise<Map<CommonAnim, AnimConfig>>[] = [];
for (let ca = 0; ca < commonAnimIds.length; ca++) {
const commonAnimId = commonAnimIds[ca];
commonAnimFetches.push(scene.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, "-")}.json`)
commonAnimFetches.push(globalScene.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, "-")}.json`)
.then(response => response.json())
.then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas))));
}
@ -469,7 +467,7 @@ export function initCommonAnims(scene: BattleScene): Promise<void> {
});
}
export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
export function initMoveAnim(move: Moves): Promise<void> {
return new Promise(resolve => {
if (moveAnims.has(move)) {
if (moveAnims.get(move) !== null) {
@ -494,7 +492,7 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP;
const fetchAnimAndResolve = (move: Moves) => {
scene.cachedFetch(`./battle-anims/${Utils.animationFileName(move)}.json`)
globalScene.cachedFetch(`./battle-anims/${Utils.animationFileName(move)}.json`)
.then(response => {
const contentType = response.headers.get("content-type");
if (!response.ok || contentType?.indexOf("application/json") === -1) {
@ -516,7 +514,7 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
: (allMoves[move].getAttrs(DelayedAttackAttr)[0]
?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]);
if (chargeAnimSource) {
initMoveChargeAnim(scene, chargeAnimSource.chargeAnim).then(() => resolve());
initMoveChargeAnim(chargeAnimSource.chargeAnim).then(() => resolve());
} else {
resolve();
}
@ -557,10 +555,9 @@ function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) {
/**
* Fetches animation configs to be used in a Mystery Encounter
* @param scene
* @param encounterAnim one or more animations to fetch
*/
export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
const anims = Array.isArray(encounterAnim) ? encounterAnim : [ encounterAnim ];
const encounterAnimNames = Utils.getEnumKeys(EncounterAnim);
const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
@ -568,14 +565,14 @@ export async function initEncounterAnims(scene: BattleScene, encounterAnim: Enco
if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) {
continue;
}
encounterAnimFetches.push(scene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`)
encounterAnimFetches.push(globalScene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`)
.then(response => response.json())
.then(cas => encounterAnims.set(anim, new AnimConfig(cas))));
}
await Promise.allSettled(encounterAnimFetches);
}
export function initMoveChargeAnim(scene: BattleScene, chargeAnim: ChargeAnim): Promise<void> {
export function initMoveChargeAnim(chargeAnim: ChargeAnim): Promise<void> {
return new Promise(resolve => {
if (chargeAnims.has(chargeAnim)) {
if (chargeAnims.get(chargeAnim) !== null) {
@ -590,7 +587,7 @@ export function initMoveChargeAnim(scene: BattleScene, chargeAnim: ChargeAnim):
}
} else {
chargeAnims.set(chargeAnim, null);
scene.cachedFetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/\_/g, "-")}.json`)
globalScene.cachedFetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/\_/g, "-")}.json`)
.then(response => response.json())
.then(ca => {
if (Array.isArray(ca)) {
@ -623,23 +620,22 @@ function populateMoveChargeAnim(chargeAnim: ChargeAnim, animSource: any) {
chargeAnims.set(chargeAnim, [ chargeAnims.get(chargeAnim) as AnimConfig, moveChargeAnim ]);
}
export function loadCommonAnimAssets(scene: BattleScene, startLoad?: boolean): Promise<void> {
export function loadCommonAnimAssets(startLoad?: boolean): Promise<void> {
return new Promise(resolve => {
loadAnimAssets(scene, Array.from(commonAnims.values()), startLoad).then(() => resolve());
loadAnimAssets(Array.from(commonAnims.values()), startLoad).then(() => resolve());
});
}
/**
* Loads encounter animation assets to scene
* MUST be called after {@linkcode initEncounterAnims()} to load all required animations properly
* @param scene
* @param startLoad
*/
export async function loadEncounterAnimAssets(scene: BattleScene, startLoad?: boolean): Promise<void> {
await loadAnimAssets(scene, Array.from(encounterAnims.values()), startLoad);
export async function loadEncounterAnimAssets(startLoad?: boolean): Promise<void> {
await loadAnimAssets(Array.from(encounterAnims.values()), startLoad);
}
export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLoad?: boolean): Promise<void> {
export function loadMoveAnimAssets(moveIds: Moves[], startLoad?: boolean): Promise<void> {
return new Promise(resolve => {
const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat();
for (const moveId of moveIds) {
@ -655,11 +651,11 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo
}
}
}
loadAnimAssets(scene, moveAnimations, startLoad).then(() => resolve());
loadAnimAssets(moveAnimations, startLoad).then(() => resolve());
});
}
function loadAnimAssets(scene: BattleScene, anims: AnimConfig[], startLoad?: boolean): Promise<void> {
function loadAnimAssets(anims: AnimConfig[], startLoad?: boolean): Promise<void> {
return new Promise(resolve => {
const backgrounds = new Set<string>();
const sounds = new Set<string>();
@ -676,19 +672,19 @@ function loadAnimAssets(scene: BattleScene, anims: AnimConfig[], startLoad?: boo
backgrounds.add(abg);
}
if (a.graphic) {
scene.loadSpritesheet(a.graphic, "battle_anims", 96);
globalScene.loadSpritesheet(a.graphic, "battle_anims", 96);
}
}
for (const bg of backgrounds) {
scene.loadImage(bg, "battle_anims");
globalScene.loadImage(bg, "battle_anims");
}
for (const s of sounds) {
scene.loadSe(s, "battle_anims", s);
globalScene.loadSe(s, "battle_anims", s);
}
if (startLoad) {
scene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve());
if (!scene.load.isLoading()) {
scene.load.start();
globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve());
if (!globalScene.load.isLoading()) {
globalScene.load.start();
}
} else {
resolve();
@ -778,7 +774,7 @@ export abstract class BattleAnim {
return false;
}
private getGraphicFrameData(scene: BattleScene, frames: AnimFrame[], onSubstitute?: boolean): Map<integer, Map<AnimFrameTarget, GraphicFrameData>> {
private getGraphicFrameData(frames: AnimFrame[], onSubstitute?: boolean): Map<integer, Map<AnimFrameTarget, GraphicFrameData>> {
const ret: Map<integer, Map<AnimFrameTarget, GraphicFrameData>> = new Map([
[ AnimFrameTarget.GRAPHIC, new Map<AnimFrameTarget, GraphicFrameData>() ],
[ AnimFrameTarget.USER, new Map<AnimFrameTarget, GraphicFrameData>() ],
@ -835,7 +831,7 @@ export abstract class BattleAnim {
return ret;
}
play(scene: BattleScene, onSubstitute?: boolean, callback?: Function) {
play(onSubstitute?: boolean, callback?: Function) {
const isOppAnim = this.isOppAnim();
const user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct?
const target = !isOppAnim ? this.target! : this.user!;
@ -907,7 +903,7 @@ export abstract class BattleAnim {
}
};
if (!scene.moveAnimations && !this.playRegardlessOfIssues) {
if (!globalScene.moveAnimations && !this.playRegardlessOfIssues) {
return cleanUpAndComplete();
}
@ -924,7 +920,7 @@ export abstract class BattleAnim {
let r = anim?.frames.length ?? 0;
let f = 0;
scene.tweens.addCounter({
globalScene.tweens.addCounter({
duration: Utils.getFrameMs(3),
repeat: anim?.frames.length ?? 0,
onRepeat: () => {
@ -934,7 +930,7 @@ export abstract class BattleAnim {
}
const spriteFrames = anim!.frames[f]; // TODO: is the bang correcT?
const frameData = this.getGraphicFrameData(scene, anim!.frames[f], onSubstitute); // TODO: is the bang correct?
const frameData = this.getGraphicFrameData(anim!.frames[f], onSubstitute); // TODO: is the bang correct?
let u = 0;
let t = 0;
let g = 0;
@ -950,19 +946,19 @@ export abstract class BattleAnim {
const spriteSource = isUser ? userSprite : targetSprite;
if ((isUser ? u : t) === sprites.length) {
if (isUser || !targetSubstitute) {
const sprite = scene.addPokemonSprite(isUser ? user! : target, 0, 0, spriteSource!.texture, spriteSource!.frame.name, true); // TODO: are those bangs correct?
const sprite = globalScene.addPokemonSprite(isUser ? user! : target, 0, 0, spriteSource!.texture, spriteSource!.frame.name, true); // TODO: are those bangs correct?
[ "spriteColors", "fusionSpriteColors" ].map(k => sprite.pipelineData[k] = (isUser ? user! : target).getSprite().pipelineData[k]); // TODO: are those bangs correct?
sprite.setPipelineData("spriteKey", (isUser ? user! : target).getBattleSpriteKey());
sprite.setPipelineData("shiny", (isUser ? user : target).shiny);
sprite.setPipelineData("variant", (isUser ? user : target).variant);
sprite.setPipelineData("ignoreFieldPos", true);
spriteSource.on("animationupdate", (_anim, frame) => sprite.setFrame(frame.textureFrame));
scene.field.add(sprite);
globalScene.field.add(sprite);
sprites.push(sprite);
} else {
const sprite = scene.addFieldSprite(spriteSource.x, spriteSource.y, spriteSource.texture);
const sprite = globalScene.addFieldSprite(spriteSource.x, spriteSource.y, spriteSource.texture);
spriteSource.on("animationupdate", (_anim, frame) => sprite.setFrame(frame.textureFrame));
scene.field.add(sprite);
globalScene.field.add(sprite);
sprites.push(sprite);
}
}
@ -987,9 +983,9 @@ export abstract class BattleAnim {
} else {
const sprites = spriteCache[AnimFrameTarget.GRAPHIC];
if (g === sprites.length) {
const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim!.graphic, 1); // TODO: is the bang correct?
const newSprite: Phaser.GameObjects.Sprite = globalScene.addFieldSprite(0, 0, anim!.graphic, 1); // TODO: is the bang correct?
sprites.push(newSprite);
scene.field.add(newSprite);
globalScene.field.add(newSprite);
spritePriorities.push(1);
}
@ -1000,22 +996,22 @@ export abstract class BattleAnim {
const setSpritePriority = (priority: integer) => {
switch (priority) {
case 0:
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getEnemyPokemon(false) ?? scene.getPlayerPokemon(false)!); // TODO: is this bang correct?
globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, globalScene.getEnemyPokemon(false) ?? globalScene.getPlayerPokemon(false)!); // TODO: is this bang correct?
break;
case 1:
scene.field.moveTo(moveSprite, scene.field.getAll().length - 1);
globalScene.field.moveTo(moveSprite, globalScene.field.getAll().length - 1);
break;
case 2:
switch (frame.focus) {
case AnimFocus.USER:
if (this.bgSprite) {
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite);
globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite);
} else {
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
}
break;
case AnimFocus.TARGET:
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
break;
default:
setSpritePriority(1);
@ -1025,10 +1021,10 @@ export abstract class BattleAnim {
case 3:
switch (frame.focus) {
case AnimFocus.USER:
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
break;
case AnimFocus.TARGET:
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
break;
default:
setSpritePriority(1);
@ -1056,7 +1052,7 @@ export abstract class BattleAnim {
}
if (anim?.frameTimedEvents.has(f)) {
for (const event of anim.frameTimedEvents.get(f)!) { // TODO: is this bang correct?
r = Math.max((anim.frames.length - f) + event.execute(scene, this), r);
r = Math.max((anim.frames.length - f) + event.execute(this), r);
}
}
const targets = Utils.getEnumValues(AnimFrameTarget);
@ -1086,7 +1082,7 @@ export abstract class BattleAnim {
}
}
if (r) {
scene.tweens.addCounter({
globalScene.tweens.addCounter({
duration: Utils.getFrameMs(r),
onComplete: () => cleanUpAndComplete()
});
@ -1123,8 +1119,6 @@ export abstract class BattleAnim {
}
/**
*
* @param scene
* @param targetInitialX
* @param targetInitialY
* @param frameTimeMult
@ -1135,7 +1129,7 @@ export abstract class BattleAnim {
* - 5 is on top of player sprite
* @param callback
*/
playWithoutTargets(scene: BattleScene, targetInitialX: number, targetInitialY: number, frameTimeMult: number, frameTimedEventPriority?: 0 | 1 | 3 | 5, callback?: Function) {
playWithoutTargets(targetInitialX: number, targetInitialY: number, frameTimeMult: number, frameTimedEventPriority?: 0 | 1 | 3 | 5, callback?: Function) {
const spriteCache: SpriteCache = {
[AnimFrameTarget.GRAPHIC]: [],
[AnimFrameTarget.USER]: [],
@ -1156,7 +1150,7 @@ export abstract class BattleAnim {
}
};
if (!scene.moveAnimations && !this.playRegardlessOfIssues) {
if (!globalScene.moveAnimations && !this.playRegardlessOfIssues) {
return cleanUpAndComplete();
}
@ -1168,13 +1162,13 @@ export abstract class BattleAnim {
let totalFrames = anim!.frames.length;
let frameCount = 0;
let existingFieldSprites = scene.field.getAll().slice(0);
let existingFieldSprites = globalScene.field.getAll().slice(0);
scene.tweens.addCounter({
globalScene.tweens.addCounter({
duration: Utils.getFrameMs(3) * frameTimeMult,
repeat: anim!.frames.length,
onRepeat: () => {
existingFieldSprites = scene.field.getAll().slice(0);
existingFieldSprites = globalScene.field.getAll().slice(0);
const spriteFrames = anim!.frames[frameCount];
const frameData = this.getGraphicFrameDataWithoutTarget(anim!.frames[frameCount], targetInitialX, targetInitialY);
let graphicFrameCount = 0;
@ -1186,9 +1180,9 @@ export abstract class BattleAnim {
const sprites = spriteCache[AnimFrameTarget.GRAPHIC];
if (graphicFrameCount === sprites.length) {
const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim!.graphic, 1);
const newSprite: Phaser.GameObjects.Sprite = globalScene.addFieldSprite(0, 0, anim!.graphic, 1);
sprites.push(newSprite);
scene.field.add(newSprite);
globalScene.field.add(newSprite);
}
const graphicIndex = graphicFrameCount++;
@ -1197,11 +1191,11 @@ export abstract class BattleAnim {
const setSpritePriority = (priority: integer) => {
if (existingFieldSprites.length > priority) {
// Move to specified priority index
const index = scene.field.getIndex(existingFieldSprites[priority]);
scene.field.moveTo(moveSprite, index);
const index = globalScene.field.getIndex(existingFieldSprites[priority]);
globalScene.field.moveTo(moveSprite, index);
} else {
// Move to top of scene
scene.field.moveTo(moveSprite, scene.field.getAll().length - 1);
globalScene.field.moveTo(moveSprite, globalScene.field.getAll().length - 1);
}
};
setSpritePriority(frame.priority);
@ -1221,7 +1215,7 @@ export abstract class BattleAnim {
}
if (anim?.frameTimedEvents.get(frameCount)) {
for (const event of anim.frameTimedEvents.get(frameCount)!) {
totalFrames = Math.max((anim.frames.length - frameCount) + event.execute(scene, this, frameTimedEventPriority), totalFrames);
totalFrames = Math.max((anim.frames.length - frameCount) + event.execute(this, frameTimedEventPriority), totalFrames);
}
}
const targets = Utils.getEnumValues(AnimFrameTarget);
@ -1248,7 +1242,7 @@ export abstract class BattleAnim {
}
}
if (totalFrames) {
scene.tweens.addCounter({
globalScene.tweens.addCounter({
duration: Utils.getFrameMs(totalFrames),
onComplete: () => cleanUpAndComplete()
});
@ -1282,7 +1276,7 @@ export class MoveAnim extends BattleAnim {
public move: Moves;
constructor(move: Moves, user: Pokemon, target: BattlerIndex, playOnEmptyField: boolean = false) {
super(user, user.scene.getField()[target], playOnEmptyField);
super(user, globalScene.getField()[target], playOnEmptyField);
this.move = move;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { HitResult } from "../field/pokemon";
import type Pokemon from "../field/pokemon";
import { HitResult } from "../field/pokemon";
import { getStatusEffectHealText } from "./status-effect";
import * as Utils from "../utils";
import { DoubleBerryEffectAbAttr, PostItemLostAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs, applyPostItemLostAbAttrs } from "./ability";
@ -9,6 +10,7 @@ import { BerryType } from "#enums/berry-type";
import { Stat, type BattleStat } from "#app/enums/stat";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { globalScene } from "#app/global-scene";
export function getBerryName(berryType: BerryType): string {
return i18next.t(`berry:${BerryType[berryType]}.name`);
@ -73,7 +75,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
}
const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4));
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(),
hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true));
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false);
};
@ -83,7 +85,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
pokemon.battleData.berriesEaten.push(berryType);
}
if (pokemon.status) {
pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
globalScene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
}
pokemon.resetStatus(true, true);
pokemon.updateInfo();
@ -102,7 +104,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
const stat: BattleStat = berryType - BerryType.ENIGMA;
const statStages = new Utils.NumberHolder(1);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value));
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], statStages.value));
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false);
};
case BerryType.LANSAT:
@ -121,7 +123,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK);
const stages = new Utils.NumberHolder(2);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value));
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ randStat ], stages.value));
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false);
};
case BerryType.LEPPA:
@ -132,7 +134,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct?
if (ppRestoreMove !== undefined) {
ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0);
pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) }));
globalScene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) }));
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false);
}
};

View File

@ -1,18 +1,22 @@
import * as Utils from "#app/utils";
import i18next from "i18next";
import { defaultStarterSpecies, DexAttrProps, GameData } from "#app/system/game-data";
import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import type { DexAttrProps, GameData } from "#app/system/game-data";
import { defaultStarterSpecies } from "#app/system/game-data";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import Pokemon, { PokemonMove } from "#app/field/pokemon";
import { BattleType, FixedBattleConfig } from "#app/battle";
import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon";
import type { FixedBattleConfig } from "#app/battle";
import { BattleType } from "#app/battle";
import Trainer, { TrainerVariant } from "#app/field/trainer";
import { GameMode } from "#app/game-mode";
import type { GameMode } from "#app/game-mode";
import { Type } from "#enums/type";
import { Challenges } from "#enums/challenges";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import { Nature } from "#enums/nature";
import { Moves } from "#enums/moves";
import type { Moves } from "#enums/moves";
import { TypeColor, TypeShadow } from "#enums/color";
import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { pokemonFormChanges } from "#app/data/pokemon-forms";
@ -84,6 +88,11 @@ export enum ChallengeType {
* Modifies what weight AI pokemon have when generating movesets. UNIMPLEMENTED.
*/
MOVE_WEIGHT,
/**
* Modifies what the pokemon stats for Flip Stat Mode.
*/
FLIP_STAT,
}
/**
@ -401,6 +410,16 @@ export abstract class Challenge {
applyMoveWeight(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.IntegerHolder): boolean {
return false;
}
/**
* An apply function for FlipStats. Derived classes should alter this.
* @param pokemon {@link Pokemon} What pokemon would learn the move.
* @param baseStats What are the stats to flip.
* @returns {@link boolean} Whether this function did anything.
*/
applyFlipStat(pokemon: Pokemon, baseStats: number[]) {
return false;
}
}
type ChallengeCondition = (data: GameData) => boolean;
@ -467,7 +486,7 @@ export class SingleGenerationChallenge extends Challenge {
if (trainerTypes.length === 0) {
return false;
} else {
battleConfig.setBattleType(BattleType.TRAINER).setGetTrainerFunc(scene => new Trainer(scene, trainerTypes[this.value - 1], TrainerVariant.DEFAULT));
battleConfig.setBattleType(BattleType.TRAINER).setGetTrainerFunc(() => new Trainer(trainerTypes[this.value - 1], TrainerVariant.DEFAULT));
return true;
}
}
@ -701,6 +720,33 @@ export class InverseBattleChallenge extends Challenge {
}
}
/**
* Implements a flip stat challenge.
*/
export class FlipStatChallenge extends Challenge {
constructor() {
super(Challenges.FLIP_STAT, 1);
}
override applyFlipStat(pokemon: Pokemon, baseStats: number[]) {
const origStats = Utils.deepCopy(baseStats);
baseStats[0] = origStats[5];
baseStats[1] = origStats[4];
baseStats[2] = origStats[3];
baseStats[3] = origStats[2];
baseStats[4] = origStats[1];
baseStats[5] = origStats[0];
return true;
}
static loadChallenge(source: FlipStatChallenge | any): FlipStatChallenge {
const newChallenge = new FlipStatChallenge();
newChallenge.value = source.value;
newChallenge.severity = source.severity;
return newChallenge;
}
}
/**
* Lowers the amount of starter points available.
*/
@ -886,6 +932,9 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_WEIGHT, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, weight: Utils.IntegerHolder): boolean;
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FLIP_STAT, pokemon: Pokemon, baseStats: number[]): boolean;
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean {
let ret = false;
gameMode.challenges.forEach(c => {
@ -930,6 +979,9 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
case ChallengeType.MOVE_WEIGHT:
ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]);
break;
case ChallengeType.FLIP_STAT:
ret ||= c.applyFlipStat(args[0], args[1]);
break;
}
}
});
@ -955,6 +1007,8 @@ export function copyChallenge(source: Challenge | any): Challenge {
return FreshStartChallenge.loadChallenge(source);
case Challenges.INVERSE_BATTLE:
return InverseBattleChallenge.loadChallenge(source);
case Challenges.FLIP_STAT:
return FlipStatChallenge.loadChallenge(source);
}
throw new Error("Unknown challenge copied");
}
@ -967,5 +1021,6 @@ export function initChallenges() {
new SingleTypeChallenge(),
new FreshStartChallenge(),
new InverseBattleChallenge(),
new FlipStatChallenge()
);
}

View File

@ -1,11 +1,12 @@
import { Abilities } from "#enums/abilities";
import { Type } from "#enums/type";
import type { Abilities } from "#enums/abilities";
import type { Type } from "#enums/type";
import { isNullOrUndefined } from "#app/utils";
import { Nature } from "#enums/nature";
import type { Nature } from "#enums/nature";
/**
* Data that can customize a Pokemon in non-standard ways from its Species
* Currently only used by Mystery Encounters and Mints.
* Used by Mystery Encounters and Mints
* Also used as a counter how often a Pokemon got hit until new arena encounter
*/
export class CustomPokemonData {
public spriteScale: number;
@ -13,6 +14,8 @@ export class CustomPokemonData {
public passive: Abilities | -1;
public nature: Nature | -1;
public types: Type[];
/** `hitsReceivedCount` aka `hitsRecCount` saves how often the pokemon got hit until a new arena encounter (used for Rage Fist) */
public hitsRecCount: number;
constructor(data?: CustomPokemonData | Partial<CustomPokemonData>) {
if (!isNullOrUndefined(data)) {
@ -24,5 +27,10 @@ export class CustomPokemonData {
this.passive = this.passive ?? -1;
this.nature = this.nature ?? -1;
this.types = this.types ?? [];
this.hitsRecCount = this.hitsRecCount ?? 0;
}
resetHitReceivedCount(): void {
this.hitsRecCount = 0;
}
}

View File

@ -1,12 +1,14 @@
import { PartyMemberStrength } from "#enums/party-member-strength";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import type { Species } from "#enums/species";
import { globalScene } from "#app/global-scene";
import { PlayerPokemon } from "#app/field/pokemon";
import { Starter } from "#app/ui/starter-select-ui-handler";
import type { Starter } from "#app/ui/starter-select-ui-handler";
import * as Utils from "#app/utils";
import PokemonSpecies, { PokemonSpeciesForm, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import type { PokemonSpeciesForm } from "#app/data/pokemon-species";
import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import { Biome } from "#app/enums/biome";
export interface DailyRunConfig {
seed: integer;
@ -21,17 +23,17 @@ export function fetchDailyRunSeed(): Promise<string | null> {
});
}
export function getDailyRunStarters(scene: BattleScene, seed: string): Starter[] {
export function getDailyRunStarters(seed: string): Starter[] {
const starters: Starter[] = [];
scene.executeWithSeedOffset(() => {
const startingLevel = scene.gameMode.getStartingLevel();
globalScene.executeWithSeedOffset(() => {
const startingLevel = globalScene.gameMode.getStartingLevel();
if (/\d{18}$/.test(seed)) {
for (let s = 0; s < 3; s++) {
const offset = 6 + s * 6;
const starterSpeciesForm = getPokemonSpeciesForm(parseInt(seed.slice(offset, offset + 4)) as Species, parseInt(seed.slice(offset + 4, offset + 6)));
starters.push(getDailyRunStarter(scene, starterSpeciesForm, startingLevel));
starters.push(getDailyRunStarter(starterSpeciesForm, startingLevel));
}
return;
}
@ -48,17 +50,17 @@ export function getDailyRunStarters(scene: BattleScene, seed: string): Starter[]
.filter(s => speciesStarterCosts[s] === cost);
const randPkmSpecies = getPokemonSpecies(Utils.randSeedItem(costSpecies));
const starterSpecies = getPokemonSpecies(randPkmSpecies.getTrainerSpeciesForLevel(startingLevel, true, PartyMemberStrength.STRONGER));
starters.push(getDailyRunStarter(scene, starterSpecies, startingLevel));
starters.push(getDailyRunStarter(starterSpecies, startingLevel));
}
}, 0, seed);
return starters;
}
function getDailyRunStarter(scene: BattleScene, starterSpeciesForm: PokemonSpeciesForm, startingLevel: integer): Starter {
function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLevel: integer): Starter {
const starterSpecies = starterSpeciesForm instanceof PokemonSpecies ? starterSpeciesForm : getPokemonSpecies(starterSpeciesForm.speciesId);
const formIndex = starterSpeciesForm instanceof PokemonSpecies ? undefined : starterSpeciesForm.formIndex;
const pokemon = new PlayerPokemon(scene, starterSpecies, startingLevel, undefined, formIndex, undefined, undefined, undefined, undefined, undefined, undefined);
const pokemon = new PlayerPokemon(starterSpecies, startingLevel, undefined, formIndex, undefined, undefined, undefined, undefined, undefined, undefined);
const starter: Starter = {
species: starterSpecies,
dexAttr: pokemon.getDexAttr(),
@ -70,3 +72,76 @@ function getDailyRunStarter(scene: BattleScene, starterSpeciesForm: PokemonSpeci
pokemon.destroy();
return starter;
}
interface BiomeWeights {
[key: integer]: integer
}
// Initially weighted by amount of exits each biome has
// Town and End are set to 0 however
// And some other biomes were balanced +1/-1 based on average size of the total daily.
const dailyBiomeWeights: BiomeWeights = {
[Biome.CAVE]: 3,
[Biome.LAKE]: 3,
[Biome.PLAINS]: 3,
[Biome.SNOWY_FOREST]: 3,
[Biome.SWAMP]: 3, // 2 -> 3
[Biome.TALL_GRASS]: 3, // 2 -> 3
[Biome.ABYSS]: 2, // 3 -> 2
[Biome.RUINS]: 2,
[Biome.BADLANDS]: 2,
[Biome.BEACH]: 2,
[Biome.CONSTRUCTION_SITE]: 2,
[Biome.DESERT]: 2,
[Biome.DOJO]: 2, // 3 -> 2
[Biome.FACTORY]: 2,
[Biome.FAIRY_CAVE]: 2,
[Biome.FOREST]: 2,
[Biome.GRASS]: 2, // 1 -> 2
[Biome.MEADOW]: 2,
[Biome.MOUNTAIN]: 2, // 3 -> 2
[Biome.SEA]: 2,
[Biome.SEABED]: 2,
[Biome.SLUM]: 2,
[Biome.TEMPLE]: 2, // 3 -> 2
[Biome.VOLCANO]: 2,
[Biome.GRAVEYARD]: 1,
[Biome.ICE_CAVE]: 1,
[Biome.ISLAND]: 1,
[Biome.JUNGLE]: 1,
[Biome.LABORATORY]: 1,
[Biome.METROPOLIS]: 1,
[Biome.POWER_PLANT]: 1,
[Biome.SPACE]: 1,
[Biome.WASTELAND]: 1,
[Biome.TOWN]: 0,
[Biome.END]: 0,
};
export function getDailyStartingBiome(): Biome {
const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END);
let totalWeight = 0;
const biomeThresholds: integer[] = [];
for (const biome of biomes) {
// Keep track of the total weight
totalWeight += dailyBiomeWeights[biome];
// Keep track of each biomes cumulative weight
biomeThresholds.push(totalWeight);
}
const randInt = Utils.randSeedInt(totalWeight);
for (let i = 0; i < biomes.length; i++) {
if (randInt < biomeThresholds[i]) {
return biomes[i];
}
}
// Fallback in case something went wrong
return biomes[Utils.randSeedInt(biomes.length)];
}

View File

@ -1,6 +1,6 @@
import BattleScene from "#app/battle-scene";
import { PlayerPokemon } from "#app/field/pokemon";
import { DexEntry, StarterDataEntry } from "#app/system/game-data";
import { globalScene } from "#app/global-scene";
import type { PlayerPokemon } from "#app/field/pokemon";
import type { DexEntry, StarterDataEntry } from "#app/system/game-data";
/**
* Stores data associated with a specific egg and the hatched pokemon
@ -17,11 +17,8 @@ export class EggHatchData {
public dexEntryBeforeUpdate: DexEntry;
/** stored copy of the hatched pokemon's starter entry before it was updated due to hatch */
public starterDataEntryBeforeUpdate: StarterDataEntry;
/** reference to the battle scene to get gamedata and update dex */
private scene: BattleScene;
constructor(scene: BattleScene, pokemon: PlayerPokemon, eggMoveIndex: number) {
this.scene = scene;
constructor(pokemon: PlayerPokemon, eggMoveIndex: number) {
this.pokemon = pokemon;
this.eggMoveIndex = eggMoveIndex;
}
@ -39,8 +36,8 @@ export class EggHatchData {
* Used before updating the dex, so comparing the pokemon to these entries will show the new attributes
*/
setDex() {
const currDexEntry = this.scene.gameData.dexData[this.pokemon.species.speciesId];
const currStarterDataEntry = this.scene.gameData.starterData[this.pokemon.species.getRootSpeciesId()];
const currDexEntry = globalScene.gameData.dexData[this.pokemon.species.speciesId];
const currStarterDataEntry = globalScene.gameData.starterData[this.pokemon.species.getRootSpeciesId()];
this.dexEntryBeforeUpdate = {
seenAttr: currDexEntry.seenAttr,
caughtAttr: currDexEntry.caughtAttr,
@ -86,9 +83,9 @@ export class EggHatchData {
*/
updatePokemon(showMessage : boolean = false) {
return new Promise<void>(resolve => {
this.scene.gameData.setPokemonCaught(this.pokemon, true, true, showMessage).then(() => {
this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex, showMessage).then((value) => {
globalScene.gameData.setPokemonCaught(this.pokemon, true, true, showMessage).then(() => {
globalScene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
globalScene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex, showMessage).then((value) => {
this.setEggMoveUnlocked(value);
resolve();
});

View File

@ -1,11 +1,13 @@
import BattleScene from "#app/battle-scene";
import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
import type BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { VariantTier } from "#enums/variant-tier";
import * as Utils from "#app/utils";
import Overrides from "#app/overrides";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { PlayerPokemon } from "#app/field/pokemon";
import type { PlayerPokemon } from "#app/field/pokemon";
import i18next from "i18next";
import { EggTier } from "#enums/egg-type";
import { Species } from "#enums/species";
@ -22,9 +24,8 @@ export interface IEggOptions {
/** Timestamp when this egg got created */
timestamp?: number;
/**
* Defines if the egg got pulled from a gacha or not. If true, egg pity and pull statistics will be applyed.
* Defines if the egg got pulled from a gacha or not. If true, egg pity and pull statistics will be applied.
* Egg will be automaticly added to the game data.
* NEEDS `scene` `eggOption` to work.
*/
pulled?: boolean;
/**
@ -32,7 +33,7 @@ export interface IEggOptions {
* Will also define the text displayed in the egg list.
*/
sourceType?: EggSourceType;
/** Needs to be defined if `eggOption` pulled is defined or if no species or `isShiny` is defined since this will be needed to generate them. */
/** Legacy field, kept for backwards-compatibility */
scene?: BattleScene;
/**
* Sets the tier of the egg. Only species of this tier can be hatched from this egg.
@ -41,10 +42,7 @@ export interface IEggOptions {
tier?: EggTier;
/** Sets how many waves it will take till this egg hatches. */
hatchWaves?: number;
/**
* Sets the exact species that will hatch from this egg.
* Needs `scene` `eggOption` if not provided.
*/
/** Sets the exact species that will hatch from this egg. */
species?: Species;
/** Defines if the hatched pokemon will be a shiny. */
isShiny?: boolean;
@ -56,8 +54,7 @@ export interface IEggOptions {
* Defines if the egg will hatch with the hidden ability of this species.
* If no hidden ability exist, a random one will get choosen.
*/
overrideHiddenAbility?: boolean,
overrideHiddenAbility?: boolean;
/** Can customize the message displayed for where the egg was obtained */
eggDescriptor?: string;
}
@ -148,7 +145,7 @@ export class Egg {
// If egg was pulled, check if egg pity needs to override the egg tier
if (eggOptions?.pulled) {
// Needs this._tier and this._sourceType to work
this.checkForPityTierOverrides(eggOptions.scene!); // TODO: is this bang correct?
this.checkForPityTierOverrides();
}
this._id = eggOptions?.id ?? Utils.randInt(EGG_SEED, EGG_SEED * this._tier);
@ -160,7 +157,7 @@ export class Egg {
// First roll shiny and variant so we can filter if species with an variant exist
this._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
this._variantTier = eggOptions?.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant());
this._species = eggOptions?.species ?? this.rollSpecies(eggOptions!.scene!)!; // TODO: Are those bangs correct?
this._species = eggOptions?.species ?? this.rollSpecies()!; // TODO: Is this bang correct?
this._overrideHiddenAbility = eggOptions?.overrideHiddenAbility ?? false;
@ -178,19 +175,15 @@ export class Egg {
// Needs this._tier so it needs to be generated afer the tier override if bought from same species
this._eggMoveIndex = eggOptions?.eggMoveIndex ?? this.rollEggMoveIndex();
if (eggOptions?.pulled) {
this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct?
this.addEggToGameData(eggOptions.scene!); // TODO: is this bang correct?
this.increasePullStatistic();
this.addEggToGameData();
}
};
if (eggOptions?.scene) {
const seedOverride = Utils.randomString(24);
eggOptions?.scene.executeWithSeedOffset(() => {
generateEggProperties(eggOptions);
}, 0, seedOverride);
} else { // For legacy eggs without scene
const seedOverride = Utils.randomString(24);
globalScene.executeWithSeedOffset(() => {
generateEggProperties(eggOptions);
}
}, 0, seedOverride);
this._eggDescriptor = eggOptions?.eggDescriptor;
}
@ -212,14 +205,14 @@ export class Egg {
}
// Generates a PlayerPokemon from an egg
public generatePlayerPokemon(scene: BattleScene): PlayerPokemon {
public generatePlayerPokemon(): PlayerPokemon {
let ret: PlayerPokemon;
const generatePlayerPokemonHelper = (scene: BattleScene) => {
const generatePlayerPokemonHelper = () => {
// Legacy egg wants to hatch. Generate missing properties
if (!this._species) {
this._isShiny = this.rollShiny();
this._species = this.rollSpecies(scene!)!; // TODO: are these bangs correct?
this._species = this.rollSpecies()!; // TODO: is this bang correct?
}
let pokemonSpecies = getPokemonSpecies(this._species);
@ -238,7 +231,7 @@ export class Egg {
}
// This function has way to many optional parameters
ret = scene.addPlayerPokemon(pokemonSpecies, 1, abilityIndex, undefined, undefined, false);
ret = globalScene.addPlayerPokemon(pokemonSpecies, 1, abilityIndex, undefined, undefined, false);
ret.shiny = this._isShiny;
ret.variant = this._variantTier;
@ -250,16 +243,16 @@ export class Egg {
};
ret = ret!; // Tell TS compiler it's defined now
scene.executeWithSeedOffset(() => {
generatePlayerPokemonHelper(scene);
globalScene.executeWithSeedOffset(() => {
generatePlayerPokemonHelper();
}, this._id, EGG_SEED.toString());
return ret;
}
// Doesn't need to be called if the egg got pulled by a gacha machiene
public addEggToGameData(scene: BattleScene): void {
scene.gameData.eggs.push(this);
public addEggToGameData(): void {
globalScene.gameData.eggs.push(this);
}
public getEggDescriptor(): string {
@ -291,12 +284,12 @@ export class Egg {
return i18next.t("egg:hatchWavesMessageLongTime");
}
public getEggTypeDescriptor(scene: BattleScene): string {
public getEggTypeDescriptor(): string {
switch (this.sourceType) {
case EggSourceType.SAME_SPECIES_EGG:
return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName() });
case EggSourceType.GACHA_LEGENDARY:
return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`;
return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.timestamp)).getName()})`;
case EggSourceType.GACHA_SHINY:
return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny");
case EggSourceType.GACHA_MOVE:
@ -356,8 +349,8 @@ export class Egg {
return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.RARE : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.EPIC : EggTier.LEGENDARY;
}
private rollSpecies(scene: BattleScene): Species | null {
if (!scene) {
private rollSpecies(): Species | null {
if (!globalScene) {
return null;
}
/**
@ -376,7 +369,7 @@ export class Egg {
} else if (this.tier === EggTier.LEGENDARY
&& this._sourceType === EggSourceType.GACHA_LEGENDARY) {
if (!Utils.randSeedInt(2)) {
return getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp);
return getLegendaryGachaSpeciesForTimestamp(this.timestamp);
}
}
@ -410,8 +403,8 @@ export class Egg {
.filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1);
// If this is the 10th egg without unlocking something new, attempt to force it.
if (scene.gameData.unlockPity[this.tier] >= 9) {
const lockedPool = speciesPool.filter(s => !scene.gameData.dexData[s].caughtAttr && !scene.gameData.eggs.some(e => e.species === s));
if (globalScene.gameData.unlockPity[this.tier] >= 9) {
const lockedPool = speciesPool.filter(s => !globalScene.gameData.dexData[s].caughtAttr && !globalScene.gameData.eggs.some(e => e.species === s));
if (lockedPool.length) { // Skip this if everything is unlocked
speciesPool = lockedPool;
}
@ -454,10 +447,10 @@ export class Egg {
}
species = species!; // tell TS compiled it's defined now!
if (!!scene.gameData.dexData[species].caughtAttr || scene.gameData.eggs.some(e => e.species === species)) {
scene.gameData.unlockPity[this.tier] = Math.min(scene.gameData.unlockPity[this.tier] + 1, 10);
if (globalScene.gameData.dexData[species].caughtAttr || globalScene.gameData.eggs.some(e => e.species === species)) {
globalScene.gameData.unlockPity[this.tier] = Math.min(globalScene.gameData.unlockPity[this.tier] + 1, 10);
} else {
scene.gameData.unlockPity[this.tier] = 0;
globalScene.gameData.unlockPity[this.tier] = 0;
}
return species;
@ -465,7 +458,7 @@ export class Egg {
/**
* Rolls whether the egg is shiny or not.
* @returns True if the egg is shiny
* @returns `true` if the egg is shiny
**/
private rollShiny(): boolean {
let shinyChance = GACHA_DEFAULT_SHINY_RATE;
@ -485,6 +478,7 @@ export class Egg {
// Uses the same logic as pokemon.generateVariant(). I would like to only have this logic in one
// place but I don't want to touch the pokemon class.
// TODO: Remove this or replace the one in the Pokemon class.
private rollVariant(): VariantTier {
if (!this.isShiny) {
return VariantTier.STANDARD;
@ -500,38 +494,38 @@ export class Egg {
}
}
private checkForPityTierOverrides(scene: BattleScene): void {
private checkForPityTierOverrides(): void {
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
scene.gameData.eggPity[EggTier.RARE] += 1;
scene.gameData.eggPity[EggTier.EPIC] += 1;
scene.gameData.eggPity[EggTier.LEGENDARY] += 1 + tierValueOffset;
globalScene.gameData.eggPity[EggTier.RARE] += 1;
globalScene.gameData.eggPity[EggTier.EPIC] += 1;
globalScene.gameData.eggPity[EggTier.LEGENDARY] += 1 + tierValueOffset;
// These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered.
if (scene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) {
if (globalScene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.LEGENDARY;
} else if (scene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) {
} else if (globalScene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.EPIC;
} else if (scene.gameData.eggPity[EggTier.RARE] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) {
} else if (globalScene.gameData.eggPity[EggTier.RARE] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.RARE;
}
scene.gameData.eggPity[this._tier] = 0;
globalScene.gameData.eggPity[this._tier] = 0;
}
private increasePullStatistic(scene: BattleScene): void {
scene.gameData.gameStats.eggsPulled++;
private increasePullStatistic(): void {
globalScene.gameData.gameStats.eggsPulled++;
if (this.isManaphyEgg()) {
scene.gameData.gameStats.manaphyEggsPulled++;
globalScene.gameData.gameStats.manaphyEggsPulled++;
this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.EPIC);
return;
}
switch (this.tier) {
case EggTier.RARE:
scene.gameData.gameStats.rareEggsPulled++;
globalScene.gameData.gameStats.rareEggsPulled++;
break;
case EggTier.EPIC:
scene.gameData.gameStats.epicEggsPulled++;
globalScene.gameData.gameStats.epicEggsPulled++;
break;
case EggTier.LEGENDARY:
scene.gameData.gameStats.legendaryEggsPulled++;
globalScene.gameData.gameStats.legendaryEggsPulled++;
break;
}
}
@ -552,7 +546,7 @@ export function getValidLegendaryGachaSpecies() : Species[] {
.filter(s => getPokemonSpecies(s).isObtainable() && s !== Species.ETERNATUS);
}
export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species {
export function getLegendaryGachaSpeciesForTimestamp(timestamp: number): Species {
const legendarySpecies = getValidLegendaryGachaSpecies();
let ret: Species;
@ -563,7 +557,7 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta
const offset = Math.floor(Math.floor(dayTimestamp / 86400000) / legendarySpecies.length); // Cycle number
const index = Math.floor(dayTimestamp / 86400000) % legendarySpecies.length; // Index within cycle
scene.executeWithSeedOffset(() => {
globalScene.executeWithSeedOffset(() => {
ret = Phaser.Math.RND.shuffle(legendarySpecies)[index];
}, offset, EGG_SEED.toString());
ret = ret!; // tell TS compiler it's

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,17 @@
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { globalScene } from "#app/global-scene";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs, } from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { randSeedInt } from "#app/utils";
import i18next from "i18next";
import { IEggOptions } from "#app/data/egg";
import type { IEggOptions } from "#app/data/egg";
import { EggSourceType } from "#enums/egg-source-types";
import { EggTier } from "#enums/egg-type";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
@ -36,8 +38,8 @@ export const ATrainersTestEncounter: MysteryEncounter =
},
])
.withAutoHideIntroVisuals(false)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Randomly pick from 1 of the 5 stat trainers to spawn
let trainerType: TrainerType;
@ -138,23 +140,22 @@ export const ATrainersTestEncounter: MysteryEncounter =
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Battle the stat trainer for an Egg and great rewards
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
await transitionMysteryEncounterIntroVisuals(scene);
await transitionMysteryEncounterIntroVisuals();
const eggOptions: IEggOptions = {
scene,
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.EPIC
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]);
await initBattleWithEnemyConfig(scene, config);
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]);
await initBattleWithEnemyConfig(config);
}
)
.withSimpleOption(
@ -162,21 +163,20 @@ export const ATrainersTestEncounter: MysteryEncounter =
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Full heal party
scene.unshiftPhase(new PartyHealPhase(scene, true));
globalScene.unshiftPhase(new PartyHealPhase(true));
const eggOptions: IEggOptions = {
scene,
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.RARE
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`));
setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]);
leaveEncounterWithoutBattle(scene);
setEncounterRewards({ fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]);
leaveEncounterWithoutBattle();
}
)
.withOutroDialogue([

View File

@ -1,10 +1,14 @@
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import Pokemon, { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import { BerryModifierType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -19,8 +23,8 @@ import { BattlerIndex } from "#app/battle";
import { applyModifierTypeToPlayerPokemon, catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { TrainerSlot } from "#app/data/trainer-config";
import { PokeballType } from "#enums/pokeball";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
import { BerryType } from "#enums/berry-type";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
import type { BerryType } from "#enums/berry-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
@ -170,18 +174,18 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
scene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
scene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");
globalScene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
globalScene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");
// Get all player berry items, remove from party, and store reference
const berryItems = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
// Sort berries by party member ID to more easily re-add later if necessary
const berryItemsMap = new Map<number, BerryModifier[]>();
scene.getPlayerParty().forEach(pokemon => {
globalScene.getPlayerParty().forEach(pokemon => {
const pokemonBerries = berryItems.filter(b => b.pokemonId === pokemon.id);
if (pokemonBerries?.length > 0) {
berryItemsMap.set(pokemon.id, pokemonBerries);
@ -196,7 +200,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
// Can't define stack count on a ModifierType, have to just create separate instances for each stack
// Overflow berries will be "lost" on the boss, but it's un-catchable anyway
for (let i = 0; i < berryMod.stackCount; i++) {
const modifierType = generateModifierType(scene, modifierTypes.BERRY, [ berryMod.berryType ]) as PokemonHeldItemModifierType;
const modifierType = generateModifierType(modifierTypes.BERRY, [ berryMod.berryType ]) as PokemonHeldItemModifierType;
bossModifierConfigs.push({ modifier: modifierType });
}
});
@ -204,7 +208,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
// Do NOT remove the real berries yet or else it will be persisted in the session data
// SpDef buff below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ?
[ Stat.SPDEF ] :
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
@ -221,8 +225,8 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
modifierConfigs: bossModifierConfigs,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}:option.1.boss_enraged`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
}
}
],
@ -233,18 +237,18 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
doGreedentSpriteSteal(scene);
doBerrySpritePile(scene);
.withOnVisualsStart(() => {
doGreedentSpriteSteal();
doBerrySpritePile();
// Remove the berries from the party
// Session has been safely saved at this point, so data won't be lost
const berryItems = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
berryItems.forEach(berryMod => {
scene.removeModifier(berryMod);
globalScene.removeModifier(berryMod);
});
scene.updateModifiers(true);
globalScene.updateModifiers(true);
return true;
})
@ -260,26 +264,26 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Provides 1x Reviver Seed to each party member at end of battle
const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED);
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED);
encounter.setDialogueToken("foodReward", revSeed?.name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"));
const givePartyPokemonReviverSeeds = () => {
const party = scene.getPlayerParty();
const party = globalScene.getPlayerParty();
party.forEach(p => {
const heldItems = p.getHeldItems();
if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) {
const seedModifier = revSeed.newModifier(p);
scene.addModifier(seedModifier, false, false, false, true);
globalScene.addModifier(seedModifier, false, false, false, true);
}
});
queueEncounterMessage(scene, `${namespace}:option.1.food_stash`);
queueEncounterMessage(`${namespace}:option.1.food_stash`);
};
setEncounterRewards(scene, { fillRemaining: true }, undefined, givePartyPokemonReviverSeeds);
setEncounterRewards({ fillRemaining: true }, undefined, givePartyPokemonReviverSeeds);
encounter.startOfBattleEffects.push({
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [ BattlerIndex.ENEMY ],
@ -287,8 +291,8 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
ignorePp: true
});
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
await transitionMysteryEncounterIntroVisuals(true, true, 500);
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
})
.build()
)
@ -304,12 +308,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const berryMap = encounter.misc.berryItemsMap;
// Returns 2/5 of the berries stolen to each Pokemon
const party = scene.getPlayerParty();
const party = globalScene.getPlayerParty();
party.forEach(pokemon => {
const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
const berryTypesAsArray: BerryType[] = [];
@ -322,15 +326,15 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
Phaser.Math.RND.shuffle(berryTypesAsArray);
const randBerryType = berryTypesAsArray.pop();
const berryModType = generateModifierType(scene, modifierTypes.BERRY, [ randBerryType ]) as BerryModifierType;
applyModifierTypeToPlayerPokemon(scene, pokemon, berryModType);
const berryModType = generateModifierType(modifierTypes.BERRY, [ randBerryType ]) as BerryModifierType;
applyModifierTypeToPlayerPokemon(pokemon, berryModType);
}
}
});
await scene.updateModifiers(true);
await globalScene.updateModifiers(true);
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
leaveEncounterWithoutBattle(scene, true);
await transitionMysteryEncounterIntroVisuals(true, true, 500);
leaveEncounterWithoutBattle(true);
})
.build()
)
@ -346,36 +350,36 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Animate berries being eaten
doGreedentEatBerries(scene);
doBerrySpritePile(scene, true);
doGreedentEatBerries();
doBerrySpritePile(true);
return true;
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Let it have the food
// Greedent joins the team, level equal to 2 below highest party member (shiny locked)
const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2;
const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, true);
const level = getHighestLevelPlayerPokemon(false, true).level - 2;
const greedent = new EnemyPokemon(getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, true);
greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ];
greedent.passive = true;
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await catchPokemon(scene, greedent, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(scene, true);
await transitionMysteryEncounterIntroVisuals(true, true, 500);
await catchPokemon(greedent, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(true);
})
.build()
)
.build();
function doGreedentSpriteSteal(scene: BattleScene) {
function doGreedentSpriteSteal() {
const shakeDelay = 50;
const slideDelay = 500;
const greedentSprites = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1);
const greedentSprites = globalScene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1);
scene.playSound("battle_anims/Follow Me");
scene.tweens.chain({
globalScene.playSound("battle_anims/Follow Me");
globalScene.tweens.chain({
targets: greedentSprites,
tweens: [
{ // Slide Greedent diagonally
@ -445,10 +449,10 @@ function doGreedentSpriteSteal(scene: BattleScene) {
});
}
function doGreedentEatBerries(scene: BattleScene) {
const greedentSprites = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1);
function doGreedentEatBerries() {
const greedentSprites = globalScene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1);
let index = 1;
scene.tweens.add({
globalScene.tweens.add({
targets: greedentSprites,
duration: 150,
ease: "Cubic.easeOut",
@ -456,11 +460,11 @@ function doGreedentEatBerries(scene: BattleScene) {
y: "-=8",
loop: 5,
onStart: () => {
scene.playSound("battle_anims/PRSFX- Bug Bite");
globalScene.playSound("battle_anims/PRSFX- Bug Bite");
},
onLoop: () => {
if (index % 2 === 0) {
scene.playSound("battle_anims/PRSFX- Bug Bite");
globalScene.playSound("battle_anims/PRSFX- Bug Bite");
}
index++;
}
@ -468,17 +472,15 @@ function doGreedentEatBerries(scene: BattleScene) {
}
/**
*
* @param scene
* @param isEat Default false. Will "create" pile when false, and remove pile when true.
*/
function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) {
function doBerrySpritePile(isEat: boolean = false) {
const berryAddDelay = 150;
let animationOrder = [ "starf", "sitrus", "lansat", "salac", "apicot", "enigma", "liechi", "ganlon", "lum", "petaya", "leppa" ];
if (isEat) {
animationOrder = animationOrder.reverse();
}
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
animationOrder.forEach((berry, i) => {
const introVisualsIndex = encounter.spriteConfigs.findIndex(config => config.spriteKey?.includes(berry));
let sprite: Phaser.GameObjects.Sprite, tintSprite: Phaser.GameObjects.Sprite;
@ -487,7 +489,7 @@ function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) {
sprite = sprites[0];
tintSprite = sprites[1];
}
scene.time.delayedCall(berryAddDelay * i + 400, () => {
globalScene.time.delayedCall(berryAddDelay * i + 400, () => {
if (sprite) {
sprite.setVisible(!isEat);
}
@ -497,20 +499,20 @@ function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) {
// Animate Petaya berry falling off the pile
if (berry === "petaya" && sprite && tintSprite && !isEat) {
scene.time.delayedCall(200, () => {
doBerryBounce(scene, [ sprite, tintSprite ], 30, 500);
globalScene.time.delayedCall(200, () => {
doBerryBounce([ sprite, tintSprite ], 30, 500);
});
}
});
});
}
function doBerryBounce(scene: BattleScene, berrySprites: Phaser.GameObjects.Sprite[], yd: number, baseBounceDuration: number) {
function doBerryBounce(berrySprites: Phaser.GameObjects.Sprite[], yd: number, baseBounceDuration: number) {
let bouncePower = 1;
let bounceYOffset = yd;
const doBounce = () => {
scene.tweens.add({
globalScene.tweens.add({
targets: berrySprites,
y: "+=" + bounceYOffset,
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" },
@ -522,7 +524,7 @@ function doBerryBounce(scene: BattleScene, berrySprites: Phaser.GameObjects.Spri
if (bouncePower) {
bounceYOffset = bounceYOffset * bouncePower;
scene.tweens.add({
globalScene.tweens.add({
targets: berrySprites,
y: "-=" + bounceYOffset,
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" },

View File

@ -2,8 +2,9 @@ import { generateModifierType, leaveEncounterWithoutBattle, setEncounterExp, upd
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
@ -69,14 +70,14 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true);
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const pokemon = getHighestStatTotalPlayerPokemon(true, true);
const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId();
const starterValue: number = speciesStarterCosts[baseSpecies] ?? 1;
const multiplier = Math.max(MONEY_MAXIMUM_MULTIPLIER / 10 * starterValue, MONEY_MINIMUM_MULTIPLIER);
const price = scene.getWaveMoneyAmount(multiplier);
const price = globalScene.getWaveMoneyAmount(multiplier);
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
encounter.setDialogueToken("price", price.toString());
@ -89,7 +90,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
// If player meets the combo OR requirements for option 2, populate the token
const opt2Req = encounter.options[1].primaryPokemonRequirements[0];
if (opt2Req.meetsRequirement(scene)) {
if (opt2Req.meetsRequirement()) {
const abilityToken = encounter.dialogueTokens["option2PrimaryAbility"];
const moveToken = encounter.dialogueTokens["option2PrimaryMove"];
if (abilityToken) {
@ -99,7 +100,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
}
}
const shinyCharm = generateModifierType(scene, modifierTypes.SHINY_CHARM);
const shinyCharm = generateModifierType(modifierTypes.SHINY_CHARM);
encounter.setDialogueToken("itemName", shinyCharm?.name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name"));
encounter.setDialogueToken("liepardName", getPokemonSpecies(Species.LIEPARD).getName());
@ -118,17 +119,17 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Update money and remove pokemon from party
updatePlayerMoney(scene, encounter.misc.price);
scene.removePokemonFromPlayerParty(encounter.misc.pokemon);
updatePlayerMoney(encounter.misc.price);
globalScene.removePokemonFromPlayerParty(encounter.misc.pokemon);
return true;
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Give the player a Shiny Charm
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.SHINY_CHARM));
leaveEncounterWithoutBattle(scene, true);
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM));
leaveEncounterWithoutBattle(true);
})
.build()
)
@ -152,15 +153,15 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Extort the rich kid for money
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Update money and remove pokemon from party
updatePlayerMoney(scene, encounter.misc.price);
updatePlayerMoney(encounter.misc.price);
setEncounterExp(scene, encounter.options[1].primaryPokemon!.id, getPokemonSpecies(Species.LIEPARD).baseExp, true);
setEncounterExp(encounter.options[1].primaryPokemon!.id, getPokemonSpecies(Species.LIEPARD).baseExp, true);
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
})
.build()
)
@ -175,9 +176,9 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
return true;
}
)

View File

@ -1,23 +1,32 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type {
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
EnemyPartyConfig, generateModifierType, generateModifierTypeOption,
generateModifierType,
generateModifierTypeOption,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, setEncounterExp,
leaveEncounterWithoutBattle,
setEncounterExp,
setEncounterRewards
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import {
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon } from "#app/field/pokemon";
import type {
BerryModifierType,
ModifierTypeOption } from "#app/modifier/modifier-type";
import {
getPartyLuckValue,
ModifierPoolType,
ModifierTypeOption, modifierTypes,
modifierTypes,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { randSeedInt, randSeedItem } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -31,7 +40,8 @@ import { BerryType } from "#enums/berry-type";
import { PERMANENT_STATS, Stat } from "#enums/stat";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/berriesAbound";
@ -54,20 +64,20 @@ export const BerriesAboundEncounter: MysteryEncounter =
text: `${namespace}:intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate boss mon
const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
let bossSpecies: PokemonSpecies;
if (scene.eventManager.isEventActive() && scene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) {
const eventEncounter = randSeedItem(scene.eventManager.activeEvent()!.uncommonBreedEncounters!);
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, scene.gameMode);
if (globalScene.eventManager.isEventActive() && globalScene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) {
const eventEncounter = randSeedItem(globalScene.eventManager.activeEvent()!.uncommonBreedEncounters!);
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, globalScene.gameMode);
bossSpecies = getPokemonSpecies( levelSpecies );
} else {
bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getPlayerParty()), true);
bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true);
}
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = {
pokemonConfigs: [{
@ -82,10 +92,10 @@ export const BerriesAboundEncounter: MysteryEncounter =
// Calculate the number of extra berries that player receives
// 10-40: 2, 40-120: 4, 120-160: 5, 160-180: 7
const numBerries =
scene.currentBattle.waveIndex > 160 ? 7
: scene.currentBattle.waveIndex > 120 ? 5
: scene.currentBattle.waveIndex > 40 ? 4 : 2;
regenerateModifierPoolThresholds(scene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
globalScene.currentBattle.waveIndex > 160 ? 7
: globalScene.currentBattle.waveIndex > 120 ? 5
: globalScene.currentBattle.waveIndex > 40 ? 4 : 2;
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
encounter.misc = { numBerries };
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
@ -113,7 +123,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
];
// Get fastest party pokemon for option 2
const fastestPokemon = getHighestStatPlayerPokemon(scene, PERMANENT_STATS[Stat.SPD], true, false);
const fastestPokemon = getHighestStatPlayerPokemon(PERMANENT_STATS[Stat.SPD], true, false);
encounter.misc.fastestPokemon = fastestPokemon;
encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD);
encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender());
@ -134,34 +144,34 @@ export const BerriesAboundEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const numBerries = encounter.misc.numBerries;
const doBerryRewards = () => {
const berryText = i18next.t(`${namespace}:berries`);
scene.playSound("item_fanfare");
queueEncounterMessage(scene, i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries }));
globalScene.playSound("item_fanfare");
queueEncounterMessage(i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries }));
// Generate a random berry and give it to the first Pokemon with room for it
for (let i = 0; i < numBerries; i++) {
tryGiveBerry(scene);
tryGiveBerry();
}
};
const shopOptions: ModifierTypeOption[] = [];
for (let i = 0; i < 5; i++) {
// Generate shop berries
const mod = generateModifierTypeOption(scene, modifierTypes.BERRY);
const mod = generateModifierTypeOption(modifierTypes.BERRY);
if (mod) {
shopOptions.push(mod);
}
}
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
}
)
.withOption(
@ -171,9 +181,9 @@ export const BerriesAboundEncounter: MysteryEncounter =
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Pick race for berries
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const fastestPokemon: PlayerPokemon = encounter.misc.fastestPokemon;
const enemySpeed: number = encounter.misc.enemySpeed;
const speedDiff = fastestPokemon.getStat(Stat.SPD) / (enemySpeed * 1.1);
@ -182,7 +192,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
const shopOptions: ModifierTypeOption[] = [];
for (let i = 0; i < 5; i++) {
// Generate shop berries
const mod = generateModifierTypeOption(scene, modifierTypes.BERRY);
const mod = generateModifierTypeOption(modifierTypes.BERRY);
if (mod) {
shopOptions.push(mod);
}
@ -193,29 +203,29 @@ export const BerriesAboundEncounter: MysteryEncounter =
const doBerryRewards = () => {
const berryText = i18next.t(`${namespace}:berries`);
scene.playSound("item_fanfare");
queueEncounterMessage(scene, i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries }));
globalScene.playSound("item_fanfare");
queueEncounterMessage(i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries }));
// Generate a random berry and give it to the first Pokemon with room for it
for (let i = 0; i < numBerries; i++) {
tryGiveBerry(scene);
tryGiveBerry();
}
};
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ?
[ Stat.DEF, Stat.SPDEF, Stat.SPD ] :
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
const config = scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
const config = globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
config.pokemonConfigs![0].tags = [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ];
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}:option.2.boss_enraged`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
queueEncounterMessage(`${namespace}:option.2.boss_enraged`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
};
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
await showEncounterText(scene, `${namespace}:option.2.selected_bad`);
await initBattleWithEnemyConfig(scene, config);
setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
await showEncounterText(`${namespace}:option.2.selected_bad`);
await initBattleWithEnemyConfig(config);
return;
} else {
// Gains 1 berry for every 10% faster the player's pokemon is than the enemy, up to a max of numBerries, minimum of 2
@ -224,19 +234,19 @@ export const BerriesAboundEncounter: MysteryEncounter =
const doFasterBerryRewards = () => {
const berryText = i18next.t(`${namespace}:berries`);
scene.playSound("item_fanfare");
queueEncounterMessage(scene, i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerriesGrabbed }));
globalScene.playSound("item_fanfare");
queueEncounterMessage(i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerriesGrabbed }));
// Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first)
for (let i = 0; i < numBerriesGrabbed; i++) {
tryGiveBerry(scene, fastestPokemon);
tryGiveBerry(fastestPokemon);
}
};
setEncounterExp(scene, fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doFasterBerryRewards);
await showEncounterText(scene, `${namespace}:option.2.selected`);
leaveEncounterWithoutBattle(scene);
setEncounterExp(fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doFasterBerryRewards);
await showEncounterText(`${namespace}:option.2.selected`);
leaveEncounterWithoutBattle();
}
})
.build()
@ -251,38 +261,38 @@ export const BerriesAboundEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
return true;
}
)
.build();
function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) {
function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) {
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType;
const berry = generateModifierType(scene, modifierTypes.BERRY, [ berryType ]) as BerryModifierType;
const berry = generateModifierType(modifierTypes.BERRY, [ berryType ]) as BerryModifierType;
const party = scene.getPlayerParty();
const party = globalScene.getPlayerParty();
// Will try to apply to prioritized pokemon first, then do normal application method if it fails
if (prioritizedPokemon) {
const heldBerriesOfType = scene.findModifier(m => m instanceof BerryModifier
const heldBerriesOfType = globalScene.findModifier(m => m instanceof BerryModifier
&& m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry);
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry);
return;
}
}
// Iterate over the party until berry was successfully given
for (const pokemon of party) {
const heldBerriesOfType = scene.findModifier(m => m instanceof BerryModifier
const heldBerriesOfType = globalScene.findModifier(m => m instanceof BerryModifier
&& m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
applyModifierTypeToPlayerPokemon(scene, pokemon, berry);
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
applyModifierTypeToPlayerPokemon(pokemon, berry);
return;
}
}

View File

@ -1,5 +1,7 @@
import type {
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
EnemyPartyConfig, generateModifierType,
generateModifierType,
generateModifierTypeOption,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
@ -17,17 +19,20 @@ import {
} from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import { isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species";
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon";
import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { Moves } from "#enums/moves";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import {
@ -37,14 +42,17 @@ import {
TypeRequirement
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { Type } from "#enums/type";
import { AttackTypeBoosterModifierType, ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type";
import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import type {
PokemonHeldItemModifier
} from "#app/modifier/modifier";
import {
AttackTypeBoosterModifier,
BypassSpeedChanceModifier,
ContactHeldItemTransferChanceModifier,
GigantamaxAccessModifier,
MegaEvolutionAccessModifier,
PokemonHeldItemModifier
MegaEvolutionAccessModifier
} from "#app/modifier/modifier";
import i18next from "i18next";
import MoveInfoOverlay from "#app/ui/move-info-overlay";
@ -214,12 +222,12 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
text: `${namespace}:intro_dialogue`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculates what trainers are available for battle in the encounter
// Bug type superfan trainer config
const config = getTrainerConfigForWave(scene.currentBattle.waveIndex);
const config = getTrainerConfigForWave(globalScene.currentBattle.waveIndex);
const spriteKey = config.getSpriteKey();
encounter.enemyPartyConfigs.push({
trainerConfig: config,
@ -227,7 +235,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
});
let beedrillKeys: { spriteKey: string, fileRoot: string }, butterfreeKeys: { spriteKey: string, fileRoot: string };
if (scene.currentBattle.waveIndex < WAVE_LEVEL_BREAKPOINTS[3]) {
if (globalScene.currentBattle.waveIndex < WAVE_LEVEL_BREAKPOINTS[3]) {
beedrillKeys = getSpriteKeysFromSpecies(Species.BEEDRILL, false);
butterfreeKeys = getSpriteKeysFromSpecies(Species.BUTTERFREE, false);
} else {
@ -270,9 +278,9 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
];
const requiredItems = [
generateModifierType(scene, modifierTypes.QUICK_CLAW),
generateModifierType(scene, modifierTypes.GRIP_CLAW),
generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.BUG ]),
generateModifierType(modifierTypes.QUICK_CLAW),
generateModifierType(modifierTypes.GRIP_CLAW),
generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.BUG ]),
];
const requiredItemString = requiredItems.map(m => m?.name ?? "unknown").join("/");
@ -295,9 +303,9 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Select battle the bug trainer
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
// Init the moves available for tutor
@ -313,9 +321,9 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
// Assigns callback that teaches move before continuing to rewards
encounter.onRewards = doBugTypeMoveTutor;
setEncounterRewards(scene, { fillRemaining: true });
await transitionMysteryEncounterIntroVisuals(scene, true, true);
await initBattleWithEnemyConfig(scene, config);
setEncounterRewards({ fillRemaining: true });
await transitionMysteryEncounterIntroVisuals(true, true);
await initBattleWithEnemyConfig(config);
}
)
.withOption(MysteryEncounterOptionBuilder
@ -326,17 +334,17 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
buttonTooltip: `${namespace}:option.2.tooltip`,
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Player shows off their bug types
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Player gets different rewards depending on the number of bug types they have
const numBugTypes = scene.getPlayerParty().filter(p => p.isOfType(Type.BUG, true)).length;
const numBugTypes = globalScene.getPlayerParty().filter(p => p.isOfType(Type.BUG, true)).length;
const numBugTypesText = i18next.t(`${namespace}:numBugTypes`, { count: numBugTypes });
encounter.setDialogueToken("numBugTypes", numBugTypesText);
if (numBugTypes < 2) {
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL ], fillRemaining: false });
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL ], fillRemaining: false });
encounter.selectedOption!.dialogue!.selected = [
{
speaker: `${namespace}:speaker`,
@ -344,7 +352,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
},
];
} else if (numBugTypes < 4) {
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL ], fillRemaining: false });
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL ], fillRemaining: false });
encounter.selectedOption!.dialogue!.selected = [
{
speaker: `${namespace}:speaker`,
@ -352,7 +360,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
},
];
} else if (numBugTypes < 6) {
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL ], fillRemaining: false });
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL ], fillRemaining: false });
encounter.selectedOption!.dialogue!.selected = [
{
speaker: `${namespace}:speaker`,
@ -362,28 +370,28 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
} else {
// If the player has any evolution/form change items that are valid for their party,
// spawn one of those items in addition to Dynamax Band, Mega Band, and Master Ball
const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(scene, modifierTypes.MASTER_BALL)! ];
const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(modifierTypes.MASTER_BALL)! ];
const specialOptions: ModifierTypeOption[] = [];
if (!scene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) {
modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.MEGA_BRACELET)!);
if (!globalScene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) {
modifierOptions.push(generateModifierTypeOption(modifierTypes.MEGA_BRACELET)!);
}
if (!scene.findModifier(m => m instanceof GigantamaxAccessModifier)) {
modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.DYNAMAX_BAND)!);
if (!globalScene.findModifier(m => m instanceof GigantamaxAccessModifier)) {
modifierOptions.push(generateModifierTypeOption(modifierTypes.DYNAMAX_BAND)!);
}
const nonRareEvolutionModifier = generateModifierTypeOption(scene, modifierTypes.EVOLUTION_ITEM);
const nonRareEvolutionModifier = generateModifierTypeOption(modifierTypes.EVOLUTION_ITEM);
if (nonRareEvolutionModifier) {
specialOptions.push(nonRareEvolutionModifier);
}
const rareEvolutionModifier = generateModifierTypeOption(scene, modifierTypes.RARE_EVOLUTION_ITEM);
const rareEvolutionModifier = generateModifierTypeOption(modifierTypes.RARE_EVOLUTION_ITEM);
if (rareEvolutionModifier) {
specialOptions.push(rareEvolutionModifier);
}
const formChangeModifier = generateModifierTypeOption(scene, modifierTypes.FORM_CHANGE_ITEM);
const formChangeModifier = generateModifierTypeOption(modifierTypes.FORM_CHANGE_ITEM);
if (formChangeModifier) {
specialOptions.push(formChangeModifier);
}
const rareFormChangeModifier = generateModifierTypeOption(scene, modifierTypes.RARE_FORM_CHANGE_ITEM);
const rareFormChangeModifier = generateModifierTypeOption(modifierTypes.RARE_FORM_CHANGE_ITEM);
if (rareFormChangeModifier) {
specialOptions.push(rareFormChangeModifier);
}
@ -391,7 +399,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
modifierOptions.push(specialOptions[randSeedInt(specialOptions.length)]);
}
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifierOptions, fillRemaining: false });
setEncounterRewards({ guaranteedModifierTypeOptions: modifierOptions, fillRemaining: false });
encounter.selectedOption!.dialogue!.selected = [
{
speaker: `${namespace}:speaker`,
@ -400,9 +408,9 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
];
}
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Player shows off their bug types
leaveEncounterWithoutBattle(scene);
leaveEncounterWithoutBattle();
})
.build())
.withOption(MysteryEncounterOptionBuilder
@ -429,8 +437,8 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
],
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
@ -466,27 +474,27 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
(item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === Type.BUG);
});
if (!hasValidItem) {
return getEncounterText(scene, `${namespace}:option.3.invalid_selection`) ?? null;
return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const modifier = encounter.misc.chosenModifier;
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
chosenPokemon.loseHeldItem(modifier, false);
scene.updateModifiers(true, true);
globalScene.updateModifiers(true, true);
const bugNet = generateModifierTypeOption(scene, modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!;
const bugNet = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!;
bugNet.type.tier = ModifierTier.ROGUE;
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ bugNet ], guaranteedModifierTypeFuncs: [ modifierTypes.REVIVER_SEED ], fillRemaining: false });
leaveEncounterWithoutBattle(scene, true);
setEncounterRewards({ guaranteedModifierTypeOptions: [ bugNet ], guaranteedModifierTypeFuncs: [ modifierTypes.REVIVER_SEED ], fillRemaining: false });
leaveEncounterWithoutBattle(true);
})
.build())
.withOutroDialogue([
@ -642,22 +650,22 @@ function getTrainerConfigForWave(waveIndex: number) {
return config;
}
function doBugTypeMoveTutor(scene: BattleScene): Promise<void> {
function doBugTypeMoveTutor(): Promise<void> {
return new Promise<void>(async resolve => {
const moveOptions = scene.currentBattle.mysteryEncounter!.misc.moveTutorOptions;
await showEncounterDialogue(scene, `${namespace}:battle_won`, `${namespace}:speaker`);
const moveOptions = globalScene.currentBattle.mysteryEncounter!.misc.moveTutorOptions;
await showEncounterDialogue(`${namespace}:battle_won`, `${namespace}:speaker`);
const overlayScale = 1;
const moveInfoOverlay = new MoveInfoOverlay(scene, {
const moveInfoOverlay = new MoveInfoOverlay({
delayVisibility: false,
scale: overlayScale,
onSide: true,
right: true,
x: 1,
y: -MoveInfoOverlay.getHeight(overlayScale, true) - 1,
width: (scene.game.canvas.width / 6) - 2,
width: (globalScene.game.canvas.width / 6) - 2,
});
scene.ui.add(moveInfoOverlay);
globalScene.ui.add(moveInfoOverlay);
const optionSelectItems = moveOptions.map((move: PokemonMove) => {
const option: OptionSelectItem = {
@ -680,7 +688,7 @@ function doBugTypeMoveTutor(scene: BattleScene): Promise<void> {
moveInfoOverlay.setVisible(false);
};
const result = await selectOptionThenPokemon(scene, optionSelectItems, `${namespace}:teach_move_prompt`, undefined, onHoverOverCancel);
const result = await selectOptionThenPokemon(optionSelectItems, `${namespace}:teach_move_prompt`, undefined, onHoverOverCancel);
// let forceExit = !!result;
if (!result) {
moveInfoOverlay.active = false;
@ -691,7 +699,7 @@ function doBugTypeMoveTutor(scene: BattleScene): Promise<void> {
// Option select complete, handle if they are learning a move
if (result && result.selectedOptionIndex < moveOptions.length) {
scene.unshiftPhase(new LearnMovePhase(scene, result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId));
globalScene.unshiftPhase(new LearnMovePhase(result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId));
}
// Complete battle and go to rewards

View File

@ -1,26 +1,30 @@
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { ModifierPoolType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Abilities } from "#enums/abilities";
import { applyAbilityOverrideToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { Type } from "#enums/type";
import type { Type } from "#enums/type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { randSeedInt, randSeedShuffle } from "#app/utils";
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { Mode } from "#app/ui/ui";
import i18next from "i18next";
import { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
import type { PlayerPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon";
import { Ability } from "#app/data/ability";
import { BerryModifier } from "#app/modifier/modifier";
import { BerryType } from "#enums/berry-type";
@ -105,8 +109,8 @@ export const ClowningAroundEncounter: MysteryEncounter =
speaker: `${namespace}:speaker`
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const clownTrainerType = TrainerType.HARLEQUIN;
const clownConfig = trainerConfigs[clownTrainerType].clone();
@ -142,7 +146,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
});
// Load animations/sfx for start of fight moves
loadCustomMovesForEncounter(scene, [ Moves.ROLE_PLAY, Moves.TAUNT ]);
loadCustomMovesForEncounter([ Moves.ROLE_PLAY, Moves.TAUNT ]);
encounter.setDialogueToken("blacephalonName", getPokemonSpecies(Species.BLACEPHALON).getName());
@ -165,12 +169,12 @@ export const ClowningAroundEncounter: MysteryEncounter =
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Spawn battle
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
setEncounterRewards(scene, { fillRemaining: true });
setEncounterRewards({ fillRemaining: true });
// TODO: when Magic Room and Wonder Room are implemented, add those to start of battle
encounter.startOfBattleEffects.push(
@ -193,28 +197,28 @@ export const ClowningAroundEncounter: MysteryEncounter =
ignorePp: true
});
await transitionMysteryEncounterIntroVisuals(scene);
await initBattleWithEnemyConfig(scene, config);
await transitionMysteryEncounterIntroVisuals();
await initBattleWithEnemyConfig(config);
})
.withPostOptionPhase(async (scene: BattleScene): Promise<boolean> => {
.withPostOptionPhase(async (): Promise<boolean> => {
// After the battle, offer the player the opportunity to permanently swap ability
const abilityWasSwapped = await handleSwapAbility(scene);
const abilityWasSwapped = await handleSwapAbility();
if (abilityWasSwapped) {
await showEncounterText(scene, `${namespace}:option.1.ability_gained`);
await showEncounterText(`${namespace}:option.1.ability_gained`);
}
// Play animations once ability swap is complete
// Trainer sprite that is shown at end of battle is not the same as mystery encounter intro visuals
scene.tweens.add({
targets: scene.currentBattle.trainer,
globalScene.tweens.add({
targets: globalScene.currentBattle.trainer,
x: "+=16",
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 250
});
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 230, 40, 2);
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
background.playWithoutTargets(230, 40, 2);
return true;
})
.build()
@ -239,13 +243,13 @@ export const ClowningAroundEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Swap player's items on pokemon with the most items
// Item comparisons look at whichever Pokemon has the greatest number of TRANSFERABLE, non-berry items
// So Vitamins, form change items, etc. are not included
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const party = scene.getPlayerParty();
const party = globalScene.getPlayerParty();
let mostHeldItemsPokemon = party[0];
let count = mostHeldItemsPokemon.getHeldItems()
.filter(m => m.isTransferable && !(m instanceof BerryModifier))
@ -270,10 +274,10 @@ export const ClowningAroundEncounter: MysteryEncounter =
items.filter(m => m instanceof BerryModifier)
.forEach(m => {
numBerries += m.stackCount;
scene.removeModifier(m);
globalScene.removeModifier(m);
});
generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries");
generateItemsOfTier(mostHeldItemsPokemon, numBerries, "Berries");
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
// For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier
@ -286,24 +290,24 @@ export const ClowningAroundEncounter: MysteryEncounter =
const tier = type.tier ?? ModifierTier.ULTRA;
if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
numRogue += m.stackCount;
scene.removeModifier(m);
globalScene.removeModifier(m);
} else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) {
numUltra += m.stackCount;
scene.removeModifier(m);
globalScene.removeModifier(m);
}
});
generateItemsOfTier(scene, mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
generateItemsOfTier(scene, mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
generateItemsOfTier(mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
})
.withOptionPhase(async (scene: BattleScene) => {
leaveEncounterWithoutBattle(scene, true);
.withOptionPhase(async () => {
leaveEncounterWithoutBattle(true);
})
.withPostOptionPhase(async (scene: BattleScene) => {
.withPostOptionPhase(async () => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 230, 40, 2);
await transitionMysteryEncounterIntroVisuals(scene, true, true, 200);
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
background.playWithoutTargets(230, 40, 2);
await transitionMysteryEncounterIntroVisuals(true, true, 200);
})
.build()
)
@ -327,10 +331,10 @@ export const ClowningAroundEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Randomize the second type of all player's pokemon
// If the pokemon does not normally have a second type, it will gain 1
for (const pokemon of scene.getPlayerParty()) {
for (const pokemon of globalScene.getPlayerParty()) {
const originalTypes = pokemon.getTypes(false, false, true);
// If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type
@ -367,14 +371,14 @@ export const ClowningAroundEncounter: MysteryEncounter =
}
}
})
.withOptionPhase(async (scene: BattleScene) => {
leaveEncounterWithoutBattle(scene, true);
.withOptionPhase(async () => {
leaveEncounterWithoutBattle(true);
})
.withPostOptionPhase(async (scene: BattleScene) => {
.withPostOptionPhase(async () => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 230, 40, 2);
await transitionMysteryEncounterIntroVisuals(scene, true, true, 200);
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
background.playWithoutTargets(230, 40, 2);
await transitionMysteryEncounterIntroVisuals(true, true, 200);
})
.build()
)
@ -385,24 +389,24 @@ export const ClowningAroundEncounter: MysteryEncounter =
])
.build();
async function handleSwapAbility(scene: BattleScene) {
async function handleSwapAbility() {
return new Promise<boolean>(async resolve => {
await showEncounterDialogue(scene, `${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`);
await showEncounterText(scene, `${namespace}:option.1.apply_ability_message`);
await showEncounterDialogue(`${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`);
await showEncounterText(`${namespace}:option.1.apply_ability_message`);
scene.ui.setMode(Mode.MESSAGE).then(() => {
displayYesNoOptions(scene, resolve);
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
displayYesNoOptions(resolve);
});
});
}
function displayYesNoOptions(scene: BattleScene, resolve) {
showEncounterText(scene, `${namespace}:option.1.ability_prompt`, null, 500, false);
function displayYesNoOptions(resolve) {
showEncounterText(`${namespace}:option.1.ability_prompt`, null, 500, false);
const fullOptions = [
{
label: i18next.t("menu:yes"),
handler: () => {
onYesAbilitySwap(scene, resolve);
onYesAbilitySwap(resolve);
return true;
}
},
@ -420,29 +424,29 @@ function displayYesNoOptions(scene: BattleScene, resolve) {
maxOptions: 7,
yOffset: 0
};
scene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
}
function onYesAbilitySwap(scene: BattleScene, resolve) {
function onYesAbilitySwap(resolve) {
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Do ability swap
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
applyAbilityOverrideToPokemon(pokemon, encounter.misc.ability);
encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
globalScene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
};
const onPokemonNotSelected = () => {
scene.ui.setMode(Mode.MESSAGE).then(() => {
displayYesNoOptions(scene, resolve);
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
displayYesNoOptions(resolve);
});
};
selectPokemonForOption(scene, onPokemonSelected, onPokemonNotSelected);
selectPokemonForOption(onPokemonSelected, onPokemonNotSelected);
}
function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItems: number, tier: ModifierTier | "Berries") {
function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: ModifierTier | "Berries") {
// These pools have to be defined at runtime so that modifierTypes exist
// Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon
// This is to prevent "over-generating" a random item of a certain type during item swaps
@ -495,11 +499,11 @@ function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItem
const newItemType = pool[randIndex];
let newMod: PokemonHeldItemModifierType;
if (tier === "Berries") {
newMod = generateModifierType(scene, modifierTypes.BERRY, [ newItemType[0] ]) as PokemonHeldItemModifierType;
newMod = generateModifierType(modifierTypes.BERRY, [ newItemType[0] ]) as PokemonHeldItemModifierType;
} else {
newMod = generateModifierType(scene, newItemType[0]) as PokemonHeldItemModifierType;
newMod = generateModifierType(newItemType[0]) as PokemonHeldItemModifierType;
}
applyModifierTypeToPlayerPokemon(scene, pokemon, newMod);
applyModifierTypeToPlayerPokemon(pokemon, newMod);
// Decrement max stacks and remove from pool if at max
newItemType[1]--;
if (newItemType[1] <= 0) {

View File

@ -1,22 +1,26 @@
import { BattlerIndex } from "#app/battle";
import BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import { EncounterBattleAnim } from "#app/data/battle-anims";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { DANCING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { catchPokemon, getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { TrainerSlot } from "#app/data/trainer-config";
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { modifierTypes } from "#app/modifier/modifier-type";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import PokemonData from "#app/system/pokemon-data";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Biome } from "#enums/biome";
import { EncounterAnim } from "#enums/encounter-anims";
@ -91,10 +95,10 @@ export const DancingLessonsEncounter: MysteryEncounter =
.withAutoHideIntroVisuals(false)
.withCatchAllowed(true)
.withFleeAllowed(false)
.withOnVisualsStart((scene: BattleScene) => {
const oricorio = scene.getEnemyPokemon()!;
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, oricorio, scene.getPlayerPokemon()!);
danceAnim.play(scene, false, () => {
.withOnVisualsStart(() => {
const oricorio = globalScene.getEnemyPokemon()!;
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, oricorio, globalScene.getPlayerPokemon()!);
danceAnim.play(false, () => {
if (oricorio.shiny) {
oricorio.sparkle();
}
@ -110,12 +114,12 @@ export const DancingLessonsEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const species = getPokemonSpecies(Species.ORICORIO);
const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const enemyPokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, false);
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const enemyPokemon = new EnemyPokemon(species, level, TrainerSlot.NONE, false);
if (!enemyPokemon.moveset.some(m => m && m.getMove().id === Moves.REVELATION_DANCE)) {
if (enemyPokemon.moveset.length < 4) {
enemyPokemon.moveset.push(new PokemonMove(Moves.REVELATION_DANCE));
@ -126,7 +130,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
// Set the form index based on the biome
// Defaults to Baile style if somehow nothing matches
const currentBiome = scene.arena.biomeType;
const currentBiome = globalScene.arena.biomeType;
if (BAILE_STYLE_BIOMES.includes(currentBiome)) {
enemyPokemon.formIndex = 0;
} else if (POM_POM_STYLE_BIOMES.includes(currentBiome)) {
@ -140,14 +144,14 @@ export const DancingLessonsEncounter: MysteryEncounter =
}
const oricorioData = new PokemonData(enemyPokemon);
const oricorio = scene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData);
const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData);
// Adds a real Pokemon sprite to the field (required for the animation)
scene.getEnemyParty().forEach(enemyPokemon => {
scene.field.remove(enemyPokemon, true);
globalScene.getEnemyParty().forEach(enemyPokemon => {
globalScene.field.remove(enemyPokemon, true);
});
scene.currentBattle.enemyParty = [ oricorio ];
scene.field.add(oricorio);
globalScene.currentBattle.enemyParty = [ oricorio ];
globalScene.field.add(oricorio);
// Spawns on offscreen field
oricorio.x -= 300;
encounter.loadAssets.push(oricorio.loadAssets());
@ -160,8 +164,8 @@ export const DancingLessonsEncounter: MysteryEncounter =
// Gets +1 to all stats except SPD on battle start
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}:option.1.boss_enraged`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF ], 1));
queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF ], 1));
}
}],
};
@ -186,9 +190,9 @@ export const DancingLessonsEncounter: MysteryEncounter =
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.startOfBattleEffects.push({
sourceBattlerIndex: BattlerIndex.ENEMY,
@ -197,9 +201,9 @@ export const DancingLessonsEncounter: MysteryEncounter =
ignorePp: true
});
await hideOricorioPokemon(scene);
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.BATON ], fillRemaining: true });
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
await hideOricorioPokemon();
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.BATON ], fillRemaining: true });
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
})
.build()
)
@ -215,25 +219,25 @@ export const DancingLessonsEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Learn its Dance
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
scene.unshiftPhase(new LearnMovePhase(scene, scene.getPlayerParty().indexOf(pokemon), Moves.REVELATION_DANCE));
globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(pokemon), Moves.REVELATION_DANCE));
// Play animation again to "learn" the dance
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon());
danceAnim.play(scene);
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, globalScene.getEnemyPokemon()!, globalScene.getPlayerPokemon());
danceAnim.play();
};
return selectPokemonForOption(scene, onPokemonSelected);
return selectPokemonForOption(onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Learn its Dance
await hideOricorioPokemon(scene);
leaveEncounterWithoutBattle(scene, true);
await hideOricorioPokemon();
leaveEncounterWithoutBattle(true);
})
.build()
)
@ -252,9 +256,9 @@ export const DancingLessonsEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Open menu for selecting pokemon with a Dancing move
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for nature selection
return pokemon.moveset
@ -281,20 +285,20 @@ export const DancingLessonsEncounter: MysteryEncounter =
if (!pokemon.isAllowedInBattle()) {
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
}
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}:invalid_selection`) ?? null;
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Show the Oricorio a dance, and recruit it
const encounter = scene.currentBattle.mysteryEncounter!;
const oricorio = encounter.misc.oricorioData.toPokemon(scene);
const encounter = globalScene.currentBattle.mysteryEncounter!;
const oricorio = encounter.misc.oricorioData.toPokemon();
oricorio.passive = true;
// Ensure the Oricorio's moveset gains the Dance move the player used
@ -307,18 +311,18 @@ export const DancingLessonsEncounter: MysteryEncounter =
}
}
await hideOricorioPokemon(scene);
await catchPokemon(scene, oricorio, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(scene, true);
await hideOricorioPokemon();
await catchPokemon(oricorio, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(true);
})
.build()
)
.build();
function hideOricorioPokemon(scene: BattleScene) {
function hideOricorioPokemon() {
return new Promise<void>(resolve => {
const oricorioSprite = scene.getEnemyParty()[0];
scene.tweens.add({
const oricorioSprite = globalScene.getEnemyParty()[0];
globalScene.tweens.add({
targets: oricorioSprite,
x: "+=16",
y: "-=16",
@ -326,7 +330,7 @@ function hideOricorioPokemon(scene: BattleScene) {
ease: "Sine.easeInOut",
duration: 750,
onComplete: () => {
scene.field.remove(oricorioSprite, true);
globalScene.field.remove(oricorioSprite, true);
resolve();
}
});

View File

@ -1,18 +1,21 @@
import { Type } from "#enums/type";
import type { Type } from "#enums/type";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import { modifierTypes } from "#app/modifier/modifier-type";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
import { getRandomPlayerPokemon, getRandomSpeciesByStarterCost } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { PokemonFormChangeItemModifier } from "#app/modifier/modifier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { Challenges } from "#enums/challenges";
@ -138,16 +141,16 @@ export const DarkDealEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Removes random pokemon (including fainted) from party and adds name to dialogue data tokens
// Will never return last battle able mon and instead pick fainted/unable to battle
const removedPokemon = getRandomPlayerPokemon(scene, true, false, true);
const removedPokemon = getRandomPlayerPokemon(true, false, true);
// Get all the pokemon's held items
const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
scene.removePokemonFromPlayerParty(removedPokemon);
globalScene.removePokemonFromPlayerParty(removedPokemon);
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.setDialogueToken("pokeName", removedPokemon.getNameToRender());
// Store removed pokemon types
@ -156,16 +159,16 @@ export const DarkDealEncounter: MysteryEncounter =
modifiers
};
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Give the player 5 Rogue Balls
const encounter = scene.currentBattle.mysteryEncounter!;
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ROGUE_BALL));
const encounter = globalScene.currentBattle.mysteryEncounter!;
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.ROGUE_BALL));
// Start encounter with random legendary (7-10 starter strength) that has level additive
// If this is a mono-type challenge, always ensure the required type is filtered for
let bossTypes: Type[] = encounter.misc.removedTypes;
const singleTypeChallenges = scene.gameMode.challenges.filter(c => c.value && c.id === Challenges.SINGLE_TYPE);
if (scene.gameMode.isChallenge && singleTypeChallenges.length > 0) {
const singleTypeChallenges = globalScene.gameMode.challenges.filter(c => c.value && c.id === Challenges.SINGLE_TYPE);
if (globalScene.gameMode.isChallenge && singleTypeChallenges.length > 0) {
bossTypes = singleTypeChallenges.map(c => (c.value - 1) as Type);
}
@ -191,7 +194,7 @@ export const DarkDealEncounter: MysteryEncounter =
const config: EnemyPartyConfig = {
pokemonConfigs: [ pokemonConfig ],
};
await initBattleWithEnemyConfig(scene, config);
await initBattleWithEnemyConfig(config);
})
.build()
)
@ -206,9 +209,9 @@ export const DarkDealEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
return true;
}
)

View File

@ -1,18 +1,22 @@
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, PreserveBerryModifier } from "#app/modifier/modifier";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
import { BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PreserveBerryModifier } from "#app/modifier/modifier";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import i18next from "#app/plugins/i18n";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { randSeedItem } from "#app/utils";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -36,19 +40,19 @@ const OPTION_3_DISALLOWED_MODIFIERS = [
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
const doEventReward = (scene: BattleScene) => {
const event_buff = scene.eventManager.activeEvent()?.delibirdyBuff ?? [];
const doEventReward = () => {
const event_buff = globalScene.eventManager.activeEvent()?.delibirdyBuff ?? [];
if (event_buff.length > 0) {
const candidates = event_buff.filter((c => {
const mtype = generateModifierType(scene, modifierTypes[c]);
const existingCharm = scene.findModifier(m => m.type.id === mtype?.id);
return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount(scene));
const mtype = generateModifierType(modifierTypes[c]);
const existingCharm = globalScene.findModifier(m => m.type.id === mtype?.id);
return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount());
}));
if (candidates.length > 0) {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes[randSeedItem(candidates)]));
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes[randSeedItem(candidates)]));
} else {
// At max stacks, give a Voucher instead
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.VOUCHER));
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.VOUCHER));
}
}
};
@ -114,15 +118,15 @@ export const DelibirdyEncounter: MysteryEncounter =
text: `${namespace}:outro`,
}
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.setDialogueToken("delibirdName", getPokemonSpecies(Species.DELIBIRD).getName());
scene.loadBgm("mystery_encounter_delibirdy", "mystery_encounter_delibirdy.mp3");
globalScene.loadBgm("mystery_encounter_delibirdy", "mystery_encounter_delibirdy.mp3");
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
scene.fadeAndSwitchBgm("mystery_encounter_delibirdy");
.withOnVisualsStart(() => {
globalScene.fadeAndSwitchBgm("mystery_encounter_delibirdy");
return true;
})
.withOption(
@ -138,29 +142,29 @@ export const DelibirdyEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false);
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false);
return true;
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Give the player an Amulet Coin
// Check if the player has max stacks of that item already
const existing = scene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier;
const existing = globalScene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getPlayerPokemon()!, shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
doEventReward(scene);
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
doEventReward();
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.AMULET_COIN));
doEventReward(scene);
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.AMULET_COIN));
doEventReward();
}
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
})
.build()
)
@ -178,8 +182,8 @@ export const DelibirdyEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter((it) => {
@ -205,57 +209,57 @@ export const DelibirdyEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon has valid item, it can be selected
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon);
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}:invalid_selection`) ?? null;
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const modifier: BerryModifier | PokemonInstantReviveModifier = encounter.misc.chosenModifier;
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
// Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed
if (modifier instanceof BerryModifier) {
// Check if the player has max stacks of that Candy Jar already
const existing = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier;
const existing = globalScene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getPlayerPokemon()!, shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
doEventReward(scene);
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
doEventReward();
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.CANDY_JAR));
doEventReward(scene);
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.CANDY_JAR));
doEventReward();
}
} else {
// Check if the player has max stacks of that Berry Pouch already
const existing = scene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier;
const existing = globalScene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getPlayerPokemon()!, shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
doEventReward(scene);
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
doEventReward();
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH));
doEventReward(scene);
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.BERRY_POUCH));
doEventReward();
}
}
chosenPokemon.loseHeldItem(modifier, false);
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
})
.build()
)
@ -273,8 +277,8 @@ export const DelibirdyEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter((it) => {
@ -300,39 +304,39 @@ export const DelibirdyEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon has valid item, it can be selected
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}:invalid_selection`) ?? null;
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const modifier = encounter.misc.chosenModifier;
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
// Check if the player has max stacks of Healing Charm already
const existing = scene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier;
const existing = globalScene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getPlayerParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
doEventReward(scene);
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerParty()[0], shellBell);
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
doEventReward();
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM));
doEventReward(scene);
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.HEALING_CHARM));
doEventReward();
}
chosenPokemon.loseHeldItem(modifier, false);
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
})
.build()
)

View File

@ -2,12 +2,13 @@ import {
leaveEncounterWithoutBattle,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { ModifierTypeFunc, modifierTypes } from "#app/modifier/modifier-type";
import type { ModifierTypeFunc } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, {
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import {
MysteryEncounterBuilder,
} from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -60,7 +61,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
},
async (scene: BattleScene) => {
async () => {
// Choose TMs
const modifiers: ModifierTypeFunc[] = [];
let i = 0;
@ -77,8 +78,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
i++;
}
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle(scene);
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle();
}
)
.withSimpleOption(
@ -86,7 +87,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
},
async (scene: BattleScene) => {
async () => {
// Choose Vitamins
const modifiers: ModifierTypeFunc[] = [];
let i = 0;
@ -101,8 +102,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
i++;
}
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle(scene);
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle();
}
)
.withSimpleOption(
@ -110,7 +111,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
},
async (scene: BattleScene) => {
async () => {
// Choose X Items
const modifiers: ModifierTypeFunc[] = [];
let i = 0;
@ -125,8 +126,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
i++;
}
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle(scene);
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle();
}
)
.withSimpleOption(
@ -134,7 +135,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
buttonLabel: `${namespace}:option.4.label`,
buttonTooltip: `${namespace}:option.4.tooltip`,
},
async (scene: BattleScene) => {
async () => {
// Choose Pokeballs
const modifiers: ModifierTypeFunc[] = [];
let i = 0;
@ -153,8 +154,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
i++;
}
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle(scene);
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle();
}
)
.withOutroDialogue([

View File

@ -1,12 +1,13 @@
import { MoveCategory } from "#app/data/move";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Stat } from "#enums/stat";
@ -64,8 +65,8 @@ export const FieldTripEncounter: MysteryEncounter =
buttonTooltip: `${namespace}:option.1.tooltip`,
secondOptionPrompt: `${namespace}:second_option_prompt`,
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for Pokemon move valid for this option
return pokemon.moveset.map((move: PokemonMove) => {
@ -74,7 +75,7 @@ export const FieldTripEncounter: MysteryEncounter =
handler: () => {
// Pokemon and move selected
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:physical`));
pokemonAndMoveChosen(scene, pokemon, move, MoveCategory.PHYSICAL);
pokemonAndMoveChosen(pokemon, move, MoveCategory.PHYSICAL);
return true;
},
};
@ -82,23 +83,23 @@ export const FieldTripEncounter: MysteryEncounter =
});
};
return selectPokemonForOption(scene, onPokemonSelected);
return selectPokemonForOption(onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
if (encounter.misc.correctMove) {
const modifiers = [
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.ATK ])!,
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.DEF ])!,
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!,
generateModifierTypeOption(scene, modifierTypes.DIRE_HIT)!,
generateModifierTypeOption(scene, modifierTypes.RARER_CANDY)!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.ATK ])!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.DEF ])!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!,
generateModifierTypeOption(modifierTypes.DIRE_HIT)!,
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
];
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
}
leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove);
leaveEncounterWithoutBattle(!encounter.misc.correctMove);
})
.build()
)
@ -110,8 +111,8 @@ export const FieldTripEncounter: MysteryEncounter =
buttonTooltip: `${namespace}:option.2.tooltip`,
secondOptionPrompt: `${namespace}:second_option_prompt`,
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for Pokemon move valid for this option
return pokemon.moveset.map((move: PokemonMove) => {
@ -120,7 +121,7 @@ export const FieldTripEncounter: MysteryEncounter =
handler: () => {
// Pokemon and move selected
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:special`));
pokemonAndMoveChosen(scene, pokemon, move, MoveCategory.SPECIAL);
pokemonAndMoveChosen(pokemon, move, MoveCategory.SPECIAL);
return true;
},
};
@ -128,23 +129,23 @@ export const FieldTripEncounter: MysteryEncounter =
});
};
return selectPokemonForOption(scene, onPokemonSelected);
return selectPokemonForOption(onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
if (encounter.misc.correctMove) {
const modifiers = [
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPATK ])!,
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPDEF ])!,
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!,
generateModifierTypeOption(scene, modifierTypes.DIRE_HIT)!,
generateModifierTypeOption(scene, modifierTypes.RARER_CANDY)!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPATK ])!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPDEF ])!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!,
generateModifierTypeOption(modifierTypes.DIRE_HIT)!,
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
];
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
}
leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove);
leaveEncounterWithoutBattle(!encounter.misc.correctMove);
})
.build()
)
@ -156,8 +157,8 @@ export const FieldTripEncounter: MysteryEncounter =
buttonTooltip: `${namespace}:option.3.tooltip`,
secondOptionPrompt: `${namespace}:second_option_prompt`,
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for Pokemon move valid for this option
return pokemon.moveset.map((move: PokemonMove) => {
@ -166,7 +167,7 @@ export const FieldTripEncounter: MysteryEncounter =
handler: () => {
// Pokemon and move selected
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:status`));
pokemonAndMoveChosen(scene, pokemon, move, MoveCategory.STATUS);
pokemonAndMoveChosen(pokemon, move, MoveCategory.STATUS);
return true;
},
};
@ -174,30 +175,30 @@ export const FieldTripEncounter: MysteryEncounter =
});
};
return selectPokemonForOption(scene, onPokemonSelected);
return selectPokemonForOption(onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
if (encounter.misc.correctMove) {
const modifiers = [
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.ACC ])!,
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!,
generateModifierTypeOption(scene, modifierTypes.GREAT_BALL)!,
generateModifierTypeOption(scene, modifierTypes.IV_SCANNER)!,
generateModifierTypeOption(scene, modifierTypes.RARER_CANDY)!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.ACC ])!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!,
generateModifierTypeOption(modifierTypes.GREAT_BALL)!,
generateModifierTypeOption(modifierTypes.IV_SCANNER)!,
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
];
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
}
leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove);
leaveEncounterWithoutBattle(!encounter.misc.correctMove);
})
.build()
)
.build();
function pokemonAndMoveChosen(scene: BattleScene, pokemon: PlayerPokemon, move: PokemonMove, correctMoveCategory: MoveCategory) {
const encounter = scene.currentBattle.mysteryEncounter!;
function pokemonAndMoveChosen(pokemon: PlayerPokemon, move: PokemonMove, correctMoveCategory: MoveCategory) {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const correctMove = move.getMove().category === correctMoveCategory;
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
encounter.setDialogueToken("move", move.getName());
@ -214,7 +215,7 @@ function pokemonAndMoveChosen(scene: BattleScene, pokemon: PlayerPokemon, move:
text: `${namespace}:incorrect_exp`,
},
];
setEncounterExp(scene, scene.getPlayerParty().map((p) => p.id), 50);
setEncounterExp(globalScene.getPlayerParty().map((p) => p.id), 50);
} else {
encounter.selectedOption!.dialogue!.selected = [
{
@ -228,7 +229,7 @@ function pokemonAndMoveChosen(scene: BattleScene, pokemon: PlayerPokemon, move:
text: `${namespace}:correct_exp`,
},
];
setEncounterExp(scene, [ pokemon.id ], 100);
setEncounterExp([ pokemon.id ], 100);
}
encounter.misc = {
correctMove: correctMove,

View File

@ -1,16 +1,20 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { AttackTypeBoosterModifierType, modifierTypes, } from "#app/modifier/modifier-type";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { modifierTypes, } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { AbilityRequirement, CombinationPokemonRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { Species } from "#enums/species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Gender } from "#app/data/gender";
import { Type } from "#enums/type";
import { BattlerIndex } from "#app/battle";
import Pokemon, { PokemonMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon";
import { Moves } from "#enums/moves";
import { EncounterBattleAnim } from "#app/data/battle-anims";
import { WeatherType } from "#enums/weather-type";
@ -58,8 +62,8 @@ export const FieryFalloutEncounter: MysteryEncounter =
text: `${namespace}:intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate boss mons
const volcaronaSpecies = getPokemonSpecies(Species.VOLCARONA);
@ -71,7 +75,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
gender: Gender.MALE,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1));
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1));
}
},
{
@ -80,7 +84,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
gender: Gender.FEMALE,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1));
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1));
}
}
],
@ -113,25 +117,25 @@ export const FieryFalloutEncounter: MysteryEncounter =
];
// Load animations/sfx for Volcarona moves
loadCustomMovesForEncounter(scene, [ Moves.FIRE_SPIN, Moves.QUIVER_DANCE ]);
loadCustomMovesForEncounter([ Moves.FIRE_SPIN, Moves.QUIVER_DANCE ]);
scene.arena.trySetWeather(WeatherType.SUNNY, true);
globalScene.arena.trySetWeather(WeatherType.SUNNY, true);
encounter.setDialogueToken("volcaronaName", getPokemonSpecies(Species.VOLCARONA).getName());
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
.withOnVisualsStart(() => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.MAGMA_BG, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 200, 70, 2, 3);
const animation = new EncounterBattleAnim(EncounterAnim.MAGMA_SPOUT, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
animation.playWithoutTargets(scene, 80, 100, 2);
scene.time.delayedCall(600, () => {
animation.playWithoutTargets(scene, -20, 100, 2);
const background = new EncounterBattleAnim(EncounterAnim.MAGMA_BG, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
background.playWithoutTargets(200, 70, 2, 3);
const animation = new EncounterBattleAnim(EncounterAnim.MAGMA_SPOUT, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
animation.playWithoutTargets(80, 100, 2);
globalScene.time.delayedCall(600, () => {
animation.playWithoutTargets(-20, 100, 2);
});
scene.time.delayedCall(1200, () => {
animation.playWithoutTargets(scene, 140, 150, 2);
globalScene.time.delayedCall(1200, () => {
animation.playWithoutTargets(140, 150, 2);
});
return true;
@ -150,10 +154,10 @@ export const FieryFalloutEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter!;
setEncounterRewards(scene, { fillRemaining: true }, undefined, () => giveLeadPokemonAttackTypeBoostItem(scene));
const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ fillRemaining: true }, undefined, () => giveLeadPokemonAttackTypeBoostItem());
encounter.startOfBattleEffects.push(
{
@ -168,7 +172,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
move: new PokemonMove(Moves.FIRE_SPIN),
ignorePp: true
});
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
}
)
.withSimpleOption(
@ -181,15 +185,15 @@ export const FieryFalloutEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Damage non-fire types and burn 1 random non-fire type member + give it Heatproof
const encounter = scene.currentBattle.mysteryEncounter!;
const nonFireTypes = scene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE));
const encounter = globalScene.currentBattle.mysteryEncounter!;
const nonFireTypes = globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE));
for (const pkm of nonFireTypes) {
const percentage = DAMAGE_PERCENTAGE / 100;
const damage = Math.floor(pkm.getMaxHp() * percentage);
applyDamageToPokemon(scene, pkm, damage);
applyDamageToPokemon(pkm, damage);
}
// Burn random member
@ -201,7 +205,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
// Burn applied
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
encounter.setDialogueToken("abilityName", new Ability(Abilities.HEATPROOF, 3).name);
queueEncounterMessage(scene, `${namespace}:option.2.target_burned`);
queueEncounterMessage(`${namespace}:option.2.target_burned`);
// Also permanently change the burned Pokemon's ability to Heatproof
applyAbilityOverrideToPokemon(chosenPokemon, Abilities.HEATPROOF);
@ -209,7 +213,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
}
// No rewards
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
}
)
.withOption(
@ -231,44 +235,44 @@ export const FieryFalloutEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Do NOT await this, to prevent player from repeatedly pressing options
transitionMysteryEncounterIntroVisuals(scene, false, false, 2000);
transitionMysteryEncounterIntroVisuals(false, false, 2000);
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Fire types help calm the Volcarona
const encounter = scene.currentBattle.mysteryEncounter!;
await transitionMysteryEncounterIntroVisuals(scene);
setEncounterRewards(scene,
const encounter = globalScene.currentBattle.mysteryEncounter!;
await transitionMysteryEncounterIntroVisuals();
setEncounterRewards(
{ fillRemaining: true },
undefined,
() => {
giveLeadPokemonAttackTypeBoostItem(scene);
giveLeadPokemonAttackTypeBoostItem();
});
const primary = encounter.options[2].primaryPokemon!;
setEncounterExp(scene, [ primary.id ], getPokemonSpecies(Species.VOLCARONA).baseExp * 2);
leaveEncounterWithoutBattle(scene);
setEncounterExp([ primary.id ], getPokemonSpecies(Species.VOLCARONA).baseExp * 2);
leaveEncounterWithoutBattle();
})
.build()
)
.build();
function giveLeadPokemonAttackTypeBoostItem(scene: BattleScene) {
function giveLeadPokemonAttackTypeBoostItem() {
// Give first party pokemon attack type boost item for free at end of battle
const leadPokemon = scene.getPlayerParty()?.[0];
const leadPokemon = globalScene.getPlayerParty()?.[0];
if (leadPokemon) {
// Generate type booster held item, default to Charcoal if item fails to generate
let boosterModifierType = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType;
let boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType;
if (!boosterModifierType) {
boosterModifierType = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FIRE ]) as AttackTypeBoosterModifierType;
boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FIRE ]) as AttackTypeBoosterModifierType;
}
applyModifierTypeToPlayerPokemon(scene, leadPokemon, boosterModifierType);
applyModifierTypeToPlayerPokemon(leadPokemon, boosterModifierType);
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.setDialogueToken("itemName", boosterModifierType.name);
encounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
queueEncounterMessage(scene, `${namespace}:found_item`);
queueEncounterMessage(`${namespace}:found_item`);
}
}

View File

@ -1,23 +1,28 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type {
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
EnemyPartyConfig,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, setEncounterExp,
leaveEncounterWithoutBattle,
setEncounterExp,
setEncounterRewards
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import Pokemon, { EnemyPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon } from "#app/field/pokemon";
import { ModifierTier } from "#app/modifier/modifier-tier";
import type {
ModifierTypeOption } from "#app/modifier/modifier-type";
import {
getPartyLuckValue,
getPlayerModifierTypeOptions,
ModifierPoolType,
ModifierTypeOption,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -29,7 +34,8 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun
import { randSeedInt, randSeedItem } from "#app/utils";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/fightOrFlight";
@ -52,20 +58,20 @@ export const FightOrFlightEncounter: MysteryEncounter =
text: `${namespace}:intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate boss mon
const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
let bossSpecies: PokemonSpecies;
if (scene.eventManager.isEventActive() && scene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) {
const eventEncounter = randSeedItem(scene.eventManager.activeEvent()!.uncommonBreedEncounters!);
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, scene.gameMode);
if (globalScene.eventManager.isEventActive() && globalScene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) {
const eventEncounter = randSeedItem(globalScene.eventManager.activeEvent()!.uncommonBreedEncounters!);
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, globalScene.gameMode);
bossSpecies = getPokemonSpecies( levelSpecies );
} else {
bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getPlayerParty()), true);
bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true);
}
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", bossPokemon.getNameToRender());
const config: EnemyPartyConfig = {
pokemonConfigs: [{
@ -75,10 +81,10 @@ export const FightOrFlightEncounter: MysteryEncounter =
isBoss: true,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}:option.1.stat_boost`);
queueEncounterMessage(`${namespace}:option.1.stat_boost`);
// Randomly boost 1 stat 2 stages
// Cannot boost Spd, Acc, or Evasion
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randSeedInt(4, 1) ], 2));
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ randSeedInt(4, 1) ], 2));
}
}],
};
@ -87,18 +93,18 @@ export const FightOrFlightEncounter: MysteryEncounter =
// Calculate item
// Waves 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER
const tier =
scene.currentBattle.waveIndex > 160
globalScene.currentBattle.waveIndex > 160
? ModifierTier.MASTER
: scene.currentBattle.waveIndex > 120
: globalScene.currentBattle.waveIndex > 120
? ModifierTier.ROGUE
: scene.currentBattle.waveIndex > 40
: globalScene.currentBattle.waveIndex > 40
? ModifierTier.ULTRA
: ModifierTier.GREAT;
regenerateModifierPoolThresholds(scene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
let item: ModifierTypeOption | null = null;
// TMs and Candy Jar excluded from possible rewards as they're too swingy in value for a singular item reward
while (!item || item.type.id.includes("TM_") || item.type.id === "CANDY_JAR") {
item = getPlayerModifierTypeOptions(1, scene.getPlayerParty(), [], { guaranteedModifierTiers: [ tier ], allowLuckUpgrades: false })[0];
item = getPlayerModifierTypeOptions(1, globalScene.getPlayerParty(), [], { guaranteedModifierTiers: [ tier ], allowLuckUpgrades: false })[0];
}
encounter.setDialogueToken("itemName", item.type.name);
encounter.misc = item;
@ -144,12 +150,12 @@ export const FightOrFlightEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Pick battle
// Pokemon will randomly boost 1 stat by 2 stages
const item = scene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ item ], fillRemaining: false });
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false });
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
}
)
.withOption(
@ -166,16 +172,16 @@ export const FightOrFlightEncounter: MysteryEncounter =
}
]
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Pick steal
const encounter = scene.currentBattle.mysteryEncounter!;
const item = scene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ item ], fillRemaining: false });
const encounter = globalScene.currentBattle.mysteryEncounter!;
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false });
// Use primaryPokemon to execute the thievery
const primaryPokemon = encounter.options[1].primaryPokemon!;
setEncounterExp(scene, primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
leaveEncounterWithoutBattle(scene);
setEncounterExp(primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
leaveEncounterWithoutBattle();
})
.build()
)
@ -189,9 +195,9 @@ export const FightOrFlightEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
return true;
}
)

View File

@ -1,10 +1,13 @@
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { TrainerSlot } from "#app/data/trainer-config";
import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { FieldPosition } from "#app/field/pokemon";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -80,14 +83,14 @@ export const FunAndGamesEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
scene.loadBgm("mystery_encounter_fun_and_games", "mystery_encounter_fun_and_games.mp3");
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
globalScene.loadBgm("mystery_encounter_fun_and_games", "mystery_encounter_fun_and_games.mp3");
encounter.setDialogueToken("wobbuffetName", getPokemonSpecies(Species.WOBBUFFET).getName());
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
scene.fadeAndSwitchBgm("mystery_encounter_fun_and_games");
.withOnVisualsStart(() => {
globalScene.fadeAndSwitchBgm("mystery_encounter_fun_and_games");
return true;
})
.withOption(MysteryEncounterOptionBuilder
@ -102,9 +105,9 @@ export const FunAndGamesEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Select Pokemon for minigame
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.misc = {
playerPokemon: pokemon,
@ -113,28 +116,28 @@ export const FunAndGamesEncounter: MysteryEncounter =
// Only Pokemon that are not KOed/legal can be selected
const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Start minigame
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.misc.turnsRemaining = 3;
// Update money
const moneyCost = (encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney;
updatePlayerMoney(scene, -moneyCost, true, false);
await showEncounterText(scene, i18next.t("mysteryEncounterMessages:paid_money", { amount: moneyCost }));
updatePlayerMoney(-moneyCost, true, false);
await showEncounterText(i18next.t("mysteryEncounterMessages:paid_money", { amount: moneyCost }));
// Handlers for battle events
encounter.onTurnStart = handleNextTurn; // triggered during TurnInitPhase
encounter.doContinueEncounter = handleLoseMinigame; // triggered during MysteryEncounterRewardsPhase, post VictoryPhase if the player KOs Wobbuffet
hideShowmanIntroSprite(scene);
await summonPlayerPokemon(scene);
await showWobbuffetHealthBar(scene);
hideShowmanIntroSprite();
await summonPlayerPokemon();
await showWobbuffetHealthBar();
return true;
})
@ -150,22 +153,22 @@ export const FunAndGamesEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Leave encounter with no rewards or exp
await transitionMysteryEncounterIntroVisuals(scene, true, true);
leaveEncounterWithoutBattle(scene, true);
await transitionMysteryEncounterIntroVisuals(true, true);
leaveEncounterWithoutBattle(true);
return true;
}
)
.build();
async function summonPlayerPokemon(scene: BattleScene) {
async function summonPlayerPokemon() {
return new Promise<void>(async resolve => {
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const playerPokemon = encounter.misc.playerPokemon;
// Swaps the chosen Pokemon and the first player's lead Pokemon in the party
const party = scene.getPlayerParty();
const party = globalScene.getPlayerParty();
const chosenIndex = party.indexOf(playerPokemon);
if (chosenIndex !== 0) {
const leadPokemon = party[0];
@ -175,36 +178,36 @@ async function summonPlayerPokemon(scene: BattleScene) {
// Do trainer summon animation
let playerAnimationPromise: Promise<void> | undefined;
scene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(playerPokemon) }));
scene.pbTray.hide();
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
scene.time.delayedCall(562, () => {
scene.trainer.setFrame("2");
scene.time.delayedCall(64, () => {
scene.trainer.setFrame("3");
globalScene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(playerPokemon) }));
globalScene.pbTray.hide();
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
globalScene.time.delayedCall(562, () => {
globalScene.trainer.setFrame("2");
globalScene.time.delayedCall(64, () => {
globalScene.trainer.setFrame("3");
});
});
scene.tweens.add({
targets: scene.trainer,
globalScene.tweens.add({
targets: globalScene.trainer,
x: -36,
duration: 1000,
onComplete: () => scene.trainer.setVisible(false)
onComplete: () => globalScene.trainer.setVisible(false)
});
scene.time.delayedCall(750, () => {
playerAnimationPromise = summonPlayerPokemonAnimation(scene, playerPokemon);
globalScene.time.delayedCall(750, () => {
playerAnimationPromise = summonPlayerPokemonAnimation(playerPokemon);
});
// Also loads Wobbuffet data (cannot be shiny)
const enemySpecies = getPokemonSpecies(Species.WOBBUFFET);
scene.currentBattle.enemyParty = [];
const wobbuffet = scene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false, true);
globalScene.currentBattle.enemyParty = [];
const wobbuffet = globalScene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false, true);
wobbuffet.ivs = [ 0, 0, 0, 0, 0, 0 ];
wobbuffet.setNature(Nature.MILD);
wobbuffet.setAlpha(0);
wobbuffet.setVisible(false);
wobbuffet.calculateStats();
scene.currentBattle.enemyParty[0] = wobbuffet;
scene.gameData.setPokemonSeen(wobbuffet, true);
globalScene.currentBattle.enemyParty[0] = wobbuffet;
globalScene.gameData.setPokemonSeen(wobbuffet, true);
await wobbuffet.loadAssets();
const id = setInterval(checkPlayerAnimationPromise, 500);
async function checkPlayerAnimationPromise() {
@ -217,37 +220,37 @@ async function summonPlayerPokemon(scene: BattleScene) {
});
}
function handleLoseMinigame(scene: BattleScene) {
function handleLoseMinigame() {
return new Promise<void>(async resolve => {
// Check Wobbuffet is still alive
const wobbuffet = scene.getEnemyPokemon();
const wobbuffet = globalScene.getEnemyPokemon();
if (!wobbuffet || wobbuffet.isFainted(true) || wobbuffet.hp === 0) {
// Player loses
// End the battle
if (wobbuffet) {
wobbuffet.hideInfo();
scene.field.remove(wobbuffet);
globalScene.field.remove(wobbuffet);
}
transitionMysteryEncounterIntroVisuals(scene, true, true);
scene.currentBattle.enemyParty = [];
scene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(scene, true);
await showEncounterText(scene, `${namespace}:ko`);
const reviveCost = scene.getWaveMoneyAmount(1.5);
updatePlayerMoney(scene, -reviveCost, true, false);
transitionMysteryEncounterIntroVisuals(true, true);
globalScene.currentBattle.enemyParty = [];
globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(true);
await showEncounterText(`${namespace}:ko`);
const reviveCost = globalScene.getWaveMoneyAmount(1.5);
updatePlayerMoney(-reviveCost, true, false);
}
resolve();
});
}
function handleNextTurn(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!;
function handleNextTurn() {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const wobbuffet = scene.getEnemyPokemon();
const wobbuffet = globalScene.getEnemyPokemon();
if (!wobbuffet) {
// Should never be triggered, just handling the edge case
handleLoseMinigame(scene);
handleLoseMinigame();
return true;
}
if (encounter.misc.turnsRemaining <= 0) {
@ -257,15 +260,15 @@ function handleNextTurn(scene: BattleScene) {
let isHealPhase = false;
if (healthRatio < 0.03) {
// Grand prize
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.MULTI_LENS ], fillRemaining: false });
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.MULTI_LENS ], fillRemaining: false });
resultMessageKey = `${namespace}:best_result`;
} else if (healthRatio < 0.15) {
// 2nd prize
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SCOPE_LENS ], fillRemaining: false });
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SCOPE_LENS ], fillRemaining: false });
resultMessageKey = `${namespace}:great_result`;
} else if (healthRatio < 0.33) {
// 3rd prize
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.WIDE_LENS ], fillRemaining: false });
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.WIDE_LENS ], fillRemaining: false });
resultMessageKey = `${namespace}:good_result`;
} else {
// No prize
@ -275,22 +278,22 @@ function handleNextTurn(scene: BattleScene) {
// End the battle
wobbuffet.hideInfo();
scene.field.remove(wobbuffet);
scene.currentBattle.enemyParty = [];
scene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(scene, isHealPhase);
globalScene.field.remove(wobbuffet);
globalScene.currentBattle.enemyParty = [];
globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(isHealPhase);
// Must end the TurnInit phase prematurely so battle phases aren't added to queue
queueEncounterMessage(scene, `${namespace}:end_game`);
queueEncounterMessage(scene, resultMessageKey);
queueEncounterMessage(`${namespace}:end_game`);
queueEncounterMessage(resultMessageKey);
// Skip remainder of TurnInitPhase
return true;
} else {
if (encounter.misc.turnsRemaining < 3) {
// Display charging messages on turns that aren't the initial turn
queueEncounterMessage(scene, `${namespace}:charging_continue`);
queueEncounterMessage(`${namespace}:charging_continue`);
}
queueEncounterMessage(scene, `${namespace}:turn_remaining_${encounter.misc.turnsRemaining}`);
queueEncounterMessage(`${namespace}:turn_remaining_${encounter.misc.turnsRemaining}`);
encounter.misc.turnsRemaining--;
}
@ -298,33 +301,33 @@ function handleNextTurn(scene: BattleScene) {
return false;
}
async function showWobbuffetHealthBar(scene: BattleScene) {
const wobbuffet = scene.getEnemyPokemon()!;
async function showWobbuffetHealthBar() {
const wobbuffet = globalScene.getEnemyPokemon()!;
scene.add.existing(wobbuffet);
scene.field.add(wobbuffet);
globalScene.add.existing(wobbuffet);
globalScene.field.add(wobbuffet);
const playerPokemon = scene.getPlayerPokemon() as Pokemon;
const playerPokemon = globalScene.getPlayerPokemon() as Pokemon;
if (playerPokemon?.isOnField()) {
scene.field.moveBelow(wobbuffet, playerPokemon);
globalScene.field.moveBelow(wobbuffet, playerPokemon);
}
// Show health bar and trigger cry
wobbuffet.showInfo();
scene.time.delayedCall(1000, () => {
globalScene.time.delayedCall(1000, () => {
wobbuffet.cry();
});
wobbuffet.resetSummonData();
// Track the HP change across turns
scene.currentBattle.mysteryEncounter!.misc.wobbuffetHealth = wobbuffet.hp;
globalScene.currentBattle.mysteryEncounter!.misc.wobbuffetHealth = wobbuffet.hp;
}
function summonPlayerPokemonAnimation(scene: BattleScene, pokemon: PlayerPokemon): Promise<void> {
function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise<void> {
return new Promise<void>(resolve => {
const pokeball = scene.addFieldSprite(36, 80, "pb", getPokeballAtlasKey(pokemon.pokeball));
const pokeball = globalScene.addFieldSprite(36, 80, "pb", getPokeballAtlasKey(pokemon.pokeball));
pokeball.setVisible(false);
pokeball.setOrigin(0.5, 0.625);
scene.field.add(pokeball);
globalScene.field.add(pokeball);
pokemon.setFieldPosition(FieldPosition.CENTER, 0);
@ -332,32 +335,32 @@ function summonPlayerPokemonAnimation(scene: BattleScene, pokemon: PlayerPokemon
pokeball.setVisible(true);
scene.tweens.add({
globalScene.tweens.add({
targets: pokeball,
duration: 650,
x: 100 + fpOffset[0]
});
scene.tweens.add({
globalScene.tweens.add({
targets: pokeball,
duration: 150,
ease: "Cubic.easeOut",
y: 70 + fpOffset[1],
onComplete: () => {
scene.tweens.add({
globalScene.tweens.add({
targets: pokeball,
duration: 500,
ease: "Cubic.easeIn",
angle: 1440,
y: 132 + fpOffset[1],
onComplete: () => {
scene.playSound("se/pb_rel");
globalScene.playSound("se/pb_rel");
pokeball.destroy();
scene.add.existing(pokemon);
scene.field.add(pokemon);
addPokeballOpenParticles(scene, pokemon.x, pokemon.y - 16, pokemon.pokeball);
scene.updateModifiers(true);
scene.updateFieldScale();
globalScene.add.existing(pokemon);
globalScene.field.add(pokemon);
addPokeballOpenParticles(pokemon.x, pokemon.y - 16, pokemon.pokeball);
globalScene.updateModifiers(true);
globalScene.updateFieldScale();
pokemon.showInfo();
pokemon.playAnim();
pokemon.setVisible(true);
@ -365,8 +368,8 @@ function summonPlayerPokemonAnimation(scene: BattleScene, pokemon: PlayerPokemon
pokemon.setScale(0.5);
pokemon.tint(getPokeballTintColor(pokemon.pokeball));
pokemon.untint(250, "Sine.easeIn");
scene.updateFieldScale();
scene.tweens.add({
globalScene.updateFieldScale();
globalScene.tweens.add({
targets: pokemon,
duration: 250,
ease: "Sine.easeIn",
@ -375,15 +378,15 @@ function summonPlayerPokemonAnimation(scene: BattleScene, pokemon: PlayerPokemon
pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 });
pokemon.getSprite().clearTint();
pokemon.resetSummonData();
scene.time.delayedCall(1000, () => {
globalScene.time.delayedCall(1000, () => {
if (pokemon.isShiny()) {
scene.unshiftPhase(new ShinySparklePhase(scene, pokemon.getBattlerIndex()));
globalScene.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex()));
}
pokemon.resetTurnData();
scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
scene.pushPhase(new PostSummonPhase(scene, pokemon.getBattlerIndex()));
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
globalScene.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex()));
resolve();
});
}
@ -395,13 +398,13 @@ function summonPlayerPokemonAnimation(scene: BattleScene, pokemon: PlayerPokemon
});
}
function hideShowmanIntroSprite(scene: BattleScene) {
const carnivalGame = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(0)[0];
const wobbuffet = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1)[0];
const showMan = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(2)[0];
function hideShowmanIntroSprite() {
const carnivalGame = globalScene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(0)[0];
const wobbuffet = globalScene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1)[0];
const showMan = globalScene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(2)[0];
// Hide the showman
scene.tweens.add({
globalScene.tweens.add({
targets: showMan,
x: "+=16",
y: "-=16",
@ -411,7 +414,7 @@ function hideShowmanIntroSprite(scene: BattleScene) {
});
// Slide the Wobbuffet and Game over slightly
scene.tweens.add({
globalScene.tweens.add({
targets: [ wobbuffet, carnivalGame ],
x: "+=16",
ease: "Sine.easeInOut",

View File

@ -2,20 +2,26 @@ import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterReward
import { TrainerSlot, } from "#app/data/trainer-config";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { MusicPreference } from "#app/system/settings/settings";
import { getPlayerModifierTypeOptions, ModifierPoolType, ModifierTypeOption, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import type { ModifierTypeOption } from "#app/modifier/modifier-type";
import { getPlayerModifierTypeOptions, ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { Species } from "#enums/species";
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import { getTypeRgb } from "#app/data/type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { NumberHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils";
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import PokemonData from "#app/system/pokemon-data";
import i18next from "i18next";
import { Gender, getGenderSymbol } from "#app/data/gender";
@ -102,24 +108,24 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Load bgm
let bgmKey: string;
if (scene.musicPreference === MusicPreference.GENFIVE) {
if (globalScene.musicPreference === MusicPreference.GENFIVE) {
bgmKey = "mystery_encounter_gen_5_gts";
scene.loadBgm(bgmKey, `${bgmKey}.mp3`);
globalScene.loadBgm(bgmKey, `${bgmKey}.mp3`);
} else {
// Mixed option
bgmKey = "mystery_encounter_gen_6_gts";
scene.loadBgm(bgmKey, `${bgmKey}.mp3`);
globalScene.loadBgm(bgmKey, `${bgmKey}.mp3`);
}
// Load possible trade options
// Maps current party member's id to 3 EnemyPokemon objects
// None of the trade options can be the same species
const tradeOptionsMap: Map<number, EnemyPokemon[]> = getPokemonTradeOptions(scene);
const tradeOptionsMap: Map<number, EnemyPokemon[]> = getPokemonTradeOptions();
encounter.misc = {
tradeOptionsMap,
bgmKey
@ -127,8 +133,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
scene.fadeAndSwitchBgm(scene.currentBattle.mysteryEncounter!.misc.bgmKey);
.withOnVisualsStart(() => {
globalScene.fadeAndSwitchBgm(globalScene.currentBattle.mysteryEncounter!.misc.bgmKey);
return true;
})
.withOption(
@ -140,8 +146,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
buttonTooltip: `${namespace}:option.1.tooltip`,
secondOptionPrompt: `${namespace}:option.1.trade_options_prompt`,
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get the trade species options for the selected pokemon
const tradeOptionsMap: Map<number, EnemyPokemon[]> = encounter.misc.tradeOptionsMap;
@ -165,17 +171,17 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const formName = tradePokemon.species.forms && tradePokemon.species.forms.length > tradePokemon.formIndex ? tradePokemon.species.forms[tradePokemon.formIndex].formName : null;
const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : "");
const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : "");
showEncounterText(scene, `${line1}\n${line2}`, 0, 0, false);
showEncounterText(`${line1}\n${line2}`, 0, 0, false);
},
};
return option;
});
};
return selectPokemonForOption(scene, onPokemonSelected);
return selectPokemonForOption(onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon;
const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon;
const modifiers = tradedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier));
@ -185,32 +191,32 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
encounter.setDialogueToken("tradeTrainerName", traderName.trim());
// Remove the original party member from party
scene.removePokemonFromPlayerParty(tradedPokemon, false);
globalScene.removePokemonFromPlayerParty(tradedPokemon, false);
// Set data properly, then generate the new Pokemon's assets
receivedPokemonData.passive = tradedPokemon.passive;
// Pokeball to Ultra ball, randomly
receivedPokemonData.pokeball = randInt(4) as PokeballType;
const dataSource = new PokemonData(receivedPokemonData);
const newPlayerPokemon = scene.addPlayerPokemon(receivedPokemonData.species, receivedPokemonData.level, dataSource.abilityIndex, dataSource.formIndex, dataSource.gender, dataSource.shiny, dataSource.variant, dataSource.ivs, dataSource.nature, dataSource);
scene.getPlayerParty().push(newPlayerPokemon);
const newPlayerPokemon = globalScene.addPlayerPokemon(receivedPokemonData.species, receivedPokemonData.level, dataSource.abilityIndex, dataSource.formIndex, dataSource.gender, dataSource.shiny, dataSource.variant, dataSource.ivs, dataSource.nature, dataSource);
globalScene.getPlayerParty().push(newPlayerPokemon);
await newPlayerPokemon.loadAssets();
for (const mod of modifiers) {
mod.pokemonId = newPlayerPokemon.id;
scene.addModifier(mod, true, false, false, true);
globalScene.addModifier(mod, true, false, false, true);
}
// Show the trade animation
await showTradeBackground(scene);
await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon);
await showEncounterText(scene, `${namespace}:trade_received`, null, 0, true, 4000);
scene.playBgm(encounter.misc.bgmKey);
await addPokemonDataToDexAndValidateAchievements(scene, newPlayerPokemon);
await hideTradeBackground(scene);
await showTradeBackground();
await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon);
await showEncounterText(`${namespace}:trade_received`, null, 0, true, 4000);
globalScene.playBgm(encounter.misc.bgmKey);
await addPokemonDataToDexAndValidateAchievements(newPlayerPokemon);
await hideTradeBackground();
tradedPokemon.destroy();
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
})
.build()
)
@ -222,19 +228,19 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Randomly generate a Wonder Trade pokemon
const randomTradeOption = generateTradeOption(scene.getPlayerParty().map(p => p.species));
const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false);
const randomTradeOption = generateTradeOption(globalScene.getPlayerParty().map(p => p.species));
const tradePokemon = new EnemyPokemon(randomTradeOption, pokemon.level, TrainerSlot.NONE, false);
// Extra shiny roll at 1/128 odds (boosted by events and charms)
if (!tradePokemon.shiny) {
const shinyThreshold = new NumberHolder(WONDER_TRADE_SHINY_CHANCE);
if (scene.eventManager.isEventActive()) {
shinyThreshold.value *= scene.eventManager.getShinyMultiplier();
if (globalScene.eventManager.isEventActive()) {
shinyThreshold.value *= globalScene.eventManager.getShinyMultiplier();
}
scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
// Base shiny chance of 512/65536 -> 1/128, affected by events and Shiny Charms
// Maximum shiny chance of 4096/65536 -> 1/16, cannot improve further after that
@ -248,7 +254,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
if (tradePokemon.species.abilityHidden) {
if (tradePokemon.abilityIndex < hiddenIndex) {
const hiddenAbilityChance = new NumberHolder(64);
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
@ -281,10 +287,10 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
encounter.misc.receivedPokemon = tradePokemon;
};
return selectPokemonForOption(scene, onPokemonSelected);
return selectPokemonForOption(onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon;
const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon;
const modifiers = tradedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier));
@ -294,31 +300,31 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
encounter.setDialogueToken("tradeTrainerName", traderName.trim());
// Remove the original party member from party
scene.removePokemonFromPlayerParty(tradedPokemon, false);
globalScene.removePokemonFromPlayerParty(tradedPokemon, false);
// Set data properly, then generate the new Pokemon's assets
receivedPokemonData.passive = tradedPokemon.passive;
receivedPokemonData.pokeball = randInt(4) as PokeballType;
const dataSource = new PokemonData(receivedPokemonData);
const newPlayerPokemon = scene.addPlayerPokemon(receivedPokemonData.species, receivedPokemonData.level, dataSource.abilityIndex, dataSource.formIndex, dataSource.gender, dataSource.shiny, dataSource.variant, dataSource.ivs, dataSource.nature, dataSource);
scene.getPlayerParty().push(newPlayerPokemon);
const newPlayerPokemon = globalScene.addPlayerPokemon(receivedPokemonData.species, receivedPokemonData.level, dataSource.abilityIndex, dataSource.formIndex, dataSource.gender, dataSource.shiny, dataSource.variant, dataSource.ivs, dataSource.nature, dataSource);
globalScene.getPlayerParty().push(newPlayerPokemon);
await newPlayerPokemon.loadAssets();
for (const mod of modifiers) {
mod.pokemonId = newPlayerPokemon.id;
scene.addModifier(mod, true, false, false, true);
globalScene.addModifier(mod, true, false, false, true);
}
// Show the trade animation
await showTradeBackground(scene);
await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon);
await showEncounterText(scene, `${namespace}:trade_received`, null, 0, true, 4000);
scene.playBgm(encounter.misc.bgmKey);
await addPokemonDataToDexAndValidateAchievements(scene, newPlayerPokemon);
await hideTradeBackground(scene);
await showTradeBackground();
await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon);
await showEncounterText(`${namespace}:trade_received`, null, 0, true, 4000);
globalScene.playBgm(encounter.misc.bgmKey);
await addPokemonDataToDexAndValidateAchievements(newPlayerPokemon);
await hideTradeBackground();
tradedPokemon.destroy();
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
})
.build()
)
@ -330,8 +336,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
buttonTooltip: `${namespace}:option.3.tooltip`,
secondOptionPrompt: `${namespace}:option.3.trade_options_prompt`,
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter((it) => {
@ -359,18 +365,18 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
return it.isTransferable;
}).length > 0;
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}:option.3.invalid_selection`) ?? null;
return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const modifier = encounter.misc.chosenModifier as PokemonHeldItemModifier;
const party = scene.getPlayerParty();
const party = globalScene.getPlayerParty();
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
// Check tier of the traded item, the received item will be one tier up
@ -397,16 +403,16 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
}
encounter.setDialogueToken("itemName", item.type.name);
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ item ], fillRemaining: false });
setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false });
chosenPokemon.loseHeldItem(modifier, false);
await scene.updateModifiers(true, true);
await globalScene.updateModifiers(true, true);
// Generate a trainer name
const traderName = generateRandomTraderName();
encounter.setDialogueToken("tradeTrainerName", traderName.trim());
await showEncounterText(scene, `${namespace}:item_trade_selected`);
leaveEncounterWithoutBattle(scene);
await showEncounterText(`${namespace}:item_trade_selected`);
leaveEncounterWithoutBattle();
})
.build()
)
@ -420,26 +426,26 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
return true;
}
)
.build();
function getPokemonTradeOptions(scene: BattleScene): Map<number, EnemyPokemon[]> {
function getPokemonTradeOptions(): Map<number, EnemyPokemon[]> {
const tradeOptionsMap: Map<number, EnemyPokemon[]> = new Map<number, EnemyPokemon[]>();
// Starts by filtering out any current party members as valid resulting species
const alreadyUsedSpecies: PokemonSpecies[] = scene.getPlayerParty().map(p => p.species);
const alreadyUsedSpecies: PokemonSpecies[] = globalScene.getPlayerParty().map(p => p.species);
scene.getPlayerParty().forEach(pokemon => {
globalScene.getPlayerParty().forEach(pokemon => {
// If the party member is legendary/mythical, the only trade options available are always pulled from generation-specific legendary trade pools
if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) {
const generation = pokemon.species.generation;
const tradeOptions: EnemyPokemon[] = LEGENDARY_TRADE_POOLS[generation].map(s => {
const pokemonSpecies = getPokemonSpecies(s);
return new EnemyPokemon(scene, pokemonSpecies, 5, TrainerSlot.NONE, false);
return new EnemyPokemon(pokemonSpecies, 5, TrainerSlot.NONE, false);
});
tradeOptionsMap.set(pokemon.id, tradeOptions);
} else {
@ -454,7 +460,7 @@ function getPokemonTradeOptions(scene: BattleScene): Map<number, EnemyPokemon[]>
// Add trade options to map
tradeOptionsMap.set(pokemon.id, tradeOptions.map(s => {
return new EnemyPokemon(scene, s, pokemon.level, TrainerSlot.NONE, false);
return new EnemyPokemon(s, pokemon.level, TrainerSlot.NONE, false);
}));
}
});
@ -497,28 +503,28 @@ function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?:
return newSpecies!;
}
function showTradeBackground(scene: BattleScene) {
function showTradeBackground() {
return new Promise<void>(resolve => {
const tradeContainer = scene.add.container(0, -scene.game.canvas.height / 6);
const tradeContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6);
tradeContainer.setName("Trade Background");
const flyByStaticBg = scene.add.rectangle(0, 0, scene.game.canvas.width / 6, scene.game.canvas.height / 6, 0);
const flyByStaticBg = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0);
flyByStaticBg.setName("Black Background");
flyByStaticBg.setOrigin(0, 0);
flyByStaticBg.setVisible(false);
tradeContainer.add(flyByStaticBg);
const tradeBaseBg = scene.add.image(0, 0, "default_bg");
const tradeBaseBg = globalScene.add.image(0, 0, "default_bg");
tradeBaseBg.setName("Trade Background Image");
tradeBaseBg.setOrigin(0, 0);
tradeContainer.add(tradeBaseBg);
scene.fieldUI.add(tradeContainer);
scene.fieldUI.bringToTop(tradeContainer);
globalScene.fieldUI.add(tradeContainer);
globalScene.fieldUI.bringToTop(tradeContainer);
tradeContainer.setVisible(true);
tradeContainer.alpha = 0;
scene.tweens.add({
globalScene.tweens.add({
targets: tradeContainer,
alpha: 1,
duration: 500,
@ -530,17 +536,17 @@ function showTradeBackground(scene: BattleScene) {
});
}
function hideTradeBackground(scene: BattleScene) {
function hideTradeBackground() {
return new Promise<void>(resolve => {
const transformationContainer = scene.fieldUI.getByName("Trade Background");
const transformationContainer = globalScene.fieldUI.getByName("Trade Background");
scene.tweens.add({
globalScene.tweens.add({
targets: transformationContainer,
alpha: 0,
duration: 1000,
ease: "Sine.easeInOut",
onComplete: () => {
scene.fieldUI.remove(transformationContainer, true);
globalScene.fieldUI.remove(transformationContainer, true);
resolve();
}
});
@ -549,13 +555,12 @@ function hideTradeBackground(scene: BattleScene) {
/**
* Initiates an "evolution-like" animation to transform a previousPokemon (presumably from the player's party) into a new one, not necessarily an evolution species.
* @param scene
* @param tradedPokemon
* @param receivedPokemon
*/
function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon, receivedPokemon: PlayerPokemon) {
function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: PlayerPokemon) {
return new Promise<void>(resolve => {
const tradeContainer = scene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
const tradeContainer = globalScene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image;
let tradedPokemonSprite: Phaser.GameObjects.Sprite;
@ -564,8 +569,8 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon
let receivedPokemonTintSprite: Phaser.GameObjects.Sprite;
const getPokemonSprite = () => {
const ret = scene.addPokemonSprite(tradedPokemon, tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pkmn__sub");
ret.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
const ret = globalScene.addPokemonSprite(tradedPokemon, tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pkmn__sub");
ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
return ret;
};
@ -589,7 +594,7 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon
console.error(`Failed to play animation for ${spriteKey}`, err);
}
sprite.setPipeline(scene.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()) });
sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey());
sprite.setPipelineData("shiny", tradedPokemon.shiny);
@ -610,7 +615,7 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon
console.error(`Failed to play animation for ${spriteKey}`, err);
}
sprite.setPipeline(scene.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()) });
sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey());
sprite.setPipelineData("shiny", receivedPokemon.shiny);
@ -625,45 +630,45 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon
// Traded pokemon pokeball
const tradedPbAtlasKey = getPokeballAtlasKey(tradedPokemon.pokeball);
const tradedPokeball: Phaser.GameObjects.Sprite = scene.add.sprite(tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pb", tradedPbAtlasKey);
const tradedPokeball: Phaser.GameObjects.Sprite = globalScene.add.sprite(tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pb", tradedPbAtlasKey);
tradedPokeball.setVisible(false);
tradeContainer.add(tradedPokeball);
// Received pokemon pokeball
const receivedPbAtlasKey = getPokeballAtlasKey(receivedPokemon.pokeball);
const receivedPokeball: Phaser.GameObjects.Sprite = scene.add.sprite(tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pb", receivedPbAtlasKey);
const receivedPokeball: Phaser.GameObjects.Sprite = globalScene.add.sprite(tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pb", receivedPbAtlasKey);
receivedPokeball.setVisible(false);
tradeContainer.add(receivedPokeball);
scene.tweens.add({
globalScene.tweens.add({
targets: tradedPokemonSprite,
alpha: 1,
ease: "Cubic.easeInOut",
duration: 500,
onComplete: async () => {
scene.fadeOutBgm(1000, false);
await showEncounterText(scene, `${namespace}:pokemon_trade_selected`);
globalScene.fadeOutBgm(1000, false);
await showEncounterText(`${namespace}:pokemon_trade_selected`);
tradedPokemon.cry();
scene.playBgm("evolution");
await showEncounterText(scene, `${namespace}:pokemon_trade_goodbye`);
globalScene.playBgm("evolution");
await showEncounterText(`${namespace}:pokemon_trade_goodbye`);
tradedPokeball.setAlpha(0);
tradedPokeball.setVisible(true);
scene.tweens.add({
globalScene.tweens.add({
targets: tradedPokeball,
alpha: 1,
ease: "Cubic.easeInOut",
duration: 250,
onComplete: () => {
tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}_opening`);
scene.time.delayedCall(17, () => tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}_open`));
scene.playSound("se/pb_rel");
globalScene.time.delayedCall(17, () => tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}_open`));
globalScene.playSound("se/pb_rel");
tradedPokemonTintSprite.setVisible(true);
// TODO: need to add particles to fieldUI instead of field
// addPokeballOpenParticles(scene, tradedPokemon.x, tradedPokemon.y, tradedPokemon.pokeball);
// addPokeballOpenParticles(tradedPokemon.x, tradedPokemon.y, tradedPokemon.pokeball);
scene.tweens.add({
globalScene.tweens.add({
targets: [ tradedPokemonTintSprite, tradedPokemonSprite ],
duration: 500,
ease: "Sine.easeIn",
@ -672,30 +677,30 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon
tradedPokemonSprite.setVisible(false);
tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}_opening`);
tradedPokemonTintSprite.setVisible(false);
scene.playSound("se/pb_catch");
scene.time.delayedCall(17, () => tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}`));
globalScene.playSound("se/pb_catch");
globalScene.time.delayedCall(17, () => tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}`));
scene.tweens.add({
globalScene.tweens.add({
targets: tradedPokeball,
y: "+=10",
duration: 200,
delay: 250,
ease: "Cubic.easeIn",
onComplete: () => {
scene.playSound("se/pb_bounce_1");
globalScene.playSound("se/pb_bounce_1");
scene.tweens.add({
globalScene.tweens.add({
targets: tradedPokeball,
y: "-=100",
duration: 200,
delay: 1000,
ease: "Cubic.easeInOut",
onStart: () => {
scene.playSound("se/pb_throw");
globalScene.playSound("se/pb_throw");
},
onComplete: async () => {
await doPokemonTradeFlyBySequence(scene, tradedPokemonSprite, receivedPokemonSprite);
await doTradeReceivedSequence(scene, receivedPokemon, receivedPokemonSprite, receivedPokemonTintSprite, receivedPokeball, receivedPbAtlasKey);
await doPokemonTradeFlyBySequence(tradedPokemonSprite, receivedPokemonSprite);
await doTradeReceivedSequence(receivedPokemon, receivedPokemonSprite, receivedPokemonTintSprite, receivedPokeball, receivedPbAtlasKey);
resolve();
}
});
@ -710,9 +715,9 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon
});
}
function doPokemonTradeFlyBySequence(scene: BattleScene, tradedPokemonSprite: Phaser.GameObjects.Sprite, receivedPokemonSprite: Phaser.GameObjects.Sprite) {
function doPokemonTradeFlyBySequence(tradedPokemonSprite: Phaser.GameObjects.Sprite, receivedPokemonSprite: Phaser.GameObjects.Sprite) {
return new Promise<void>(resolve => {
const tradeContainer = scene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
const tradeContainer = globalScene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image;
const flyByStaticBg = tradeContainer.getByName("Black Background") as Phaser.GameObjects.Rectangle;
flyByStaticBg.setVisible(true);
@ -733,47 +738,47 @@ function doPokemonTradeFlyBySequence(scene: BattleScene, tradedPokemonSprite: Ph
const BASE_ANIM_DURATION = 1000;
// Fade out trade background
scene.tweens.add({
globalScene.tweens.add({
targets: tradeBaseBg,
alpha: 0,
ease: "Cubic.easeInOut",
duration: FADE_DELAY,
onComplete: () => {
scene.tweens.add({
globalScene.tweens.add({
targets: [ receivedPokemonSprite, tradedPokemonSprite ],
y: tradeBaseBg.displayWidth / 2 - 100,
ease: "Cubic.easeInOut",
duration: BASE_ANIM_DURATION * 3,
onComplete: () => {
scene.tweens.add({
globalScene.tweens.add({
targets: receivedPokemonSprite,
x: tradeBaseBg.displayWidth / 4,
ease: "Cubic.easeInOut",
duration: BASE_ANIM_DURATION / 2,
delay: ANIM_DELAY
});
scene.tweens.add({
globalScene.tweens.add({
targets: tradedPokemonSprite,
x: tradeBaseBg.displayWidth * 3 / 4,
ease: "Cubic.easeInOut",
duration: BASE_ANIM_DURATION / 2,
delay: ANIM_DELAY,
onComplete: () => {
scene.tweens.add({
globalScene.tweens.add({
targets: receivedPokemonSprite,
y: "+=200",
ease: "Cubic.easeInOut",
duration: BASE_ANIM_DURATION * 2,
delay: ANIM_DELAY,
});
scene.tweens.add({
globalScene.tweens.add({
targets: tradedPokemonSprite,
y: "-=200",
ease: "Cubic.easeInOut",
duration: BASE_ANIM_DURATION * 2,
delay: ANIM_DELAY,
onComplete: () => {
scene.tweens.add({
globalScene.tweens.add({
targets: tradeBaseBg,
alpha: 1,
ease: "Cubic.easeInOut",
@ -793,9 +798,9 @@ function doPokemonTradeFlyBySequence(scene: BattleScene, tradedPokemonSprite: Ph
});
}
function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPokemon, receivedPokemonSprite: Phaser.GameObjects.Sprite, receivedPokemonTintSprite: Phaser.GameObjects.Sprite, receivedPokeballSprite: Phaser.GameObjects.Sprite, receivedPbAtlasKey: string) {
function doTradeReceivedSequence(receivedPokemon: PlayerPokemon, receivedPokemonSprite: Phaser.GameObjects.Sprite, receivedPokemonTintSprite: Phaser.GameObjects.Sprite, receivedPokeballSprite: Phaser.GameObjects.Sprite, receivedPbAtlasKey: string) {
return new Promise<void>(resolve => {
const tradeContainer = scene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
const tradeContainer = globalScene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image;
receivedPokemonSprite.setVisible(false);
@ -812,7 +817,7 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke
// Received pokemon sparkles
let pokemonShinySparkle: Phaser.GameObjects.Sprite;
if (receivedPokemon.shiny) {
pokemonShinySparkle = scene.add.sprite(receivedPokemonSprite.x, receivedPokemonSprite.y, "shiny");
pokemonShinySparkle = globalScene.add.sprite(receivedPokemonSprite.x, receivedPokemonSprite.y, "shiny");
pokemonShinySparkle.setVisible(false);
tradeContainer.add(pokemonShinySparkle);
}
@ -820,19 +825,19 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke
const BASE_ANIM_DURATION = 1000;
// Pokeball falls to the screen
scene.playSound("se/pb_throw");
scene.tweens.add({
globalScene.playSound("se/pb_throw");
globalScene.tweens.add({
targets: receivedPokeballSprite,
y: "+=100",
ease: "Cubic.easeInOut",
duration: BASE_ANIM_DURATION,
onComplete: () => {
scene.playSound("se/pb_bounce_1");
scene.time.delayedCall(100, () => scene.playSound("se/pb_bounce_1"));
globalScene.playSound("se/pb_bounce_1");
globalScene.time.delayedCall(100, () => globalScene.playSound("se/pb_bounce_1"));
scene.time.delayedCall(2000, () => {
scene.playSound("se/pb_rel");
scene.fadeOutBgm(500, false);
globalScene.time.delayedCall(2000, () => {
globalScene.playSound("se/pb_rel");
globalScene.fadeOutBgm(500, false);
receivedPokemon.cry();
receivedPokemonTintSprite.scale = 0.25;
receivedPokemonTintSprite.alpha = 1;
@ -841,14 +846,14 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke
receivedPokemonTintSprite.alpha = 1;
receivedPokemonTintSprite.setVisible(true);
receivedPokeballSprite.setTexture("pb", `${receivedPbAtlasKey}_opening`);
scene.time.delayedCall(17, () => receivedPokeballSprite.setTexture("pb", `${receivedPbAtlasKey}_open`));
scene.tweens.add({
globalScene.time.delayedCall(17, () => receivedPokeballSprite.setTexture("pb", `${receivedPbAtlasKey}_open`));
globalScene.tweens.add({
targets: receivedPokemonSprite,
duration: 250,
ease: "Sine.easeOut",
scale: 1
});
scene.tweens.add({
globalScene.tweens.add({
targets: receivedPokemonTintSprite,
duration: 250,
ease: "Sine.easeOut",
@ -856,12 +861,12 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke
alpha: 0,
onComplete: () => {
if (receivedPokemon.shiny) {
scene.time.delayedCall(500, () => {
doShinySparkleAnim(scene, pokemonShinySparkle, receivedPokemon.variant);
globalScene.time.delayedCall(500, () => {
doShinySparkleAnim(pokemonShinySparkle, receivedPokemon.variant);
});
}
receivedPokeballSprite.destroy();
scene.time.delayedCall(2000, () => resolve());
globalScene.time.delayedCall(2000, () => resolve());
}
});
});

View File

@ -2,8 +2,9 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter-phase-utils";
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
@ -41,8 +42,8 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
},
])
.withIntroDialogue([{ text: `${namespace}:intro` }])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.setDialogueToken("damagePercentage", String(DAMAGE_PERCENTAGE));
encounter.setDialogueToken("option1RequiredMove", new PokemonMove(OPTION_1_REQUIRED_MOVE).getName());
@ -70,7 +71,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
},
],
})
.withOptionPhase(async (scene: BattleScene) => handlePokemonGuidingYouPhase(scene))
.withOptionPhase(async () => handlePokemonGuidingYouPhase())
.build()
)
.withOption(
@ -89,7 +90,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
},
],
})
.withOptionPhase(async (scene: BattleScene) => handlePokemonGuidingYouPhase(scene))
.withOptionPhase(async () => handlePokemonGuidingYouPhase())
.build()
)
.withSimpleOption(
@ -103,16 +104,16 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
},
],
},
async (scene: BattleScene) => {
const allowedPokemon = scene.getPlayerParty().filter((p) => p.isAllowedInBattle());
async () => {
const allowedPokemon = globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle());
for (const pkm of allowedPokemon) {
const percentage = DAMAGE_PERCENTAGE / 100;
const damage = Math.floor(pkm.getMaxHp() * percentage);
applyDamageToPokemon(scene, pkm, damage);
applyDamageToPokemon(pkm, damage);
}
leaveEncounterWithoutBattle(scene);
leaveEncounterWithoutBattle();
return true;
}
@ -126,19 +127,17 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
/**
* Generic handler for using a guiding pokemon to guide you back.
*
* @param scene Battle scene
*/
function handlePokemonGuidingYouPhase(scene: BattleScene) {
function handlePokemonGuidingYouPhase() {
const laprasSpecies = getPokemonSpecies(Species.LAPRAS);
const { mysteryEncounter } = scene.currentBattle;
const { mysteryEncounter } = globalScene.currentBattle;
if (mysteryEncounter?.selectedOption?.primaryPokemon?.id) {
setEncounterExp(scene, mysteryEncounter.selectedOption.primaryPokemon.id, laprasSpecies.baseExp, true);
setEncounterExp(mysteryEncounter.selectedOption.primaryPokemon.id, laprasSpecies.baseExp, true);
} else {
console.warn("Lost at sea: No guide pokemon found but pokemon guides player. huh!?");
}
leaveEncounterWithoutBattle(scene);
leaveEncounterWithoutBattle();
return true;
}

View File

@ -1,5 +1,6 @@
import type {
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
EnemyPartyConfig,
initBattleWithEnemyConfig,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
@ -13,9 +14,10 @@ import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import * as Utils from "#app/utils";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
@ -37,12 +39,12 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
text: `${namespace}:intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculates what trainers are available for battle in the encounter
// Normal difficulty trainer is randomly pulled from biome
const normalTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex);
const normalTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex);
const normalConfig = trainerConfigs[normalTrainerType].clone();
let female = false;
if (normalConfig.hasGenders) {
@ -57,16 +59,16 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
// Hard difficulty trainer is another random trainer, but with AVERAGE_BALANCED config
// Number of mons is based off wave: 1-20 is 2, 20-40 is 3, etc. capping at 6 after wave 100
let retries = 0;
let hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex);
let hardTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex);
while (retries < 5 && hardTrainerType === normalTrainerType) {
// Will try to use a different trainer from the normal trainer type
hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex);
hardTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex);
retries++;
}
const hardTemplate = new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER, false, true),
new TrainerPartyTemplate(
Math.min(Math.ceil(scene.currentBattle.waveIndex / 20), 5),
Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 20), 5),
PartyMemberStrength.AVERAGE,
false,
true
@ -87,8 +89,8 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
// Brutal trainer is pulled from pool of boss trainers (gym leaders) for the biome
// They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons
const brutalTrainerType = scene.arena.randomTrainerType(
scene.currentBattle.waveIndex,
const brutalTrainerType = globalScene.arena.randomTrainerType(
globalScene.currentBattle.waveIndex,
true
);
const e4Template = trainerPartyTemplates.ELITE_FOUR;
@ -145,18 +147,18 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Spawn standard trainer battle with memory mushroom reward
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM ], fillRemaining: true });
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM ], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let initBattlePromise: Promise<void>;
scene.executeWithSeedOffset(() => {
initBattlePromise = initBattleWithEnemyConfig(scene, config);
}, scene.currentBattle.waveIndex * 10);
globalScene.executeWithSeedOffset(() => {
initBattlePromise = initBattleWithEnemyConfig(config);
}, globalScene.currentBattle.waveIndex * 10);
await initBattlePromise!;
}
)
@ -170,18 +172,18 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Spawn hard fight
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: true });
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let initBattlePromise: Promise<void>;
scene.executeWithSeedOffset(() => {
initBattlePromise = initBattleWithEnemyConfig(scene, config);
}, scene.currentBattle.waveIndex * 100);
globalScene.executeWithSeedOffset(() => {
initBattlePromise = initBattleWithEnemyConfig(config);
}, globalScene.currentBattle.waveIndex * 100);
await initBattlePromise!;
}
)
@ -195,21 +197,21 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Spawn brutal fight
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[2];
// To avoid player level snowballing from picking this option
encounter.expMultiplier = 0.9;
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true });
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let initBattlePromise: Promise<void>;
scene.executeWithSeedOffset(() => {
initBattlePromise = initBattleWithEnemyConfig(scene, config);
}, scene.currentBattle.waveIndex * 1000);
globalScene.executeWithSeedOffset(() => {
initBattlePromise = initBattleWithEnemyConfig(config);
}, globalScene.currentBattle.waveIndex * 1000);
await initBattlePromise!;
}
)

View File

@ -1,8 +1,10 @@
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
@ -66,8 +68,8 @@ export const MysteriousChestEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate boss mon
const config: EnemyPartyConfig = {
@ -106,9 +108,9 @@ export const MysteriousChestEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Play animation
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const introVisuals = encounter.introVisuals!;
// Determine roll first
@ -128,13 +130,13 @@ export const MysteriousChestEncounter: MysteryEncounter =
introVisuals.spriteConfigs[1].disableAnimation = false;
introVisuals.playAnim();
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Open the chest
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const roll = encounter.misc.roll;
if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) {
// Choose between 2 COMMON / 2 GREAT tier items (20%)
setEncounterRewards(scene, {
setEncounterRewards({
guaranteedModifierTiers: [
ModifierTier.COMMON,
ModifierTier.COMMON,
@ -143,11 +145,11 @@ export const MysteriousChestEncounter: MysteryEncounter =
],
});
// Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}:option.1.normal`);
leaveEncounterWithoutBattle(scene);
queueEncounterMessage(`${namespace}:option.1.normal`);
leaveEncounterWithoutBattle();
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) {
// Choose between 3 ULTRA tier items (30%)
setEncounterRewards(scene, {
setEncounterRewards({
guaranteedModifierTiers: [
ModifierTier.ULTRA,
ModifierTier.ULTRA,
@ -155,39 +157,39 @@ export const MysteriousChestEncounter: MysteryEncounter =
],
});
// Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}:option.1.good`);
leaveEncounterWithoutBattle(scene);
queueEncounterMessage(`${namespace}:option.1.good`);
leaveEncounterWithoutBattle();
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) {
// Choose between 2 ROGUE tier items (10%)
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE ]});
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE ]});
// Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}:option.1.great`);
leaveEncounterWithoutBattle(scene);
queueEncounterMessage(`${namespace}:option.1.great`);
leaveEncounterWithoutBattle();
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT - MASTER_REWARDS_PERCENT) {
// Choose 1 MASTER tier item (5%)
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.MASTER ]});
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.MASTER ]});
// Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}:option.1.amazing`);
leaveEncounterWithoutBattle(scene);
queueEncounterMessage(`${namespace}:option.1.amazing`);
leaveEncounterWithoutBattle();
} else {
// Your highest level unfainted Pokemon gets OHKO. Start battle against a Gimmighoul (35%)
const highestLevelPokemon = getHighestLevelPlayerPokemon(scene, true, false);
koPlayerPokemon(scene, highestLevelPokemon);
const highestLevelPokemon = getHighestLevelPlayerPokemon(true, false);
koPlayerPokemon(highestLevelPokemon);
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
await showEncounterText(scene, `${namespace}:option.1.bad`);
await showEncounterText(`${namespace}:option.1.bad`);
// Handle game over edge case
const allowedPokemon = scene.getPokemonAllowedInBattle();
const allowedPokemon = globalScene.getPokemonAllowedInBattle();
if (allowedPokemon.length === 0) {
// If there are no longer any legal pokemon in the party, game over.
scene.clearPhaseQueue();
scene.unshiftPhase(new GameOverPhase(scene));
globalScene.clearPhaseQueue();
globalScene.unshiftPhase(new GameOverPhase());
} else {
// Show which Pokemon was KOed, then start battle against Gimmighoul
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
setEncounterRewards(scene, { fillRemaining: true });
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
await transitionMysteryEncounterIntroVisuals(true, true, 500);
setEncounterRewards({ fillRemaining: true });
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
}
}
})
@ -203,9 +205,9 @@ export const MysteriousChestEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
return true;
}
)

View File

@ -1,8 +1,9 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -10,7 +11,8 @@ import { Stat } from "#enums/stat";
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import i18next from "i18next";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
@ -52,20 +54,20 @@ export const PartTimerEncounter: MysteryEncounter =
text: `${namespace}:intro_dialogue`,
},
])
.withOnInit((scene: BattleScene) => {
.withOnInit(() => {
// Load sfx
scene.loadSe("PRSFX- Horn Drill1", "battle_anims", "PRSFX- Horn Drill1.wav");
scene.loadSe("PRSFX- Horn Drill3", "battle_anims", "PRSFX- Horn Drill3.wav");
scene.loadSe("PRSFX- Guillotine2", "battle_anims", "PRSFX- Guillotine2.wav");
scene.loadSe("PRSFX- Heavy Slam2", "battle_anims", "PRSFX- Heavy Slam2.wav");
globalScene.loadSe("PRSFX- Horn Drill1", "battle_anims", "PRSFX- Horn Drill1.wav");
globalScene.loadSe("PRSFX- Horn Drill3", "battle_anims", "PRSFX- Horn Drill3.wav");
globalScene.loadSe("PRSFX- Guillotine2", "battle_anims", "PRSFX- Guillotine2.wav");
globalScene.loadSe("PRSFX- Heavy Slam2", "battle_anims", "PRSFX- Heavy Slam2.wav");
scene.loadSe("PRSFX- Agility", "battle_anims", "PRSFX- Agility.wav");
scene.loadSe("PRSFX- Extremespeed1", "battle_anims", "PRSFX- Extremespeed1.wav");
scene.loadSe("PRSFX- Accelerock1", "battle_anims", "PRSFX- Accelerock1.wav");
globalScene.loadSe("PRSFX- Agility", "battle_anims", "PRSFX- Agility.wav");
globalScene.loadSe("PRSFX- Extremespeed1", "battle_anims", "PRSFX- Extremespeed1.wav");
globalScene.loadSe("PRSFX- Accelerock1", "battle_anims", "PRSFX- Accelerock1.wav");
scene.loadSe("PRSFX- Captivate", "battle_anims", "PRSFX- Captivate.wav");
scene.loadSe("PRSFX- Attract2", "battle_anims", "PRSFX- Attract2.wav");
scene.loadSe("PRSFX- Aurora Veil2", "battle_anims", "PRSFX- Aurora Veil2.wav");
globalScene.loadSe("PRSFX- Captivate", "battle_anims", "PRSFX- Captivate.wav");
globalScene.loadSe("PRSFX- Attract2", "battle_anims", "PRSFX- Attract2.wav");
globalScene.loadSe("PRSFX- Aurora Veil2", "battle_anims", "PRSFX- Aurora Veil2.wav");
return true;
})
@ -84,8 +86,8 @@ export const PartTimerEncounter: MysteryEncounter =
}
]
})
.withPreOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
@ -109,41 +111,41 @@ export const PartTimerEncounter: MysteryEncounter =
}
});
setEncounterExp(scene, pokemon.id, 100);
setEncounterExp(pokemon.id, 100);
// Hide intro visuals
transitionMysteryEncounterIntroVisuals(scene, true, false);
transitionMysteryEncounterIntroVisuals(true, false);
// Play sfx for "working"
doDeliverySfx(scene);
doDeliverySfx();
};
// Only Pokemon non-KOd pokemon can be selected
const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Pick Deliveries
// Bring visuals back in
await transitionMysteryEncounterIntroVisuals(scene, false, false);
await transitionMysteryEncounterIntroVisuals(false, false);
const moneyMultiplier = scene.currentBattle.mysteryEncounter!.misc.moneyMultiplier;
const moneyMultiplier = globalScene.currentBattle.mysteryEncounter!.misc.moneyMultiplier;
// Give money and do dialogue
if (moneyMultiplier > 2.5) {
await showEncounterDialogue(scene, `${namespace}:job_complete_good`, `${namespace}:speaker`);
await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`);
} else {
await showEncounterDialogue(scene, `${namespace}:job_complete_bad`, `${namespace}:speaker`);
await showEncounterDialogue(`${namespace}:job_complete_bad`, `${namespace}:speaker`);
}
const moneyChange = scene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(scene, moneyChange, true, false);
await showEncounterText(scene, i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
await showEncounterText(scene, `${namespace}:pokemon_tired`);
const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(moneyChange, true, false);
await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
await showEncounterText(`${namespace}:pokemon_tired`);
setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene);
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle();
})
.build()
)
@ -158,8 +160,8 @@ export const PartTimerEncounter: MysteryEncounter =
}
]
})
.withPreOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
@ -186,41 +188,41 @@ export const PartTimerEncounter: MysteryEncounter =
}
});
setEncounterExp(scene, pokemon.id, 100);
setEncounterExp(pokemon.id, 100);
// Hide intro visuals
transitionMysteryEncounterIntroVisuals(scene, true, false);
transitionMysteryEncounterIntroVisuals(true, false);
// Play sfx for "working"
doStrongWorkSfx(scene);
doStrongWorkSfx();
};
// Only Pokemon non-KOd pokemon can be selected
const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Pick Move Warehouse items
// Bring visuals back in
await transitionMysteryEncounterIntroVisuals(scene, false, false);
await transitionMysteryEncounterIntroVisuals(false, false);
const moneyMultiplier = scene.currentBattle.mysteryEncounter!.misc.moneyMultiplier;
const moneyMultiplier = globalScene.currentBattle.mysteryEncounter!.misc.moneyMultiplier;
// Give money and do dialogue
if (moneyMultiplier > 2.5) {
await showEncounterDialogue(scene, `${namespace}:job_complete_good`, `${namespace}:speaker`);
await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`);
} else {
await showEncounterDialogue(scene, `${namespace}:job_complete_bad`, `${namespace}:speaker`);
await showEncounterDialogue(`${namespace}:job_complete_bad`, `${namespace}:speaker`);
}
const moneyChange = scene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(scene, moneyChange, true, false);
await showEncounterText(scene, i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
await showEncounterText(scene, `${namespace}:pokemon_tired`);
const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(moneyChange, true, false);
await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
await showEncounterText(`${namespace}:pokemon_tired`);
setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene);
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle();
})
.build()
)
@ -238,8 +240,8 @@ export const PartTimerEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const selectedPokemon = encounter.selectedOption?.primaryPokemon!;
encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender());
@ -251,28 +253,28 @@ export const PartTimerEncounter: MysteryEncounter =
}
});
setEncounterExp(scene, selectedPokemon.id, 100);
setEncounterExp(selectedPokemon.id, 100);
// Hide intro visuals
transitionMysteryEncounterIntroVisuals(scene, true, false);
transitionMysteryEncounterIntroVisuals(true, false);
// Play sfx for "working"
doSalesSfx(scene);
doSalesSfx();
return true;
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Assist with Sales
// Bring visuals back in
await transitionMysteryEncounterIntroVisuals(scene, false, false);
await transitionMysteryEncounterIntroVisuals(false, false);
// Give money and do dialogue
await showEncounterDialogue(scene, `${namespace}:job_complete_good`, `${namespace}:speaker`);
const moneyChange = scene.getWaveMoneyAmount(2.5);
updatePlayerMoney(scene, moneyChange, true, false);
await showEncounterText(scene, i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
await showEncounterText(scene, `${namespace}:pokemon_tired`);
await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`);
const moneyChange = globalScene.getWaveMoneyAmount(2.5);
updatePlayerMoney(moneyChange, true, false);
await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
await showEncounterText(`${namespace}:pokemon_tired`);
setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene);
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle();
})
.build()
)
@ -284,51 +286,51 @@ export const PartTimerEncounter: MysteryEncounter =
])
.build();
function doStrongWorkSfx(scene: BattleScene) {
scene.playSound("battle_anims/PRSFX- Horn Drill1");
scene.playSound("battle_anims/PRSFX- Horn Drill1");
function doStrongWorkSfx() {
globalScene.playSound("battle_anims/PRSFX- Horn Drill1");
globalScene.playSound("battle_anims/PRSFX- Horn Drill1");
scene.time.delayedCall(1000, () => {
scene.playSound("battle_anims/PRSFX- Guillotine2");
globalScene.time.delayedCall(1000, () => {
globalScene.playSound("battle_anims/PRSFX- Guillotine2");
});
scene.time.delayedCall(2000, () => {
scene.playSound("battle_anims/PRSFX- Heavy Slam2");
globalScene.time.delayedCall(2000, () => {
globalScene.playSound("battle_anims/PRSFX- Heavy Slam2");
});
scene.time.delayedCall(2500, () => {
scene.playSound("battle_anims/PRSFX- Guillotine2");
globalScene.time.delayedCall(2500, () => {
globalScene.playSound("battle_anims/PRSFX- Guillotine2");
});
}
function doDeliverySfx(scene: BattleScene) {
scene.playSound("battle_anims/PRSFX- Accelerock1");
function doDeliverySfx() {
globalScene.playSound("battle_anims/PRSFX- Accelerock1");
scene.time.delayedCall(1500, () => {
scene.playSound("battle_anims/PRSFX- Extremespeed1");
globalScene.time.delayedCall(1500, () => {
globalScene.playSound("battle_anims/PRSFX- Extremespeed1");
});
scene.time.delayedCall(2000, () => {
scene.playSound("battle_anims/PRSFX- Extremespeed1");
globalScene.time.delayedCall(2000, () => {
globalScene.playSound("battle_anims/PRSFX- Extremespeed1");
});
scene.time.delayedCall(2250, () => {
scene.playSound("battle_anims/PRSFX- Agility");
globalScene.time.delayedCall(2250, () => {
globalScene.playSound("battle_anims/PRSFX- Agility");
});
}
function doSalesSfx(scene: BattleScene) {
scene.playSound("battle_anims/PRSFX- Captivate");
function doSalesSfx() {
globalScene.playSound("battle_anims/PRSFX- Captivate");
scene.time.delayedCall(1500, () => {
scene.playSound("battle_anims/PRSFX- Attract2");
globalScene.time.delayedCall(1500, () => {
globalScene.playSound("battle_anims/PRSFX- Attract2");
});
scene.time.delayedCall(2000, () => {
scene.playSound("battle_anims/PRSFX- Aurora Veil2");
globalScene.time.delayedCall(2000, () => {
globalScene.playSound("battle_anims/PRSFX- Aurora Veil2");
});
scene.time.delayedCall(3000, () => {
scene.playSound("battle_anims/PRSFX- Attract2");
globalScene.time.delayedCall(3000, () => {
globalScene.playSound("battle_anims/PRSFX- Attract2");
});
}

View File

@ -1,15 +1,18 @@
import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounterOption, { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { TrainerSlot } from "#app/data/trainer-config";
import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier";
import { EnemyPokemon } from "#app/field/pokemon";
import type { EnemyPokemon } from "#app/field/pokemon";
import { PokeballType } from "#enums/pokeball";
import { PlayerGender } from "#enums/player-gender";
import { IntegerHolder, randSeedInt } from "#app/utils";
import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterCost, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -59,8 +62,8 @@ export const SafariZoneEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
scene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString());
.withOnInit(() => {
globalScene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString());
return true;
})
.withOption(MysteryEncounterOptionBuilder
@ -75,25 +78,25 @@ export const SafariZoneEncounter: MysteryEncounter =
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Start safari encounter
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.continuousEncounter = true;
encounter.misc = {
safariPokemonRemaining: NUM_SAFARI_ENCOUNTERS
};
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
// Load bait/mud assets
scene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
scene.loadSe("PRSFX- Sludge Bomb2", "battle_anims", "PRSFX- Sludge Bomb2.wav");
scene.loadSe("PRSFX- Taunt2", "battle_anims", "PRSFX- Taunt2.wav");
scene.loadAtlas("safari_zone_bait", "mystery-encounters");
scene.loadAtlas("safari_zone_mud", "mystery-encounters");
globalScene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
globalScene.loadSe("PRSFX- Sludge Bomb2", "battle_anims", "PRSFX- Sludge Bomb2.wav");
globalScene.loadSe("PRSFX- Taunt2", "battle_anims", "PRSFX- Taunt2.wav");
globalScene.loadAtlas("safari_zone_bait", "mystery-encounters");
globalScene.loadAtlas("safari_zone_mud", "mystery-encounters");
// Clear enemy party
scene.currentBattle.enemyParty = [];
await transitionMysteryEncounterIntroVisuals(scene);
await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, hideDescription: true });
globalScene.currentBattle.enemyParty = [];
await transitionMysteryEncounterIntroVisuals();
await summonSafariPokemon();
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, hideDescription: true });
return true;
})
.build()
@ -108,9 +111,9 @@ export const SafariZoneEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
return true;
}
)
@ -143,26 +146,26 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
}
],
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Throw a ball option
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const pokemon = encounter.misc.pokemon;
const catchResult = await throwPokeball(scene, pokemon);
const catchResult = await throwPokeball(pokemon);
if (catchResult) {
// You caught pokemon
// Check how many safari pokemon left
if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true });
await summonSafariPokemon();
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true });
} else {
// End safari mode
encounter.continuousEncounter = false;
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
}
} else {
// Pokemon catch failed, end turn
await doEndTurn(scene, 0);
await doEndTurn(0);
}
return true;
})
@ -178,22 +181,22 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Throw bait option
const pokemon = scene.currentBattle.mysteryEncounter!.misc.pokemon;
await throwBait(scene, pokemon);
const pokemon = globalScene.currentBattle.mysteryEncounter!.misc.pokemon;
await throwBait(pokemon);
// 100% chance to increase catch stage +2
tryChangeCatchStage(scene, 2);
tryChangeCatchStage(2);
// 80% chance to increase flee stage +1
const fleeChangeResult = tryChangeFleeStage(scene, 1, 8);
const fleeChangeResult = tryChangeFleeStage(1, 8);
if (!fleeChangeResult) {
await showEncounterText(scene, getEncounterText(scene, `${namespace}:safari.busy_eating`) ?? "", null, 1000, false );
await showEncounterText(getEncounterText(`${namespace}:safari.busy_eating`) ?? "", null, 1000, false );
} else {
await showEncounterText(scene, getEncounterText(scene, `${namespace}:safari.eating`) ?? "", null, 1000, false);
await showEncounterText(getEncounterText(`${namespace}:safari.eating`) ?? "", null, 1000, false);
}
await doEndTurn(scene, 1);
await doEndTurn(1);
return true;
})
.build(),
@ -208,21 +211,21 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Throw mud option
const pokemon = scene.currentBattle.mysteryEncounter!.misc.pokemon;
await throwMud(scene, pokemon);
const pokemon = globalScene.currentBattle.mysteryEncounter!.misc.pokemon;
await throwMud(pokemon);
// 100% chance to decrease flee stage -2
tryChangeFleeStage(scene, -2);
tryChangeFleeStage(-2);
// 80% chance to decrease catch stage -1
const catchChangeResult = tryChangeCatchStage(scene, -1, 8);
const catchChangeResult = tryChangeCatchStage(-1, 8);
if (!catchChangeResult) {
await showEncounterText(scene, getEncounterText(scene, `${namespace}:safari.beside_itself_angry`) ?? "", null, 1000, false );
await showEncounterText(getEncounterText(`${namespace}:safari.beside_itself_angry`) ?? "", null, 1000, false );
} else {
await showEncounterText(scene, getEncounterText(scene, `${namespace}:safari.angry`) ?? "", null, 1000, false );
await showEncounterText(getEncounterText(`${namespace}:safari.angry`) ?? "", null, 1000, false );
}
await doEndTurn(scene, 2);
await doEndTurn(2);
return true;
})
.build(),
@ -232,40 +235,40 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
buttonLabel: `${namespace}:safari.4.label`,
buttonTooltip: `${namespace}:safari.4.tooltip`,
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Flee option
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const pokemon = encounter.misc.pokemon;
await doPlayerFlee(scene, pokemon);
await doPlayerFlee(pokemon);
// Check how many safari pokemon left
if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true });
await summonSafariPokemon();
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true });
} else {
// End safari mode
encounter.continuousEncounter = false;
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
}
return true;
})
.build()
];
async function summonSafariPokemon(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!;
async function summonSafariPokemon() {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Message pokemon remaining
encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining);
scene.queueMessage(getEncounterText(scene, `${namespace}:safari.remaining_count`) ?? "", null, true);
globalScene.queueMessage(getEncounterText(`${namespace}:safari.remaining_count`) ?? "", null, true);
// Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken
// Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
let enemySpecies;
let pokemon;
scene.executeWithSeedOffset(() => {
globalScene.executeWithSeedOffset(() => {
enemySpecies = getSafariSpeciesSpawn();
const level = scene.currentBattle.getLevelForWave();
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, scene.gameMode));
pokemon = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false);
const level = globalScene.currentBattle.getLevelForWave();
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, globalScene.gameMode));
pokemon = globalScene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false);
// Roll shiny twice
if (!pokemon.shiny) {
@ -277,7 +280,7 @@ async function summonSafariPokemon(scene: BattleScene) {
const hiddenIndex = pokemon.species.ability2 ? 2 : 1;
if (pokemon.abilityIndex < hiddenIndex) {
const hiddenAbilityChance = new IntegerHolder(256);
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
@ -289,10 +292,10 @@ async function summonSafariPokemon(scene: BattleScene) {
pokemon.calculateStats();
scene.currentBattle.enemyParty.unshift(pokemon);
}, scene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining);
globalScene.currentBattle.enemyParty.unshift(pokemon);
}, globalScene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining);
scene.gameData.setPokemonSeen(pokemon, true);
globalScene.gameData.setPokemonSeen(pokemon, true);
await pokemon.loadAssets();
// Reset safari catch and flee rates
@ -301,7 +304,7 @@ async function summonSafariPokemon(scene: BattleScene) {
encounter.misc.pokemon = pokemon;
encounter.misc.safariPokemonRemaining -= 1;
scene.unshiftPhase(new SummonPhase(scene, 0, false));
globalScene.unshiftPhase(new SummonPhase(0, false));
encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon));
@ -310,49 +313,49 @@ async function summonSafariPokemon(scene: BattleScene) {
// shows up and the IV scanner breaks. For now, we place the IV scanner code
// separately so that at least the IV scanner works.
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) {
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
globalScene.pushPhase(new ScanIvsPhase(pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
}
}
function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
function throwPokeball(pokemon: EnemyPokemon): Promise<boolean> {
const baseCatchRate = pokemon.species.catchRate;
// Catch stage ranges from -6 to +6 (like stat boost stages)
const safariCatchStage = scene.currentBattle.mysteryEncounter!.misc.catchStage;
const safariCatchStage = globalScene.currentBattle.mysteryEncounter!.misc.catchStage;
// Catch modifier ranges from 2/8 (-6 stage) to 8/2 (+6)
const safariModifier = (2 + Math.min(Math.max(safariCatchStage, 0), 6)) / (2 - Math.max(Math.min(safariCatchStage, 0), -6));
// Catch rate same as safari ball
const pokeballMultiplier = 1.5;
const catchRate = Math.round(baseCatchRate * pokeballMultiplier * safariModifier);
const ballTwitchRate = Math.round(1048560 / Math.sqrt(Math.sqrt(16711680 / catchRate)));
return trainerThrowPokeball(scene, pokemon, PokeballType.POKEBALL, ballTwitchRate);
return trainerThrowPokeball(pokemon, PokeballType.POKEBALL, ballTwitchRate);
}
async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
async function throwBait(pokemon: EnemyPokemon): Promise<boolean> {
const originalY: number = pokemon.y;
const fpOffset = pokemon.getFieldPositionOffset();
const bait: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "safari_zone_bait", "0001.png");
const bait: Phaser.GameObjects.Sprite = globalScene.addFieldSprite(16 + 75, 80 + 25, "safari_zone_bait", "0001.png");
bait.setOrigin(0.5, 0.625);
scene.field.add(bait);
globalScene.field.add(bait);
return new Promise(resolve => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
scene.playSound("se/pb_throw");
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
globalScene.playSound("se/pb_throw");
// Trainer throw frames
scene.trainer.setFrame("2");
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
scene.trainer.setFrame("3");
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
globalScene.trainer.setFrame("2");
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
globalScene.trainer.setFrame("3");
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => {
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
});
});
// Pokeball move and catch logic
scene.tweens.add({
globalScene.tweens.add({
targets: bait,
x: { value: 210 + fpOffset[0], ease: "Linear" },
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
@ -360,8 +363,8 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boo
onComplete: () => {
let index = 1;
scene.time.delayedCall(768, () => {
scene.tweens.add({
globalScene.time.delayedCall(768, () => {
globalScene.tweens.add({
targets: pokemon,
duration: 150,
ease: "Cubic.easeOut",
@ -369,12 +372,12 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boo
y: originalY - 5,
loop: 6,
onStart: () => {
scene.playSound("battle_anims/PRSFX- Bug Bite");
globalScene.playSound("battle_anims/PRSFX- Bug Bite");
bait.setFrame("0002.png");
},
onLoop: () => {
if (index % 2 === 0) {
scene.playSound("battle_anims/PRSFX- Bug Bite");
globalScene.playSound("battle_anims/PRSFX- Bug Bite");
}
if (index === 4) {
bait.setFrame("0003.png");
@ -382,7 +385,7 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boo
index++;
},
onComplete: () => {
scene.time.delayedCall(256, () => {
globalScene.time.delayedCall(256, () => {
bait.destroy();
resolve(true);
});
@ -395,55 +398,55 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boo
});
}
async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
async function throwMud(pokemon: EnemyPokemon): Promise<boolean> {
const originalY: number = pokemon.y;
const fpOffset = pokemon.getFieldPositionOffset();
const mud: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 35, "safari_zone_mud", "0001.png");
const mud: Phaser.GameObjects.Sprite = globalScene.addFieldSprite(16 + 75, 80 + 35, "safari_zone_mud", "0001.png");
mud.setOrigin(0.5, 0.625);
scene.field.add(mud);
globalScene.field.add(mud);
return new Promise(resolve => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
scene.playSound("se/pb_throw");
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
globalScene.playSound("se/pb_throw");
// Trainer throw frames
scene.trainer.setFrame("2");
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
scene.trainer.setFrame("3");
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
globalScene.trainer.setFrame("2");
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
globalScene.trainer.setFrame("3");
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => {
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
});
});
// Mud throw and splat
scene.tweens.add({
globalScene.tweens.add({
targets: mud,
x: { value: 230 + fpOffset[0], ease: "Linear" },
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
duration: 500,
onComplete: () => {
// Mud frame 2
scene.playSound("battle_anims/PRSFX- Sludge Bomb2");
globalScene.playSound("battle_anims/PRSFX- Sludge Bomb2");
mud.setFrame("0002.png");
// Mud splat
scene.time.delayedCall(200, () => {
globalScene.time.delayedCall(200, () => {
mud.setFrame("0003.png");
scene.time.delayedCall(400, () => {
globalScene.time.delayedCall(400, () => {
mud.setFrame("0004.png");
});
});
// Fade mud then angry animation
scene.tweens.add({
globalScene.tweens.add({
targets: mud,
alpha: 0,
ease: "Cubic.easeIn",
duration: 1000,
onComplete: () => {
mud.destroy();
scene.tweens.add({
globalScene.tweens.add({
targets: pokemon,
duration: 300,
ease: "Cubic.easeOut",
@ -451,10 +454,10 @@ async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<bool
y: originalY - 20,
loop: 1,
onStart: () => {
scene.playSound("battle_anims/PRSFX- Taunt2");
globalScene.playSound("battle_anims/PRSFX- Taunt2");
},
onLoop: () => {
scene.playSound("battle_anims/PRSFX- Taunt2");
globalScene.playSound("battle_anims/PRSFX- Taunt2");
},
onComplete: () => {
resolve(true);
@ -478,53 +481,53 @@ function isPokemonFlee(pokemon: EnemyPokemon, fleeStage: number): boolean {
return roll < fleeRate;
}
function tryChangeFleeStage(scene: BattleScene, change: number, chance?: number): boolean {
function tryChangeFleeStage(change: number, chance?: number): boolean {
if (chance && randSeedInt(10) >= chance) {
return false;
}
const currentFleeStage = scene.currentBattle.mysteryEncounter!.misc.fleeStage ?? 0;
scene.currentBattle.mysteryEncounter!.misc.fleeStage = Math.min(Math.max(currentFleeStage + change, -6), 6);
const currentFleeStage = globalScene.currentBattle.mysteryEncounter!.misc.fleeStage ?? 0;
globalScene.currentBattle.mysteryEncounter!.misc.fleeStage = Math.min(Math.max(currentFleeStage + change, -6), 6);
return true;
}
function tryChangeCatchStage(scene: BattleScene, change: number, chance?: number): boolean {
function tryChangeCatchStage(change: number, chance?: number): boolean {
if (chance && randSeedInt(10) >= chance) {
return false;
}
const currentCatchStage = scene.currentBattle.mysteryEncounter!.misc.catchStage ?? 0;
scene.currentBattle.mysteryEncounter!.misc.catchStage = Math.min(Math.max(currentCatchStage + change, -6), 6);
const currentCatchStage = globalScene.currentBattle.mysteryEncounter!.misc.catchStage ?? 0;
globalScene.currentBattle.mysteryEncounter!.misc.catchStage = Math.min(Math.max(currentCatchStage + change, -6), 6);
return true;
}
async function doEndTurn(scene: BattleScene, cursorIndex: number) {
async function doEndTurn(cursorIndex: number) {
// First cleanup and destroy old Pokemon objects that were left in the enemyParty
// They are left in enemyParty temporarily so that VictoryPhase properly handles EXP
const party = scene.getEnemyParty();
const party = globalScene.getEnemyParty();
if (party.length > 1) {
for (let i = 1; i < party.length; i++) {
party[i].destroy();
}
scene.currentBattle.enemyParty = party.slice(0, 1);
globalScene.currentBattle.enemyParty = party.slice(0, 1);
}
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const pokemon = encounter.misc.pokemon;
const isFlee = isPokemonFlee(pokemon, encounter.misc.fleeStage);
if (isFlee) {
// Pokemon flees!
await doPokemonFlee(scene, pokemon);
await doPokemonFlee(pokemon);
// Check how many safari pokemon left
if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
await summonSafariPokemon();
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
} else {
// End safari mode
encounter.continuousEncounter = false;
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
}
} else {
scene.queueMessage(getEncounterText(scene, `${namespace}:safari.watching`) ?? "", 0, null, 1000);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
globalScene.queueMessage(getEncounterText(`${namespace}:safari.watching`) ?? "", 0, null, 1000);
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
}
}

View File

@ -1,18 +1,20 @@
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon, isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Nature } from "#enums/nature";
import type { Nature } from "#enums/nature";
import { getNatureName } from "#app/data/nature";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import i18next from "i18next";
@ -79,15 +81,15 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Update money
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
// Calculate modifiers and dialogue tokens
const modifiers = [
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER)!,
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER)!,
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
];
encounter.setDialogueToken("boost1", modifiers[0].name);
encounter.setDialogueToken("boost2", modifiers[1].name);
@ -103,34 +105,34 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
if (!pokemon.isAllowedInChallenge()) {
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
}
if (!encounter.pokemonMeetsPrimaryRequirements(scene, pokemon)) {
return getEncounterText(scene, `${namespace}:invalid_selection`) ?? null;
if (!encounter.pokemonMeetsPrimaryRequirements(pokemon)) {
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Choose Cheap Option
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const chosenPokemon = encounter.misc.chosenPokemon;
const modifiers = encounter.misc.modifiers;
for (const modType of modifiers) {
await applyModifierTypeToPlayerPokemon(scene, chosenPokemon, modType);
await applyModifierTypeToPlayerPokemon(chosenPokemon, modType);
}
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
})
.withPostOptionPhase(async (scene: BattleScene) => {
.withPostOptionPhase(async () => {
// Damage and status applied after dealer leaves (to make thematic sense)
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const chosenPokemon = encounter.misc.chosenPokemon as PlayerPokemon;
// Pokemon takes half max HP damage and nature is randomized (does not update dex)
applyDamageToPokemon(scene, chosenPokemon, Math.floor(chosenPokemon.getMaxHp() / 2));
applyDamageToPokemon(chosenPokemon, Math.floor(chosenPokemon.getMaxHp() / 2));
const currentNature = chosenPokemon.nature;
let newNature = randSeedInt(25) as Nature;
@ -140,8 +142,8 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
chosenPokemon.setCustomNature(newNature);
encounter.setDialogueToken("newNature", getNatureName(newNature));
queueEncounterMessage(scene, `${namespace}:cheap_side_effects`);
setEncounterExp(scene, [ chosenPokemon.id ], 100);
queueEncounterMessage(`${namespace}:cheap_side_effects`);
setEncounterExp([ chosenPokemon.id ], 100);
await chosenPokemon.updateInfo();
})
.build()
@ -159,15 +161,15 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Update money
updatePlayerMoney(scene, -(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney);
updatePlayerMoney(-(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney);
// Calculate modifiers and dialogue tokens
const modifiers = [
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER)!,
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER)!,
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
];
encounter.setDialogueToken("boost1", modifiers[0].name);
encounter.setDialogueToken("boost2", modifiers[1].name);
@ -179,30 +181,30 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
// Only Pokemon that can gain benefits are unfainted
const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Choose Expensive Option
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const chosenPokemon = encounter.misc.chosenPokemon;
const modifiers = encounter.misc.modifiers;
for (const modType of modifiers) {
await applyModifierTypeToPlayerPokemon(scene, chosenPokemon, modType);
await applyModifierTypeToPlayerPokemon(chosenPokemon, modType);
}
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
})
.withPostOptionPhase(async (scene: BattleScene) => {
.withPostOptionPhase(async () => {
// Status applied after dealer leaves (to make thematic sense)
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const chosenPokemon = encounter.misc.chosenPokemon;
queueEncounterMessage(scene, `${namespace}:no_bad_effects`);
setEncounterExp(scene, [ chosenPokemon.id ], 100);
queueEncounterMessage(`${namespace}:no_bad_effects`);
setEncounterExp([ chosenPokemon.id ], 100);
await chosenPokemon.updateInfo();
})
@ -219,9 +221,9 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
}
]
},
async (scene: BattleScene) => {
async () => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
return true;
}
)

View File

@ -1,13 +1,16 @@
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import { StatusEffect } from "#enums/status-effect";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
@ -51,8 +54,8 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
text: `${namespace}:intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
console.log(encounter);
// Calculate boss mon
@ -65,11 +68,11 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
moveSet: [ Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType,
stackCount: 2
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType,
stackCount: 2
},
],
@ -83,7 +86,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
encounter.enemyPartyConfigs = [ config ];
// Load animations/sfx for Snorlax fight start moves
loadCustomMovesForEncounter(scene, [ Moves.SNORE ]);
loadCustomMovesForEncounter([ Moves.SNORE ]);
encounter.setDialogueToken("snorlaxName", getPokemonSpecies(Species.SNORLAX).getName());
@ -103,10 +106,10 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter!;
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: true });
const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: true });
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
@ -120,7 +123,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
move: new PokemonMove(Moves.SNORE),
ignorePp: true
});
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
}
)
.withSimpleOption(
@ -133,12 +136,12 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Fall asleep waiting for Snorlax
// Full heal party
scene.unshiftPhase(new PartyHealPhase(scene, true));
queueEncounterMessage(scene, `${namespace}:option.2.rest_result`);
leaveEncounterWithoutBattle(scene);
globalScene.unshiftPhase(new PartyHealPhase(true));
queueEncounterMessage(`${namespace}:option.2.rest_result`);
leaveEncounterWithoutBattle();
}
)
.withOption(
@ -155,13 +158,13 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
}
]
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Steal the Snorlax's Leftovers
const instance = scene.currentBattle.mysteryEncounter!;
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: false });
const instance = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: false });
// Snorlax exp to Pokemon that did the stealing
setEncounterExp(scene, instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp);
leaveEncounterWithoutBattle(scene);
setEncounterExp(instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp);
leaveEncounterWithoutBattle();
})
.build()
)

View File

@ -1,10 +1,13 @@
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierTypeOption, initBattleWithEnemyConfig, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoneyRequirement, WaveModulusRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import Pokemon, { EnemyPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon } from "#app/field/pokemon";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import PokemonData from "#app/system/pokemon-data";
@ -62,9 +65,9 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
const price = scene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER);
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const price = globalScene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER);
encounter.setDialogueToken("price", price.toString());
encounter.misc = {
price
@ -85,14 +88,14 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
}
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Update money
updatePlayerMoney(scene, -scene.currentBattle.mysteryEncounter!.misc.price, true, false);
updatePlayerMoney(-globalScene.currentBattle.mysteryEncounter!.misc.price, true, false);
})
.withOptionPhase(async (scene: BattleScene) => {
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit(scene);
setEncounterRewards(scene, { fillRemaining: true });
await initBattleWithEnemyConfig(scene, config);
.withOptionPhase(async () => {
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit();
setEncounterRewards({ fillRemaining: true });
await initBattleWithEnemyConfig(config);
})
.build()
)
@ -110,11 +113,11 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
}
],
})
.withOptionPhase(async (scene: BattleScene) => {
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit(scene);
setEncounterRewards(scene, { fillRemaining: true });
setEncounterExp(scene, scene.currentBattle.mysteryEncounter!.selectedOption!.primaryPokemon!.id, 100);
await initBattleWithEnemyConfig(scene, config);
.withOptionPhase(async () => {
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit();
setEncounterRewards({ fillRemaining: true });
setEncounterExp(globalScene.currentBattle.mysteryEncounter!.selectedOption!.primaryPokemon!.id, 100);
await initBattleWithEnemyConfig(config);
})
.build()
)
@ -128,14 +131,14 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Inspect the Machine
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Init enemy
const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getPlayerParty()), true);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true);
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = {
pokemonConfigs: [{
@ -146,36 +149,36 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
}],
};
const magnet = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.STEEL ])!;
const metalCoat = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.ELECTRIC ])!;
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ magnet, metalCoat ], fillRemaining: true });
await transitionMysteryEncounterIntroVisuals(scene, true, true);
await initBattleWithEnemyConfig(scene, config);
const magnet = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.STEEL ])!;
const metalCoat = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.ELECTRIC ])!;
setEncounterRewards({ guaranteedModifierTypeOptions: [ magnet, metalCoat ], fillRemaining: true });
await transitionMysteryEncounterIntroVisuals(true, true);
await initBattleWithEnemyConfig(config);
}
)
.build();
async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!;
async function doBiomeTransitionDialogueAndBattleInit() {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate new biome (cannot be current biome)
const filteredBiomes = BIOME_CANDIDATES.filter(b => scene.arena.biomeType !== b);
const filteredBiomes = BIOME_CANDIDATES.filter(b => globalScene.arena.biomeType !== b);
const newBiome = filteredBiomes[randSeedInt(filteredBiomes.length)];
// Show dialogue and transition biome
await showEncounterText(scene, `${namespace}:transport`);
await Promise.all([ animateBiomeChange(scene, newBiome), transitionMysteryEncounterIntroVisuals(scene) ]);
scene.playBgm();
await showEncounterText(scene, `${namespace}:attacked`);
await showEncounterText(`${namespace}:transport`);
await Promise.all([ animateBiomeChange(newBiome), transitionMysteryEncounterIntroVisuals() ]);
globalScene.playBgm();
await showEncounterText(`${namespace}:attacked`);
// Init enemy
const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getPlayerParty()), true);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true);
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ?
[ Stat.DEF, Stat.SPDEF, Stat.SPD ] :
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
@ -187,8 +190,8 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
isBoss: true,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}:boss_enraged`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
queueEncounterMessage(`${namespace}:boss_enraged`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
}
}],
};
@ -196,46 +199,46 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
return config;
}
async function animateBiomeChange(scene: BattleScene, nextBiome: Biome) {
async function animateBiomeChange(nextBiome: Biome) {
return new Promise<void>(resolve => {
scene.tweens.add({
targets: [ scene.arenaEnemy, scene.lastEnemyTrainer ],
globalScene.tweens.add({
targets: [ globalScene.arenaEnemy, globalScene.lastEnemyTrainer ],
x: "+=300",
duration: 2000,
onComplete: () => {
scene.newArena(nextBiome);
globalScene.newArena(nextBiome);
const biomeKey = getBiomeKey(nextBiome);
const bgTexture = `${biomeKey}_bg`;
scene.arenaBgTransition.setTexture(bgTexture);
scene.arenaBgTransition.setAlpha(0);
scene.arenaBgTransition.setVisible(true);
scene.arenaPlayerTransition.setBiome(nextBiome);
scene.arenaPlayerTransition.setAlpha(0);
scene.arenaPlayerTransition.setVisible(true);
globalScene.arenaBgTransition.setTexture(bgTexture);
globalScene.arenaBgTransition.setAlpha(0);
globalScene.arenaBgTransition.setVisible(true);
globalScene.arenaPlayerTransition.setBiome(nextBiome);
globalScene.arenaPlayerTransition.setAlpha(0);
globalScene.arenaPlayerTransition.setVisible(true);
scene.tweens.add({
targets: [ scene.arenaPlayer, scene.arenaBgTransition, scene.arenaPlayerTransition ],
globalScene.tweens.add({
targets: [ globalScene.arenaPlayer, globalScene.arenaBgTransition, globalScene.arenaPlayerTransition ],
duration: 1000,
ease: "Sine.easeInOut",
alpha: (target: any) => target === scene.arenaPlayer ? 0 : 1,
alpha: (target: any) => target === globalScene.arenaPlayer ? 0 : 1,
onComplete: () => {
scene.arenaBg.setTexture(bgTexture);
scene.arenaPlayer.setBiome(nextBiome);
scene.arenaPlayer.setAlpha(1);
scene.arenaEnemy.setBiome(nextBiome);
scene.arenaEnemy.setAlpha(1);
scene.arenaNextEnemy.setBiome(nextBiome);
scene.arenaBgTransition.setVisible(false);
scene.arenaPlayerTransition.setVisible(false);
if (scene.lastEnemyTrainer) {
scene.lastEnemyTrainer.destroy();
globalScene.arenaBg.setTexture(bgTexture);
globalScene.arenaPlayer.setBiome(nextBiome);
globalScene.arenaPlayer.setAlpha(1);
globalScene.arenaEnemy.setBiome(nextBiome);
globalScene.arenaEnemy.setAlpha(1);
globalScene.arenaNextEnemy.setBiome(nextBiome);
globalScene.arenaBgTransition.setVisible(false);
globalScene.arenaPlayerTransition.setVisible(false);
if (globalScene.lastEnemyTrainer) {
globalScene.lastEnemyTrainer.destroy();
}
resolve();
scene.tweens.add({
targets: scene.arenaEnemy,
globalScene.tweens.add({
targets: globalScene.arenaEnemy,
x: "-=300",
});
}

View File

@ -1,9 +1,11 @@
import { EnemyPartyConfig, generateModifierType, handleMysteryEncounterBattleFailed, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
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 { trainerConfigs } from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import { randSeedShuffle } from "#app/utils";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import type MysteryEncounter from "../mystery-encounter";
import { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { Biome } from "#enums/biome";
@ -14,17 +16,18 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { Nature } from "#enums/nature";
import { Moves } from "#enums/moves";
import { PlayerPokemon } from "#app/field/pokemon";
import type { PlayerPokemon } from "#app/field/pokemon";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { IEggOptions } from "#app/data/egg";
import type { IEggOptions } from "#app/data/egg";
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 { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
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";
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/theExpertPokemonBreeder";
@ -93,14 +96,14 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
text: `${namespace}:intro_dialogue`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
const waveIndex = scene.currentBattle.waveIndex;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const waveIndex = globalScene.currentBattle.waveIndex;
// Calculates what trainers are available for battle in the encounter
// If player is in space biome, uses special "Space" version of the trainer
encounter.enemyPartyConfigs = [
getPartyConfig(scene)
getPartyConfig()
];
const cleffaSpecies = waveIndex < FIRST_STAGE_EVOLUTION_WAVE ? Species.CLEFFA : waveIndex < FINAL_STAGE_EVOLUTION_WAVE ? Species.CLEFAIRY : Species.CLEFABLE;
@ -125,7 +128,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
];
// Determine the 3 pokemon the player can battle with
let partyCopy = scene.getPlayerParty().slice(0);
let partyCopy = globalScene.getPlayerParty().slice(0);
partyCopy = partyCopy
.filter(p => p.isAllowedInBattle())
.sort((a, b) => a.friendship - b.friendship);
@ -139,7 +142,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
// Dialogue and egg calcs for Pokemon 1
const [ pokemon1CommonEggs, pokemon1RareEggs ] = calculateEggRewardsForPokemon(pokemon1);
let pokemon1Tooltip = getEncounterText(scene, `${namespace}:option.1.tooltip_base`)!;
let pokemon1Tooltip = getEncounterText(`${namespace}:option.1.tooltip_base`)!;
if (pokemon1RareEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon1RareEggs, rarity: i18next.t("egg:greatTier") });
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
@ -154,7 +157,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
// Dialogue and egg calcs for Pokemon 2
const [ pokemon2CommonEggs, pokemon2RareEggs ] = calculateEggRewardsForPokemon(pokemon2);
let pokemon2Tooltip = getEncounterText(scene, `${namespace}:option.2.tooltip_base`)!;
let pokemon2Tooltip = getEncounterText(`${namespace}:option.2.tooltip_base`)!;
if (pokemon2RareEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2RareEggs, rarity: i18next.t("egg:greatTier") });
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
@ -169,7 +172,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
// Dialogue and egg calcs for Pokemon 3
const [ pokemon3CommonEggs, pokemon3RareEggs ] = calculateEggRewardsForPokemon(pokemon3);
let pokemon3Tooltip = getEncounterText(scene, `${namespace}:option.3.tooltip_base`)!;
let pokemon3Tooltip = getEncounterText(`${namespace}:option.3.tooltip_base`)!;
if (pokemon3RareEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon3RareEggs, rarity: i18next.t("egg:greatTier") });
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
@ -212,22 +215,22 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Spawn battle with first pokemon
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
const { pokemon1, pokemon1CommonEggs, pokemon1RareEggs } = encounter.misc;
encounter.misc.chosenPokemon = pokemon1;
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs);
setEncounterRewards(scene,
const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs);
setEncounterRewards(
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true },
eggOptions,
() => doPostEncounterCleanup(scene));
() => doPostEncounterCleanup());
// Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1);
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon1);
// Configure outro dialogue for egg rewards
encounter.dialogue.outro = [
@ -248,7 +251,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
}
encounter.onGameOver = onGameOver;
await initBattleWithEnemyConfig(scene, config);
await initBattleWithEnemyConfig(config);
})
.build()
)
@ -264,22 +267,22 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Spawn battle with second pokemon
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
const { pokemon2, pokemon2CommonEggs, pokemon2RareEggs } = encounter.misc;
encounter.misc.chosenPokemon = pokemon2;
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs);
setEncounterRewards(scene,
const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs);
setEncounterRewards(
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true },
eggOptions,
() => doPostEncounterCleanup(scene));
() => doPostEncounterCleanup());
// Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2);
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon2);
// Configure outro dialogue for egg rewards
encounter.dialogue.outro = [
@ -300,7 +303,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
}
encounter.onGameOver = onGameOver;
await initBattleWithEnemyConfig(scene, config);
await initBattleWithEnemyConfig(config);
})
.build()
)
@ -316,22 +319,22 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Spawn battle with third pokemon
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
const { pokemon3, pokemon3CommonEggs, pokemon3RareEggs } = encounter.misc;
encounter.misc.chosenPokemon = pokemon3;
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs);
setEncounterRewards(scene,
const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs);
setEncounterRewards(
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true },
eggOptions,
() => doPostEncounterCleanup(scene));
() => doPostEncounterCleanup());
// Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3);
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon3);
// Configure outro dialogue for egg rewards
encounter.dialogue.outro = [
@ -352,7 +355,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
}
encounter.onGameOver = onGameOver;
await initBattleWithEnemyConfig(scene, config);
await initBattleWithEnemyConfig(config);
})
.build()
)
@ -364,9 +367,9 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
])
.build();
function getPartyConfig(scene: BattleScene): EnemyPartyConfig {
function getPartyConfig(): EnemyPartyConfig {
// Bug type superfan trainer config
const waveIndex = scene.currentBattle.waveIndex;
const waveIndex = globalScene.currentBattle.waveIndex;
const breederConfig = trainerConfigs[TrainerType.EXPERT_POKEMON_BREEDER].clone();
breederConfig.name = i18next.t(trainerNameKey);
@ -386,14 +389,14 @@ function getPartyConfig(scene: BattleScene): EnemyPartyConfig {
ivs: [ 31, 31, 31, 31, 31, 31 ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.TERA_SHARD, [ Type.STEEL ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.TERA_SHARD, [ Type.STEEL ]) as PokemonHeldItemModifierType,
}
]
}
]
};
if (scene.arena.biomeType === Biome.SPACE) {
if (globalScene.arena.biomeType === Biome.SPACE) {
// All 3 members always Cleffa line, but different configs
baseConfig.pokemonConfigs!.push({
nickname: i18next.t(`${namespace}:cleffa_2_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }),
@ -476,14 +479,13 @@ function calculateEggRewardsForPokemon(pokemon: PlayerPokemon): [number, number]
return [ numCommons, numRares ];
}
function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number) {
function getEggOptions(commonEggs: number, rareEggs: number) {
const eggDescription = i18next.t(`${namespace}:title`) + ":\n" + i18next.t(trainerNameKey);
const eggOptions: IEggOptions[] = [];
if (commonEggs > 0) {
for (let i = 0; i < commonEggs; i++) {
eggOptions.push({
scene,
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: eggDescription,
@ -494,7 +496,6 @@ function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number)
if (rareEggs > 0) {
for (let i = 0; i < rareEggs; i++) {
eggOptions.push({
scene,
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: eggDescription,
@ -506,36 +507,36 @@ function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number)
return eggOptions;
}
function removePokemonFromPartyAndStoreHeldItems(scene: BattleScene, encounter: MysteryEncounter, chosenPokemon: PlayerPokemon) {
const party = scene.getPlayerParty();
function removePokemonFromPartyAndStoreHeldItems(encounter: MysteryEncounter, chosenPokemon: PlayerPokemon) {
const party = globalScene.getPlayerParty();
const chosenIndex = party.indexOf(chosenPokemon);
party[chosenIndex] = party[0];
party[0] = chosenPokemon;
encounter.misc.originalParty = scene.getPlayerParty().slice(1);
encounter.misc.originalParty = globalScene.getPlayerParty().slice(1);
encounter.misc.originalPartyHeldItems = encounter.misc.originalParty
.map(p => p.getHeldItems());
scene["party"] = [
globalScene["party"] = [
chosenPokemon
];
}
function restorePartyAndHeldItems(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!;
function restorePartyAndHeldItems() {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Restore original party
scene.getPlayerParty().push(...encounter.misc.originalParty);
globalScene.getPlayerParty().push(...encounter.misc.originalParty);
// Restore held items
const originalHeldItems = encounter.misc.originalPartyHeldItems;
originalHeldItems.forEach((pokemonHeldItemsList: PokemonHeldItemModifier[]) => {
pokemonHeldItemsList.forEach(heldItem => {
scene.addModifier(heldItem, true, false, false, true);
globalScene.addModifier(heldItem, true, false, false, true);
});
});
scene.updateModifiers(true);
globalScene.updateModifiers(true);
}
function onGameOver(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!;
function onGameOver() {
const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.dialogue.outro = [
{
@ -545,7 +546,7 @@ function onGameOver(scene: BattleScene) {
];
// Restore original party, player loses all friendship with chosen mon (it remains fainted)
restorePartyAndHeldItems(scene);
restorePartyAndHeldItems();
const chosenPokemon = encounter.misc.chosenPokemon;
chosenPokemon.friendship = 0;
@ -556,33 +557,33 @@ function onGameOver(scene: BattleScene) {
encounter.misc.encounterFailed = true;
// Revert BGM
scene.playBgm(scene.arena.bgm);
globalScene.playBgm(globalScene.arena.bgm);
// Clear any leftover battle phases
scene.clearPhaseQueue();
scene.clearPhaseQueueSplice();
globalScene.clearPhaseQueue();
globalScene.clearPhaseQueueSplice();
// Return enemy Pokemon
const pokemon = scene.getEnemyPokemon();
const pokemon = globalScene.getEnemyPokemon();
if (pokemon) {
scene.playSound("se/pb_rel");
globalScene.playSound("se/pb_rel");
pokemon.hideInfo();
pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, "Sine.easeIn");
scene.tweens.add({
globalScene.tweens.add({
targets: pokemon,
duration: 250,
ease: "Sine.easeIn",
scale: 0.5,
onComplete: () => {
scene.field.remove(pokemon, true);
globalScene.field.remove(pokemon, true);
}
});
}
// Show the enemy trainer
scene.time.delayedCall(250, () => {
const sprites = scene.currentBattle.trainer?.getSprites();
const tintSprites = scene.currentBattle.trainer?.getTintSprites();
globalScene.time.delayedCall(250, () => {
const sprites = globalScene.currentBattle.trainer?.getSprites();
const tintSprites = globalScene.currentBattle.trainer?.getTintSprites();
if (sprites && tintSprites) {
for (let i = 0; i < sprites.length; i++) {
sprites[i].setVisible(true);
@ -591,8 +592,8 @@ function onGameOver(scene: BattleScene) {
tintSprites[i].clearTint();
}
}
scene.tweens.add({
targets: scene.currentBattle.trainer,
globalScene.tweens.add({
targets: globalScene.currentBattle.trainer,
x: "-=16",
y: "+=16",
alpha: 1,
@ -602,16 +603,16 @@ function onGameOver(scene: BattleScene) {
});
handleMysteryEncounterBattleFailed(scene, true);
handleMysteryEncounterBattleFailed(true);
return false;
}
function doPostEncounterCleanup(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!;
function doPostEncounterCleanup() {
const encounter = globalScene.currentBattle.mysteryEncounter!;
if (!encounter.misc.encounterFailed) {
// Give 20 friendship to the chosen pokemon
encounter.misc.chosenPokemon.addFriendship(FRIENDSHIP_ADDED);
restorePartyAndHeldItems(scene);
restorePartyAndHeldItems();
}
}

View File

@ -1,15 +1,18 @@
import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { catchPokemon, getRandomSpeciesByStarterCost, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { Species } from "#enums/species";
import { PokeballType } from "#enums/pokeball";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import type { EnemyPokemon } from "#app/field/pokemon";
import { PlayerPokemon } from "#app/field/pokemon";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import PokemonData from "#app/system/pokemon-data";
@ -58,8 +61,8 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
let species = getSalesmanSpeciesOffer();
let tries = 0;
@ -74,9 +77,9 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) {
// If no HA mon found or you roll 1%, give shiny Magikarp with random variant
species = getPokemonSpecies(Species.MAGIKARP);
pokemon = new PlayerPokemon(scene, species, 5, 2, species.formIndex, undefined, true);
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex, undefined, true);
} else {
pokemon = new PlayerPokemon(scene, species, 5, 2, species.formIndex);
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex);
}
pokemon.generateAndPopulateMoveset();
@ -101,7 +104,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}:description_shiny`;
encounter.options[0].dialogue!.buttonTooltip = `${namespace}:option.1.tooltip_shiny`;
}
const price = scene.getWaveMoneyAmount(priceMultiplier);
const price = globalScene.getWaveMoneyAmount(priceMultiplier);
encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
encounter.setDialogueToken("price", price.toString());
encounter.misc = {
@ -127,24 +130,24 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
}
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const price = encounter.misc.price;
const purchasedPokemon = encounter.misc.pokemon as PlayerPokemon;
// Update money
updatePlayerMoney(scene, -price, true, false);
updatePlayerMoney(-price, true, false);
// Show dialogue
await showEncounterDialogue(scene, `${namespace}:option.1.selected_dialogue`, `${namespace}:speaker`);
await transitionMysteryEncounterIntroVisuals(scene);
await showEncounterDialogue(`${namespace}:option.1.selected_dialogue`, `${namespace}:speaker`);
await transitionMysteryEncounterIntroVisuals();
// "Catch" purchased pokemon
const data = new PokemonData(purchasedPokemon);
data.player = false;
await catchPokemon(scene, data.toPokemon(scene) as EnemyPokemon, null, PokeballType.POKEBALL, true, true);
await catchPokemon(data.toPokemon() as EnemyPokemon, null, PokeballType.POKEBALL, true, true);
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
})
.build()
)
@ -158,9 +161,9 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
return true;
}
)

View File

@ -1,12 +1,16 @@
import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Species } from "#enums/species";
import { Nature } from "#enums/nature";
import Pokemon, { PokemonMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { Moves } from "#enums/moves";
@ -67,8 +71,8 @@ export const TheStrongStuffEncounter: MysteryEncounter =
text: `${namespace}:intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate boss mon
const config: EnemyPartyConfig = {
@ -85,26 +89,26 @@ export const TheStrongStuffEncounter: MysteryEncounter =
moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.APICOT ]) as PokemonHeldItemModifierType
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.APICOT ]) as PokemonHeldItemModifierType
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType,
stackCount: 2
}
],
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}:option.2.stat_boost`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.DEF, Stat.SPDEF ], 2));
queueEncounterMessage(`${namespace}:option.2.stat_boost`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.DEF, Stat.SPDEF ], 2));
}
}
],
@ -112,7 +116,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
encounter.enemyPartyConfigs = [ config ];
loadCustomMovesForEncounter(scene, [ Moves.GASTRO_ACID, Moves.STEALTH_ROCK ]);
loadCustomMovesForEncounter([ Moves.GASTRO_ACID, Moves.STEALTH_ROCK ]);
encounter.setDialogueToken("shuckleName", getPokemonSpecies(Species.SHUCKLE).getName());
@ -132,16 +136,16 @@ export const TheStrongStuffEncounter: MysteryEncounter =
}
]
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Do blackout and hide intro visuals during blackout
scene.time.delayedCall(750, () => {
transitionMysteryEncounterIntroVisuals(scene, true, true, 50);
globalScene.time.delayedCall(750, () => {
transitionMysteryEncounterIntroVisuals(true, true, 50);
});
// -15 to all base stats of highest BST (halved for HP), +10 to all base stats of rest of party (halved for HP)
// Sort party by bst
const sortedParty = scene.getPlayerParty().slice(0)
const sortedParty = globalScene.getPlayerParty().slice(0)
.sort((pokemon1, pokemon2) => {
const pokemon1Bst = pokemon1.calculateBaseStats().reduce((a, b) => a + b, 0);
const pokemon2Bst = pokemon2.calculateBaseStats().reduce((a, b) => a + b, 0);
@ -161,15 +165,15 @@ export const TheStrongStuffEncounter: MysteryEncounter =
encounter.setDialogueToken("reductionValue", HIGH_BST_REDUCTION_VALUE.toString());
encounter.setDialogueToken("increaseValue", BST_INCREASE_VALUE.toString());
await showEncounterText(scene, `${namespace}:option.1.selected_2`, null, undefined, true);
await showEncounterText(`${namespace}:option.1.selected_2`, null, undefined, true);
encounter.dialogue.outro = [
{
text: `${namespace}:outro`,
}
];
setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene, true);
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle(true);
return true;
}
)
@ -183,10 +187,10 @@ export const TheStrongStuffEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter!;
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SOUL_DEW ], fillRemaining: true });
const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SOUL_DEW ], fillRemaining: true });
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
@ -202,8 +206,8 @@ export const TheStrongStuffEncounter: MysteryEncounter =
});
encounter.dialogue.outro = [];
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
await transitionMysteryEncounterIntroVisuals(true, true, 500);
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
}
)
.build();

View File

@ -1,8 +1,11 @@
import { EnemyPartyConfig, generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species";
@ -83,15 +86,15 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
},
])
.withAutoHideIntroVisuals(false)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Loaded back to front for pop() operations
encounter.enemyPartyConfigs.push(getVitoTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getVickyTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getViviTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getVictoriaTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getVictorTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getVitoTrainerConfig());
encounter.enemyPartyConfigs.push(getVickyTrainerConfig());
encounter.enemyPartyConfigs.push(getViviTrainerConfig());
encounter.enemyPartyConfigs.push(getVictoriaTrainerConfig());
encounter.enemyPartyConfigs.push(getVictorTrainerConfig());
return true;
})
@ -110,13 +113,13 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Spawn 5 trainer battles back to back with Macho Brace in rewards
scene.currentBattle.mysteryEncounter!.doContinueEncounter = async (scene: BattleScene) => {
await endTrainerBattleAndShowDialogue(scene);
globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = async () => {
await endTrainerBattleAndShowDialogue();
};
await transitionMysteryEncounterIntroVisuals(scene, true, false);
await spawnNextTrainerOrEndEncounter(scene);
await transitionMysteryEncounterIntroVisuals(true, false);
await spawnNextTrainerOrEndEncounter();
}
)
.withSimpleOption(
@ -130,47 +133,47 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Refuse the challenge, they full heal the party and give the player a Rarer Candy
scene.unshiftPhase(new PartyHealPhase(scene, true));
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.RARER_CANDY ], fillRemaining: false });
leaveEncounterWithoutBattle(scene);
globalScene.unshiftPhase(new PartyHealPhase(true));
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.RARER_CANDY ], fillRemaining: false });
leaveEncounterWithoutBattle();
}
)
.build();
async function spawnNextTrainerOrEndEncounter(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!;
async function spawnNextTrainerOrEndEncounter() {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const nextConfig = encounter.enemyPartyConfigs.pop();
if (!nextConfig) {
await transitionMysteryEncounterIntroVisuals(scene, false, false);
await showEncounterDialogue(scene, `${namespace}:victory`, `${namespace}:speaker`);
await transitionMysteryEncounterIntroVisuals(false, false);
await showEncounterDialogue(`${namespace}:victory`, `${namespace}:speaker`);
// Give 10x Voucher
const newModifier = modifierTypes.VOUCHER_PREMIUM().newModifier();
await scene.addModifier(newModifier);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }));
await globalScene.addModifier(newModifier);
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }));
await showEncounterDialogue(scene, `${namespace}:victory_2`, `${namespace}:speaker`);
scene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in
const machoBrace = generateModifierTypeOption(scene, modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!;
await showEncounterDialogue(`${namespace}:victory_2`, `${namespace}:speaker`);
globalScene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in
const machoBrace = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!;
machoBrace.type.tier = ModifierTier.MASTER;
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ machoBrace ], fillRemaining: false });
setEncounterRewards({ guaranteedModifierTypeOptions: [ machoBrace ], fillRemaining: false });
encounter.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(scene, false, MysteryEncounterMode.NO_BATTLE);
leaveEncounterWithoutBattle(false, MysteryEncounterMode.NO_BATTLE);
} else {
await initBattleWithEnemyConfig(scene, nextConfig);
await initBattleWithEnemyConfig(nextConfig);
}
}
function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise<void> {
function endTrainerBattleAndShowDialogue(): Promise<void> {
return new Promise(async resolve => {
if (scene.currentBattle.mysteryEncounter!.enemyPartyConfigs.length === 0) {
if (globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs.length === 0) {
// Battle is over
const trainer = scene.currentBattle.trainer;
const trainer = globalScene.currentBattle.trainer;
if (trainer) {
scene.tweens.add({
globalScene.tweens.add({
targets: trainer,
x: "+=16",
y: "-=16",
@ -178,38 +181,38 @@ function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise<void> {
ease: "Sine.easeInOut",
duration: 750,
onComplete: () => {
scene.field.remove(trainer, true);
globalScene.field.remove(trainer, true);
}
});
}
await spawnNextTrainerOrEndEncounter(scene);
await spawnNextTrainerOrEndEncounter();
resolve(); // Wait for all dialogue/post battle stuff to complete before resolving
} else {
scene.arena.resetArenaEffects();
const playerField = scene.getPlayerField();
globalScene.arena.resetArenaEffects();
const playerField = globalScene.getPlayerField();
playerField.forEach((pokemon) => pokemon.lapseTag(BattlerTagType.COMMANDED));
playerField.forEach((_, p) => scene.unshiftPhase(new ReturnPhase(scene, p)));
playerField.forEach((_, p) => globalScene.unshiftPhase(new ReturnPhase(p)));
for (const pokemon of scene.getPlayerParty()) {
for (const pokemon of globalScene.getPlayerParty()) {
// Only trigger form change when Eiscue is in Noice form
// Hardcoded Eiscue for now in case it is fused with another pokemon
if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) {
scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
}
pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
}
scene.unshiftPhase(new ShowTrainerPhase(scene));
globalScene.unshiftPhase(new ShowTrainerPhase());
// Hide the trainer and init next battle
const trainer = scene.currentBattle.trainer;
const trainer = globalScene.currentBattle.trainer;
// Unassign previous trainer from battle so it isn't destroyed before animation completes
scene.currentBattle.trainer = null;
await spawnNextTrainerOrEndEncounter(scene);
globalScene.currentBattle.trainer = null;
await spawnNextTrainerOrEndEncounter();
if (trainer) {
scene.tweens.add({
globalScene.tweens.add({
targets: trainer,
x: "+=16",
y: "-=16",
@ -217,7 +220,7 @@ function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise<void> {
ease: "Sine.easeInOut",
duration: 750,
onComplete: () => {
scene.field.remove(trainer, true);
globalScene.field.remove(trainer, true);
resolve();
}
});
@ -226,7 +229,7 @@ function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise<void> {
});
}
function getVictorTrainerConfig(scene: BattleScene): EnemyPartyConfig {
function getVictorTrainerConfig(): EnemyPartyConfig {
return {
trainerType: TrainerType.VICTOR,
pokemonConfigs: [
@ -238,11 +241,11 @@ function getVictorTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.FACADE, Moves.BRAVE_BIRD, Moves.PROTECT, Moves.QUICK_ATTACK ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
isTransferable: false
},
{
modifier: generateModifierType(scene, modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
},
@ -256,11 +259,11 @@ function getVictorTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.FACADE, Moves.OBSTRUCT, Moves.NIGHT_SLASH, Moves.FIRE_PUNCH ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
isTransferable: false
},
{
modifier: generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
@ -270,7 +273,7 @@ function getVictorTrainerConfig(scene: BattleScene): EnemyPartyConfig {
};
}
function getVictoriaTrainerConfig(scene: BattleScene): EnemyPartyConfig {
function getVictoriaTrainerConfig(): EnemyPartyConfig {
return {
trainerType: TrainerType.VICTORIA,
pokemonConfigs: [
@ -282,11 +285,11 @@ function getVictoriaTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.SYNTHESIS, Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.SLEEP_POWDER ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType,
isTransferable: false
},
{
modifier: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
@ -300,12 +303,12 @@ function getVictoriaTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.PSYSHOCK, Moves.MOONBLAST, Moves.SHADOW_BALL, Moves.WILL_O_WISP ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.PSYCHIC ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.PSYCHIC ]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false
},
{
modifier: generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FAIRY ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FAIRY ]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false
}
@ -315,7 +318,7 @@ function getVictoriaTrainerConfig(scene: BattleScene): EnemyPartyConfig {
};
}
function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig {
function getViviTrainerConfig(): EnemyPartyConfig {
return {
trainerType: TrainerType.VIVI,
pokemonConfigs: [
@ -327,12 +330,12 @@ function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.WATERFALL, Moves.MEGAHORN, Moves.KNOCK_OFF, Moves.REST ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
},
{
modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [ Stat.HP ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [ Stat.HP ]) as PokemonHeldItemModifierType,
stackCount: 4,
isTransferable: false
}
@ -346,12 +349,12 @@ function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.SPORE, Moves.SWORDS_DANCE, Moves.SEED_BOMB, Moves.DRAIN_PUNCH ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [ Stat.HP ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [ Stat.HP ]) as PokemonHeldItemModifierType,
stackCount: 4,
isTransferable: false
},
{
modifier: generateModifierType(scene, modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
isTransferable: false
}
]
@ -364,7 +367,7 @@ function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.EARTH_POWER, Moves.FIRE_BLAST, Moves.YAWN, Moves.PROTECT ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 3,
isTransferable: false
},
@ -374,7 +377,7 @@ function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig {
};
}
function getVickyTrainerConfig(scene: BattleScene): EnemyPartyConfig {
function getVickyTrainerConfig(): EnemyPartyConfig {
return {
trainerType: TrainerType.VICKY,
pokemonConfigs: [
@ -386,7 +389,7 @@ function getVickyTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.AXE_KICK, Moves.ICE_PUNCH, Moves.ZEN_HEADBUTT, Moves.BULLET_PUNCH ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType,
isTransferable: false
}
]
@ -395,7 +398,7 @@ function getVickyTrainerConfig(scene: BattleScene): EnemyPartyConfig {
};
}
function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig {
function getVitoTrainerConfig(): EnemyPartyConfig {
return {
trainerType: TrainerType.VITO,
pokemonConfigs: [
@ -407,7 +410,7 @@ function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.THUNDERBOLT, Moves.GIGA_DRAIN, Moves.FOUL_PLAY, Moves.THUNDER_WAVE ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [ Stat.SPD ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [ Stat.SPD ]) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
@ -421,47 +424,47 @@ function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.ICE_BEAM, Moves.EARTHQUAKE ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.APICOT ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.APICOT ]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.STARF ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.STARF ]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.SALAC ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SALAC ]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.LANSAT ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LANSAT ]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.LIECHI ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LIECHI ]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.PETAYA ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.PETAYA ]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.LEPPA ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LEPPA ]) as PokemonHeldItemModifierType,
stackCount: 2,
}
]
@ -474,7 +477,7 @@ function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.DRILL_PECK, Moves.QUICK_ATTACK, Moves.THRASH, Moves.KNOCK_OFF ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
@ -488,7 +491,7 @@ function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.PSYCHIC, Moves.SHADOW_BALL, Moves.FOCUS_BLAST, Moves.THUNDERBOLT ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
},
@ -502,7 +505,7 @@ function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig {
moveSet: [ Moves.EARTHQUAKE, Moves.U_TURN, Moves.FLARE_BLITZ, Moves.ROCK_SLIDE ],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
},

View File

@ -1,22 +1,26 @@
import { Ability, allAbilities } from "#app/data/ability";
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { Ability } from "#app/data/ability";
import { allAbilities } from "#app/data/ability";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getNatureName } from "#app/data/nature";
import { speciesStarterCosts } from "#app/data/balance/starters";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { AbilityAttr } from "#app/system/game-data";
import PokemonData from "#app/system/pokemon-data";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { isNullOrUndefined, randSeedShuffle } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
import i18next from "i18next";
import { getStatKey } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
@ -71,8 +75,8 @@ export const TrainingSessionEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.misc = {
playerPokemon: pokemon,
@ -81,24 +85,24 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Only Pokemon that are not KOed/legal can be trained
const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
// Spawn light training session with chosen pokemon
// Every 50 waves, add +1 boss segment, capping at 5
const segments = Math.min(
2 + Math.floor(scene.currentBattle.waveIndex / 50),
2 + Math.floor(globalScene.currentBattle.waveIndex / 50),
5
);
const modifiers = new ModifiersHolder();
const config = getEnemyConfig(scene, playerPokemon, segments, modifiers);
scene.removePokemonFromPlayerParty(playerPokemon, false);
const config = getEnemyConfig(playerPokemon, segments, modifiers);
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => {
encounter.setDialogueToken("stat1", "-");
@ -148,23 +152,23 @@ export const TrainingSessionEncounter: MysteryEncounter =
if (improvedCount > 0) {
playerPokemon.calculateStats();
scene.gameData.updateSpeciesDexIvs(playerPokemon.species.getRootSpeciesId(true), playerPokemon.ivs);
scene.gameData.setPokemonCaught(playerPokemon, false);
globalScene.gameData.updateSpeciesDexIvs(playerPokemon.species.getRootSpeciesId(true), playerPokemon.ivs);
globalScene.gameData.setPokemonCaught(playerPokemon, false);
}
// Add pokemon and mods back
scene.getPlayerParty().push(playerPokemon);
globalScene.getPlayerParty().push(playerPokemon);
for (const mod of modifiers.value) {
mod.pokemonId = playerPokemon.id;
scene.addModifier(mod, true, false, false, true);
globalScene.addModifier(mod, true, false, false, true);
}
scene.updateModifiers(true);
queueEncounterMessage(scene, `${namespace}:option.1.finished`);
globalScene.updateModifiers(true);
queueEncounterMessage(`${namespace}:option.1.finished`);
};
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
await initBattleWithEnemyConfig(scene, config);
await initBattleWithEnemyConfig(config);
})
.build()
)
@ -182,15 +186,15 @@ export const TrainingSessionEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
.withPreOptionPhase(async (): Promise<boolean> => {
// Open menu for selecting pokemon and Nature
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const natures = new Array(25).fill(null).map((val, i) => i as Nature);
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for nature selection
return natures.map((nature: Nature) => {
const option: OptionSelectItem = {
label: getNatureName(nature, true, true, true, scene.uiTheme),
label: getNatureName(nature, true, true, true, globalScene.uiTheme),
handler: () => {
// Pokemon and second option selected
encounter.setDialogueToken("nature", getNatureName(nature));
@ -207,40 +211,40 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Only Pokemon that are not KOed/legal can be trained
const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
// Spawn medium training session with chosen pokemon
// Every 40 waves, add +1 boss segment, capping at 6
const segments = Math.min(2 + Math.floor(scene.currentBattle.waveIndex / 40), 6);
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 40), 6);
const modifiers = new ModifiersHolder();
const config = getEnemyConfig(scene, playerPokemon, segments, modifiers);
scene.removePokemonFromPlayerParty(playerPokemon, false);
const config = getEnemyConfig(playerPokemon, segments, modifiers);
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => {
queueEncounterMessage(scene, `${namespace}:option.2.finished`);
queueEncounterMessage(`${namespace}:option.2.finished`);
// Add the pokemon back to party with Nature change
playerPokemon.setCustomNature(encounter.misc.chosenNature);
scene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature);
globalScene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature);
// Add pokemon and modifiers back
scene.getPlayerParty().push(playerPokemon);
globalScene.getPlayerParty().push(playerPokemon);
for (const mod of modifiers.value) {
mod.pokemonId = playerPokemon.id;
scene.addModifier(mod, true, false, false, true);
globalScene.addModifier(mod, true, false, false, true);
}
scene.updateModifiers(true);
globalScene.updateModifiers(true);
};
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
await initBattleWithEnemyConfig(scene, config);
await initBattleWithEnemyConfig(config);
})
.build()
)
@ -258,9 +262,9 @@ export const TrainingSessionEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
.withPreOptionPhase(async (): Promise<boolean> => {
// Open menu for selecting pokemon and ability to learn
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for ability selection
const speciesForm = !!pokemon.getFusionSpeciesForm()
@ -286,7 +290,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
return true;
},
onHover: () => {
showEncounterText(scene, ability.description, 0, 0, false);
showEncounterText(ability.description, 0, 0, false);
},
};
optionSelectItems.push(option);
@ -298,28 +302,28 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Only Pokemon that are not KOed/legal can be trained
const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
// Spawn hard training session with chosen pokemon
// Every 30 waves, add +1 boss segment, capping at 6
// Also starts with +1 to all stats
const segments = Math.min(2 + Math.floor(scene.currentBattle.waveIndex / 30), 6);
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 30), 6);
const modifiers = new ModifiersHolder();
const config = getEnemyConfig(scene, playerPokemon, segments, modifiers);
const config = getEnemyConfig(playerPokemon, segments, modifiers);
config.pokemonConfigs![0].tags = [
BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON,
];
scene.removePokemonFromPlayerParty(playerPokemon, false);
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => {
queueEncounterMessage(scene, `${namespace}:option.3.finished`);
queueEncounterMessage(`${namespace}:option.3.finished`);
// Add the pokemon back to party with ability change
const abilityIndex = encounter.misc.abilityIndex;
@ -330,8 +334,8 @@ export const TrainingSessionEncounter: MysteryEncounter =
const rootFusionSpecies = playerPokemon.fusionSpecies?.getRootSpeciesId();
if (!isNullOrUndefined(rootFusionSpecies)
&& speciesStarterCosts.hasOwnProperty(rootFusionSpecies)
&& !!scene.gameData.dexData[rootFusionSpecies].caughtAttr) {
scene.gameData.starterData[rootFusionSpecies].abilityAttr |= playerPokemon.fusionAbilityIndex !== 1 || playerPokemon.fusionSpecies?.ability2
&& !!globalScene.gameData.dexData[rootFusionSpecies].caughtAttr) {
globalScene.gameData.starterData[rootFusionSpecies].abilityAttr |= playerPokemon.fusionAbilityIndex !== 1 || playerPokemon.fusionSpecies?.ability2
? 1 << playerPokemon.fusionAbilityIndex
: AbilityAttr.ABILITY_HIDDEN;
}
@ -340,20 +344,20 @@ export const TrainingSessionEncounter: MysteryEncounter =
}
playerPokemon.calculateStats();
scene.gameData.setPokemonCaught(playerPokemon, false);
globalScene.gameData.setPokemonCaught(playerPokemon, false);
// Add pokemon and mods back
scene.getPlayerParty().push(playerPokemon);
globalScene.getPlayerParty().push(playerPokemon);
for (const mod of modifiers.value) {
mod.pokemonId = playerPokemon.id;
scene.addModifier(mod, true, false, false, true);
globalScene.addModifier(mod, true, false, false, true);
}
scene.updateModifiers(true);
globalScene.updateModifiers(true);
};
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
await initBattleWithEnemyConfig(scene, config);
await initBattleWithEnemyConfig(config);
})
.build()
)
@ -367,15 +371,15 @@ export const TrainingSessionEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
return true;
}
)
.build();
function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon, segments: number, modifiers: ModifiersHolder): EnemyPartyConfig {
function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifiers: ModifiersHolder): EnemyPartyConfig {
playerPokemon.resetSummonData();
// Passes modifiers by reference

View File

@ -1,8 +1,11 @@
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import type { EnemyPartyConfig, EnemyPokemonConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -58,8 +61,8 @@ export const TrashToTreasureEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate boss mon (shiny locked)
const bossSpecies = getPokemonSpecies(Species.GARBODOR);
@ -79,10 +82,10 @@ export const TrashToTreasureEncounter: MysteryEncounter =
encounter.enemyPartyConfigs = [ config ];
// Load animations/sfx for Garbodor fight start moves
loadCustomMovesForEncounter(scene, [ Moves.TOXIC, Moves.AMNESIA ]);
loadCustomMovesForEncounter([ Moves.TOXIC, Moves.AMNESIA ]);
scene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav");
scene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav");
globalScene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav");
globalScene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav");
encounter.setDialogueToken("costMultiplier", SHOP_ITEM_COST_MULTIPLIER.toString());
@ -100,24 +103,24 @@ export const TrashToTreasureEncounter: MysteryEncounter =
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Play Dig2 and then Venom Drench sfx
doGarbageDig(scene);
doGarbageDig();
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Gain 2 Leftovers and 2 Shell Bell
await transitionMysteryEncounterIntroVisuals(scene);
await tryApplyDigRewardItems(scene);
await transitionMysteryEncounterIntroVisuals();
await tryApplyDigRewardItems();
const blackSludge = generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [ SHOP_ITEM_COST_MULTIPLIER ]);
const blackSludge = generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [ SHOP_ITEM_COST_MULTIPLIER ]);
const modifier = blackSludge?.newModifier();
if (modifier) {
await scene.addModifier(modifier, false, false, false, true);
scene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 });
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: modifier.type.name }), null, undefined, true);
await globalScene.addModifier(modifier, false, false, false, true);
globalScene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 });
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: modifier.type.name }), null, undefined, true);
}
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
})
.build()
)
@ -133,15 +136,15 @@ export const TrashToTreasureEncounter: MysteryEncounter =
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Investigate garbage, battle Gmax Garbodor
scene.setFieldScale(0.75);
await showEncounterText(scene, `${namespace}:option.2.selected_2`);
await transitionMysteryEncounterIntroVisuals(scene);
globalScene.setFieldScale(0.75);
await showEncounterText(`${namespace}:option.2.selected_2`);
await transitionMysteryEncounterIntroVisuals();
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true });
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true });
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
@ -155,81 +158,81 @@ export const TrashToTreasureEncounter: MysteryEncounter =
move: new PokemonMove(Moves.AMNESIA),
ignorePp: true
});
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
})
.build()
)
.build();
async function tryApplyDigRewardItems(scene: BattleScene) {
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
const leftovers = generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType;
async function tryApplyDigRewardItems() {
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
const leftovers = generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType;
const party = scene.getPlayerParty();
const party = globalScene.getPlayerParty();
// Iterate over the party until an item was successfully given
// First leftovers
for (const pokemon of party) {
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, leftovers);
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) {
await applyModifierTypeToPlayerPokemon(pokemon, leftovers);
break;
}
}
// Second leftovers
for (const pokemon of party) {
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, leftovers);
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) {
await applyModifierTypeToPlayerPokemon(pokemon, leftovers);
break;
}
}
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGainCount", { modifierName: leftovers.name, count: 2 }), null, undefined, true);
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGainCount", { modifierName: leftovers.name, count: 2 }), null, undefined, true);
// First Shell bell
for (const pokemon of party) {
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, shellBell);
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) {
await applyModifierTypeToPlayerPokemon(pokemon, shellBell);
break;
}
}
// Second Shell bell
for (const pokemon of party) {
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, shellBell);
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) {
await applyModifierTypeToPlayerPokemon(pokemon, shellBell);
break;
}
}
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGainCount", { modifierName: shellBell.name, count: 2 }), null, undefined, true);
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGainCount", { modifierName: shellBell.name, count: 2 }), null, undefined, true);
}
function doGarbageDig(scene: BattleScene) {
scene.playSound("battle_anims/PRSFX- Dig2");
scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => {
scene.playSound("battle_anims/PRSFX- Dig2");
scene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 });
function doGarbageDig() {
globalScene.playSound("battle_anims/PRSFX- Dig2");
globalScene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => {
globalScene.playSound("battle_anims/PRSFX- Dig2");
globalScene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 });
});
scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME * 2, () => {
scene.playSound("battle_anims/PRSFX- Dig2");
globalScene.time.delayedCall(SOUND_EFFECT_WAIT_TIME * 2, () => {
globalScene.playSound("battle_anims/PRSFX- Dig2");
});
}

View File

@ -1,11 +1,14 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import Pokemon, { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import { getPartyLuckValue } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoveRequirement, PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -13,7 +16,7 @@ import { TrainerSlot } from "#app/data/trainer-config";
import { catchPokemon, getHighestLevelPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonData from "#app/system/pokemon-data";
import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils";
import { Moves } from "#enums/moves";
import type { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
import { SelfStatusMove } from "#app/data/move";
import { PokeballType } from "#enums/pokeball";
@ -23,7 +26,8 @@ import { BerryModifier } from "#app/modifier/modifier";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/uncommonBreed";
@ -46,21 +50,21 @@ export const UncommonBreedEncounter: MysteryEncounter =
text: `${namespace}:intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate boss mon
// Level equal to 2 below highest party member
const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2;
const level = getHighestLevelPlayerPokemon(false, true).level - 2;
let species: PokemonSpecies;
if (scene.eventManager.isEventActive() && scene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) {
const eventEncounter = randSeedItem(scene.eventManager.activeEvent()!.uncommonBreedEncounters!);
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, scene.gameMode);
if (globalScene.eventManager.isEventActive() && globalScene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) {
const eventEncounter = randSeedItem(globalScene.eventManager.activeEvent()!.uncommonBreedEncounters!);
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, globalScene.gameMode);
species = getPokemonSpecies( levelSpecies );
} else {
species = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getPlayerParty()), true);
species = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true);
}
const pokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, true);
const pokemon = new EnemyPokemon(species, level, TrainerSlot.NONE, true);
// Pokemon will always have one of its egg moves in its moveset
const eggMoves = pokemon.getEggMoves();
@ -81,7 +85,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
}
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ?
[ Stat.DEF, Stat.SPDEF, Stat.SPD ] :
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
@ -93,8 +97,8 @@ export const UncommonBreedEncounter: MysteryEncounter =
isBoss: false,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}:option.1.stat_boost`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
queueEncounterMessage(`${namespace}:option.1.stat_boost`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
}
}],
};
@ -115,16 +119,16 @@ export const UncommonBreedEncounter: MysteryEncounter =
];
encounter.setDialogueToken("enemyPokemon", pokemon.getNameToRender());
scene.loadSe("PRSFX- Spotlight2", "battle_anims", "PRSFX- Spotlight2.wav");
globalScene.loadSe("PRSFX- Spotlight2", "battle_anims", "PRSFX- Spotlight2.wav");
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
.withOnVisualsStart(() => {
// Animate the pokemon
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const pokemonSprite = encounter.introVisuals!.getSprites();
// Bounce at the end, then shiny sparkle if the Pokemon is shiny
scene.tweens.add({
globalScene.tweens.add({
targets: pokemonSprite,
duration: 300,
ease: "Cubic.easeOut",
@ -134,7 +138,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
onComplete: () => encounter.introVisuals?.playShinySparkles()
});
scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2"));
globalScene.time.delayedCall(500, () => globalScene.playSound("battle_anims/PRSFX- Spotlight2"));
return true;
})
.setLocalizationKey(`${namespace}`)
@ -151,9 +155,9 @@ export const UncommonBreedEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const eggMove = encounter.misc.eggMove;
if (!isNullOrUndefined(eggMove)) {
@ -171,8 +175,8 @@ export const UncommonBreedEncounter: MysteryEncounter =
});
}
setEncounterRewards(scene, { fillRemaining: true });
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
setEncounterRewards({ fillRemaining: true });
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
}
)
.withOption(
@ -189,33 +193,33 @@ export const UncommonBreedEncounter: MysteryEncounter =
}
]
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Give it some food
// Remove 4 random berries from player's party
// Get all player berry items, remove from party, and store reference
const berryItems: BerryModifier[] = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
const berryItems: BerryModifier[] = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
for (let i = 0; i < 4; i++) {
const index = randSeedInt(berryItems.length);
const randBerry = berryItems[index];
randBerry.stackCount--;
if (randBerry.stackCount === 0) {
scene.removeModifier(randBerry);
globalScene.removeModifier(randBerry);
berryItems.splice(index, 1);
}
}
await scene.updateModifiers(true, true);
await globalScene.updateModifiers(true, true);
// Pokemon joins the team, with 2 egg moves
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const pokemon = encounter.misc.pokemon;
// Give 1 additional egg move
givePokemonExtraEggMove(pokemon, encounter.misc.eggMove);
await catchPokemon(scene, pokemon, null, PokeballType.POKEBALL, false);
setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene);
await catchPokemon(pokemon, null, PokeballType.POKEBALL, false);
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle();
})
.build()
)
@ -233,10 +237,10 @@ export const UncommonBreedEncounter: MysteryEncounter =
}
]
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Attract the pokemon with a move
// Pokemon joins the team, with 2 egg moves and IVs rolled an additional time
const encounter = scene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const pokemon = encounter.misc.pokemon;
// Give 1 additional egg move
@ -248,12 +252,12 @@ export const UncommonBreedEncounter: MysteryEncounter =
return newValue > iv ? newValue : iv;
});
await catchPokemon(scene, pokemon, null, PokeballType.POKEBALL, false);
await catchPokemon(pokemon, null, PokeballType.POKEBALL, false);
if (encounter.selectedOption?.primaryPokemon?.id) {
setEncounterExp(scene, encounter.selectedOption.primaryPokemon.id, pokemon.getExpValue(), false);
setEncounterExp(encounter.selectedOption.primaryPokemon.id, pokemon.getExpValue(), false);
}
setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene);
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle();
})
.build()
)

View File

@ -1,20 +1,27 @@
import { Type } from "#enums/type";
import type { Type } from "#enums/type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils";
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon";
import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils";
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#app/modifier/modifier";
import { achvs } from "#app/system/achv";
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import i18next from "#app/plugins/i18n";
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
import { getLevelTotalExp } from "#app/data/exp";
@ -25,7 +32,7 @@ import { PlayerGender } from "#enums/player-gender";
import { TrainerType } from "#enums/trainer-type";
import PokemonData from "#app/system/pokemon-data";
import { Nature } from "#enums/nature";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
import { trainerConfigs, TrainerPartyTemplate } from "#app/data/trainer-config";
import { PartyMemberStrength } from "#enums/party-member-strength";
@ -138,21 +145,21 @@ export const WeirdDreamEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
scene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3");
.withOnInit(() => {
globalScene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3");
// Calculate all the newly transformed Pokemon and begin asset load
const teamTransformations = getTeamTransformations(scene);
const teamTransformations = getTeamTransformations();
const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets());
scene.currentBattle.mysteryEncounter!.misc = {
globalScene.currentBattle.mysteryEncounter!.misc = {
teamTransformations,
loadAssets
};
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
scene.fadeAndSwitchBgm("mystery_encounter_weird_dream");
.withOnVisualsStart(() => {
globalScene.fadeAndSwitchBgm("mystery_encounter_weird_dream");
return true;
})
.withOption(
@ -168,25 +175,25 @@ export const WeirdDreamEncounter: MysteryEncounter =
}
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
.withPreOptionPhase(async () => {
// Play the animation as the player goes through the dialogue
scene.time.delayedCall(1000, () => {
doShowDreamBackground(scene);
globalScene.time.delayedCall(1000, () => {
doShowDreamBackground();
});
for (const transformation of scene.currentBattle.mysteryEncounter!.misc.teamTransformations) {
scene.removePokemonFromPlayerParty(transformation.previousPokemon, false);
scene.getPlayerParty().push(transformation.newPokemon);
for (const transformation of globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations) {
globalScene.removePokemonFromPlayerParty(transformation.previousPokemon, false);
globalScene.getPlayerParty().push(transformation.newPokemon);
}
})
.withOptionPhase(async (scene: BattleScene) => {
.withOptionPhase(async () => {
// Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue
const cutsceneDialoguePromise = showEncounterText(scene, `${namespace}:option.1.cutscene`);
const cutsceneDialoguePromise = showEncounterText(`${namespace}:option.1.cutscene`);
// Change the entire player's party
// Wait for all new Pokemon assets to be loaded before showing transformation animations
await Promise.all(scene.currentBattle.mysteryEncounter!.misc.loadAssets);
const transformations = scene.currentBattle.mysteryEncounter!.misc.teamTransformations;
await Promise.all(globalScene.currentBattle.mysteryEncounter!.misc.loadAssets);
const transformations = globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations;
// If there are 1-3 transformations, do them centered back to back
// Otherwise, the first 3 transformations are executed side-by-side, then any remaining 1-3 transformations occur in those same respective positions
@ -195,21 +202,21 @@ export const WeirdDreamEncounter: MysteryEncounter =
const pokemon1 = transformation.previousPokemon;
const pokemon2 = transformation.newPokemon;
await doPokemonTransformationSequence(scene, pokemon1, pokemon2, TransformationScreenPosition.CENTER);
await doPokemonTransformationSequence(pokemon1, pokemon2, TransformationScreenPosition.CENTER);
}
} else {
await doSideBySideTransformations(scene, transformations);
await doSideBySideTransformations(transformations);
}
// Make sure player has finished cutscene dialogue
await cutsceneDialoguePromise;
doHideDreamBackground(scene);
await showEncounterText(scene, `${namespace}:option.1.dream_complete`);
doHideDreamBackground();
await showEncounterText(`${namespace}:option.1.dream_complete`);
await doNewTeamPostProcess(scene, transformations);
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT, modifierTypes.MINT ], fillRemaining: false });
leaveEncounterWithoutBattle(scene, true);
await doNewTeamPostProcess(transformations);
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT, modifierTypes.MINT ], fillRemaining: false });
leaveEncounterWithoutBattle(true);
})
.build()
)
@ -223,9 +230,9 @@ export const WeirdDreamEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Battle your "future" team for some item rewards
const transformations: PokemonTransformation[] = scene.currentBattle.mysteryEncounter!.misc.teamTransformations;
const transformations: PokemonTransformation[] = globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations;
// Uses the pokemon that player's party would have transformed into
const enemyPokemonConfigs: EnemyPokemonConfig[] = [];
@ -233,7 +240,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
const newPokemon = transformation.newPokemon;
const previousPokemon = transformation.previousPokemon;
await postProcessTransformedPokemon(scene, previousPokemon, newPokemon, newPokemon.species.getRootSpeciesId(), true);
await postProcessTransformedPokemon(previousPokemon, newPokemon, newPokemon.species.getRootSpeciesId(), true);
const dataSource = new PokemonData(newPokemon);
dataSource.player = false;
@ -251,7 +258,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
if (shouldGetOldGateau(newPokemon)) {
const stats = getOldGateauBoostedStats(newPokemon);
newPokemonHeldItemConfigs.push({
modifier: generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [ OLD_GATEAU_STATS_UP, stats ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [ OLD_GATEAU_STATS_UP, stats ]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false
});
@ -268,7 +275,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
enemyPokemonConfigs.push(enemyConfig);
}
const genderIndex = scene.gameData.gender ?? PlayerGender.UNSET;
const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET;
const trainerConfig = trainerConfigs[genderIndex === PlayerGender.FEMALE ? TrainerType.FUTURE_SELF_F : TrainerType.FUTURE_SELF_M].clone();
trainerConfig.setPartyTemplates(new TrainerPartyTemplate(transformations.length, PartyMemberStrength.STRONG));
const enemyPartyConfig: EnemyPartyConfig = {
@ -280,7 +287,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
const onBeforeRewards = () => {
// Before battle rewards, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently)
// One random pokemon will get its passive unlocked
const passiveDisabledPokemon = scene.getPlayerParty().filter(p => !p.passive);
const passiveDisabledPokemon = globalScene.getPlayerParty().filter(p => !p.passive);
if (passiveDisabledPokemon?.length > 0) {
const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)];
enablePassiveMon.passive = true;
@ -288,10 +295,10 @@ export const WeirdDreamEncounter: MysteryEncounter =
}
};
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: false }, undefined, onBeforeRewards);
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: false }, undefined, onBeforeRewards);
await showEncounterText(scene, `${namespace}:option.2.selected_2`, null, undefined, true);
await initBattleWithEnemyConfig(scene, enemyPartyConfig);
await showEncounterText(`${namespace}:option.2.selected_2`, null, undefined, true);
await initBattleWithEnemyConfig(enemyPartyConfig);
}
)
.withSimpleOption(
@ -304,9 +311,9 @@ export const WeirdDreamEncounter: MysteryEncounter =
},
],
},
async (scene: BattleScene) => {
async () => {
// Leave, reduce party levels by 10%
for (const pokemon of scene.getPlayerParty()) {
for (const pokemon of globalScene.getPlayerParty()) {
pokemon.level = Math.max(Math.ceil((100 - PERCENT_LEVEL_LOSS_ON_REFUSE) / 100 * pokemon.level), 1);
pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
pokemon.levelExp = 0;
@ -316,7 +323,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
await pokemon.updateInfo();
}
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(true);
return true;
}
)
@ -329,8 +336,8 @@ interface PokemonTransformation {
heldItems: PokemonHeldItemModifier[];
}
function getTeamTransformations(scene: BattleScene): PokemonTransformation[] {
const party = scene.getPlayerParty();
function getTeamTransformations(): PokemonTransformation[] {
const party = globalScene.getPlayerParty();
// Removes all pokemon from the party
const alreadyUsedSpecies: PokemonSpecies[] = party.map(p => p.species);
const pokemonTransformations: PokemonTransformation[] = party.map(p => {
@ -379,37 +386,37 @@ function getTeamTransformations(scene: BattleScene): PokemonTransformation[] {
for (const transformation of pokemonTransformations) {
const newAbilityIndex = randSeedInt(transformation.newSpecies.getAbilityCount());
transformation.newPokemon = scene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined);
transformation.newPokemon = globalScene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined);
}
return pokemonTransformations;
}
async function doNewTeamPostProcess(scene: BattleScene, transformations: PokemonTransformation[]) {
async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
let atLeastOneNewStarter = false;
for (const transformation of transformations) {
const previousPokemon = transformation.previousPokemon;
const newPokemon = transformation.newPokemon;
const speciesRootForm = newPokemon.species.getRootSpeciesId();
if (await postProcessTransformedPokemon(scene, previousPokemon, newPokemon, speciesRootForm)) {
if (await postProcessTransformedPokemon(previousPokemon, newPokemon, speciesRootForm)) {
atLeastOneNewStarter = true;
}
// Copy old items to new pokemon
for (const item of transformation.heldItems) {
item.pokemonId = newPokemon.id;
await scene.addModifier(item, false, false, false, true);
await globalScene.addModifier(item, false, false, false, true);
}
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
if (shouldGetOldGateau(newPokemon)) {
const stats = getOldGateauBoostedStats(newPokemon);
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU()
.generateType(scene.getPlayerParty(), [ OLD_GATEAU_STATS_UP, stats ])
.generateType(globalScene.getPlayerParty(), [ OLD_GATEAU_STATS_UP, stats ])
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU);
const modifier = modType?.newModifier(newPokemon);
if (modifier) {
await scene.addModifier(modifier, false, false, false, true);
await globalScene.addModifier(modifier, false, false, false, true);
}
}
@ -418,7 +425,7 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
}
// One random pokemon will get its passive unlocked
const passiveDisabledPokemon = scene.getPlayerParty().filter(p => !p.passive);
const passiveDisabledPokemon = globalScene.getPlayerParty().filter(p => !p.passive);
if (passiveDisabledPokemon?.length > 0) {
const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)];
enablePassiveMon.passive = true;
@ -427,27 +434,26 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
// If at least one new starter was unlocked, play 1 fanfare
if (atLeastOneNewStarter) {
scene.playSound("level_up_fanfare");
globalScene.playSound("level_up_fanfare");
}
}
/**
* Applies special changes to the newly transformed pokemon, such as passing previous moves, gaining egg moves, etc.
* Returns whether the transformed pokemon unlocks a new starter for the player.
* @param scene
* @param previousPokemon
* @param newPokemon
* @param speciesRootForm
* @param forBattle Default `false`. If false, will perform achievements and dex unlocks for the player.
*/
async function postProcessTransformedPokemon(scene: BattleScene, previousPokemon: PlayerPokemon, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise<boolean> {
async function postProcessTransformedPokemon(previousPokemon: PlayerPokemon, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise<boolean> {
let isNewStarter = false;
// Roll HA a second time
if (newPokemon.species.abilityHidden) {
const hiddenIndex = newPokemon.species.ability2 ? 2 : 1;
if (newPokemon.abilityIndex < hiddenIndex) {
const hiddenAbilityChance = new IntegerHolder(256);
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
@ -469,26 +475,26 @@ async function postProcessTransformedPokemon(scene: BattleScene, previousPokemon
// For pokemon at/below 570 BST or any shiny pokemon, unlock it permanently as if you had caught it
if (!forBattle && (newPokemon.getSpeciesForm().getBaseStatTotal() <= NON_LEGENDARY_BST_THRESHOLD || newPokemon.isShiny())) {
if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) {
scene.validateAchv(achvs.HIDDEN_ABILITY);
globalScene.validateAchv(achvs.HIDDEN_ABILITY);
}
if (newPokemon.species.subLegendary) {
scene.validateAchv(achvs.CATCH_SUB_LEGENDARY);
globalScene.validateAchv(achvs.CATCH_SUB_LEGENDARY);
}
if (newPokemon.species.legendary) {
scene.validateAchv(achvs.CATCH_LEGENDARY);
globalScene.validateAchv(achvs.CATCH_LEGENDARY);
}
if (newPokemon.species.mythical) {
scene.validateAchv(achvs.CATCH_MYTHICAL);
globalScene.validateAchv(achvs.CATCH_MYTHICAL);
}
scene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs);
const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false);
globalScene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs);
const newStarterUnlocked = await globalScene.gameData.setPokemonCaught(newPokemon, true, false, false);
if (newStarterUnlocked) {
isNewStarter = true;
await showEncounterText(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() }));
await showEncounterText(i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() }));
}
}
@ -504,8 +510,8 @@ async function postProcessTransformedPokemon(scene: BattleScene, previousPokemon
});
// For pokemon that the player owns (including ones just caught), gain a candy
if (!forBattle && !!scene.gameData.dexData[speciesRootForm].caughtAttr) {
scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1);
if (!forBattle && !!globalScene.gameData.dexData[speciesRootForm].caughtAttr) {
globalScene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1);
}
// Set the moveset of the new pokemon to be the same as previous, but with 1 egg move and 1 (attempted) STAB move of the new species
@ -515,7 +521,7 @@ async function postProcessTransformedPokemon(scene: BattleScene, previousPokemon
newPokemon.moveset = previousPokemon.moveset.slice(0);
const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(scene, newPokemon, speciesRootForm, forBattle);
const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(newPokemon, speciesRootForm, forBattle);
// Try to add a favored STAB move (might fail if Pokemon already knows a bunch of moves from newPokemonGeneratedMoveset)
addFavoredMoveToNewPokemonMoveset(newPokemon, newPokemonGeneratedMoveset, newEggMoveIndex);
@ -597,31 +603,31 @@ function getTransformedSpecies(originalBst: number, bstSearchRange: [number, num
return newSpecies;
}
function doShowDreamBackground(scene: BattleScene) {
const transformationContainer = scene.add.container(0, -scene.game.canvas.height / 6);
function doShowDreamBackground() {
const transformationContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6);
transformationContainer.name = "Dream Background";
// In case it takes a bit for video to load
const transformationStaticBg = scene.add.rectangle(0, 0, scene.game.canvas.width / 6, scene.game.canvas.height / 6, 0);
const transformationStaticBg = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0);
transformationStaticBg.setName("Black Background");
transformationStaticBg.setOrigin(0, 0);
transformationContainer.add(transformationStaticBg);
transformationStaticBg.setVisible(true);
const transformationVideoBg: Phaser.GameObjects.Video = scene.add.video(0, 0, "evo_bg").stop();
const transformationVideoBg: Phaser.GameObjects.Video = globalScene.add.video(0, 0, "evo_bg").stop();
transformationVideoBg.setLoop(true);
transformationVideoBg.setOrigin(0, 0);
transformationVideoBg.setScale(0.4359673025);
transformationContainer.add(transformationVideoBg);
scene.fieldUI.add(transformationContainer);
scene.fieldUI.bringToTop(transformationContainer);
globalScene.fieldUI.add(transformationContainer);
globalScene.fieldUI.bringToTop(transformationContainer);
transformationVideoBg.play();
transformationContainer.setVisible(true);
transformationContainer.alpha = 0;
scene.tweens.add({
globalScene.tweens.add({
targets: transformationContainer,
alpha: 1,
duration: 3000,
@ -629,39 +635,39 @@ function doShowDreamBackground(scene: BattleScene) {
});
}
function doHideDreamBackground(scene: BattleScene) {
const transformationContainer = scene.fieldUI.getByName("Dream Background");
function doHideDreamBackground() {
const transformationContainer = globalScene.fieldUI.getByName("Dream Background");
scene.tweens.add({
globalScene.tweens.add({
targets: transformationContainer,
alpha: 0,
duration: 3000,
ease: "Sine.easeInOut",
onComplete: () => {
scene.fieldUI.remove(transformationContainer, true);
globalScene.fieldUI.remove(transformationContainer, true);
}
});
}
function doSideBySideTransformations(scene: BattleScene, transformations: PokemonTransformation[]) {
function doSideBySideTransformations(transformations: PokemonTransformation[]) {
return new Promise<void>(resolve => {
const allTransformationPromises: Promise<void>[] = [];
for (let i = 0; i < 3; i++) {
const delay = i * 4000;
scene.time.delayedCall(delay, () => {
globalScene.time.delayedCall(delay, () => {
const transformation = transformations[i];
const pokemon1 = transformation.previousPokemon;
const pokemon2 = transformation.newPokemon;
const screenPosition = i as TransformationScreenPosition;
const transformationPromise = doPokemonTransformationSequence(scene, pokemon1, pokemon2, screenPosition)
const transformationPromise = doPokemonTransformationSequence(pokemon1, pokemon2, screenPosition)
.then(() => {
if (transformations.length > i + 3) {
const nextTransformationAtPosition = transformations[i + 3];
const nextPokemon1 = nextTransformationAtPosition.previousPokemon;
const nextPokemon2 = nextTransformationAtPosition.newPokemon;
allTransformationPromises.push(doPokemonTransformationSequence(scene, nextPokemon1, nextPokemon2, screenPosition));
allTransformationPromises.push(doPokemonTransformationSequence(nextPokemon1, nextPokemon2, screenPosition));
}
});
allTransformationPromises.push(transformationPromise);
@ -682,11 +688,10 @@ function doSideBySideTransformations(scene: BattleScene, transformations: Pokemo
/**
* Returns index of the new egg move within the Pokemon's moveset (not the index of the move in `speciesEggMoves`)
* @param scene
* @param newPokemon
* @param speciesRootForm
*/
async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise<number | null> {
async function addEggMoveToNewPokemonMoveset(newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise<number | null> {
let eggMoveIndex: null | number = null;
const eggMoves = newPokemon.getEggMoves()?.slice(0);
if (eggMoves) {
@ -712,8 +717,8 @@ async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: Pla
}
// For pokemon that the player owns (including ones just caught), unlock the egg move
if (!forBattle && !isNullOrUndefined(randomEggMoveIndex) && !!scene.gameData.dexData[speciesRootForm].caughtAttr) {
await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true);
if (!forBattle && !isNullOrUndefined(randomEggMoveIndex) && !!globalScene.gameData.dexData[speciesRootForm].caughtAttr) {
await globalScene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true);
}
}
}

View File

@ -1,4 +1,4 @@
import { TextStyle } from "#app/ui/text";
import type { TextStyle } from "#app/ui/text";
export class TextDisplay {
speaker?: string;

View File

@ -1,15 +1,17 @@
import { OptionTextDisplay } from "#app/data/mystery-encounters/mystery-encounter-dialogue";
import { Moves } from "#app/enums/moves";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import BattleScene from "#app/battle-scene";
import { Type } from "#enums/type";
import type { OptionTextDisplay } from "#app/data/mystery-encounters/mystery-encounter-dialogue";
import type { Moves } from "#app/enums/moves";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene";
import type { Type } from "#enums/type";
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
import type { CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
import { CanLearnMoveRequirement } from "./requirements/can-learn-move-requirement";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
export type OptionPhaseCallback = (scene: BattleScene) => Promise<void | boolean>;
export type OptionPhaseCallback = () => Promise<void | boolean>;
/**
* Used by {@linkcode MysteryEncounterOptionBuilder} class to define required/optional properties on the {@linkcode MysteryEncounterOption} class when building.
@ -74,21 +76,19 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
/**
* Returns true if all {@linkcode EncounterRequirement}s for the option are met
* @param scene
*/
meetsRequirements(scene: BattleScene): boolean {
return !this.requirements.some(requirement => !requirement.meetsRequirement(scene))
&& this.meetsSupportingRequirementAndSupportingPokemonSelected(scene)
&& this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene);
meetsRequirements(): boolean {
return !this.requirements.some(requirement => !requirement.meetsRequirement())
&& this.meetsSupportingRequirementAndSupportingPokemonSelected()
&& this.meetsPrimaryRequirementAndPrimaryPokemonSelected();
}
/**
* Returns true if all PRIMARY {@linkcode EncounterRequirement}s for the option are met
* @param scene
* @param pokemon
*/
pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon): boolean {
return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getPlayerParty()).map(p => p.id).includes(pokemon.id));
pokemonMeetsPrimaryRequirements(pokemon: Pokemon): boolean {
return !this.primaryPokemonRequirements.some(req => !req.queryParty(globalScene.getPlayerParty()).map(p => p.id).includes(pokemon.id));
}
/**
@ -96,16 +96,15 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
* AND there is a valid Pokemon assigned to {@linkcode primaryPokemon}.
* If both {@linkcode primaryPokemonRequirements} and {@linkcode secondaryPokemonRequirements} are defined,
* can cause scenarios where there are not enough Pokemon that are sufficient for all requirements.
* @param scene
*/
meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene): boolean {
meetsPrimaryRequirementAndPrimaryPokemonSelected(): boolean {
if (!this.primaryPokemonRequirements || this.primaryPokemonRequirements.length === 0) {
return true;
}
let qualified: PlayerPokemon[] = scene.getPlayerParty();
let qualified: PlayerPokemon[] = globalScene.getPlayerParty();
for (const req of this.primaryPokemonRequirements) {
if (req.meetsRequirement(scene)) {
const queryParty = req.queryParty(scene.getPlayerParty());
if (req.meetsRequirement()) {
const queryParty = req.queryParty(globalScene.getPlayerParty());
qualified = qualified.filter(pkmn => queryParty.includes(pkmn));
} else {
this.primaryPokemon = undefined;
@ -154,18 +153,17 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
* AND there is a valid Pokemon assigned to {@linkcode secondaryPokemon} (if applicable).
* If both {@linkcode primaryPokemonRequirements} and {@linkcode secondaryPokemonRequirements} are defined,
* can cause scenarios where there are not enough Pokemon that are sufficient for all requirements.
* @param scene
*/
meetsSupportingRequirementAndSupportingPokemonSelected(scene: BattleScene): boolean {
meetsSupportingRequirementAndSupportingPokemonSelected(): boolean {
if (!this.secondaryPokemonRequirements || this.secondaryPokemonRequirements.length === 0) {
this.secondaryPokemon = [];
return true;
}
let qualified: PlayerPokemon[] = scene.getPlayerParty();
let qualified: PlayerPokemon[] = globalScene.getPlayerParty();
for (const req of this.secondaryPokemonRequirements) {
if (req.meetsRequirement(scene)) {
const queryParty = req.queryParty(scene.getPlayerParty());
if (req.meetsRequirement()) {
const queryParty = req.queryParty(globalScene.getPlayerParty());
qualified = qualified.filter(pkmn => queryParty.includes(pkmn));
} else {
this.secondaryPokemon = [];

View File

@ -1,4 +1,4 @@
import BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import { allAbilities } from "#app/data/ability";
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { Nature } from "#enums/nature";
@ -6,35 +6,33 @@ import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from
import { StatusEffect } from "#enums/status-effect";
import { Type } from "#enums/type";
import { WeatherType } from "#enums/weather-type";
import { PlayerPokemon } from "#app/field/pokemon";
import type { PlayerPokemon } from "#app/field/pokemon";
import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { isNullOrUndefined } from "#app/utils";
import { Abilities } from "#enums/abilities";
import type { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import { SpeciesFormKey } from "#enums/species-form-key";
import { TimeOfDay } from "#enums/time-of-day";
export interface EncounterRequirement {
meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
meetsRequirement(): boolean; // Boolean to see if a requirement is met
getDialogueToken(pokemon?: PlayerPokemon): [string, string];
}
export abstract class EncounterSceneRequirement implements EncounterRequirement {
/**
* Returns whether the EncounterSceneRequirement's... requirements, are met by the given scene
* @param partyPokemon
*/
abstract meetsRequirement(scene: BattleScene): boolean;
abstract meetsRequirement(): boolean;
/**
* Returns a dialogue token key/value pair for a given Requirement.
* Should be overridden by child Requirement classes.
* @param scene
* @param pokemon
*/
abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
abstract getDialogueToken(pokemon?: PlayerPokemon): [string, string];
}
/**
@ -61,33 +59,31 @@ export class CombinationSceneRequirement extends EncounterSceneRequirement {
/**
* Checks if all/any requirements are met (depends on {@linkcode isAnd})
* @param scene The {@linkcode BattleScene} to check against
* @returns true if all/any requirements are met (depends on {@linkcode isAnd})
*/
override meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(): boolean {
return this.isAnd
? this.requirements.every(req => req.meetsRequirement(scene))
: this.requirements.some(req => req.meetsRequirement(scene));
? this.requirements.every(req => req.meetsRequirement())
: this.requirements.some(req => req.meetsRequirement());
}
/**
* Retrieves a dialogue token key/value pair for the given {@linkcode EncounterSceneRequirement | requirements}.
* @param scene The {@linkcode BattleScene} to check against
* @param pokemon The {@linkcode PlayerPokemon} to check against
* @returns A dialogue token key/value pair
* @throws An {@linkcode Error} if {@linkcode isAnd} is `true` (not supported)
*/
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
if (this.isAnd) {
throw new Error("Not implemented (Sorry)");
} else {
for (const req of this.requirements) {
if (req.meetsRequirement(scene)) {
return req.getDialogueToken(scene, pokemon);
if (req.meetsRequirement()) {
return req.getDialogueToken(pokemon);
}
}
return this.requirements[0].getDialogueToken(scene, pokemon);
return this.requirements[0].getDialogueToken(pokemon);
}
}
}
@ -98,9 +94,8 @@ export abstract class EncounterPokemonRequirement implements EncounterRequiremen
/**
* Returns whether the EncounterPokemonRequirement's... requirements, are met by the given scene
* @param partyPokemon
*/
abstract meetsRequirement(scene: BattleScene): boolean;
abstract meetsRequirement(): boolean;
/**
* Returns all party members that are compatible with this requirement. For non pokemon related requirements, the entire party is returned.
@ -111,10 +106,9 @@ export abstract class EncounterPokemonRequirement implements EncounterRequiremen
/**
* Returns a dialogue token key/value pair for a given Requirement.
* Should be overridden by child Requirement classes.
* @param scene
* @param pokemon
*/
abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
abstract getDialogueToken(pokemon?: PlayerPokemon): [string, string];
}
/**
@ -143,13 +137,12 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
/**
* Checks if all/any requirements are met (depends on {@linkcode isAnd})
* @param scene The {@linkcode BattleScene} to check against
* @returns true if all/any requirements are met (depends on {@linkcode isAnd})
*/
override meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(): boolean {
return this.isAnd
? this.requirements.every(req => req.meetsRequirement(scene))
: this.requirements.some(req => req.meetsRequirement(scene));
? this.requirements.every(req => req.meetsRequirement())
: this.requirements.some(req => req.meetsRequirement());
}
/**
@ -168,22 +161,21 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
/**
* Retrieves a dialogue token key/value pair for the given {@linkcode EncounterPokemonRequirement | requirements}.
* @param scene The {@linkcode BattleScene} to check against
* @param pokemon The {@linkcode PlayerPokemon} to check against
* @returns A dialogue token key/value pair
* @throws An {@linkcode Error} if {@linkcode isAnd} is `true` (not supported)
*/
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
if (this.isAnd) {
throw new Error("Not implemented (Sorry)");
} else {
for (const req of this.requirements) {
if (req.meetsRequirement(scene)) {
return req.getDialogueToken(scene, pokemon);
if (req.meetsRequirement()) {
return req.getDialogueToken(pokemon);
}
}
return this.requirements[0].getDialogueToken(scene, pokemon);
return this.requirements[0].getDialogueToken(pokemon);
}
}
}
@ -200,12 +192,12 @@ export class PreviousEncounterRequirement extends EncounterSceneRequirement {
this.previousEncounterRequirement = previousEncounterRequirement;
}
override meetsRequirement(scene: BattleScene): boolean {
return scene.mysteryEncounterSaveData.encounteredEvents.some(e => e.type === this.previousEncounterRequirement);
override meetsRequirement(): boolean {
return globalScene.mysteryEncounterSaveData.encounteredEvents.some(e => e.type === this.previousEncounterRequirement);
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return [ "previousEncounter", scene.mysteryEncounterSaveData.encounteredEvents.find(e => e.type === this.previousEncounterRequirement)?.[0].toString() ?? "" ];
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return [ "previousEncounter", globalScene.mysteryEncounterSaveData.encounteredEvents.find(e => e.type === this.previousEncounterRequirement)?.[0].toString() ?? "" ];
}
}
@ -222,9 +214,9 @@ export class WaveRangeRequirement extends EncounterSceneRequirement {
this.waveRange = waveRange;
}
override meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(): boolean {
if (!isNullOrUndefined(this.waveRange) && this.waveRange[0] <= this.waveRange[1]) {
const waveIndex = scene.currentBattle.waveIndex;
const waveIndex = globalScene.currentBattle.waveIndex;
if (waveIndex >= 0 && (this.waveRange[0] >= 0 && this.waveRange[0] > waveIndex) || (this.waveRange[1] >= 0 && this.waveRange[1] < waveIndex)) {
return false;
}
@ -232,8 +224,8 @@ export class WaveRangeRequirement extends EncounterSceneRequirement {
return true;
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return [ "waveIndex", scene.currentBattle.waveIndex.toString() ];
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return [ "waveIndex", globalScene.currentBattle.waveIndex.toString() ];
}
}
@ -257,12 +249,12 @@ export class WaveModulusRequirement extends EncounterSceneRequirement {
this.modulusValue = modulusValue;
}
override meetsRequirement(scene: BattleScene): boolean {
return this.waveModuli.includes(scene.currentBattle.waveIndex % this.modulusValue);
override meetsRequirement(): boolean {
return this.waveModuli.includes(globalScene.currentBattle.waveIndex % this.modulusValue);
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return [ "waveIndex", scene.currentBattle.waveIndex.toString() ];
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return [ "waveIndex", globalScene.currentBattle.waveIndex.toString() ];
}
}
@ -274,8 +266,8 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [ timeOfDay ];
}
override meetsRequirement(scene: BattleScene): boolean {
const timeOfDay = scene.arena?.getTimeOfDay();
override meetsRequirement(): boolean {
const timeOfDay = globalScene.arena?.getTimeOfDay();
if (!isNullOrUndefined(timeOfDay) && this.requiredTimeOfDay?.length > 0 && !this.requiredTimeOfDay.includes(timeOfDay)) {
return false;
}
@ -283,8 +275,8 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
return true;
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return [ "timeOfDay", TimeOfDay[scene.arena.getTimeOfDay()].toLocaleLowerCase() ];
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return [ "timeOfDay", TimeOfDay[globalScene.arena.getTimeOfDay()].toLocaleLowerCase() ];
}
}
@ -296,8 +288,8 @@ export class WeatherRequirement extends EncounterSceneRequirement {
this.requiredWeather = Array.isArray(weather) ? weather : [ weather ];
}
override meetsRequirement(scene: BattleScene): boolean {
const currentWeather = scene.arena.weather?.weatherType;
override meetsRequirement(): boolean {
const currentWeather = globalScene.arena.weather?.weatherType;
if (!isNullOrUndefined(currentWeather) && this.requiredWeather?.length > 0 && !this.requiredWeather.includes(currentWeather!)) {
return false;
}
@ -305,8 +297,8 @@ export class WeatherRequirement extends EncounterSceneRequirement {
return true;
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const currentWeather = scene.arena.weather?.weatherType;
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const currentWeather = globalScene.arena.weather?.weatherType;
let token = "";
if (!isNullOrUndefined(currentWeather)) {
token = WeatherType[currentWeather].replace("_", " ").toLocaleLowerCase();
@ -331,9 +323,9 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
}
override meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(): boolean {
if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange[0] <= this.partySizeRange[1]) {
const partySize = this.excludeDisallowedPokemon ? scene.getPokemonAllowedInBattle().length : scene.getPlayerParty().length;
const partySize = this.excludeDisallowedPokemon ? globalScene.getPokemonAllowedInBattle().length : globalScene.getPlayerParty().length;
if (partySize >= 0 && (this.partySizeRange[0] >= 0 && this.partySizeRange[0] > partySize) || (this.partySizeRange[1] >= 0 && this.partySizeRange[1] < partySize)) {
return false;
}
@ -342,8 +334,8 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
return true;
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return [ "partySize", scene.getPlayerParty().length.toString() ];
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return [ "partySize", globalScene.getPlayerParty().length.toString() ];
}
}
@ -357,14 +349,14 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [ heldItem ];
}
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getPlayerParty();
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon) || this.requiredHeldItemModifiers?.length < 0) {
return false;
}
let modifierCount = 0;
this.requiredHeldItemModifiers.forEach(modifier => {
const matchingMods = scene.findModifiers(m => m.constructor.name === modifier);
const matchingMods = globalScene.findModifiers(m => m.constructor.name === modifier);
if (matchingMods?.length > 0) {
matchingMods.forEach(matchingMod => {
modifierCount += matchingMod.stackCount;
@ -375,7 +367,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
return modifierCount >= this.minNumberOfItems;
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return [ "requiredItem", this.requiredHeldItemModifiers[0] ];
}
}
@ -390,20 +382,20 @@ export class MoneyRequirement extends EncounterSceneRequirement {
this.scalingMultiplier = scalingMultiplier ?? 0;
}
override meetsRequirement(scene: BattleScene): boolean {
const money = scene.money;
override meetsRequirement(): boolean {
const money = globalScene.money;
if (isNullOrUndefined(money)) {
return false;
}
if (this.scalingMultiplier > 0) {
this.requiredMoney = scene.getWaveMoneyAmount(this.scalingMultiplier);
this.requiredMoney = globalScene.getWaveMoneyAmount(this.scalingMultiplier);
}
return !(this.requiredMoney > 0 && this.requiredMoney > money);
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const value = this.scalingMultiplier > 0 ? scene.getWaveMoneyAmount(this.scalingMultiplier).toString() : this.requiredMoney.toString();
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const value = this.scalingMultiplier > 0 ? globalScene.getWaveMoneyAmount(this.scalingMultiplier).toString() : this.requiredMoney.toString();
return [ "money", value ];
}
}
@ -420,8 +412,8 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
this.requiredSpecies = Array.isArray(species) ? species : [ species ];
}
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getPlayerParty();
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon) || this.requiredSpecies?.length < 0) {
return false;
}
@ -437,7 +429,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
if (pokemon?.species.speciesId && this.requiredSpecies.includes(pokemon.species.speciesId)) {
return [ "species", Species[pokemon.species.speciesId] ];
}
@ -458,8 +450,8 @@ export class NatureRequirement extends EncounterPokemonRequirement {
this.requiredNature = Array.isArray(nature) ? nature : [ nature ];
}
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getPlayerParty();
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon) || this.requiredNature?.length < 0) {
return false;
}
@ -475,7 +467,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon.nature)) {
return [ "nature", Nature[pokemon.nature] ];
}
@ -497,8 +489,8 @@ export class TypeRequirement extends EncounterPokemonRequirement {
this.requiredType = Array.isArray(type) ? type : [ type ];
}
override meetsRequirement(scene: BattleScene): boolean {
let partyPokemon = scene.getPlayerParty();
override meetsRequirement(): boolean {
let partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon)) {
return false;
@ -520,7 +512,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const includedTypes = this.requiredType.filter((ty) => pokemon?.getTypes().includes(ty));
if (includedTypes.length > 0) {
return [ "type", Type[includedTypes[0]] ];
@ -544,8 +536,8 @@ export class MoveRequirement extends EncounterPokemonRequirement {
this.requiredMoves = Array.isArray(moves) ? moves : [ moves ];
}
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getPlayerParty();
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) {
return false;
}
@ -566,7 +558,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const includedMoves = pokemon?.moveset.filter((move) => move?.moveId && this.requiredMoves.includes(move.moveId));
if (includedMoves && includedMoves.length > 0 && includedMoves[0]) {
return [ "move", includedMoves[0].getName() ];
@ -593,8 +585,8 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [ learnableMove ];
}
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getPlayerParty();
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) {
return false;
}
@ -610,7 +602,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const includedCompatMoves = this.requiredMoves.filter((reqMove) => pokemon?.compatibleTms.filter((tm) => !pokemon.moveset.find(m => m?.moveId === tm)).includes(reqMove));
if (includedCompatMoves.length > 0) {
return [ "compatibleMove", Moves[includedCompatMoves[0]] ];
@ -634,8 +626,8 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
this.requiredAbilities = Array.isArray(abilities) ? abilities : [ abilities ];
}
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getPlayerParty();
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon) || this.requiredAbilities?.length < 0) {
return false;
}
@ -655,7 +647,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(_scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const matchingAbility = this.requiredAbilities.find(a => pokemon?.hasAbility(a, false));
if (!isNullOrUndefined(matchingAbility)) {
return [ "ability", allAbilities[matchingAbility].name ];
@ -676,8 +668,8 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [ statusEffect ];
}
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getPlayerParty();
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon) || this.requiredStatusEffect?.length < 0) {
return false;
}
@ -713,7 +705,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const reqStatus = this.requiredStatusEffect.filter((a) => {
if (a === StatusEffect.NONE) {
return isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === a;
@ -745,8 +737,8 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [ formChangeItem ];
}
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getPlayerParty();
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon) || this.requiredFormChangeItem?.length < 0) {
return false;
}
@ -775,7 +767,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const requiredItems = this.requiredFormChangeItem.filter((formChangeItem) => this.filterByForm(pokemon, formChangeItem));
if (requiredItems.length > 0) {
return [ "formChangeItem", FormChangeItem[requiredItems[0]] ];
@ -797,8 +789,8 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [ evolutionItems ];
}
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getPlayerParty();
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon) || this.requiredEvolutionItem?.length < 0) {
return false;
}
@ -825,7 +817,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const requiredItems = this.requiredEvolutionItem.filter((evoItem) => this.filterByEvo(pokemon, evoItem));
if (requiredItems.length > 0) {
return [ "evolutionItem", EvolutionItem[requiredItems[0]] ];
@ -848,8 +840,8 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
this.requireTransferable = requireTransferable;
}
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getPlayerParty();
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon)) {
return false;
}
@ -873,7 +865,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const requiredItems = pokemon?.getHeldItems().filter((it) => {
return this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem)
&& (!this.requireTransferable || it.isTransferable);
@ -899,8 +891,8 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
this.requireTransferable = requireTransferable;
}
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getPlayerParty();
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon)) {
return false;
}
@ -928,7 +920,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const requiredItems = pokemon?.getHeldItems().filter((it) => {
return this.requiredHeldItemTypes.some(heldItemType =>
it instanceof AttackTypeBoosterModifier
@ -954,10 +946,10 @@ export class LevelRequirement extends EncounterPokemonRequirement {
this.requiredLevelRange = requiredLevelRange;
}
override meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(): boolean {
// Party Pokemon inside required level range
if (!isNullOrUndefined(this.requiredLevelRange) && this.requiredLevelRange[0] <= this.requiredLevelRange[1]) {
const partyPokemon = scene.getPlayerParty();
const partyPokemon = globalScene.getPlayerParty();
const pokemonInRange = this.queryParty(partyPokemon);
if (pokemonInRange.length < this.minNumberOfPokemon) {
return false;
@ -975,7 +967,7 @@ export class LevelRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return [ "level", pokemon?.level.toString() ?? "" ];
}
}
@ -992,10 +984,10 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
this.requiredFriendshipRange = requiredFriendshipRange;
}
override meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(): boolean {
// Party Pokemon inside required friendship range
if (!isNullOrUndefined(this.requiredFriendshipRange) && this.requiredFriendshipRange[0] <= this.requiredFriendshipRange[1]) {
const partyPokemon = scene.getPlayerParty();
const partyPokemon = globalScene.getPlayerParty();
const pokemonInRange = this.queryParty(partyPokemon);
if (pokemonInRange.length < this.minNumberOfPokemon) {
return false;
@ -1013,7 +1005,7 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return [ "friendship", pokemon?.friendship.toString() ?? "" ];
}
}
@ -1035,10 +1027,10 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
this.requiredHealthRange = requiredHealthRange;
}
override meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(): boolean {
// Party Pokemon's health inside required health range
if (!isNullOrUndefined(this.requiredHealthRange) && this.requiredHealthRange[0] <= this.requiredHealthRange[1]) {
const partyPokemon = scene.getPlayerParty();
const partyPokemon = globalScene.getPlayerParty();
const pokemonInRange = this.queryParty(partyPokemon);
if (pokemonInRange.length < this.minNumberOfPokemon) {
return false;
@ -1058,7 +1050,7 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const hpRatio = pokemon?.getHpRatio();
if (!isNullOrUndefined(hpRatio)) {
return [ "healthRatio", Math.floor(hpRatio * 100).toString() + "%" ];
@ -1079,10 +1071,10 @@ export class WeightRequirement extends EncounterPokemonRequirement {
this.requiredWeightRange = requiredWeightRange;
}
override meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(): boolean {
// Party Pokemon's weight inside required weight range
if (!isNullOrUndefined(this.requiredWeightRange) && this.requiredWeightRange[0] <= this.requiredWeightRange[1]) {
const partyPokemon = scene.getPlayerParty();
const partyPokemon = globalScene.getPlayerParty();
const pokemonInRange = this.queryParty(partyPokemon);
if (pokemonInRange.length < this.minNumberOfPokemon) {
return false;
@ -1100,7 +1092,7 @@ export class WeightRequirement extends EncounterPokemonRequirement {
}
}
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return [ "weight", pokemon?.getWeight().toString() ?? "" ];
}
}

View File

@ -1,7 +1,7 @@
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters";
import { isNullOrUndefined } from "#app/utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
export class SeenEncounterData {
type: MysteryEncounterType;

View File

@ -1,21 +1,26 @@
import { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro";
import * as Utils from "#app/utils";
import { StatusEffect } from "#enums/status-effect";
import MysteryEncounterDialogue, { OptionTextDisplay } from "./mystery-encounter-dialogue";
import MysteryEncounterOption, { MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option";
import type { StatusEffect } from "#enums/status-effect";
import type { OptionTextDisplay } from "./mystery-encounter-dialogue";
import type MysteryEncounterDialogue from "./mystery-encounter-dialogue";
import type { OptionPhaseCallback } from "./mystery-encounter-option";
import type MysteryEncounterOption from "./mystery-encounter-option";
import { MysteryEncounterOptionBuilder } from "./mystery-encounter-option";
import { EncounterPokemonRequirement, EncounterSceneRequirement, HealthRatioRequirement, PartySizeRequirement, StatusEffectRequirement, WaveRangeRequirement } from "./mystery-encounter-requirements";
import { BattlerIndex } from "#app/battle";
import type { BattlerIndex } from "#app/battle";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { GameModes } from "#app/game-mode";
import { EncounterAnim } from "#enums/encounter-anims";
import { Challenges } from "#enums/challenges";
import type { GameModes } from "#app/game-mode";
import type { EncounterAnim } from "#enums/encounter-anims";
import type { Challenges } from "#enums/challenges";
import { globalScene } from "#app/global-scene";
export interface EncounterStartOfBattleEffect {
sourcePokemon?: Pokemon;
@ -55,11 +60,11 @@ export interface IMysteryEncounter {
skipToFightInput: boolean;
preventGameStatsUpdates: boolean;
onInit?: (scene: BattleScene) => boolean;
onVisualsStart?: (scene: BattleScene) => boolean;
doEncounterExp?: (scene: BattleScene) => boolean;
doEncounterRewards?: (scene: BattleScene) => boolean;
doContinueEncounter?: (scene: BattleScene) => Promise<void>;
onInit?: () => boolean;
onVisualsStart?: () => boolean;
doEncounterExp?: () => boolean;
doEncounterRewards?: () => boolean;
doContinueEncounter?: () => Promise<void>;
requirements: EncounterSceneRequirement[];
primaryPokemonRequirements: EncounterPokemonRequirement[];
@ -159,24 +164,24 @@ export default class MysteryEncounter implements IMysteryEncounter {
// #region Event callback functions
/** Event when Encounter is first loaded, use it for data conditioning */
onInit?: (scene: BattleScene) => boolean;
onInit?: () => boolean;
/** Event when battlefield visuals have finished sliding in and the encounter dialogue begins */
onVisualsStart?: (scene: BattleScene) => boolean;
onVisualsStart?: () => boolean;
/** Event triggered prior to {@linkcode CommandPhase}, during {@linkcode TurnInitPhase} */
onTurnStart?: (scene: BattleScene) => boolean;
onTurnStart?: () => boolean;
/** Event prior to any rewards logic in {@linkcode MysteryEncounterRewardsPhase} */
onRewards?: (scene: BattleScene) => Promise<void>;
onRewards?: () => Promise<void>;
/** Will provide the player party EXP before rewards are displayed for that wave */
doEncounterExp?: (scene: BattleScene) => boolean;
doEncounterExp?: () => boolean;
/** Will provide the player a rewards shop for that wave */
doEncounterRewards?: (scene: BattleScene) => boolean;
doEncounterRewards?: () => boolean;
/** Will execute callback during VictoryPhase of a continuousEncounter */
doContinueEncounter?: (scene: BattleScene) => Promise<void>;
doContinueEncounter?: () => Promise<void>;
/**
* Can perform special logic when a ME battle is lost, before GameOver/battle retry prompt.
* Should return `true` if it is treated as "real" Game Over, `false` if not.
*/
onGameOver?: (scene: BattleScene) => boolean;
onGameOver?: () => boolean;
/**
* Requirements
@ -296,13 +301,12 @@ export default class MysteryEncounter implements IMysteryEncounter {
/**
* Checks if the current scene state meets the requirements for the {@linkcode MysteryEncounter} to spawn
* This is used to filter the pool of encounters down to only the ones with all requirements met
* @param scene
* @returns
*/
meetsRequirements(scene: BattleScene): boolean {
const sceneReq = !this.requirements.some(requirement => !requirement.meetsRequirement(scene));
const secReqs = this.meetsSecondaryRequirementAndSecondaryPokemonSelected(scene); // secondary is checked first to handle cases of primary overlapping with secondary
const priReqs = this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene);
meetsRequirements(): boolean {
const sceneReq = !this.requirements.some(requirement => !requirement.meetsRequirement());
const secReqs = this.meetsSecondaryRequirementAndSecondaryPokemonSelected(); // secondary is checked first to handle cases of primary overlapping with secondary
const priReqs = this.meetsPrimaryRequirementAndPrimaryPokemonSelected();
return sceneReq && secReqs && priReqs;
}
@ -310,11 +314,10 @@ export default class MysteryEncounter implements IMysteryEncounter {
/**
* Checks if a specific player pokemon meets all given primary EncounterPokemonRequirements
* Used automatically as part of {@linkcode meetsRequirements}, but can also be used to manually check certain Pokemon where needed
* @param scene
* @param pokemon
*/
pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon): boolean {
return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getPlayerParty()).map(p => p.id).includes(pokemon.id));
pokemonMeetsPrimaryRequirements(pokemon: Pokemon): boolean {
return !this.primaryPokemonRequirements.some(req => !req.queryParty(globalScene.getPlayerParty()).map(p => p.id).includes(pokemon.id));
}
/**
@ -322,22 +325,21 @@ export default class MysteryEncounter implements IMysteryEncounter {
* AND there is a valid Pokemon assigned to {@linkcode primaryPokemon}.
* If both {@linkcode primaryPokemonRequirements} and {@linkcode secondaryPokemonRequirements} are defined,
* can cause scenarios where there are not enough Pokemon that are sufficient for all requirements.
* @param scene
*/
private meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene): boolean {
private meetsPrimaryRequirementAndPrimaryPokemonSelected(): boolean {
if (!this.primaryPokemonRequirements || this.primaryPokemonRequirements.length === 0) {
const activeMon = scene.getPlayerParty().filter(p => p.isActive(true));
const activeMon = globalScene.getPlayerParty().filter(p => p.isActive(true));
if (activeMon.length > 0) {
this.primaryPokemon = activeMon[0];
} else {
this.primaryPokemon = scene.getPlayerParty().filter(p => p.isAllowedInBattle())[0];
this.primaryPokemon = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle())[0];
}
return true;
}
let qualified: PlayerPokemon[] = scene.getPlayerParty();
let qualified: PlayerPokemon[] = globalScene.getPlayerParty();
for (const req of this.primaryPokemonRequirements) {
if (req.meetsRequirement(scene)) {
qualified = qualified.filter(pkmn => req.queryParty(scene.getPlayerParty()).includes(pkmn));
if (req.meetsRequirement()) {
qualified = qualified.filter(pkmn => req.queryParty(globalScene.getPlayerParty()).includes(pkmn));
} else {
this.primaryPokemon = undefined;
return false;
@ -386,18 +388,17 @@ export default class MysteryEncounter implements IMysteryEncounter {
* AND there is a valid Pokemon assigned to {@linkcode secondaryPokemon} (if applicable).
* If both {@linkcode primaryPokemonRequirements} and {@linkcode secondaryPokemonRequirements} are defined,
* can cause scenarios where there are not enough Pokemon that are sufficient for all requirements.
* @param scene
*/
private meetsSecondaryRequirementAndSecondaryPokemonSelected(scene: BattleScene): boolean {
private meetsSecondaryRequirementAndSecondaryPokemonSelected(): boolean {
if (!this.secondaryPokemonRequirements || this.secondaryPokemonRequirements.length === 0) {
this.secondaryPokemon = [];
return true;
}
let qualified: PlayerPokemon[] = scene.getPlayerParty();
let qualified: PlayerPokemon[] = globalScene.getPlayerParty();
for (const req of this.secondaryPokemonRequirements) {
if (req.meetsRequirement(scene)) {
qualified = qualified.filter(pkmn => req.queryParty(scene.getPlayerParty()).includes(pkmn));
if (req.meetsRequirement()) {
qualified = qualified.filter(pkmn => req.queryParty(globalScene.getPlayerParty()).includes(pkmn));
} else {
this.secondaryPokemon = [];
return false;
@ -409,10 +410,9 @@ export default class MysteryEncounter implements IMysteryEncounter {
/**
* Initializes encounter intro sprites based on the sprite configs defined in spriteConfigs
* @param scene
*/
initIntroVisuals(scene: BattleScene): void {
this.introVisuals = new MysteryEncounterIntroVisuals(scene, this);
initIntroVisuals(): void {
this.introVisuals = new MysteryEncounterIntroVisuals(this);
}
/**
@ -420,11 +420,11 @@ export default class MysteryEncounter implements IMysteryEncounter {
* Will use the first support pokemon in list
* For multiple support pokemon in the dialogue token, it will have to be overridden.
*/
populateDialogueTokensFromRequirements(scene: BattleScene): void {
this.meetsRequirements(scene);
populateDialogueTokensFromRequirements(): void {
this.meetsRequirements();
if (this.requirements?.length > 0) {
for (const req of this.requirements) {
const dialogueToken = req.getDialogueToken(scene);
const dialogueToken = req.getDialogueToken();
if (dialogueToken?.length === 2) {
this.setDialogueToken(...dialogueToken);
}
@ -434,7 +434,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
this.setDialogueToken("primaryName", this.primaryPokemon.getNameToRender());
for (const req of this.primaryPokemonRequirements) {
if (!req.invertQuery) {
const value = req.getDialogueToken(scene, this.primaryPokemon);
const value = req.getDialogueToken(this.primaryPokemon);
if (value?.length === 2) {
this.setDialogueToken("primary" + capitalizeFirstLetter(value[0]), value[1]);
}
@ -445,7 +445,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
this.setDialogueToken("secondaryName", this.secondaryPokemon[0].getNameToRender());
for (const req of this.secondaryPokemonRequirements) {
if (!req.invertQuery) {
const value = req.getDialogueToken(scene, this.secondaryPokemon[0]);
const value = req.getDialogueToken(this.secondaryPokemon[0]);
if (value?.length === 2) {
this.setDialogueToken("primary" + capitalizeFirstLetter(value[0]), value[1]);
}
@ -457,11 +457,11 @@ export default class MysteryEncounter implements IMysteryEncounter {
// Dialogue tokens for options
for (let i = 0; i < this.options.length; i++) {
const opt = this.options[i];
opt.meetsRequirements(scene);
opt.meetsRequirements();
const j = i + 1;
if (opt.requirements.length > 0) {
for (const req of opt.requirements) {
const dialogueToken = req.getDialogueToken(scene);
const dialogueToken = req.getDialogueToken();
if (dialogueToken?.length === 2) {
this.setDialogueToken("option" + j + capitalizeFirstLetter(dialogueToken[0]), dialogueToken[1]);
}
@ -471,7 +471,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
this.setDialogueToken("option" + j + "PrimaryName", opt.primaryPokemon.getNameToRender());
for (const req of opt.primaryPokemonRequirements) {
if (!req.invertQuery) {
const value = req.getDialogueToken(scene, opt.primaryPokemon);
const value = req.getDialogueToken(opt.primaryPokemon);
if (value?.length === 2) {
this.setDialogueToken("option" + j + "Primary" + capitalizeFirstLetter(value[0]), value[1]);
}
@ -482,7 +482,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
this.setDialogueToken("option" + j + "SecondaryName", opt.secondaryPokemon[0].getNameToRender());
for (const req of opt.secondaryPokemonRequirements) {
if (!req.invertQuery) {
const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]);
const value = req.getDialogueToken(opt.secondaryPokemon[0]);
if (value?.length === 2) {
this.setDialogueToken("option" + j + "Secondary" + capitalizeFirstLetter(value[0]), value[1]);
}
@ -518,10 +518,9 @@ export default class MysteryEncounter implements IMysteryEncounter {
/**
* Maintains seed offset for RNG consistency
* Increments if the same {@linkcode MysteryEncounter} has multiple option select cycles
* @param scene
*/
updateSeedOffset(scene: BattleScene) {
const currentOffset = this.seedOffset ?? scene.currentBattle.waveIndex * 1000;
updateSeedOffset() {
const currentOffset = this.seedOffset ?? globalScene.currentBattle.waveIndex * 1000;
this.seedOffset = currentOffset + 512;
}
}
@ -858,7 +857,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param doEncounterRewards Synchronous callback function to perform during rewards phase of the encounter
* @returns
*/
withRewards(doEncounterRewards: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterRewards">> {
withRewards(doEncounterRewards: () => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterRewards">> {
return Object.assign(this, { doEncounterRewards: doEncounterRewards });
}
@ -872,7 +871,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param doEncounterExp Synchronous callback function to perform during rewards phase of the encounter
* @returns
*/
withExp(doEncounterExp: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterExp">> {
withExp(doEncounterExp: () => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterExp">> {
return Object.assign(this, { doEncounterExp: doEncounterExp });
}
@ -883,7 +882,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param onInit Synchronous callback function to perform as soon as the encounter is selected for the next phase
* @returns
*/
withOnInit(onInit: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "onInit">> {
withOnInit(onInit: () => boolean): this & Required<Pick<IMysteryEncounter, "onInit">> {
return Object.assign(this, { onInit });
}
@ -893,7 +892,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param onVisualsStart Synchronous callback function to perform as soon as the enemy field finishes sliding in
* @returns
*/
withOnVisualsStart(onVisualsStart: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "onVisualsStart">> {
withOnVisualsStart(onVisualsStart: () => boolean): this & Required<Pick<IMysteryEncounter, "onVisualsStart">> {
return Object.assign(this, { onVisualsStart: onVisualsStart });
}

View File

@ -10,7 +10,7 @@ import { MysteriousChestEncounter } from "./encounters/mysterious-chest-encounte
import { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-encounter";
import { SlumberingSnorlaxEncounter } from "./encounters/slumbering-snorlax-encounter";
import { TrainingSessionEncounter } from "./encounters/training-session-encounter";
import MysteryEncounter from "./mystery-encounter";
import type MysteryEncounter from "./mystery-encounter";
import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter";
import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter";
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter";

Some files were not shown because too many files have changed in this diff Show More