Merge branch 'beta' into evilteammonogen

This commit is contained in:
damocleas 2025-01-19 14:16:17 -05:00 committed by GitHub
commit d01e57585c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
76 changed files with 1722 additions and 4682 deletions

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.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.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: 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

@ -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

@ -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 4928231e22a06dce2b55d9b04cd2b283c2ee4afb
Subproject commit 7bfcbccb9b8192b1059ca7c4c7e7d24901cf579d

View File

@ -112,7 +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 { globalScene, initGlobalScene } from "#app/global-scene";
import { initGlobalScene } from "#app/global-scene";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -363,28 +363,30 @@ 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 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[key] = c;
this.cachedFetch(`./images/pokemon/variant/${fileRoot}.json`)
.then((res) => res.json())
.then((c) => {
variantColorCache[spriteKey] = c;
resolve();
});
});
};
populateVariantColors(spriteKey);
} else {
resolve();
}
});
}
async preload() {
@ -392,10 +394,10 @@ export default class BattleScene extends SceneBase {
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", ++globalScene.rngCounter, ret / (max - min), `min: ${min} / max: ${max}` ];
args.push(`seed: ${globalScene.rngSeedOverride || globalScene.waveSeed || globalScene.seed}`);
if (globalScene.rngOffset) {
args.push(`offset: ${globalScene.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;
@ -408,7 +410,7 @@ export default class BattleScene extends SceneBase {
}
create() {
globalScene.scene.remove(LoadingScene.KEY);
this.scene.remove(LoadingScene.KEY);
initGameSpeed.apply(this);
this.inputController = new InputsController();
this.uiInputs = new UiInputs(this.inputController);
@ -1191,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();
@ -1865,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);
@ -1878,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];
}
}
@ -2949,7 +2954,7 @@ export default class BattleScene extends SceneBase {
*/
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));
globalScene.executeWithSeedOffset(() => {
this.executeWithSeedOffset(() => {
const shuffleModifiers = mods => {
if (mods.length < 1) {
return mods;
@ -2958,7 +2963,7 @@ export default class BattleScene extends SceneBase {
return [ mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand)) ];
};
modifiers = shuffleModifiers(modifiers);
}, globalScene.currentBattle.turn << 4, globalScene.waveSeed);
}, this.currentBattle.turn << 4, this.waveSeed);
return this.applyModifiersInternal(modifiers, player, args);
}

View File

@ -7,7 +7,7 @@ import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/mod
import type { PokeballType } from "#enums/pokeball";
import { trainerConfigs } from "#app/data/trainer-config";
import { SpeciesFormKey } from "#enums/species-form-key";
import type { 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";
@ -64,7 +64,7 @@ export enum BattlerIndex {
export interface TurnCommand {
command: Command;
cursor?: number;
move?: QueuedMove;
move?: TurnMove;
targets?: BattlerIndex[];
skip?: boolean;
args?: any[];

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

@ -612,7 +612,7 @@ export class InterruptedTag extends BattlerTag {
super.onAdd(pokemon);
pokemon.getMoveQueue().shift();
pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.OTHER });
pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.OTHER, targets: []});
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {

View File

@ -89,6 +89,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,
}
/**
@ -406,6 +411,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;
@ -740,6 +755,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.
*/
@ -925,6 +967,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 => {
@ -969,6 +1014,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;
}
}
});
@ -994,6 +1042,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");
}
@ -1006,5 +1056,6 @@ export function initChallenges() {
new SingleTypeChallenge(),
new FreshStartChallenge(),
new InverseBattleChallenge(),
new FlipStatChallenge()
);
}

View File

@ -5,7 +5,8 @@ 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

@ -8,6 +8,7 @@ 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;
@ -71,3 +72,76 @@ function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLeve
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)];
}

File diff suppressed because it is too large Load Diff

View File

@ -516,8 +516,7 @@ export abstract class PokemonSpeciesForm {
globalScene.anims.get(spriteKey).frameRate = 10;
}
const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, "");
globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant);
resolve();
globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant).then(() => resolve());
});
if (startLoad) {
if (!globalScene.load.isLoading()) {
@ -948,19 +947,6 @@ export class PokemonForm extends PokemonSpeciesForm {
}
}
export const noStarterFormKeys: string[] = [
SpeciesFormKey.MEGA,
SpeciesFormKey.MEGA_X,
SpeciesFormKey.MEGA_Y,
SpeciesFormKey.PRIMAL,
SpeciesFormKey.ORIGIN,
SpeciesFormKey.THERIAN,
SpeciesFormKey.GIGANTAMAX,
SpeciesFormKey.GIGANTAMAX_RAPID,
SpeciesFormKey.GIGANTAMAX_SINGLE,
SpeciesFormKey.ETERNAMAX
].map(k => k.toString());
/**
* Method to get the daily list of starters with Pokerus.
* @returns A list of starters with Pokerus
@ -1834,7 +1820,7 @@ export function initSpecies() {
new PokemonSpecies(Species.COFAGRIGUS, 5, false, false, false, "Coffin Pokémon", Type.GHOST, null, 1.7, 76.5, Abilities.MUMMY, Abilities.NONE, Abilities.NONE, 483, 58, 50, 145, 95, 105, 30, 90, 50, 169, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(Species.TIRTOUGA, 5, false, false, false, "Prototurtle Pokémon", Type.WATER, Type.ROCK, 0.7, 16.5, Abilities.SOLID_ROCK, Abilities.STURDY, Abilities.SWIFT_SWIM, 355, 54, 78, 103, 53, 45, 22, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false),
new PokemonSpecies(Species.CARRACOSTA, 5, false, false, false, "Prototurtle Pokémon", Type.WATER, Type.ROCK, 1.2, 81, Abilities.SOLID_ROCK, Abilities.STURDY, Abilities.SWIFT_SWIM, 495, 74, 108, 133, 83, 65, 32, 45, 50, 173, GrowthRate.MEDIUM_FAST, 87.5, false),
new PokemonSpecies(Species.ARCHEN, 5, false, false, false, "First Bird Pokémon", Type.ROCK, Type.FLYING, 0.5, 9.5, Abilities.DEFEATIST, Abilities.NONE, Abilities.EMERGENCY_EXIT, 401, 55, 112, 45, 74, 45, 70, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden
new PokemonSpecies(Species.ARCHEN, 5, false, false, false, "First Bird Pokémon", Type.ROCK, Type.FLYING, 0.5, 9.5, Abilities.DEFEATIST, Abilities.NONE, Abilities.WIMP_OUT, 401, 55, 112, 45, 74, 45, 70, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden
new PokemonSpecies(Species.ARCHEOPS, 5, false, false, false, "First Bird Pokémon", Type.ROCK, Type.FLYING, 1.4, 32, Abilities.DEFEATIST, Abilities.NONE, Abilities.EMERGENCY_EXIT, 567, 75, 140, 65, 112, 65, 110, 45, 50, 177, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden
new PokemonSpecies(Species.TRUBBISH, 5, false, false, false, "Trash Bag Pokémon", Type.POISON, null, 0.6, 31, Abilities.STENCH, Abilities.STICKY_HOLD, Abilities.AFTERMATH, 329, 50, 50, 62, 40, 62, 65, 190, 50, 66, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(Species.GARBODOR, 5, false, false, false, "Trash Heap Pokémon", Type.POISON, null, 1.9, 107.3, Abilities.STENCH, Abilities.WEAK_ARMOR, Abilities.AFTERMATH, 474, 80, 95, 82, 60, 82, 75, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, true,

View File

@ -5,4 +5,5 @@ export enum Challenges {
LOWER_STARTER_POINTS,
FRESH_START,
INVERSE_BATTLE,
FLIP_STAT,
}

View File

@ -215,11 +215,12 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
resolve();
}
const shinyPromises: Promise<void>[] = [];
this.spriteConfigs.forEach((config) => {
if (config.isPokemon) {
globalScene.loadPokemonAtlas(config.spriteKey, config.fileRoot);
if (config.isShiny) {
globalScene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant);
shinyPromises.push(globalScene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant));
}
} else if (config.isItem) {
globalScene.loadAtlas("items", "");
@ -254,7 +255,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
return true;
});
resolve();
Promise.all(shinyPromises).then(() => resolve());
});
if (!globalScene.load.isLoading()) {

View File

@ -1057,6 +1057,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
calculateBaseStats(): number[] {
const baseStats = this.getSpeciesForm(true).baseStats.slice(0);
applyChallenges(globalScene.gameMode, ChallengeType.FLIP_STAT, this, baseStats);
// Shuckle Juice
globalScene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats);
// Old Gateau
@ -3298,7 +3299,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
getMoveQueue(): QueuedMove[] {
getMoveQueue(): TurnMove[] {
return this.summonData.moveQueue;
}
@ -4810,19 +4811,21 @@ export class EnemyPokemon extends Pokemon {
* the Pokemon the move will target.
* @returns this Pokemon's next move in the format {move, moveTargets}
*/
getNextMove(): QueuedMove {
getNextMove(): TurnMove {
// If this Pokemon has a move already queued, return it.
const queuedMove = this.getMoveQueue().length
? this.getMoveset().find(m => m?.moveId === this.getMoveQueue()[0].move)
: null;
const moveQueue = this.getMoveQueue();
if (moveQueue.length !== 0) {
const queuedMove = moveQueue[0];
if (queuedMove) {
if (queuedMove.isUsable(this, this.getMoveQueue()[0].ignorePP)) {
return { move: queuedMove.moveId, targets: this.getMoveQueue()[0].targets, ignorePP: this.getMoveQueue()[0].ignorePP };
const moveIndex = this.getMoveset().findIndex(m => m?.moveId === queuedMove.move);
if ((moveIndex > -1 && this.getMoveset()[moveIndex]!.isUsable(this, queuedMove.ignorePP)) || queuedMove.virtual) {
return queuedMove;
} else {
this.getMoveQueue().shift();
return this.getNextMove();
}
}
}
// Filter out any moves this Pokemon cannot use
let movePool = this.getMoveset().filter(m => m?.isUsable(this));
@ -5242,15 +5245,10 @@ export class EnemyPokemon extends Pokemon {
export interface TurnMove {
move: Moves;
targets?: BattlerIndex[];
result: MoveResult;
targets: BattlerIndex[];
result?: MoveResult;
virtual?: boolean;
turn?: number;
}
export interface QueuedMove {
move: Moves;
targets: BattlerIndex[];
ignorePP?: boolean;
}
@ -5266,7 +5264,7 @@ export interface AttackMoveResult {
export class PokemonSummonData {
/** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */
public statStages: number[] = [ 0, 0, 0, 0, 0, 0, 0 ];
public moveQueue: QueuedMove[] = [];
public moveQueue: TurnMove[] = [];
public tags: BattlerTag[] = [];
public abilitySuppressed: boolean = false;
public abilitiesApplied: Abilities[] = [];
@ -5284,7 +5282,10 @@ export class PokemonSummonData {
}
export class PokemonBattleData {
/** counts the hits the pokemon received */
public hitCount: number = 0;
/** used for {@linkcode Moves.RAGE_FIST} in order to save hit Counts received before Rage Fist is applied */
public prevHitCount: number = 0;
public endured: boolean = false;
public berriesEaten: BerryType[] = [];
public abilitiesApplied: Abilities[] = [];

View File

@ -428,7 +428,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
}
// Prompts reroll of party member species if species already present in the enemy party
if (this.checkDuplicateSpecies(ret, baseSpecies)) {
if (this.checkDuplicateSpecies(baseSpecies.speciesId)) {
console.log("Duplicate species detected, prompting reroll...");
retry = true;
}
@ -443,17 +443,23 @@ export default class Trainer extends Phaser.GameObjects.Container {
/**
* Checks if the enemy trainer already has the Pokemon species in their party
* @param {PokemonSpecies} species {@linkcode PokemonSpecies}
* @param {PokemonSpecies} baseSpecies {@linkcode PokemonSpecies} - baseSpecies of the Pokemon if species is forced to evolve
* @param baseSpecies - The base {@linkcode Species} of the current Pokemon
* @returns `true` if the species is already present in the party
*/
checkDuplicateSpecies(species: PokemonSpecies, baseSpecies: PokemonSpecies): boolean {
const staticPartyPokemon = (signatureSpecies[TrainerType[this.config.trainerType]] ?? []).flat(1);
const currentPartySpecies = globalScene.getEnemyParty().map(p => {
return p.species.speciesId;
checkDuplicateSpecies(baseSpecies: Species): boolean {
const staticSpecies = (signatureSpecies[TrainerType[this.config.trainerType]] ?? []).flat(1).map(s => {
let root = s;
while (pokemonPrevolutions.hasOwnProperty(root)) {
root = pokemonPrevolutions[root];
}
return root;
});
return currentPartySpecies.includes(species.speciesId) || staticPartyPokemon.includes(baseSpecies.speciesId);
const currentSpecies = globalScene.getEnemyParty().map(p => {
return p.species.getRootSpeciesId();
});
return currentSpecies.includes(baseSpecies) || staticSpecies.includes(baseSpecies);
}
getPartyMemberMatchupScores(trainerSlot: TrainerSlot = TrainerSlot.NONE, forSwitch: boolean = false): [integer, integer][] {

View File

@ -12,6 +12,7 @@ import { Biome } from "#enums/biome";
import { Species } from "#enums/species";
import { Challenges } from "./enums/challenges";
import { globalScene } from "#app/global-scene";
import { getDailyStartingBiome } from "./data/daily-run";
export enum GameModes {
CLASSIC,
@ -120,7 +121,7 @@ export class GameMode implements GameModeConfig {
getStartingBiome(): Biome {
switch (this.modeId) {
case GameModes.DAILY:
return globalScene.generateRandomBiome(this.getWaveForDifficulty(1));
return getDailyStartingBiome();
default:
return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN;
}

View File

@ -319,6 +319,7 @@ export class LoadingScene extends SceneBase {
this.loadSe("pb_move");
this.loadSe("pb_catch");
this.loadSe("pb_lock");
this.loadSe("crit_throw");
this.loadSe("pb_tray_enter");
this.loadSe("pb_tray_ball");

View File

@ -1,19 +1,20 @@
/* eslint-disable @typescript-eslint/consistent-type-imports */
import { type PokeballCounts } from "#app/battle-scene";
import type { Gender } from "#app/data/gender";
import type { Variant } from "#app/data/variant";
import { Gender } from "#app/data/gender";
import { Variant } from "#app/data/variant";
import { type ModifierOverride } from "#app/modifier/modifier-type";
import type { Unlockables } from "#app/system/unlockables";
import { Unlockables } from "#app/system/unlockables";
import { Abilities } from "#enums/abilities";
import { Biome } from "#enums/biome";
import type { EggTier } from "#enums/egg-type";
import type { Moves } from "#enums/moves";
import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { EggTier } from "#enums/egg-type";
import { Moves } from "#enums/moves";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PokeballType } from "#enums/pokeball";
import type { Species } from "#enums/species";
import { Species } from "#enums/species";
import { StatusEffect } from "#enums/status-effect";
import type { TimeOfDay } from "#enums/time-of-day";
import type { VariantTier } from "#enums/variant-tier";
import { TimeOfDay } from "#enums/time-of-day";
import { VariantTier } from "#enums/variant-tier";
import { WeatherType } from "#enums/weather-type";
/**

View File

@ -64,7 +64,7 @@ export class AttemptCapturePhase extends PokemonPhase {
this.pokeball.setOrigin(0.5, 0.625);
globalScene.field.add(this.pokeball);
globalScene.playSound("se/pb_throw", isCritical ? { rate: 0.2 } : undefined); // Crit catch throws are higher pitched
globalScene.playSound(isCritical ? "se/crit_throw" : "se/pb_throw");
globalScene.time.delayedCall(300, () => {
globalScene.field.moveBelow(this.pokeball as Phaser.GameObjects.GameObject, pokemon);
});

View File

@ -11,7 +11,7 @@ import { BattlerTagType } from "#app/enums/battler-tag-type";
import { Biome } from "#app/enums/biome";
import { Moves } from "#app/enums/moves";
import { PokeballType } from "#enums/pokeball";
import type { PlayerPokemon } from "#app/field/pokemon";
import type { PlayerPokemon, TurnMove } from "#app/field/pokemon";
import { FieldPosition } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages";
import { Command } from "#app/ui/command-ui-handler";
@ -86,19 +86,19 @@ export class CommandPhase extends FieldPhase {
const moveQueue = playerPokemon.getMoveQueue();
while (moveQueue.length && moveQueue[0]
&& moveQueue[0].move && (!playerPokemon.getMoveset().find(m => m?.moveId === moveQueue[0].move)
&& moveQueue[0].move && !moveQueue[0].virtual && (!playerPokemon.getMoveset().find(m => m?.moveId === moveQueue[0].move)
|| !playerPokemon.getMoveset()[playerPokemon.getMoveset().findIndex(m => m?.moveId === moveQueue[0].move)]!.isUsable(playerPokemon, moveQueue[0].ignorePP))) { // TODO: is the bang correct?
moveQueue.shift();
}
if (moveQueue.length) {
if (moveQueue.length > 0) {
const queuedMove = moveQueue[0];
if (!queuedMove.move) {
this.handleCommand(Command.FIGHT, -1, false);
this.handleCommand(Command.FIGHT, -1);
} else {
const moveIndex = playerPokemon.getMoveset().findIndex(m => m?.moveId === queuedMove.move);
if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) { // TODO: is the bang correct?
this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, { targets: queuedMove.targets, multiple: queuedMove.targets.length > 1 });
if ((moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) || queuedMove.virtual) { // TODO: is the bang correct?
this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, queuedMove);
} else {
globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}
@ -120,12 +120,24 @@ export class CommandPhase extends FieldPhase {
switch (command) {
case Command.FIGHT:
let useStruggle = false;
const turnMove: TurnMove | undefined = (args.length === 2 ? (args[1] as TurnMove) : undefined);
if (cursor === -1 ||
playerPokemon.trySelectMove(cursor, args[0] as boolean) ||
(useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) {
const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct?
let moveId: Moves;
if (useStruggle) {
moveId = Moves.STRUGGLE;
} else if (turnMove !== undefined) {
moveId = turnMove.move;
} else if (cursor > -1) {
moveId = playerPokemon.getMoveset()[cursor]!.moveId;
} else {
moveId = Moves.NONE;
}
const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args };
const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2];
const moveTargets: MoveTargetSet = turnMove === undefined ? getMoveTargets(playerPokemon, moveId) : { targets: turnMove.targets, multiple: turnMove.targets.length > 1 };
if (!moveId) {
turnCommand.targets = [ this.fieldIndex ];
}

View File

@ -104,6 +104,12 @@ export class EncounterPhase extends BattlePhase {
}
if (!this.loaded) {
if (battle.battleType === BattleType.TRAINER) {
//resets hitRecCount during Trainer ecnounter
for (const pokemon of globalScene.getPlayerParty()) {
if (pokemon) {
pokemon.customPokemonData.resetHitReceivedCount();
}
}
battle.enemyParty[e] = battle.trainer?.genPartyMember(e)!; // TODO:: is the bang correct here?
} else {
let enemySpecies = globalScene.randomSpecies(battle.waveIndex, level, true);

View File

@ -296,11 +296,6 @@ export class MovePhase extends BattlePhase {
globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed));
}
// Update the battle's "last move" pointer, unless we're currently mimicking a move.
if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) {
globalScene.currentBattle.lastMove = this.move.moveId;
}
/**
* Determine if the move is successful (meaning that its damage/effects can be attempted)
* by checking that all of the following are true:
@ -324,6 +319,14 @@ export class MovePhase extends BattlePhase {
const success = passesConditions && !failedDueToWeather && !failedDueToTerrain;
// Update the battle's "last move" pointer, unless we're currently mimicking a move.
if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) {
// The last move used is unaffected by moves that fail
if (success) {
globalScene.currentBattle.lastMove = this.move.moveId;
}
}
/**
* If the move has not failed, trigger ability-based user type changes and then execute it.
*
@ -518,7 +521,7 @@ export class MovePhase extends BattlePhase {
frenzyMissFunc(this.pokemon, this.move.getMove());
}
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL });
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL, targets: this.targets });
this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
this.pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);

View File

@ -14,6 +14,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
for (const pokemon of globalScene.getPlayerParty()) {
if (pokemon) {
pokemon.resetBattleData();
pokemon.customPokemonData.resetHitReceivedCount();
}
}

View File

@ -5,7 +5,7 @@ import i18next from "i18next";
import * as Utils from "../utils";
import { PlayerGender } from "#enums/player-gender";
import type { Challenge } from "#app/data/challenge";
import { FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge, InverseBattleChallenge } from "#app/data/challenge";
import { FlipStatChallenge, FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge, InverseBattleChallenge } from "#app/data/challenge";
import type { ConditionFn } from "#app/@types/common";
import { Stat, getShortenedStatKey } from "#app/enums/stat";
import { Challenges } from "#app/enums/challenges";
@ -280,6 +280,10 @@ export function getAchievementDescription(localizationKey: string): string {
return i18next.t("achv:FRESH_START.description", { context: genderStr });
case "INVERSE_BATTLE":
return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr });
case "FLIP_STATS":
return i18next.t("achv:FLIP_STATS.description", { context: genderStr });
case "FLIP_INVERSE":
return i18next.t("achv:FLIP_INVERSE.description", { context: genderStr });
case "BREEDERS_IN_SPACE":
return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr });
default:
@ -330,35 +334,37 @@ export const achvs = {
PERFECT_IVS: new Achv("PERFECT_IVS", "", "PERFECT_IVS.description", "blunder_policy", 100),
CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY", "", "CLASSIC_VICTORY.description", "relic_crown", 150, (_) => globalScene.gameData.gameStats.sessionsWon === 0),
UNEVOLVED_CLASSIC_VICTORY: new Achv("UNEVOLVED_CLASSIC_VICTORY", "", "UNEVOLVED_CLASSIC_VICTORY.description", "eviolite", 175, (_) => globalScene.getPlayerParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)),
MONO_GEN_ONE_VICTORY: new ChallengeAchv("MONO_GEN_ONE", "", "MONO_GEN_ONE.description", "ribbon_gen1", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_TWO_VICTORY: new ChallengeAchv("MONO_GEN_TWO", "", "MONO_GEN_TWO.description", "ribbon_gen2", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_THREE_VICTORY: new ChallengeAchv("MONO_GEN_THREE", "", "MONO_GEN_THREE.description", "ribbon_gen3", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_FOUR_VICTORY: new ChallengeAchv("MONO_GEN_FOUR", "", "MONO_GEN_FOUR.description", "ribbon_gen4", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_FIVE_VICTORY: new ChallengeAchv("MONO_GEN_FIVE", "", "MONO_GEN_FIVE.description", "ribbon_gen5", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_SIX_VICTORY: new ChallengeAchv("MONO_GEN_SIX", "", "MONO_GEN_SIX.description", "ribbon_gen6", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_SEVEN_VICTORY: new ChallengeAchv("MONO_GEN_SEVEN", "", "MONO_GEN_SEVEN.description", "ribbon_gen7", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_EIGHT_VICTORY: new ChallengeAchv("MONO_GEN_EIGHT", "", "MONO_GEN_EIGHT.description", "ribbon_gen8", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_NINE_VICTORY: new ChallengeAchv("MONO_GEN_NINE", "", "MONO_GEN_NINE.description", "ribbon_gen9", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_NORMAL: new ChallengeAchv("MONO_NORMAL", "", "MONO_NORMAL.description", "silk_scarf", 100, (c) => c instanceof SingleTypeChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_FIGHTING: new ChallengeAchv("MONO_FIGHTING", "", "MONO_FIGHTING.description", "black_belt", 100, (c) => c instanceof SingleTypeChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_FLYING: new ChallengeAchv("MONO_FLYING", "", "MONO_FLYING.description", "sharp_beak", 100, (c) => c instanceof SingleTypeChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_POISON: new ChallengeAchv("MONO_POISON", "", "MONO_POISON.description", "poison_barb", 100, (c) => c instanceof SingleTypeChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GROUND: new ChallengeAchv("MONO_GROUND", "", "MONO_GROUND.description", "soft_sand", 100, (c) => c instanceof SingleTypeChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_ROCK: new ChallengeAchv("MONO_ROCK", "", "MONO_ROCK.description", "hard_stone", 100, (c) => c instanceof SingleTypeChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_BUG: new ChallengeAchv("MONO_BUG", "", "MONO_BUG.description", "silver_powder", 100, (c) => c instanceof SingleTypeChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GHOST: new ChallengeAchv("MONO_GHOST", "", "MONO_GHOST.description", "spell_tag", 100, (c) => c instanceof SingleTypeChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_STEEL: new ChallengeAchv("MONO_STEEL", "", "MONO_STEEL.description", "metal_coat", 100, (c) => c instanceof SingleTypeChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_FIRE: new ChallengeAchv("MONO_FIRE", "", "MONO_FIRE.description", "charcoal", 100, (c) => c instanceof SingleTypeChallenge && c.value === 10 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_WATER: new ChallengeAchv("MONO_WATER", "", "MONO_WATER.description", "mystic_water", 100, (c) => c instanceof SingleTypeChallenge && c.value === 11 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GRASS: new ChallengeAchv("MONO_GRASS", "", "MONO_GRASS.description", "miracle_seed", 100, (c) => c instanceof SingleTypeChallenge && c.value === 12 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_ELECTRIC: new ChallengeAchv("MONO_ELECTRIC", "", "MONO_ELECTRIC.description", "magnet", 100, (c) => c instanceof SingleTypeChallenge && c.value === 13 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_PSYCHIC: new ChallengeAchv("MONO_PSYCHIC", "", "MONO_PSYCHIC.description", "twisted_spoon", 100, (c) => c instanceof SingleTypeChallenge && c.value === 14 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_ICE: new ChallengeAchv("MONO_ICE", "", "MONO_ICE.description", "never_melt_ice", 100, (c) => c instanceof SingleTypeChallenge && c.value === 15 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_DRAGON: new ChallengeAchv("MONO_DRAGON", "", "MONO_DRAGON.description", "dragon_fang", 100, (c) => c instanceof SingleTypeChallenge && c.value === 16 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_DARK: new ChallengeAchv("MONO_DARK", "", "MONO_DARK.description", "black_glasses", 100, (c) => c instanceof SingleTypeChallenge && c.value === 17 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c) => c instanceof SingleTypeChallenge && c.value === 18 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c) => c instanceof FreshStartChallenge && c.value > 0 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, c => c instanceof InverseBattleChallenge && c.value > 0),
MONO_GEN_ONE_VICTORY: new ChallengeAchv("MONO_GEN_ONE", "", "MONO_GEN_ONE.description", "ribbon_gen1", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_GEN_TWO_VICTORY: new ChallengeAchv("MONO_GEN_TWO", "", "MONO_GEN_TWO.description", "ribbon_gen2", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_GEN_THREE_VICTORY: new ChallengeAchv("MONO_GEN_THREE", "", "MONO_GEN_THREE.description", "ribbon_gen3", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_GEN_FOUR_VICTORY: new ChallengeAchv("MONO_GEN_FOUR", "", "MONO_GEN_FOUR.description", "ribbon_gen4", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_GEN_FIVE_VICTORY: new ChallengeAchv("MONO_GEN_FIVE", "", "MONO_GEN_FIVE.description", "ribbon_gen5", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_GEN_SIX_VICTORY: new ChallengeAchv("MONO_GEN_SIX", "", "MONO_GEN_SIX.description", "ribbon_gen6", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_GEN_SEVEN_VICTORY: new ChallengeAchv("MONO_GEN_SEVEN", "", "MONO_GEN_SEVEN.description", "ribbon_gen7", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_GEN_EIGHT_VICTORY: new ChallengeAchv("MONO_GEN_EIGHT", "", "MONO_GEN_EIGHT.description", "ribbon_gen8", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_GEN_NINE_VICTORY: new ChallengeAchv("MONO_GEN_NINE", "", "MONO_GEN_NINE.description", "ribbon_gen9", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_NORMAL: new ChallengeAchv("MONO_NORMAL", "", "MONO_NORMAL.description", "silk_scarf", 100, (c) => c instanceof SingleTypeChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_FIGHTING: new ChallengeAchv("MONO_FIGHTING", "", "MONO_FIGHTING.description", "black_belt", 100, (c) => c instanceof SingleTypeChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_FLYING: new ChallengeAchv("MONO_FLYING", "", "MONO_FLYING.description", "sharp_beak", 100, (c) => c instanceof SingleTypeChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_POISON: new ChallengeAchv("MONO_POISON", "", "MONO_POISON.description", "poison_barb", 100, (c) => c instanceof SingleTypeChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_GROUND: new ChallengeAchv("MONO_GROUND", "", "MONO_GROUND.description", "soft_sand", 100, (c) => c instanceof SingleTypeChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_ROCK: new ChallengeAchv("MONO_ROCK", "", "MONO_ROCK.description", "hard_stone", 100, (c) => c instanceof SingleTypeChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_BUG: new ChallengeAchv("MONO_BUG", "", "MONO_BUG.description", "silver_powder", 100, (c) => c instanceof SingleTypeChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_GHOST: new ChallengeAchv("MONO_GHOST", "", "MONO_GHOST.description", "spell_tag", 100, (c) => c instanceof SingleTypeChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_STEEL: new ChallengeAchv("MONO_STEEL", "", "MONO_STEEL.description", "metal_coat", 100, (c) => c instanceof SingleTypeChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_FIRE: new ChallengeAchv("MONO_FIRE", "", "MONO_FIRE.description", "charcoal", 100, (c) => c instanceof SingleTypeChallenge && c.value === 10 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_WATER: new ChallengeAchv("MONO_WATER", "", "MONO_WATER.description", "mystic_water", 100, (c) => c instanceof SingleTypeChallenge && c.value === 11 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_GRASS: new ChallengeAchv("MONO_GRASS", "", "MONO_GRASS.description", "miracle_seed", 100, (c) => c instanceof SingleTypeChallenge && c.value === 12 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_ELECTRIC: new ChallengeAchv("MONO_ELECTRIC", "", "MONO_ELECTRIC.description", "magnet", 100, (c) => c instanceof SingleTypeChallenge && c.value === 13 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_PSYCHIC: new ChallengeAchv("MONO_PSYCHIC", "", "MONO_PSYCHIC.description", "twisted_spoon", 100, (c) => c instanceof SingleTypeChallenge && c.value === 14 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_ICE: new ChallengeAchv("MONO_ICE", "", "MONO_ICE.description", "never_melt_ice", 100, (c) => c instanceof SingleTypeChallenge && c.value === 15 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_DRAGON: new ChallengeAchv("MONO_DRAGON", "", "MONO_DRAGON.description", "dragon_fang", 100, (c) => c instanceof SingleTypeChallenge && c.value === 16 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_DARK: new ChallengeAchv("MONO_DARK", "", "MONO_DARK.description", "black_glasses", 100, (c) => c instanceof SingleTypeChallenge && c.value === 17 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c) => c instanceof SingleTypeChallenge && c.value === 18 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c) => c instanceof FreshStartChallenge && c.value > 0 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)),
INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, (c) => c instanceof InverseBattleChallenge && c.value > 0),
FLIP_STATS: new ChallengeAchv("FLIP_STATS", "", "FLIP_STATS.description", "dubious_disc", 100, (c) => c instanceof FlipStatChallenge && c.value > 0),
FLIP_INVERSE: new ChallengeAchv("FLIP_INVERSE", "", "FLIP_INVERSE.description", "cracked_pot", 100, (c) => c instanceof FlipStatChallenge && c.value > 0 && globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)).setSecret(),
BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 50).setSecret(),
};

View File

@ -6,7 +6,7 @@ import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies, noStarterFormKeys } from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import * as Utils from "#app/utils";
import Overrides from "#app/overrides";
@ -1619,9 +1619,6 @@ export class GameData {
const dexEntry = this.dexData[species.speciesId];
const caughtAttr = dexEntry.caughtAttr;
const formIndex = pokemon.formIndex;
if (noStarterFormKeys.includes(pokemon.getFormKey())) {
pokemon.formIndex = 0;
}
const dexAttr = pokemon.getDexAttr();
pokemon.formIndex = formIndex;

View File

@ -0,0 +1,105 @@
import { BattlerIndex } from "#app/battle";
import { Stat } from "#app/enums/stat";
import { MoveResult } from "#app/field/pokemon";
import { CommandPhase } from "#app/phases/command-phase";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Assist", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
// Manual moveset overrides are required for the player pokemon in these tests
// because the normal moveset override doesn't allow for accurate testing of moveset changes
game.override
.ability(Abilities.BALL_FETCH)
.battleType("double")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyLevel(100)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("should only use an ally's moves", async () => {
game.override.enemyMoveset(Moves.SWORDS_DANCE);
await game.classicMode.startBattle([ Species.FEEBAS, Species.SHUCKLE ]);
const [ feebas, shuckle ] = game.scene.getPlayerField();
// These are all moves Assist cannot call; Sketch will be used to test that it can call other moves properly
game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
game.move.changeMoveset(shuckle, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
game.move.select(Moves.ASSIST, 0);
game.move.select(Moves.SKETCH, 1);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER ]);
// Player_2 uses Sketch, copies Swords Dance, Player_1 uses Assist, uses Player_2's Sketched Swords Dance
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(2); // Stat raised from Assist -> Swords Dance
});
it("should fail if there are no allies", async () => {
await game.classicMode.startBattle([ Species.FEEBAS ]);
const feebas = game.scene.getPlayerPokemon()!;
game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
game.move.select(Moves.ASSIST, 0);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
});
it("should fail if ally has no usable moves and user has usable moves", async () => {
game.override.enemyMoveset(Moves.SWORDS_DANCE);
await game.classicMode.startBattle([ Species.FEEBAS, Species.SHUCKLE ]);
const [ feebas, shuckle ] = game.scene.getPlayerField();
game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
game.move.changeMoveset(shuckle, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
game.move.select(Moves.SKETCH, 0);
game.move.select(Moves.PROTECT, 1);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2 ]);
// Player uses Sketch to copy Swords Dance, Player_2 stalls a turn. Player will attempt Assist and should have no usable moves
await game.toNextTurn();
game.move.select(Moves.ASSIST, 0);
await game.phaseInterceptor.to(CommandPhase);
game.move.select(Moves.PROTECT, 1);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
});
it("should apply secondary effects of a move", async () => {
game.override.moveset([ Moves.ASSIST, Moves.WOOD_HAMMER, Moves.WOOD_HAMMER, Moves.WOOD_HAMMER ]);
await game.classicMode.startBattle([ Species.FEEBAS, Species.SHUCKLE ]);
const [ feebas, shuckle ] = game.scene.getPlayerField();
game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
game.move.changeMoveset(shuckle, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]);
game.move.select(Moves.ASSIST, 0);
await game.phaseInterceptor.to(CommandPhase);
game.move.select(Moves.ASSIST, 1);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.isFullHp()).toBeFalsy(); // should receive recoil damage from Wood Hammer
});
});

View File

@ -0,0 +1,91 @@
import { BattlerIndex } from "#app/battle";
import { allMoves, RandomMoveAttr } from "#app/data/move";
import { Stat } from "#app/enums/stat";
import { MoveResult } from "#app/field/pokemon";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Moves - Copycat", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const randomMoveAttr = allMoves[Moves.METRONOME].getAttrs(RandomMoveAttr)[0];
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([ Moves.COPYCAT, Moves.SPIKY_SHIELD, Moves.SWORDS_DANCE, Moves.SPLASH ])
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.starterSpecies(Species.FEEBAS)
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("should copy the last move successfully executed", async () => {
game.override.enemyMoveset(Moves.SUCKER_PUNCH);
await game.classicMode.startBattle();
game.move.select(Moves.SWORDS_DANCE);
await game.toNextTurn();
game.move.select(Moves.COPYCAT); // Last successful move should be Swords Dance
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(4);
});
it("should fail when the last move used is not a valid Copycat move", async () => {
game.override.enemyMoveset(Moves.PROTECT); // Protect is not a valid move for Copycat to copy
await game.classicMode.startBattle();
game.move.select(Moves.SPIKY_SHIELD); // Spiky Shield is not a valid move for Copycat to copy
await game.toNextTurn();
game.move.select(Moves.COPYCAT);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
});
it("should copy the called move when the last move successfully calls another", async () => {
game.override
.moveset([ Moves.SPLASH, Moves.METRONOME ])
.enemyMoveset(Moves.COPYCAT);
await game.classicMode.startBattle();
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.SWORDS_DANCE);
game.move.select(Moves.METRONOME);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); // Player moves first, so enemy can copy Swords Dance
await game.toNextTurn();
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(2);
});
it("should apply secondary effects of a move", async () => {
game.override.enemyMoveset(Moves.ACID_SPRAY); // Secondary effect lowers SpDef by 2 stages
await game.classicMode.startBattle();
game.move.select(Moves.COPYCAT);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPDEF)).toBe(-2);
});
});

View File

@ -0,0 +1,113 @@
import { RechargingTag, SemiInvulnerableTag } from "#app/data/battler-tags";
import { allMoves, RandomMoveAttr } from "#app/data/move";
import { Abilities } from "#app/enums/abilities";
import { Stat } from "#app/enums/stat";
import { CommandPhase } from "#app/phases/command-phase";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
describe("Moves - Metronome", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const randomMoveAttr = allMoves[Moves.METRONOME].getAttrs(RandomMoveAttr)[0];
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([ Moves.METRONOME, Moves.SPLASH ])
.battleType("single")
.startingLevel(100)
.starterSpecies(Species.REGIELEKI)
.enemyLevel(100)
.enemySpecies(Species.SHUCKLE)
.enemyMoveset(Moves.SPLASH)
.enemyAbility(Abilities.BALL_FETCH);
});
it("should have one semi-invulnerable turn and deal damage on the second turn when a semi-invulnerable move is called", async () => {
await game.classicMode.startBattle();
const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.DIVE);
game.move.select(Moves.METRONOME);
await game.toNextTurn();
expect(player.getTag(SemiInvulnerableTag)).toBeTruthy();
await game.toNextTurn();
expect(player.getTag(SemiInvulnerableTag)).toBeFalsy();
expect(enemy.isFullHp()).toBeFalsy();
});
it("should apply secondary effects of a move", async () => {
await game.classicMode.startBattle();
const player = game.scene.getPlayerPokemon()!;
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.WOOD_HAMMER);
game.move.select(Moves.METRONOME);
await game.toNextTurn();
expect(player.isFullHp()).toBeFalsy();
});
it("should recharge after using recharge move", async () => {
await game.classicMode.startBattle();
const player = game.scene.getPlayerPokemon()!;
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.HYPER_BEAM);
vi.spyOn(allMoves[Moves.HYPER_BEAM], "accuracy", "get").mockReturnValue(100);
game.move.select(Moves.METRONOME);
await game.toNextTurn();
expect(player.getTag(RechargingTag)).toBeTruthy();
});
it("should only target ally for Aromatic Mist", async () => {
game.override.battleType("double");
await game.classicMode.startBattle([ Species.REGIELEKI, Species.RATTATA ]);
const [ leftPlayer, rightPlayer ] = game.scene.getPlayerField();
const [ leftOpp, rightOpp ] = game.scene.getEnemyField();
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.AROMATIC_MIST);
game.move.select(Moves.METRONOME, 0);
await game.phaseInterceptor.to(CommandPhase);
game.move.select(Moves.SPLASH, 1);
await game.toNextTurn();
expect(rightPlayer.getStatStage(Stat.SPDEF)).toBe(1);
expect(leftPlayer.getStatStage(Stat.SPDEF)).toBe(0);
expect(leftOpp.getStatStage(Stat.SPDEF)).toBe(0);
expect(rightOpp.getStatStage(Stat.SPDEF)).toBe(0);
});
it("should cause opponent to flee, and not crash for Roar", async () => {
await game.classicMode.startBattle();
vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.ROAR);
const enemyPokemon = game.scene.getEnemyPokemon()!;
game.move.select(Moves.METRONOME);
await game.phaseInterceptor.to("BerryPhase");
const isVisible = enemyPokemon.visible;
const hasFled = enemyPokemon.switchOutStatus;
expect(!isVisible && hasFled).toBe(true);
await game.phaseInterceptor.to("CommandPhase");
});
});

View File

@ -0,0 +1,84 @@
import { BattlerIndex } from "#app/battle";
import { Stat } from "#app/enums/stat";
import { MoveResult } from "#app/field/pokemon";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Mirror Move", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([ Moves.MIRROR_MOVE, Moves.SPLASH ])
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("should use the last move that the target used on the user", async () => {
game.override
.battleType("double")
.enemyMoveset([ Moves.TACKLE, Moves.GROWL ]);
await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]);
game.move.select(Moves.MIRROR_MOVE, 0, BattlerIndex.ENEMY); // target's last move is Tackle, enemy should receive damage from Mirror Move copying Tackle
game.move.select(Moves.SPLASH, 1);
await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2);
await game.forceEnemyMove(Moves.GROWL, BattlerIndex.PLAYER_2);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(game.scene.getEnemyField()[0].isFullHp()).toBeFalsy();
});
it("should apply secondary effects of a move", async () => {
game.override.enemyMoveset(Moves.ACID_SPRAY);
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.MIRROR_MOVE);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPDEF)).toBe(-2);
});
it("should be able to copy status moves", async () => {
game.override.enemyMoveset(Moves.GROWL);
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.MIRROR_MOVE);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(-1);
});
it("should fail if the target has not used any moves", async () => {
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.MIRROR_MOVE);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
});
});

View File

@ -0,0 +1,143 @@
import { BattlerIndex } from "#app/battle";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { allMoves } from "#app/data/move";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Moves - Rage Fist", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const move = allMoves[Moves.RAGE_FIST];
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleType("single")
.moveset([ Moves.RAGE_FIST, Moves.SPLASH, Moves.SUBSTITUTE ])
.startingLevel(100)
.enemyLevel(1)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.DOUBLE_KICK);
vi.spyOn(move, "calculateBattlePower");
});
it("should have 100 more power if hit twice before calling Rage Fist", async () => {
game.override
.enemySpecies(Species.MAGIKARP);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.phaseInterceptor.to("TurnEndPhase");
expect(move.calculateBattlePower).toHaveLastReturnedWith(150);
});
it("should maintain its power during next battle if it is within the same arena encounter", async () => {
game.override
.enemySpecies(Species.MAGIKARP)
.startingWave(1);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextWave();
game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.phaseInterceptor.to("BerryPhase", false);
expect(move.calculateBattlePower).toHaveLastReturnedWith(250);
});
it("should reset the hitRecCounter if we enter new trainer battle", async () => {
game.override
.enemySpecies(Species.MAGIKARP)
.startingWave(4);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextWave();
game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.phaseInterceptor.to("BerryPhase", false);
expect(move.calculateBattlePower).toHaveLastReturnedWith(150);
});
it("should not increase the hitCounter if Substitute is hit", async () => {
game.override
.enemySpecies(Species.MAGIKARP)
.startingWave(4);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.SUBSTITUTE);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.phaseInterceptor.to("MoveEffectPhase");
expect(game.scene.getPlayerPokemon()?.customPokemonData.hitsRecCount).toBe(0);
});
it("should reset the hitRecCounter if we enter new biome", async () => {
game.override
.enemySpecies(Species.MAGIKARP)
.startingWave(10);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.phaseInterceptor.to("BerryPhase", false);
expect(move.calculateBattlePower).toHaveLastReturnedWith(150);
});
it("should not reset the hitRecCounter if switched out", async () => {
game.override
.enemySpecies(Species.MAGIKARP)
.startingWave(1)
.enemyMoveset(Moves.TACKLE);
await game.classicMode.startBattle([ Species.CHARIZARD, Species.BLASTOISE ]);
game.move.select(Moves.SPLASH);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.toNextTurn();
game.move.select(Moves.RAGE_FIST);
await game.phaseInterceptor.to("MoveEndPhase");
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(Species.CHARIZARD);
expect(move.calculateBattlePower).toHaveLastReturnedWith(150);
});
});

View File

@ -0,0 +1,75 @@
import { Stat } from "#app/enums/stat";
import { StatusEffect } from "#app/enums/status-effect";
import { MoveResult } from "#app/field/pokemon";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Sleep Talk", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([ Moves.SPLASH, Moves.SLEEP_TALK ])
.statusEffect(StatusEffect.SLEEP)
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH)
.enemyLevel(100);
});
it("should fail when the user is not asleep", async () => {
game.override.statusEffect(StatusEffect.NONE);
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.SLEEP_TALK);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
});
it("should fail if the user has no valid moves", async () => {
game.override.moveset([ Moves.SLEEP_TALK, Moves.DIG, Moves.METRONOME, Moves.SOLAR_BEAM ]);
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.SLEEP_TALK);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
});
it("should call a random valid move if the user is asleep", async () => {
game.override.moveset([ Moves.SLEEP_TALK, Moves.DIG, Moves.FLY, Moves.SWORDS_DANCE ]); // Dig and Fly are invalid moves, Swords Dance should always be called
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.SLEEP_TALK);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK));
});
it("should apply secondary effects of a move", async () => {
game.override.moveset([ Moves.SLEEP_TALK, Moves.DIG, Moves.FLY, Moves.WOOD_HAMMER ]); // Dig and Fly are invalid moves, Wood Hammer should always be called
await game.classicMode.startBattle();
game.move.select(Moves.SLEEP_TALK);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.isFullHp()).toBeFalsy(); // Wood Hammer recoil effect should be applied
});
});

View File

@ -125,7 +125,7 @@ describe("Moves - Spit Up", () => {
game.move.select(Moves.SPIT_UP);
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.FAIL });
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.FAIL, targets: [ game.scene.getEnemyPokemon()!.getBattlerIndex() ]});
expect(spitUp.calculateBattlePower).not.toHaveBeenCalled();
});
@ -148,7 +148,7 @@ describe("Moves - Spit Up", () => {
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS });
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS, targets: [ game.scene.getEnemyPokemon()!.getBattlerIndex() ]});
expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
@ -176,7 +176,7 @@ describe("Moves - Spit Up", () => {
game.move.select(Moves.SPIT_UP);
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS });
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS, targets: [ game.scene.getEnemyPokemon()!.getBattlerIndex() ]});
expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();

View File

@ -72,7 +72,7 @@ describe("Moves - Stockpile", () => {
expect(user.getStatStage(Stat.SPDEF)).toBe(3);
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(3);
expect(user.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ result: MoveResult.FAIL, move: Moves.STOCKPILE });
expect(user.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ result: MoveResult.FAIL, move: Moves.STOCKPILE, targets: [ user.getBattlerIndex() ]});
}
}
});

View File

@ -135,7 +135,7 @@ describe("Moves - Swallow", () => {
game.move.select(Moves.SWALLOW);
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.FAIL });
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.FAIL, targets: [ pokemon.getBattlerIndex() ]});
});
describe("restores stat stage boosts granted by stacks", () => {
@ -156,7 +156,7 @@ describe("Moves - Swallow", () => {
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS });
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS, targets: [ pokemon.getBattlerIndex() ]});
expect(pokemon.getStatStage(Stat.DEF)).toBe(0);
expect(pokemon.getStatStage(Stat.SPDEF)).toBe(0);
@ -183,7 +183,7 @@ describe("Moves - Swallow", () => {
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS });
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS, targets: [ pokemon.getBattlerIndex() ]});
expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
expect(pokemon.getStatStage(Stat.SPDEF)).toBe(-2);

View File

@ -364,6 +364,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
success = this.setCursor(0);
} else if (this.rowCursor < this.shopOptionsRows.length + 1) {
success = this.setRowCursor(this.rowCursor + 1);
} else {
success = this.setRowCursor(0);
}
break;
case Button.DOWN:
@ -371,13 +373,15 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
success = this.setRowCursor(this.rowCursor - 1);
} else if (this.lockRarityButtonContainer.visible && this.cursor === 0) {
success = this.setCursor(3);
} else {
success = this.setRowCursor(this.shopOptionsRows.length + 1);
}
break;
case Button.LEFT:
if (!this.rowCursor) {
switch (this.cursor) {
case 0:
success = false;
success = this.setCursor(2);
break;
case 1:
if (this.lockRarityButtonContainer.visible) {
@ -395,11 +399,21 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
success = false;
}
break;
case 3:
if (this.lockRarityButtonContainer.visible) {
success = this.setCursor(2);
} else {
success = false;
}
}
} else if (this.cursor) {
success = this.setCursor(this.cursor - 1);
} else if (this.rowCursor === 1 && this.rerollButtonContainer.visible) {
success = this.setRowCursor(0);
} else {
if (this.rowCursor === 1 && this.options.length === 0) {
success = false;
} else {
success = this.setCursor(this.getRowItems(this.rowCursor) - 1);
}
}
break;
case Button.RIGHT:
@ -416,7 +430,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
success = this.setCursor(2);
break;
case 2:
success = false;
success = this.setCursor(0);
break;
case 3:
if (this.transferButtonContainer.visible) {
@ -428,8 +442,12 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
}
} else if (this.cursor < this.getRowItems(this.rowCursor) - 1) {
success = this.setCursor(this.cursor + 1);
} else if (this.rowCursor === 1 && this.transferButtonContainer.visible) {
} else {
if (this.rowCursor === 1 && this.options.length === 0) {
success = this.setRowCursor(0);
} else {
success = this.setCursor(0);
}
}
break;
}
@ -519,6 +537,14 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
newCursor = 2;
}
}
// Allows to find lock rarity button when looping from the top
if (rowCursor === 0 && lastRowCursor > 1 && newCursor === 0 && this.lockRarityButtonContainer.visible) {
newCursor = 3;
}
// Allows to loop to top when lock rarity button is shown
if (rowCursor === this.shopOptionsRows.length + 1 && lastRowCursor === 0 && this.cursor === 3) {
newCursor = 0;
}
this.cursor = -1;
this.setCursor(newCursor);
return true;

View File

@ -157,6 +157,12 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler {
success = (this.cursor === 0) ? this.setCursor(this.cursor) : this.setCursor(this.cursor - 1, cursorPosition);
} else if (this.scrollCursor) {
success = this.setScrollCursor(this.scrollCursor - 1, cursorPosition);
} else if ((this.cursor === 0) && (this.scrollCursor === 0)) {
this.setScrollCursor(SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN);
// Revert to avoid an extra session slot sticking out
this.revertSessionSlot(SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN);
this.setCursor(SLOTS_ON_SCREEN - 1);
success = true;
}
break;
case Button.DOWN:
@ -164,6 +170,11 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler {
success = this.setCursor(this.cursor + 1, cursorPosition);
} else if (this.scrollCursor < SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN) {
success = this.setScrollCursor(this.scrollCursor + 1, cursorPosition);
} else if ((this.cursor === SLOTS_ON_SCREEN - 1) && (this.scrollCursor === SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN)) {
this.setScrollCursor(0);
this.revertSessionSlot(SLOTS_ON_SCREEN - 1);
this.setCursor(0);
success = true;
}
break;
case Button.RIGHT:

View File

@ -89,6 +89,13 @@ export class NavigationManager {
}
}
/**
* Removes menus from the manager in preparation for reset
*/
public clearNavigationMenus() {
this.navigationMenus.length = 0;
}
}
export default class NavigationMenu extends Phaser.GameObjects.Container {

View File

@ -2698,6 +2698,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.updateScroll();
};
override destroy(): void {
// Without this the reference gets hung up and no startercontainers get GCd
this.starterContainers = [];
}
updateScroll = () => {
const maxColumns = 9;
const maxRows = 9;

View File

@ -62,4 +62,9 @@ export default abstract class UiHandler {
clear() {
this.active = false;
}
/**
* To be implemented by individual handlers when necessary to free memory
* Called when {@linkcode BattleScene} is reset
*/
destroy(): void {}
}

View File

@ -53,6 +53,7 @@ import TestDialogueUiHandler from "#app/ui/test-dialogue-ui-handler";
import AutoCompleteUiHandler from "./autocomplete-ui-handler";
import { Device } from "#enums/devices";
import MysteryEncounterUiHandler from "./mystery-encounter-ui-handler";
import { NavigationManager } from "./settings/navigationMenu";
export enum Mode {
MESSAGE,
@ -614,4 +615,14 @@ export default class UI extends Phaser.GameObjects.Container {
return globalScene.inputMethod;
}
}
/**
* Attempts to free memory held by UI handlers
* and clears menus from {@linkcode NavigationManager} to prepare for reset
*/
public freeUIData(): void {
this.handlers.forEach(h => h.destroy());
this.handlers = [];
NavigationManager.getInstance().clearNavigationMenus();
}
}