Merge pull request #17 from pom-eranian/var-honchkrow

198 430 Murkrow Honchkrow [Epic Rare] @ hamez
This commit is contained in:
pom-eranian 2024-10-11 19:12:46 -04:00 committed by GitHub
commit 0530b3681c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
105 changed files with 1899 additions and 479 deletions

14
global.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
import type { SetupServerApi } from "msw/node";
export {};
declare global {
/**
* Only used in testing.
* Can technically be undefined/null but for ease of use we are going to assume it is always defined.
* Used to load i18n files exclusively.
*
* To set up your own server in a test see `game_data.test.ts`
*/
var i18nServer: SetupServerApi;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,34 @@
{
"1": {
"d94352": "7a101c",
"314263": "462b20",
"d64252": "b3986b",
"73293a": "755237",
"ffad8c": "ad2e24",
"73283a": "630c17",
"d6404f": "8c1b23",
"efd684": "a6a6b3",
"42639c": "694c30",
"5a4a21": "25253b",
"292942": "2a1512",
"b59c21": "57566f",
"752a3c": "4d0419",
"d6bd52": "838098"
},
"2": {
"d94352": "5939a9",
"314263": "0e4333",
"d64252": "bc4b84",
"73293a": "7b2363",
"ffad8c": "b164e6",
"73283a": "630c17",
"d6404f": "8c1b23",
"efd684": "c2723a",
"42639c": "1d6e47",
"5a4a21": "4e1915",
"292942": "091e16",
"b59c21": "85412d",
"752a3c": "1e1764",
"d6bd52": "9a5524"
}
}

View File

@ -0,0 +1,40 @@
{
"1": {
"31313a": "1d1d2b",
"efeff7": "b9382d",
"8c313a": "b3986b",
"5a5a3a": "1e1e2c",
"545454": "3c3b4d",
"29213a": "271b1a",
"3a5a9c": "694c30",
"8c3039": "9e2933",
"de5057": "bd392d",
"a3a3b5": "60606c",
"a5a5b5": "7a1e21",
"de525a": "f3e3b3",
"4a2121": "755237",
"ad943a": "3f3e50",
"3a3a5a": "422e26",
"525252": "380514",
"f7de3a": "61616d"
},
"2": {
"31313a": "521a16",
"efeff7": "975bc2",
"8c313a": "bc4b84",
"5a5a3a": "4e1915",
"545454": "87432e",
"29213a": "091e16",
"3a5a9c": "1d6e47",
"8c3039": "9e2933",
"de5057": "bd392d",
"a3a3b5": "c4743b",
"a5a5b5": "4f358e",
"de525a": "f17f9c",
"4a2121": "7b2363",
"ad943a": "85412d",
"3a3a5a": "0e4333",
"525252": "1c1754",
"f7de3a": "c2723a"
}
}

View File

@ -654,6 +654,16 @@
1, 1,
1 1
], ],
"198": [
0,
1,
1
],
"198-f": [
0,
1,
1
],
"199": [ "199": [
2, 2,
1, 1,
@ -1589,6 +1599,11 @@
1, 1,
1 1
], ],
"430": [
0,
1,
1
],
"433": [ "433": [
1, 1,
1, 1,
@ -3840,6 +3855,11 @@
1, 1,
1 1
], ],
"198": [
0,
1,
1
],
"203": [ "203": [
0, 0,
1, 1,
@ -4667,6 +4687,11 @@
1, 1,
1 1
], ],
"198": [
0,
1,
1
],
"199": [ "199": [
2, 2,
1, 1,
@ -5602,6 +5627,11 @@
1, 1,
1 1
], ],
"430": [
0,
1,
1
],
"433": [ "433": [
1, 1,
1, 1,
@ -7883,6 +7913,11 @@
1, 1,
1 1
], ],
"198": [
0,
1,
1
],
"203": [ "203": [
0, 0,
1, 1,

View File

@ -0,0 +1,26 @@
{
"1": {
"d94352": "8c1b23",
"314263": "462b20",
"efd684": "a6a6b3",
"d64252": "b3986b",
"42639c": "694c30",
"5a4a21": "25253b",
"292942": "2a1512",
"b59c21": "57566f",
"73293a": "755237",
"d6bd52": "838098"
},
"2": {
"d94352": "8c1b23",
"314263": "0e4333",
"efd684": "c2723a",
"d64252": "bc4b84",
"42639c": "1d6e47",
"5a4a21": "4e1915",
"292942": "091e16",
"b59c21": "85412d",
"73293a": "7b2363",
"d6bd52": "9a5524"
}
}

View File

@ -0,0 +1,34 @@
{
"1": {
"31313a": "280113",
"8c313a": "b3986b",
"5a5a3a": "1e1e2c",
"545454": "3c3b4d",
"a3a3b5": "7a1e21",
"3a5a9c": "694c30",
"525151": "380514",
"a7a7b8": "60606c",
"efeff8": "b9382d",
"4a2121": "755237",
"ad943a": "3f3e50",
"f7de3a": "61616d",
"3a3a5a": "422e26",
"29213a": "271b1a"
},
"2": {
"31313a": "080735",
"8c313a": "bc4b84",
"5a5a3a": "4e1915",
"545454": "85412d",
"a3a3b5": "4f358e",
"3a5a9c": "1d6e47",
"525151": "1c1754",
"a7a7b8": "c27238",
"efeff8": "975bc2",
"4a2121": "7b2363",
"ad943a": "85402c",
"f7de3a": "c2723a",
"3a3a5a": "0e4333",
"29213a": "091e16"
}
}

View File

@ -0,0 +1,26 @@
{
"1": {
"d94352": "8c1b23",
"314263": "462b20",
"efd684": "a6a6b3",
"d64252": "b3986b",
"42639c": "694c30",
"5a4a21": "25253b",
"292942": "2a1512",
"b59c21": "57566f",
"73293a": "755237",
"d6bd52": "838098"
},
"2": {
"d94352": "8c1b23",
"314263": "0e4333",
"efd684": "c2723a",
"d64252": "bc4b84",
"42639c": "1d6e47",
"5a4a21": "4e1915",
"292942": "091e16",
"b59c21": "85412d",
"73293a": "7b2363",
"d6bd52": "9a5524"
}
}

View File

@ -0,0 +1,34 @@
{
"1": {
"d94352": "b3986b",
"314263": "462b20",
"d64252": "7a101c",
"73293a": "4d0419",
"ffad8c": "ad2e24",
"73283a": "630c17",
"d6404f": "8c1b23",
"efd684": "a6a6b3",
"42639c": "694c30",
"5a4a21": "25253b",
"292942": "2a1512",
"b59c21": "57566f",
"752a3c": "755237",
"d6bd52": "838098"
},
"2": {
"d94352": "bc4b84",
"314263": "0e4333",
"d64252": "5939a9",
"73293a": "1e1764",
"ffad8c": "b164e6",
"73283a": "630c17",
"d6404f": "8c1b23",
"efd684": "c2723a",
"42639c": "1d6e47",
"5a4a21": "4e1915",
"292942": "091e16",
"b59c21": "85412d",
"752a3c": "7b2363",
"d6bd52": "9a5524"
}
}

View File

@ -3161,13 +3161,17 @@ export default class BattleScene extends SceneBase {
/** /**
* Loads or generates a mystery encounter * Loads or generates a mystery encounter
* @param encounterType used to load session encounter when restarting game, etc. * @param encounterType used to load session encounter when restarting game, etc.
* @param canBypass optional boolean to indicate that the request is coming from a function that needs to access a Mystery Encounter outside of gameplay requirements
* @returns * @returns
*/ */
getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter { getMysteryEncounter(encounterType?: MysteryEncounterType, canBypass?: boolean): MysteryEncounter {
// Loading override or session encounter // Loading override or session encounter
let encounter: MysteryEncounter | null; let encounter: MysteryEncounter | null;
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) { if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) {
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE]; encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE];
} else if (canBypass) {
encounter = allMysteryEncounters[encounterType ?? -1];
return encounter;
} else { } else {
encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null; encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null;
} }

View File

@ -634,15 +634,15 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr {
* Examples include: Absorb, Draining Kiss, Bitter Blade, etc. * Examples include: Absorb, Draining Kiss, Bitter Blade, etc.
* Also displays a message to show this ability was activated. * Also displays a message to show this ability was activated.
* @param pokemon {@linkcode Pokemon} with this ability * @param pokemon {@linkcode Pokemon} with this ability
* @param passive N/A * @param _passive N/A
* @param attacker {@linkcode Pokemon} that is attacking this Pokemon * @param attacker {@linkcode Pokemon} that is attacking this Pokemon
* @param move {@linkcode PokemonMove} that is being used * @param move {@linkcode PokemonMove} that is being used
* @param hitResult N/A * @param _hitResult N/A
* @args N/A * @param _args N/A
* @returns true if healing should be reversed on a healing move, false otherwise. * @returns true if healing should be reversed on a healing move, false otherwise.
*/ */
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (move.hasAttr(HitHealAttr)) { if (move.hasAttr(HitHealAttr) && !move.hitsSubstitute(attacker, pokemon)) {
if (!simulated) { if (!simulated) {
pokemon.scene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); pokemon.scene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) }));
} }
@ -669,8 +669,8 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr {
this.allOthers = allOthers; this.allOthers = allOthers;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) { if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
if (simulated) { if (simulated) {
return true; return true;
} }
@ -707,13 +707,13 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
this.selfTarget = selfTarget; this.selfTarget = selfTarget;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate); const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate);
const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1]; const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1];
const damageReceived = lastAttackReceived?.damage || 0; const damageReceived = lastAttackReceived?.damage || 0;
if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat)) { if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat) && !move.hitsSubstitute(attacker, pokemon)) {
if (!simulated ) { if (!simulated) {
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages));
} }
return true; return true;
@ -734,8 +734,8 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr {
this.tagType = tagType; this.tagType = tagType;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) { if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag; const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag;
if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) { if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) {
if (!simulated) { if (!simulated) {
@ -758,8 +758,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
this.tagType = tagType; this.tagType = tagType;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) { if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
if (!pokemon.getTag(this.tagType) && !simulated) { if (!pokemon.getTag(this.tagType) && !simulated) {
pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); pokemon.addTag(this.tagType, undefined, undefined, pokemon.id);
pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }));
@ -771,8 +771,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
} }
export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT) { if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) {
if (simulated) { if (simulated) {
return true; return true;
} }
@ -787,7 +787,7 @@ export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
return false; return false;
} }
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
return i18next.t("abilityTriggers:postDefendTypeChange", { return i18next.t("abilityTriggers:postDefendTypeChange", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName, abilityName,
@ -805,8 +805,8 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
this.terrainType = terrainType; this.terrainType = terrainType;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT) { if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) {
if (simulated) { if (simulated) {
return pokemon.scene.arena.terrain?.terrainType !== (this.terrainType || undefined); return pokemon.scene.arena.terrain?.terrainType !== (this.terrainType || undefined);
} else { } else {
@ -829,8 +829,9 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
this.effects = effects; this.effects = effects;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status
&& (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon)) {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
if (simulated) { if (simulated) {
return attacker.canSetStatus(effect, true, false, pokemon); return attacker.canSetStatus(effect, true, false, pokemon);
@ -869,8 +870,8 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
this.turnCount = turnCount; this.turnCount = turnCount;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance && !move.hitsSubstitute(attacker, pokemon)) {
if (simulated) { if (simulated) {
return attacker.canAddTag(this.tagType); return attacker.canAddTag(this.tagType);
} else { } else {
@ -893,7 +894,11 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
this.stages = stages; this.stages = stages;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (move.hitsSubstitute(attacker, pokemon)) {
return false;
}
if (!simulated) { if (!simulated) {
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages));
} }
@ -901,7 +906,7 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
return true; return true;
} }
getCondition(): AbAttrCondition { override getCondition(): AbAttrCondition {
return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length !== 0 && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical; return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length !== 0 && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical;
} }
} }
@ -915,8 +920,9 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
this.damageRatio = damageRatio; this.damageRatio = damageRatio;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)
&& !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio));
return true; return true;
@ -925,7 +931,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
return false; return false;
} }
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
return i18next.t("abilityTriggers:postDefendContactDamage", { return i18next.t("abilityTriggers:postDefendContactDamage", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName abilityName
@ -948,8 +954,8 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
this.turns = turns; this.turns = turns;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !move.hitsSubstitute(attacker, pokemon)) {
if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) { if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) {
return false; return false;
} else { } else {
@ -963,24 +969,24 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
return false; return false;
} }
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
return i18next.t("abilityTriggers:perishBody", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName }); return i18next.t("abilityTriggers:perishBody", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName });
} }
} }
export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr {
private weatherType: WeatherType; private weatherType: WeatherType;
protected condition: PokemonDefendCondition | null; protected condition?: PokemonDefendCondition;
constructor(weatherType: WeatherType, condition?: PokemonDefendCondition) { constructor(weatherType: WeatherType, condition?: PokemonDefendCondition) {
super(); super();
this.weatherType = weatherType; this.weatherType = weatherType;
this.condition = condition ?? null; this.condition = condition;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (this.condition !== null && !this.condition(pokemon, attacker, move)) { if (this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon)) {
return false; return false;
} }
if (!pokemon.scene.arena.weather?.isImmutable()) { if (!pokemon.scene.arena.weather?.isImmutable()) {
@ -999,8 +1005,9 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
super(); super();
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)
&& !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
if (!simulated) { if (!simulated) {
const tempAbilityId = attacker.getAbility().id; const tempAbilityId = attacker.getAbility().id;
attacker.summonData.ability = pokemon.getAbility().id; attacker.summonData.ability = pokemon.getAbility().id;
@ -1012,7 +1019,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
return false; return false;
} }
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
return i18next.t("abilityTriggers:postDefendAbilitySwap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); return i18next.t("abilityTriggers:postDefendAbilitySwap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
} }
} }
@ -1025,8 +1032,9 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
this.ability = ability; this.ability = ability;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr)
&& !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
if (!simulated) { if (!simulated) {
attacker.summonData.ability = this.ability; attacker.summonData.ability = this.ability;
} }
@ -1037,7 +1045,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
return false; return false;
} }
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
return i18next.t("abilityTriggers:postDefendAbilityGive", { return i18next.t("abilityTriggers:postDefendAbilityGive", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName abilityName
@ -1056,8 +1064,8 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
this.chance = chance; this.chance = chance;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (attacker.getTag(BattlerTagType.DISABLED) === null) { if (attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon)) {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) {
if (simulated) { if (simulated) {
return true; return true;
@ -1724,17 +1732,17 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
} }
export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
private condition: PokemonDefendCondition | null; private condition?: PokemonDefendCondition;
constructor(condition?: PokemonDefendCondition) { constructor(condition?: PokemonDefendCondition) {
super(); super();
this.condition = condition ?? null; this.condition = condition;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move)) && !move.hitsSubstitute(attacker, pokemon)) {
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable);
if (heldItems.length) { if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
@ -4476,7 +4484,7 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC
export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
private multiplier: number; private multiplier: number;
private tagType: BattlerTagType; private tagType: BattlerTagType;
private recoilDamageFunc: ((pokemon: Pokemon) => number) | undefined; private recoilDamageFunc?: ((pokemon: Pokemon) => number);
private triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string; private triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string;
constructor(condition: PokemonDefendCondition, multiplier: number, tagType: BattlerTagType, triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string, recoilDamageFunc?: (pokemon: Pokemon) => number) { constructor(condition: PokemonDefendCondition, multiplier: number, tagType: BattlerTagType, triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string, recoilDamageFunc?: (pokemon: Pokemon) => number) {
@ -4492,16 +4500,16 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
* Applies the pre-defense ability to the Pokémon. * Applies the pre-defense ability to the Pokémon.
* Removes the appropriate `BattlerTagType` when hit by an attack and is in its defense form. * Removes the appropriate `BattlerTagType` when hit by an attack and is in its defense form.
* *
* @param {Pokemon} pokemon The Pokémon with the ability. * @param pokemon The Pokémon with the ability.
* @param {boolean} passive n/a * @param _passive n/a
* @param {Pokemon} attacker The attacking Pokémon. * @param attacker The attacking Pokémon.
* @param {PokemonMove} move The move being used. * @param move The move being used.
* @param {Utils.BooleanHolder} cancelled n/a * @param _cancelled n/a
* @param {any[]} args Additional arguments. * @param args Additional arguments.
* @returns {boolean} Whether the immunity was applied. * @returns `true` if the immunity was applied.
*/ */
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) { if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
if (!simulated) { if (!simulated) {
(args[0] as Utils.NumberHolder).value = this.multiplier; (args[0] as Utils.NumberHolder).value = this.multiplier;
pokemon.removeTag(this.tagType); pokemon.removeTag(this.tagType);
@ -4517,12 +4525,12 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
/** /**
* Gets the message triggered when the Pokémon avoids damage using the form-changing ability. * Gets the message triggered when the Pokémon avoids damage using the form-changing ability.
* @param {Pokemon} pokemon The Pokémon with the ability. * @param pokemon The Pokémon with the ability.
* @param {string} abilityName The name of the ability. * @param abilityName The name of the ability.
* @param {...any} args n/a * @param _args n/a
* @returns {string} The trigger message. * @returns The trigger message.
*/ */
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
return this.triggerMessageFunc(pokemon, abilityName); return this.triggerMessageFunc(pokemon, abilityName);
} }
} }
@ -5503,7 +5511,8 @@ export function initAbilities() {
.attr(NoFusionAbilityAbAttr) .attr(NoFusionAbilityAbAttr)
// Add BattlerTagType.DISGUISE if the pokemon is in its disguised form // Add BattlerTagType.DISGUISE if the pokemon is in its disguised form
.conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false) .conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false)
.attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE, .attr(FormBlockDamageAbAttr,
(target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE,
(pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }), (pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }),
(pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8)) (pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8))
.attr(PostBattleInitFormChangeAbAttr, () => 0) .attr(PostBattleInitFormChangeAbAttr, () => 0)
@ -5665,7 +5674,8 @@ export function initAbilities() {
.conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0) .conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0)
// When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE // When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE
.attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW) .attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW)
.attr(FormBlockDamageAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE, .attr(FormBlockDamageAbAttr,
(target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE,
(pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName })) (pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }))
.attr(PostBattleInitFormChangeAbAttr, () => 0) .attr(PostBattleInitFormChangeAbAttr, () => 0)
.bypassFaint() .bypassFaint()

View File

@ -0,0 +1,603 @@
import { Species } from "#enums/species";
import { EggTier } from "#enums/egg-type";
/**
* Map of all starters and their respective {@linkcode EggTier}, which determines the type of egg the starter hatches from.
*/
export const speciesEggTiers = {
[Species.BULBASAUR]: EggTier.COMMON,
[Species.CHARMANDER]: EggTier.COMMON,
[Species.SQUIRTLE]: EggTier.COMMON,
[Species.CATERPIE]: EggTier.COMMON,
[Species.WEEDLE]: EggTier.COMMON,
[Species.PIDGEY]: EggTier.COMMON,
[Species.RATTATA]: EggTier.COMMON,
[Species.SPEAROW]: EggTier.COMMON,
[Species.EKANS]: EggTier.COMMON,
[Species.PIKACHU]: EggTier.COMMON,
[Species.SANDSHREW]: EggTier.COMMON,
[Species.NIDORAN_F]: EggTier.COMMON,
[Species.NIDORAN_M]: EggTier.COMMON,
[Species.CLEFAIRY]: EggTier.COMMON,
[Species.VULPIX]: EggTier.COMMON,
[Species.JIGGLYPUFF]: EggTier.COMMON,
[Species.ZUBAT]: EggTier.COMMON,
[Species.ODDISH]: EggTier.COMMON,
[Species.PARAS]: EggTier.COMMON,
[Species.VENONAT]: EggTier.COMMON,
[Species.DIGLETT]: EggTier.COMMON,
[Species.MEOWTH]: EggTier.COMMON,
[Species.PSYDUCK]: EggTier.COMMON,
[Species.MANKEY]: EggTier.RARE,
[Species.GROWLITHE]: EggTier.RARE,
[Species.POLIWAG]: EggTier.COMMON,
[Species.ABRA]: EggTier.RARE,
[Species.MACHOP]: EggTier.COMMON,
[Species.BELLSPROUT]: EggTier.COMMON,
[Species.TENTACOOL]: EggTier.COMMON,
[Species.GEODUDE]: EggTier.COMMON,
[Species.PONYTA]: EggTier.COMMON,
[Species.SLOWPOKE]: EggTier.COMMON,
[Species.MAGNEMITE]: EggTier.RARE,
[Species.FARFETCHD]: EggTier.COMMON,
[Species.DODUO]: EggTier.COMMON,
[Species.SEEL]: EggTier.COMMON,
[Species.GRIMER]: EggTier.COMMON,
[Species.SHELLDER]: EggTier.RARE,
[Species.GASTLY]: EggTier.RARE,
[Species.ONIX]: EggTier.COMMON,
[Species.DROWZEE]: EggTier.COMMON,
[Species.KRABBY]: EggTier.COMMON,
[Species.VOLTORB]: EggTier.COMMON,
[Species.EXEGGCUTE]: EggTier.COMMON,
[Species.CUBONE]: EggTier.COMMON,
[Species.HITMONLEE]: EggTier.RARE,
[Species.HITMONCHAN]: EggTier.RARE,
[Species.LICKITUNG]: EggTier.COMMON,
[Species.KOFFING]: EggTier.COMMON,
[Species.RHYHORN]: EggTier.COMMON,
[Species.CHANSEY]: EggTier.COMMON,
[Species.TANGELA]: EggTier.COMMON,
[Species.KANGASKHAN]: EggTier.RARE,
[Species.HORSEA]: EggTier.COMMON,
[Species.GOLDEEN]: EggTier.COMMON,
[Species.STARYU]: EggTier.COMMON,
[Species.MR_MIME]: EggTier.COMMON,
[Species.SCYTHER]: EggTier.RARE,
[Species.JYNX]: EggTier.RARE,
[Species.ELECTABUZZ]: EggTier.RARE,
[Species.MAGMAR]: EggTier.RARE,
[Species.PINSIR]: EggTier.RARE,
[Species.TAUROS]: EggTier.RARE,
[Species.MAGIKARP]: EggTier.RARE,
[Species.LAPRAS]: EggTier.RARE,
[Species.DITTO]: EggTier.COMMON,
[Species.EEVEE]: EggTier.COMMON,
[Species.PORYGON]: EggTier.RARE,
[Species.OMANYTE]: EggTier.COMMON,
[Species.KABUTO]: EggTier.COMMON,
[Species.AERODACTYL]: EggTier.RARE,
[Species.SNORLAX]: EggTier.RARE,
[Species.ARTICUNO]: EggTier.EPIC,
[Species.ZAPDOS]: EggTier.EPIC,
[Species.MOLTRES]: EggTier.EPIC,
[Species.DRATINI]: EggTier.RARE,
[Species.MEWTWO]: EggTier.LEGENDARY,
[Species.MEW]: EggTier.EPIC,
[Species.CHIKORITA]: EggTier.COMMON,
[Species.CYNDAQUIL]: EggTier.COMMON,
[Species.TOTODILE]: EggTier.COMMON,
[Species.SENTRET]: EggTier.COMMON,
[Species.HOOTHOOT]: EggTier.COMMON,
[Species.LEDYBA]: EggTier.COMMON,
[Species.SPINARAK]: EggTier.COMMON,
[Species.CHINCHOU]: EggTier.COMMON,
[Species.PICHU]: EggTier.COMMON,
[Species.CLEFFA]: EggTier.COMMON,
[Species.IGGLYBUFF]: EggTier.COMMON,
[Species.TOGEPI]: EggTier.COMMON,
[Species.NATU]: EggTier.COMMON,
[Species.MAREEP]: EggTier.COMMON,
[Species.MARILL]: EggTier.RARE,
[Species.SUDOWOODO]: EggTier.COMMON,
[Species.HOPPIP]: EggTier.COMMON,
[Species.AIPOM]: EggTier.COMMON,
[Species.SUNKERN]: EggTier.COMMON,
[Species.YANMA]: EggTier.COMMON,
[Species.WOOPER]: EggTier.COMMON,
[Species.MURKROW]: EggTier.COMMON,
[Species.MISDREAVUS]: EggTier.COMMON,
[Species.UNOWN]: EggTier.COMMON,
[Species.WOBBUFFET]: EggTier.COMMON,
[Species.GIRAFARIG]: EggTier.COMMON,
[Species.PINECO]: EggTier.COMMON,
[Species.DUNSPARCE]: EggTier.COMMON,
[Species.GLIGAR]: EggTier.COMMON,
[Species.SNUBBULL]: EggTier.COMMON,
[Species.QWILFISH]: EggTier.COMMON,
[Species.SHUCKLE]: EggTier.COMMON,
[Species.HERACROSS]: EggTier.RARE,
[Species.SNEASEL]: EggTier.RARE,
[Species.TEDDIURSA]: EggTier.RARE,
[Species.SLUGMA]: EggTier.COMMON,
[Species.SWINUB]: EggTier.COMMON,
[Species.CORSOLA]: EggTier.COMMON,
[Species.REMORAID]: EggTier.COMMON,
[Species.DELIBIRD]: EggTier.COMMON,
[Species.MANTINE]: EggTier.COMMON,
[Species.SKARMORY]: EggTier.RARE,
[Species.HOUNDOUR]: EggTier.COMMON,
[Species.PHANPY]: EggTier.COMMON,
[Species.STANTLER]: EggTier.COMMON,
[Species.SMEARGLE]: EggTier.COMMON,
[Species.TYROGUE]: EggTier.COMMON,
[Species.SMOOCHUM]: EggTier.COMMON,
[Species.ELEKID]: EggTier.COMMON,
[Species.MAGBY]: EggTier.COMMON,
[Species.MILTANK]: EggTier.RARE,
[Species.RAIKOU]: EggTier.EPIC,
[Species.ENTEI]: EggTier.EPIC,
[Species.SUICUNE]: EggTier.EPIC,
[Species.LARVITAR]: EggTier.RARE,
[Species.LUGIA]: EggTier.LEGENDARY,
[Species.HO_OH]: EggTier.LEGENDARY,
[Species.CELEBI]: EggTier.EPIC,
[Species.TREECKO]: EggTier.COMMON,
[Species.TORCHIC]: EggTier.RARE,
[Species.MUDKIP]: EggTier.COMMON,
[Species.POOCHYENA]: EggTier.COMMON,
[Species.ZIGZAGOON]: EggTier.COMMON,
[Species.WURMPLE]: EggTier.COMMON,
[Species.LOTAD]: EggTier.COMMON,
[Species.SEEDOT]: EggTier.COMMON,
[Species.TAILLOW]: EggTier.COMMON,
[Species.WINGULL]: EggTier.COMMON,
[Species.RALTS]: EggTier.COMMON,
[Species.SURSKIT]: EggTier.COMMON,
[Species.SHROOMISH]: EggTier.COMMON,
[Species.SLAKOTH]: EggTier.RARE,
[Species.NINCADA]: EggTier.RARE,
[Species.WHISMUR]: EggTier.COMMON,
[Species.MAKUHITA]: EggTier.COMMON,
[Species.AZURILL]: EggTier.RARE,
[Species.NOSEPASS]: EggTier.COMMON,
[Species.SKITTY]: EggTier.COMMON,
[Species.SABLEYE]: EggTier.COMMON,
[Species.MAWILE]: EggTier.COMMON,
[Species.ARON]: EggTier.COMMON,
[Species.MEDITITE]: EggTier.COMMON,
[Species.ELECTRIKE]: EggTier.COMMON,
[Species.PLUSLE]: EggTier.COMMON,
[Species.MINUN]: EggTier.COMMON,
[Species.VOLBEAT]: EggTier.COMMON,
[Species.ILLUMISE]: EggTier.COMMON,
[Species.ROSELIA]: EggTier.COMMON,
[Species.GULPIN]: EggTier.COMMON,
[Species.CARVANHA]: EggTier.COMMON,
[Species.WAILMER]: EggTier.COMMON,
[Species.NUMEL]: EggTier.COMMON,
[Species.TORKOAL]: EggTier.COMMON,
[Species.SPOINK]: EggTier.COMMON,
[Species.SPINDA]: EggTier.COMMON,
[Species.TRAPINCH]: EggTier.COMMON,
[Species.CACNEA]: EggTier.COMMON,
[Species.SWABLU]: EggTier.COMMON,
[Species.ZANGOOSE]: EggTier.RARE,
[Species.SEVIPER]: EggTier.COMMON,
[Species.LUNATONE]: EggTier.COMMON,
[Species.SOLROCK]: EggTier.COMMON,
[Species.BARBOACH]: EggTier.COMMON,
[Species.CORPHISH]: EggTier.COMMON,
[Species.BALTOY]: EggTier.COMMON,
[Species.LILEEP]: EggTier.COMMON,
[Species.ANORITH]: EggTier.COMMON,
[Species.FEEBAS]: EggTier.RARE,
[Species.CASTFORM]: EggTier.COMMON,
[Species.KECLEON]: EggTier.COMMON,
[Species.SHUPPET]: EggTier.COMMON,
[Species.DUSKULL]: EggTier.COMMON,
[Species.TROPIUS]: EggTier.COMMON,
[Species.CHIMECHO]: EggTier.COMMON,
[Species.ABSOL]: EggTier.RARE,
[Species.WYNAUT]: EggTier.COMMON,
[Species.SNORUNT]: EggTier.COMMON,
[Species.SPHEAL]: EggTier.COMMON,
[Species.CLAMPERL]: EggTier.COMMON,
[Species.RELICANTH]: EggTier.COMMON,
[Species.LUVDISC]: EggTier.COMMON,
[Species.BAGON]: EggTier.RARE,
[Species.BELDUM]: EggTier.RARE,
[Species.REGIROCK]: EggTier.EPIC,
[Species.REGICE]: EggTier.EPIC,
[Species.REGISTEEL]: EggTier.EPIC,
[Species.LATIAS]: EggTier.EPIC,
[Species.LATIOS]: EggTier.EPIC,
[Species.KYOGRE]: EggTier.LEGENDARY,
[Species.GROUDON]: EggTier.LEGENDARY,
[Species.RAYQUAZA]: EggTier.LEGENDARY,
[Species.JIRACHI]: EggTier.EPIC,
[Species.DEOXYS]: EggTier.EPIC,
[Species.TURTWIG]: EggTier.COMMON,
[Species.CHIMCHAR]: EggTier.COMMON,
[Species.PIPLUP]: EggTier.COMMON,
[Species.STARLY]: EggTier.COMMON,
[Species.BIDOOF]: EggTier.COMMON,
[Species.KRICKETOT]: EggTier.COMMON,
[Species.SHINX]: EggTier.COMMON,
[Species.BUDEW]: EggTier.COMMON,
[Species.CRANIDOS]: EggTier.COMMON,
[Species.SHIELDON]: EggTier.COMMON,
[Species.BURMY]: EggTier.COMMON,
[Species.COMBEE]: EggTier.COMMON,
[Species.PACHIRISU]: EggTier.COMMON,
[Species.BUIZEL]: EggTier.COMMON,
[Species.CHERUBI]: EggTier.COMMON,
[Species.SHELLOS]: EggTier.COMMON,
[Species.DRIFLOON]: EggTier.COMMON,
[Species.BUNEARY]: EggTier.COMMON,
[Species.GLAMEOW]: EggTier.COMMON,
[Species.CHINGLING]: EggTier.COMMON,
[Species.STUNKY]: EggTier.COMMON,
[Species.BRONZOR]: EggTier.COMMON,
[Species.BONSLY]: EggTier.COMMON,
[Species.MIME_JR]: EggTier.COMMON,
[Species.HAPPINY]: EggTier.COMMON,
[Species.CHATOT]: EggTier.COMMON,
[Species.SPIRITOMB]: EggTier.RARE,
[Species.GIBLE]: EggTier.RARE,
[Species.MUNCHLAX]: EggTier.RARE,
[Species.RIOLU]: EggTier.COMMON,
[Species.HIPPOPOTAS]: EggTier.COMMON,
[Species.SKORUPI]: EggTier.COMMON,
[Species.CROAGUNK]: EggTier.COMMON,
[Species.CARNIVINE]: EggTier.COMMON,
[Species.FINNEON]: EggTier.COMMON,
[Species.MANTYKE]: EggTier.COMMON,
[Species.SNOVER]: EggTier.COMMON,
[Species.ROTOM]: EggTier.RARE,
[Species.UXIE]: EggTier.EPIC,
[Species.MESPRIT]: EggTier.EPIC,
[Species.AZELF]: EggTier.EPIC,
[Species.DIALGA]: EggTier.LEGENDARY,
[Species.PALKIA]: EggTier.LEGENDARY,
[Species.HEATRAN]: EggTier.EPIC,
[Species.REGIGIGAS]: EggTier.EPIC,
[Species.GIRATINA]: EggTier.LEGENDARY,
[Species.CRESSELIA]: EggTier.EPIC,
[Species.PHIONE]: EggTier.RARE,
[Species.MANAPHY]: EggTier.EPIC,
[Species.DARKRAI]: EggTier.EPIC,
[Species.SHAYMIN]: EggTier.EPIC,
[Species.ARCEUS]: EggTier.LEGENDARY,
[Species.VICTINI]: EggTier.EPIC,
[Species.SNIVY]: EggTier.COMMON,
[Species.TEPIG]: EggTier.COMMON,
[Species.OSHAWOTT]: EggTier.COMMON,
[Species.PATRAT]: EggTier.COMMON,
[Species.LILLIPUP]: EggTier.COMMON,
[Species.PURRLOIN]: EggTier.COMMON,
[Species.PANSAGE]: EggTier.COMMON,
[Species.PANSEAR]: EggTier.COMMON,
[Species.PANPOUR]: EggTier.COMMON,
[Species.MUNNA]: EggTier.COMMON,
[Species.PIDOVE]: EggTier.COMMON,
[Species.BLITZLE]: EggTier.COMMON,
[Species.ROGGENROLA]: EggTier.COMMON,
[Species.WOOBAT]: EggTier.COMMON,
[Species.DRILBUR]: EggTier.RARE,
[Species.AUDINO]: EggTier.COMMON,
[Species.TIMBURR]: EggTier.RARE,
[Species.TYMPOLE]: EggTier.COMMON,
[Species.THROH]: EggTier.RARE,
[Species.SAWK]: EggTier.RARE,
[Species.SEWADDLE]: EggTier.COMMON,
[Species.VENIPEDE]: EggTier.COMMON,
[Species.COTTONEE]: EggTier.COMMON,
[Species.PETILIL]: EggTier.COMMON,
[Species.BASCULIN]: EggTier.RARE,
[Species.SANDILE]: EggTier.RARE,
[Species.DARUMAKA]: EggTier.RARE,
[Species.MARACTUS]: EggTier.COMMON,
[Species.DWEBBLE]: EggTier.COMMON,
[Species.SCRAGGY]: EggTier.COMMON,
[Species.SIGILYPH]: EggTier.RARE,
[Species.YAMASK]: EggTier.COMMON,
[Species.TIRTOUGA]: EggTier.COMMON,
[Species.ARCHEN]: EggTier.COMMON,
[Species.TRUBBISH]: EggTier.COMMON,
[Species.ZORUA]: EggTier.COMMON,
[Species.MINCCINO]: EggTier.COMMON,
[Species.GOTHITA]: EggTier.COMMON,
[Species.SOLOSIS]: EggTier.COMMON,
[Species.DUCKLETT]: EggTier.COMMON,
[Species.VANILLITE]: EggTier.COMMON,
[Species.DEERLING]: EggTier.COMMON,
[Species.EMOLGA]: EggTier.COMMON,
[Species.KARRABLAST]: EggTier.COMMON,
[Species.FOONGUS]: EggTier.COMMON,
[Species.FRILLISH]: EggTier.COMMON,
[Species.ALOMOMOLA]: EggTier.RARE,
[Species.JOLTIK]: EggTier.COMMON,
[Species.FERROSEED]: EggTier.COMMON,
[Species.KLINK]: EggTier.COMMON,
[Species.TYNAMO]: EggTier.COMMON,
[Species.ELGYEM]: EggTier.COMMON,
[Species.LITWICK]: EggTier.COMMON,
[Species.AXEW]: EggTier.RARE,
[Species.CUBCHOO]: EggTier.COMMON,
[Species.CRYOGONAL]: EggTier.RARE,
[Species.SHELMET]: EggTier.COMMON,
[Species.STUNFISK]: EggTier.COMMON,
[Species.MIENFOO]: EggTier.COMMON,
[Species.DRUDDIGON]: EggTier.RARE,
[Species.GOLETT]: EggTier.COMMON,
[Species.PAWNIARD]: EggTier.RARE,
[Species.BOUFFALANT]: EggTier.RARE,
[Species.RUFFLET]: EggTier.COMMON,
[Species.VULLABY]: EggTier.COMMON,
[Species.HEATMOR]: EggTier.COMMON,
[Species.DURANT]: EggTier.RARE,
[Species.DEINO]: EggTier.RARE,
[Species.LARVESTA]: EggTier.RARE,
[Species.COBALION]: EggTier.EPIC,
[Species.TERRAKION]: EggTier.EPIC,
[Species.VIRIZION]: EggTier.EPIC,
[Species.TORNADUS]: EggTier.EPIC,
[Species.THUNDURUS]: EggTier.EPIC,
[Species.RESHIRAM]: EggTier.LEGENDARY,
[Species.ZEKROM]: EggTier.LEGENDARY,
[Species.LANDORUS]: EggTier.EPIC,
[Species.KYUREM]: EggTier.LEGENDARY,
[Species.KELDEO]: EggTier.EPIC,
[Species.MELOETTA]: EggTier.EPIC,
[Species.GENESECT]: EggTier.EPIC,
[Species.CHESPIN]: EggTier.COMMON,
[Species.FENNEKIN]: EggTier.COMMON,
[Species.FROAKIE]: EggTier.RARE,
[Species.BUNNELBY]: EggTier.COMMON,
[Species.FLETCHLING]: EggTier.COMMON,
[Species.SCATTERBUG]: EggTier.COMMON,
[Species.LITLEO]: EggTier.COMMON,
[Species.FLABEBE]: EggTier.COMMON,
[Species.SKIDDO]: EggTier.COMMON,
[Species.PANCHAM]: EggTier.COMMON,
[Species.FURFROU]: EggTier.COMMON,
[Species.ESPURR]: EggTier.COMMON,
[Species.HONEDGE]: EggTier.RARE,
[Species.SPRITZEE]: EggTier.COMMON,
[Species.SWIRLIX]: EggTier.COMMON,
[Species.INKAY]: EggTier.COMMON,
[Species.BINACLE]: EggTier.COMMON,
[Species.SKRELP]: EggTier.COMMON,
[Species.CLAUNCHER]: EggTier.COMMON,
[Species.HELIOPTILE]: EggTier.COMMON,
[Species.TYRUNT]: EggTier.COMMON,
[Species.AMAURA]: EggTier.COMMON,
[Species.HAWLUCHA]: EggTier.RARE,
[Species.DEDENNE]: EggTier.COMMON,
[Species.CARBINK]: EggTier.COMMON,
[Species.GOOMY]: EggTier.RARE,
[Species.KLEFKI]: EggTier.COMMON,
[Species.PHANTUMP]: EggTier.COMMON,
[Species.PUMPKABOO]: EggTier.COMMON,
[Species.BERGMITE]: EggTier.COMMON,
[Species.NOIBAT]: EggTier.COMMON,
[Species.XERNEAS]: EggTier.LEGENDARY,
[Species.YVELTAL]: EggTier.LEGENDARY,
[Species.ZYGARDE]: EggTier.LEGENDARY,
[Species.DIANCIE]: EggTier.EPIC,
[Species.HOOPA]: EggTier.EPIC,
[Species.VOLCANION]: EggTier.EPIC,
[Species.ETERNAL_FLOETTE]: EggTier.RARE,
[Species.ROWLET]: EggTier.COMMON,
[Species.LITTEN]: EggTier.COMMON,
[Species.POPPLIO]: EggTier.RARE,
[Species.PIKIPEK]: EggTier.COMMON,
[Species.YUNGOOS]: EggTier.COMMON,
[Species.GRUBBIN]: EggTier.COMMON,
[Species.CRABRAWLER]: EggTier.COMMON,
[Species.ORICORIO]: EggTier.COMMON,
[Species.CUTIEFLY]: EggTier.COMMON,
[Species.ROCKRUFF]: EggTier.COMMON,
[Species.WISHIWASHI]: EggTier.COMMON,
[Species.MAREANIE]: EggTier.COMMON,
[Species.MUDBRAY]: EggTier.COMMON,
[Species.DEWPIDER]: EggTier.COMMON,
[Species.FOMANTIS]: EggTier.COMMON,
[Species.MORELULL]: EggTier.COMMON,
[Species.SALANDIT]: EggTier.COMMON,
[Species.STUFFUL]: EggTier.COMMON,
[Species.BOUNSWEET]: EggTier.COMMON,
[Species.COMFEY]: EggTier.RARE,
[Species.ORANGURU]: EggTier.RARE,
[Species.PASSIMIAN]: EggTier.RARE,
[Species.WIMPOD]: EggTier.COMMON,
[Species.SANDYGAST]: EggTier.COMMON,
[Species.PYUKUMUKU]: EggTier.COMMON,
[Species.TYPE_NULL]: EggTier.RARE,
[Species.MINIOR]: EggTier.RARE,
[Species.KOMALA]: EggTier.COMMON,
[Species.TURTONATOR]: EggTier.RARE,
[Species.TOGEDEMARU]: EggTier.COMMON,
[Species.MIMIKYU]: EggTier.RARE,
[Species.BRUXISH]: EggTier.RARE,
[Species.DRAMPA]: EggTier.RARE,
[Species.DHELMISE]: EggTier.RARE,
[Species.JANGMO_O]: EggTier.RARE,
[Species.TAPU_KOKO]: EggTier.EPIC,
[Species.TAPU_LELE]: EggTier.EPIC,
[Species.TAPU_BULU]: EggTier.EPIC,
[Species.TAPU_FINI]: EggTier.EPIC,
[Species.COSMOG]: EggTier.EPIC,
[Species.NIHILEGO]: EggTier.EPIC,
[Species.BUZZWOLE]: EggTier.EPIC,
[Species.PHEROMOSA]: EggTier.EPIC,
[Species.XURKITREE]: EggTier.EPIC,
[Species.CELESTEELA]: EggTier.EPIC,
[Species.KARTANA]: EggTier.EPIC,
[Species.GUZZLORD]: EggTier.EPIC,
[Species.NECROZMA]: EggTier.LEGENDARY,
[Species.MAGEARNA]: EggTier.EPIC,
[Species.MARSHADOW]: EggTier.EPIC,
[Species.POIPOLE]: EggTier.EPIC,
[Species.STAKATAKA]: EggTier.EPIC,
[Species.BLACEPHALON]: EggTier.EPIC,
[Species.ZERAORA]: EggTier.EPIC,
[Species.MELTAN]: EggTier.EPIC,
[Species.ALOLA_RATTATA]: EggTier.COMMON,
[Species.ALOLA_SANDSHREW]: EggTier.COMMON,
[Species.ALOLA_VULPIX]: EggTier.COMMON,
[Species.ALOLA_DIGLETT]: EggTier.COMMON,
[Species.ALOLA_MEOWTH]: EggTier.COMMON,
[Species.ALOLA_GEODUDE]: EggTier.COMMON,
[Species.ALOLA_GRIMER]: EggTier.COMMON,
[Species.GROOKEY]: EggTier.COMMON,
[Species.SCORBUNNY]: EggTier.RARE,
[Species.SOBBLE]: EggTier.COMMON,
[Species.SKWOVET]: EggTier.COMMON,
[Species.ROOKIDEE]: EggTier.COMMON,
[Species.BLIPBUG]: EggTier.COMMON,
[Species.NICKIT]: EggTier.COMMON,
[Species.GOSSIFLEUR]: EggTier.COMMON,
[Species.WOOLOO]: EggTier.COMMON,
[Species.CHEWTLE]: EggTier.COMMON,
[Species.YAMPER]: EggTier.COMMON,
[Species.ROLYCOLY]: EggTier.COMMON,
[Species.APPLIN]: EggTier.COMMON,
[Species.SILICOBRA]: EggTier.COMMON,
[Species.CRAMORANT]: EggTier.COMMON,
[Species.ARROKUDA]: EggTier.COMMON,
[Species.TOXEL]: EggTier.COMMON,
[Species.SIZZLIPEDE]: EggTier.COMMON,
[Species.CLOBBOPUS]: EggTier.COMMON,
[Species.SINISTEA]: EggTier.COMMON,
[Species.HATENNA]: EggTier.COMMON,
[Species.IMPIDIMP]: EggTier.COMMON,
[Species.MILCERY]: EggTier.COMMON,
[Species.FALINKS]: EggTier.RARE,
[Species.PINCURCHIN]: EggTier.COMMON,
[Species.SNOM]: EggTier.COMMON,
[Species.STONJOURNER]: EggTier.COMMON,
[Species.EISCUE]: EggTier.COMMON,
[Species.INDEEDEE]: EggTier.RARE,
[Species.MORPEKO]: EggTier.COMMON,
[Species.CUFANT]: EggTier.COMMON,
[Species.DRACOZOLT]: EggTier.RARE,
[Species.ARCTOZOLT]: EggTier.RARE,
[Species.DRACOVISH]: EggTier.RARE,
[Species.ARCTOVISH]: EggTier.RARE,
[Species.DURALUDON]: EggTier.RARE,
[Species.DREEPY]: EggTier.RARE,
[Species.ZACIAN]: EggTier.LEGENDARY,
[Species.ZAMAZENTA]: EggTier.LEGENDARY,
[Species.ETERNATUS]: EggTier.COMMON,
[Species.KUBFU]: EggTier.EPIC,
[Species.ZARUDE]: EggTier.EPIC,
[Species.REGIELEKI]: EggTier.EPIC,
[Species.REGIDRAGO]: EggTier.EPIC,
[Species.GLASTRIER]: EggTier.EPIC,
[Species.SPECTRIER]: EggTier.EPIC,
[Species.CALYREX]: EggTier.LEGENDARY,
[Species.GALAR_MEOWTH]: EggTier.COMMON,
[Species.GALAR_PONYTA]: EggTier.COMMON,
[Species.GALAR_SLOWPOKE]: EggTier.COMMON,
[Species.GALAR_FARFETCHD]: EggTier.COMMON,
[Species.GALAR_CORSOLA]: EggTier.COMMON,
[Species.GALAR_ZIGZAGOON]: EggTier.COMMON,
[Species.GALAR_DARUMAKA]: EggTier.RARE,
[Species.GALAR_YAMASK]: EggTier.COMMON,
[Species.GALAR_STUNFISK]: EggTier.COMMON,
[Species.GALAR_MR_MIME]: EggTier.COMMON,
[Species.GALAR_ARTICUNO]: EggTier.EPIC,
[Species.GALAR_ZAPDOS]: EggTier.EPIC,
[Species.GALAR_MOLTRES]: EggTier.EPIC,
[Species.HISUI_GROWLITHE]: EggTier.RARE,
[Species.HISUI_VOLTORB]: EggTier.COMMON,
[Species.HISUI_QWILFISH]: EggTier.RARE,
[Species.HISUI_SNEASEL]: EggTier.RARE,
[Species.HISUI_ZORUA]: EggTier.COMMON,
[Species.ENAMORUS]: EggTier.EPIC,
[Species.SPRIGATITO]: EggTier.RARE,
[Species.FUECOCO]: EggTier.RARE,
[Species.QUAXLY]: EggTier.RARE,
[Species.LECHONK]: EggTier.COMMON,
[Species.TAROUNTULA]: EggTier.COMMON,
[Species.NYMBLE]: EggTier.COMMON,
[Species.PAWMI]: EggTier.COMMON,
[Species.TANDEMAUS]: EggTier.RARE,
[Species.FIDOUGH]: EggTier.COMMON,
[Species.SMOLIV]: EggTier.COMMON,
[Species.SQUAWKABILLY]: EggTier.COMMON,
[Species.NACLI]: EggTier.RARE,
[Species.CHARCADET]: EggTier.RARE,
[Species.TADBULB]: EggTier.COMMON,
[Species.WATTREL]: EggTier.COMMON,
[Species.MASCHIFF]: EggTier.COMMON,
[Species.SHROODLE]: EggTier.COMMON,
[Species.BRAMBLIN]: EggTier.COMMON,
[Species.TOEDSCOOL]: EggTier.COMMON,
[Species.KLAWF]: EggTier.COMMON,
[Species.CAPSAKID]: EggTier.COMMON,
[Species.RELLOR]: EggTier.COMMON,
[Species.FLITTLE]: EggTier.COMMON,
[Species.TINKATINK]: EggTier.RARE,
[Species.WIGLETT]: EggTier.COMMON,
[Species.BOMBIRDIER]: EggTier.COMMON,
[Species.FINIZEN]: EggTier.COMMON,
[Species.VAROOM]: EggTier.RARE,
[Species.CYCLIZAR]: EggTier.RARE,
[Species.ORTHWORM]: EggTier.RARE,
[Species.GLIMMET]: EggTier.RARE,
[Species.GREAVARD]: EggTier.COMMON,
[Species.FLAMIGO]: EggTier.RARE,
[Species.CETODDLE]: EggTier.COMMON,
[Species.VELUZA]: EggTier.RARE,
[Species.DONDOZO]: EggTier.RARE,
[Species.TATSUGIRI]: EggTier.RARE,
[Species.GREAT_TUSK]: EggTier.EPIC,
[Species.SCREAM_TAIL]: EggTier.EPIC,
[Species.BRUTE_BONNET]: EggTier.EPIC,
[Species.FLUTTER_MANE]: EggTier.EPIC,
[Species.SLITHER_WING]: EggTier.EPIC,
[Species.SANDY_SHOCKS]: EggTier.EPIC,
[Species.IRON_TREADS]: EggTier.EPIC,
[Species.IRON_BUNDLE]: EggTier.EPIC,
[Species.IRON_HANDS]: EggTier.EPIC,
[Species.IRON_JUGULIS]: EggTier.EPIC,
[Species.IRON_MOTH]: EggTier.EPIC,
[Species.IRON_THORNS]: EggTier.EPIC,
[Species.FRIGIBAX]: EggTier.RARE,
[Species.GIMMIGHOUL]: EggTier.RARE,
[Species.WO_CHIEN]: EggTier.EPIC,
[Species.CHIEN_PAO]: EggTier.EPIC,
[Species.TING_LU]: EggTier.EPIC,
[Species.CHI_YU]: EggTier.EPIC,
[Species.ROARING_MOON]: EggTier.EPIC,
[Species.IRON_VALIANT]: EggTier.EPIC,
[Species.KORAIDON]: EggTier.LEGENDARY,
[Species.MIRAIDON]: EggTier.LEGENDARY,
[Species.WALKING_WAKE]: EggTier.EPIC,
[Species.IRON_LEAVES]: EggTier.EPIC,
[Species.POLTCHAGEIST]: EggTier.RARE,
[Species.OKIDOGI]: EggTier.EPIC,
[Species.MUNKIDORI]: EggTier.EPIC,
[Species.FEZANDIPITI]: EggTier.EPIC,
[Species.OGERPON]: EggTier.EPIC,
[Species.GOUGING_FIRE]: EggTier.EPIC,
[Species.RAGING_BOLT]: EggTier.EPIC,
[Species.IRON_BOULDER]: EggTier.EPIC,
[Species.IRON_CROWN]: EggTier.EPIC,
[Species.TERAPAGOS]: EggTier.LEGENDARY,
[Species.PECHARUNT]: EggTier.EPIC,
[Species.PALDEA_TAUROS]: EggTier.RARE,
[Species.PALDEA_WOOPER]: EggTier.COMMON,
[Species.BLOODMOON_URSALUNA]: EggTier.EPIC,
};

View File

@ -1376,7 +1376,7 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag {
const effectPhase = pokemon.scene.getCurrentPhase(); const effectPhase = pokemon.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon(); const attacker = effectPhase.getPokemon();
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ this.stat ], this.levels));
} }
} }
@ -2139,6 +2139,10 @@ export class GulpMissileTag extends BattlerTag {
return false; return false;
} }
if (moveEffectPhase.move.getMove().hitsSubstitute(attacker, pokemon)) {
return true;
}
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
@ -2636,16 +2640,16 @@ export class ImprisonTag extends MoveRestrictionBattlerTag {
/** /**
* Battler Tag that applies the effects of Syrup Bomb to the target Pokemon. * Battler Tag that applies the effects of Syrup Bomb to the target Pokemon.
* For three turns, starting from the turn of hit, at the end of each turn, the target Pokemon's speed will decrease by 1. * For three turns, starting from the turn of hit, at the end of each turn, the target Pokemon's speed will decrease by 1.
* The tag can also expire by taking the target Pokemon off the field. * The tag can also expire by taking the target Pokemon off the field, or the Pokemon that originally used the move.
*/ */
export class SyrupBombTag extends BattlerTag { export class SyrupBombTag extends BattlerTag {
constructor() { constructor(sourceId: number) {
super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB); super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB, sourceId);
} }
/** /**
* Adds the Syrup Bomb battler tag to the target Pokemon. * Adds the Syrup Bomb battler tag to the target Pokemon.
* @param {Pokemon} pokemon the target Pokemon * @param pokemon - The target {@linkcode Pokemon}
*/ */
override onAdd(pokemon: Pokemon) { override onAdd(pokemon: Pokemon) {
super.onAdd(pokemon); super.onAdd(pokemon);
@ -2654,15 +2658,16 @@ export class SyrupBombTag extends BattlerTag {
/** /**
* Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count * Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count
* @param {Pokemon} pokemon the target Pokemon * @param pokemon - The target {@linkcode Pokemon}
* @param {BattlerTagLapseType} _lapseType * @param _lapseType - N/A
* @returns `true` if the turnCount is still greater than 0 | `false` if the turnCount is 0 or the target Pokemon has been removed from the field * @returns `true` if the `turnCount` is still greater than `0`; `false` if the `turnCount` is `0` or the target or source Pokemon has been removed from the field
*/ */
override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
if (!pokemon.isActive(true)) { if (this.sourceId && !pokemon.scene.getPokemonById(this.sourceId)?.isActive(true)) {
return false; return false;
} }
pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // Custom message in lieu of an animation in mainline // Custom message in lieu of an animation in mainline
pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.unshiftPhase(new StatStageChangePhase( pokemon.scene.unshiftPhase(new StatStageChangePhase(
pokemon.scene, pokemon.getBattlerIndex(), true, pokemon.scene, pokemon.getBattlerIndex(), true,
[ Stat.SPD ], -1, true, false, true [ Stat.SPD ], -1, true, false, true
@ -2673,12 +2678,8 @@ export class SyrupBombTag extends BattlerTag {
/** /**
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID. * Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
* * @param sourceId - The ID of the pokemon adding the tag
* @param {BattlerTagType} tagType the type of the {@linkcode BattlerTagType}. * @returns The corresponding {@linkcode BattlerTag} object.
* @param turnCount the turn count.
* @param {Moves} sourceMove the source {@linkcode Moves}.
* @param sourceId the source ID.
* @returns {BattlerTag} the corresponding {@linkcode BattlerTag} object.
*/ */
export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag { export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
switch (tagType) { switch (tagType) {
@ -2847,7 +2848,7 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.IMPRISON: case BattlerTagType.IMPRISON:
return new ImprisonTag(sourceId); return new ImprisonTag(sourceId);
case BattlerTagType.SYRUP_BOMB: case BattlerTagType.SYRUP_BOMB:
return new SyrupBombTag(); return new SyrupBombTag(sourceId);
case BattlerTagType.NONE: case BattlerTagType.NONE:
default: default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -11,6 +11,7 @@ import { EggTier } from "#enums/egg-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { EggSourceType } from "#enums/egg-source-types"; import { EggSourceType } from "#enums/egg-source-types";
import { MANAPHY_EGG_MANAPHY_RATE, SAME_SPECIES_EGG_HA_RATE, GACHA_EGG_HA_RATE, GACHA_DEFAULT_RARE_EGGMOVE_RATE, SAME_SPECIES_EGG_RARE_EGGMOVE_RATE, GACHA_MOVE_UP_RARE_EGGMOVE_RATE, GACHA_DEFAULT_SHINY_RATE, GACHA_SHINY_UP_SHINY_RATE, SAME_SPECIES_EGG_SHINY_RATE, EGG_PITY_LEGENDARY_THRESHOLD, EGG_PITY_EPIC_THRESHOLD, EGG_PITY_RARE_THRESHOLD, SHINY_VARIANT_CHANCE, SHINY_EPIC_CHANCE, GACHA_DEFAULT_COMMON_EGG_THRESHOLD, GACHA_DEFAULT_RARE_EGG_THRESHOLD, GACHA_DEFAULT_EPIC_EGG_THRESHOLD, GACHA_LEGENDARY_UP_THRESHOLD_OFFSET, HATCH_WAVES_MANAPHY_EGG, HATCH_WAVES_COMMON_EGG, HATCH_WAVES_RARE_EGG, HATCH_WAVES_EPIC_EGG, HATCH_WAVES_LEGENDARY_EGG } from "#app/data/balance/rates"; import { MANAPHY_EGG_MANAPHY_RATE, SAME_SPECIES_EGG_HA_RATE, GACHA_EGG_HA_RATE, GACHA_DEFAULT_RARE_EGGMOVE_RATE, SAME_SPECIES_EGG_RARE_EGGMOVE_RATE, GACHA_MOVE_UP_RARE_EGGMOVE_RATE, GACHA_DEFAULT_SHINY_RATE, GACHA_SHINY_UP_SHINY_RATE, SAME_SPECIES_EGG_SHINY_RATE, EGG_PITY_LEGENDARY_THRESHOLD, EGG_PITY_EPIC_THRESHOLD, EGG_PITY_RARE_THRESHOLD, SHINY_VARIANT_CHANCE, SHINY_EPIC_CHANCE, GACHA_DEFAULT_COMMON_EGG_THRESHOLD, GACHA_DEFAULT_RARE_EGG_THRESHOLD, GACHA_DEFAULT_EPIC_EGG_THRESHOLD, GACHA_LEGENDARY_UP_THRESHOLD_OFFSET, HATCH_WAVES_MANAPHY_EGG, HATCH_WAVES_COMMON_EGG, HATCH_WAVES_RARE_EGG, HATCH_WAVES_EPIC_EGG, HATCH_WAVES_LEGENDARY_EGG } from "#app/data/balance/rates";
import { speciesEggTiers } from "#app/data/balance/species-egg-tiers";
export const EGG_SEED = 1073741824; export const EGG_SEED = 1073741824;
@ -160,7 +161,7 @@ export class Egg {
// Override egg tier and hatchwaves if species was given // Override egg tier and hatchwaves if species was given
if (eggOptions?.species) { if (eggOptions?.species) {
this._tier = this.getEggTierFromSpeciesStarterValue(); this._tier = this.getEggTier();
this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves(); this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves();
} }
// If species has no variant, set variantTier to common. This needs to // If species has no variant, set variantTier to common. This needs to
@ -261,11 +262,11 @@ export class Egg {
return "Manaphy"; return "Manaphy";
} }
switch (this.tier) { switch (this.tier) {
case EggTier.GREAT: case EggTier.RARE:
return i18next.t("egg:greatTier"); return i18next.t("egg:greatTier");
case EggTier.ULTRA: case EggTier.EPIC:
return i18next.t("egg:ultraTier"); return i18next.t("egg:ultraTier");
case EggTier.MASTER: case EggTier.LEGENDARY:
return i18next.t("egg:masterTier"); return i18next.t("egg:masterTier");
default: default:
return i18next.t("egg:defaultTier"); return i18next.t("egg:defaultTier");
@ -336,9 +337,9 @@ export class Egg {
switch (eggTier ?? this._tier) { switch (eggTier ?? this._tier) {
case EggTier.COMMON: case EggTier.COMMON:
return HATCH_WAVES_COMMON_EGG; return HATCH_WAVES_COMMON_EGG;
case EggTier.GREAT: case EggTier.RARE:
return HATCH_WAVES_RARE_EGG; return HATCH_WAVES_RARE_EGG;
case EggTier.ULTRA: case EggTier.EPIC:
return HATCH_WAVES_EPIC_EGG; return HATCH_WAVES_EPIC_EGG;
} }
return HATCH_WAVES_LEGENDARY_EGG; return HATCH_WAVES_LEGENDARY_EGG;
@ -347,7 +348,7 @@ export class Egg {
private rollEggTier(): EggTier { private rollEggTier(): EggTier {
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0; const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
const tierValue = Utils.randInt(256); const tierValue = Utils.randInt(256);
return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.GREAT : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER; 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 { private rollSpecies(scene: BattleScene): Species | null {
@ -367,7 +368,7 @@ export class Egg {
*/ */
const rand = (Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1); const rand = (Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1);
return rand ? Species.PHIONE : Species.MANAPHY; return rand ? Species.PHIONE : Species.MANAPHY;
} else if (this.tier === EggTier.MASTER } else if (this.tier === EggTier.LEGENDARY
&& this._sourceType === EggSourceType.GACHA_LEGENDARY) { && this._sourceType === EggSourceType.GACHA_LEGENDARY) {
if (!Utils.randSeedInt(2)) { if (!Utils.randSeedInt(2)) {
return getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp); return getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp);
@ -378,15 +379,15 @@ export class Egg {
let maxStarterValue: integer; let maxStarterValue: integer;
switch (this.tier) { switch (this.tier) {
case EggTier.GREAT: case EggTier.RARE:
minStarterValue = 4; minStarterValue = 4;
maxStarterValue = 5; maxStarterValue = 5;
break; break;
case EggTier.ULTRA: case EggTier.EPIC:
minStarterValue = 6; minStarterValue = 6;
maxStarterValue = 7; maxStarterValue = 7;
break; break;
case EggTier.MASTER: case EggTier.LEGENDARY:
minStarterValue = 8; minStarterValue = 8;
maxStarterValue = 9; maxStarterValue = 9;
break; break;
@ -398,8 +399,8 @@ export class Egg {
const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ]; const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ];
let speciesPool = Object.keys(speciesStarterCosts) let speciesPool = Object.keys(speciesEggTiers)
.filter(s => speciesStarterCosts[s] >= minStarterValue && speciesStarterCosts[s] <= maxStarterValue) .filter(s => speciesEggTiers[s] === this.tier)
.map(s => parseInt(s) as Species) .map(s => parseInt(s) as Species)
.filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1); .filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1);
@ -430,7 +431,9 @@ export class Egg {
let totalWeight = 0; let totalWeight = 0;
const speciesWeights : number[] = []; const speciesWeights : number[] = [];
for (const speciesId of speciesPool) { for (const speciesId of speciesPool) {
let weight = Math.floor((((maxStarterValue - speciesStarterCosts[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); // Accounts for species that have starter costs outside of the normal range for their EggTier
const speciesCostClamped = Phaser.Math.Clamp(speciesStarterCosts[speciesId], minStarterValue, maxStarterValue);
let weight = Math.floor((((maxStarterValue - speciesCostClamped) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
const species = getPokemonSpecies(speciesId); const species = getPokemonSpecies(speciesId);
if (species.isRegional()) { if (species.isRegional()) {
weight = Math.floor(weight / 2); weight = Math.floor(weight / 2);
@ -498,16 +501,16 @@ export class Egg {
private checkForPityTierOverrides(scene: BattleScene): void { private checkForPityTierOverrides(scene: BattleScene): void {
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0; const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
scene.gameData.eggPity[EggTier.GREAT] += 1; scene.gameData.eggPity[EggTier.RARE] += 1;
scene.gameData.eggPity[EggTier.ULTRA] += 1; scene.gameData.eggPity[EggTier.EPIC] += 1;
scene.gameData.eggPity[EggTier.MASTER] += 1 + tierValueOffset; scene.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. // 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.MASTER] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) { if (scene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.MASTER; this._tier = EggTier.LEGENDARY;
} else if (scene.gameData.eggPity[EggTier.ULTRA] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) { } else if (scene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.ULTRA; this._tier = EggTier.EPIC;
} else if (scene.gameData.eggPity[EggTier.GREAT] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) { } else if (scene.gameData.eggPity[EggTier.RARE] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.GREAT; this._tier = EggTier.RARE;
} }
scene.gameData.eggPity[this._tier] = 0; scene.gameData.eggPity[this._tier] = 0;
} }
@ -516,38 +519,24 @@ export class Egg {
scene.gameData.gameStats.eggsPulled++; scene.gameData.gameStats.eggsPulled++;
if (this.isManaphyEgg()) { if (this.isManaphyEgg()) {
scene.gameData.gameStats.manaphyEggsPulled++; scene.gameData.gameStats.manaphyEggsPulled++;
this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.ULTRA); this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.EPIC);
return; return;
} }
switch (this.tier) { switch (this.tier) {
case EggTier.GREAT: case EggTier.RARE:
scene.gameData.gameStats.rareEggsPulled++; scene.gameData.gameStats.rareEggsPulled++;
break; break;
case EggTier.ULTRA: case EggTier.EPIC:
scene.gameData.gameStats.epicEggsPulled++; scene.gameData.gameStats.epicEggsPulled++;
break; break;
case EggTier.MASTER: case EggTier.LEGENDARY:
scene.gameData.gameStats.legendaryEggsPulled++; scene.gameData.gameStats.legendaryEggsPulled++;
break; break;
} }
} }
private getEggTierFromSpeciesStarterValue(): EggTier { private getEggTier(): EggTier {
const speciesStartValue = speciesStarterCosts[this.species]; return speciesEggTiers[this.species];
if (speciesStartValue >= 1 && speciesStartValue <= 3) {
return EggTier.COMMON;
}
if (speciesStartValue >= 4 && speciesStartValue <= 5) {
return EggTier.GREAT;
}
if (speciesStartValue >= 6 && speciesStartValue <= 7) {
return EggTier.ULTRA;
}
if (speciesStartValue >= 8) {
return EggTier.MASTER;
}
return EggTier.COMMON;
} }
//// ////
@ -556,8 +545,8 @@ export class Egg {
} }
export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species { export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species {
const legendarySpecies = Object.entries(speciesStarterCosts) const legendarySpecies = Object.entries(speciesEggTiers)
.filter(s => s[1] >= 8 && s[1] <= 9) .filter(s => s[1] === EggTier.LEGENDARY)
.map(s => parseInt(s[0])) .map(s => parseInt(s[0]))
.filter(s => getPokemonSpecies(s).isObtainable()); .filter(s => getPokemonSpecies(s).isObtainable());
@ -579,17 +568,9 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta
/** /**
* Check for a given species EggTier Value * Check for a given species EggTier Value
* @param species - Species for wich we will check the egg tier it belongs to * @param pokemonSpecies - Species for wich we will check the egg tier it belongs to
* @returns The egg tier of a given pokemon species * @returns The egg tier of a given pokemon species
*/ */
export function getEggTierForSpecies(pokemonSpecies :PokemonSpecies): EggTier { export function getEggTierForSpecies(pokemonSpecies :PokemonSpecies): EggTier {
const speciesBaseValue = speciesStarterCosts[pokemonSpecies.getRootSpeciesId()]; return speciesEggTiers[pokemonSpecies.getRootSpeciesId()];
if (speciesBaseValue <= 3) {
return EggTier.COMMON;
} else if (speciesBaseValue <= 5) {
return EggTier.GREAT;
} else if (speciesBaseValue <= 7) {
return EggTier.ULTRA;
}
return EggTier.MASTER;
} }

View File

@ -1938,12 +1938,21 @@ export class IncrementMovePriorityAttr extends MoveAttr {
* @see {@linkcode apply} * @see {@linkcode apply}
*/ */
export class MultiHitAttr extends MoveAttr { export class MultiHitAttr extends MoveAttr {
/** This move's intrinsic multi-hit type. It should never be modified. */
private readonly intrinsicMultiHitType: MultiHitType;
/** This move's current multi-hit type. It may be temporarily modified by abilities (e.g., Battle Bond). */
private multiHitType: MultiHitType; private multiHitType: MultiHitType;
constructor(multiHitType?: MultiHitType) { constructor(multiHitType?: MultiHitType) {
super(); super();
this.multiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5; this.intrinsicMultiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5;
this.multiHitType = this.intrinsicMultiHitType;
}
// Currently used by `battle_bond.test.ts`
getMultiHitType(): MultiHitType {
return this.multiHitType;
} }
/** /**
@ -1957,7 +1966,7 @@ export class MultiHitAttr extends MoveAttr {
* @returns True * @returns True
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const hitType = new Utils.NumberHolder(this.multiHitType); const hitType = new Utils.NumberHolder(this.intrinsicMultiHitType);
applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType); applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType);
this.multiHitType = hitType.value; this.multiHitType = hitType.value;
@ -4107,11 +4116,11 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr {
* @param user {@linkcode Pokemon} using the move * @param user {@linkcode Pokemon} using the move
* @param target {@linkcode Pokemon} target of the move * @param target {@linkcode Pokemon} target of the move
* @param move {@linkcode Move} with this attribute * @param move {@linkcode Move} with this attribute
* @param args [0] {@linkcode Utils.IntegerHolder} The category of the move * @param args [0] {@linkcode Utils.NumberHolder} The category of the move
* @returns true if the function succeeds * @returns true if the function succeeds
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const category = (args[0] as Utils.IntegerHolder); const category = (args[0] as Utils.NumberHolder);
if (user.getAlly() === target) { if (user.getAlly() === target) {
category.value = MoveCategory.STATUS; category.value = MoveCategory.STATUS;
@ -4835,14 +4844,14 @@ export class GulpMissileTagAttr extends MoveEffectAttr {
/** /**
* Adds BattlerTagType from GulpMissileTag based on the Pokemon's HP ratio. * Adds BattlerTagType from GulpMissileTag based on the Pokemon's HP ratio.
* @param {Pokemon} user The Pokemon using the move. * @param user The Pokemon using the move.
* @param {Pokemon} target The Pokemon being targeted by the move. * @param _target N/A
* @param {Move} move The move being used. * @param move The move being used.
* @param {any[]} args Additional arguments, if any. * @param _args N/A
* @returns Whether the BattlerTag is applied. * @returns Whether the BattlerTag is applied.
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> { apply(user: Pokemon, _target: Pokemon, move: Move, _args: any[]): boolean {
if (!super.apply(user, target, move, args)) { if (!super.apply(user, _target, move, _args)) {
return false; return false;
} }

View File

@ -128,6 +128,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
@ -149,7 +150,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
pulled: false, pulled: false,
sourceType: EggSourceType.EVENT, sourceType: EggSourceType.EVENT,
eggDescriptor: encounter.misc.trainerEggDescription, eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.ULTRA tier: EggTier.EPIC
}; };
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`)); encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]);
@ -171,7 +172,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
pulled: false, pulled: false,
sourceType: EggSourceType.EVENT, sourceType: EggSourceType.EVENT,
eggDescriptor: encounter.misc.trainerEggDescription, eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.GREAT tier: EggTier.RARE
}; };
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`)); encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`));
setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]); setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]);

View File

@ -166,6 +166,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
text: `${namespace}:intro`, text: `${namespace}:intro`,
} }
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -64,6 +64,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
}, },
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -110,6 +110,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -276,6 +276,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -148,6 +148,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -102,6 +102,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
text: `${namespace}:intro`, text: `${namespace}:intro`,
} }
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -117,6 +117,7 @@ export const DarkDealEncounter: MysteryEncounter =
.withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party .withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
.withCatchAllowed(true) .withCatchAllowed(true)
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -84,6 +84,7 @@ export const DelibirdyEncounter: MysteryEncounter =
text: `${namespace}:intro`, text: `${namespace}:intro`,
} }
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -51,6 +51,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
}, },
]) ])
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -52,6 +52,7 @@ export const FieldTripEncounter: MysteryEncounter =
}, },
]) ])
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -122,6 +122,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -120,6 +120,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -76,6 +76,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
text: `${namespace}:intro_dialogue`, text: `${namespace}:intro_dialogue`,
}, },
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -96,6 +96,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
text: `${namespace}:intro`, text: `${namespace}:intro`,
} }
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -50,6 +50,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -125,6 +125,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -61,6 +61,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
text: `${namespace}:intro`, text: `${namespace}:intro`,
} }
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -69,6 +69,7 @@ export const PartTimerEncounter: MysteryEncounter =
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -54,6 +54,7 @@ export const SafariZoneEncounter: MysteryEncounter =
text: `${namespace}:intro`, text: `${namespace}:intro`,
}, },
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -62,6 +62,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
}, },
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -88,6 +88,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -58,6 +58,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
text: `${namespace}:intro`, text: `${namespace}:intro`,
} }
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -196,6 +196,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
@ -493,7 +494,7 @@ function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number)
pulled: false, pulled: false,
sourceType: EggSourceType.EVENT, sourceType: EggSourceType.EVENT,
eggDescriptor: eggDescription, eggDescriptor: eggDescription,
tier: EggTier.GREAT tier: EggTier.RARE
}); });
} }
} }

View File

@ -53,6 +53,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
}, },
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -117,6 +117,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -94,6 +94,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -52,6 +52,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
text: `${namespace}:intro`, text: `${namespace}:intro`,
} }
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -54,6 +54,7 @@ export const TrashToTreasureEncounter: MysteryEncounter =
text: `${namespace}:intro`, text: `${namespace}:intro`,
}, },
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -125,6 +125,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2")); scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2"));
return true; return true;
}) })
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -125,6 +125,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
text: `${namespace}:intro_dialogue`, text: `${namespace}:intro_dialogue`,
}, },
]) ])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)

View File

@ -190,7 +190,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
secondaryPokemon?: PlayerPokemon[]; secondaryPokemon?: PlayerPokemon[];
// #region Post-construct / Auto-populated params // #region Post-construct / Auto-populated params
localizationKey: string;
/** /**
* Dialogue object containing all the dialogue, messages, tooltips, etc. for an encounter * Dialogue object containing all the dialogue, messages, tooltips, etc. for an encounter
*/ */
@ -264,6 +264,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
Object.assign(this, encounter); Object.assign(this, encounter);
} }
this.encounterTier = this.encounterTier ?? MysteryEncounterTier.COMMON; this.encounterTier = this.encounterTier ?? MysteryEncounterTier.COMMON;
this.localizationKey = this.localizationKey ?? "";
this.dialogue = this.dialogue ?? {}; this.dialogue = this.dialogue ?? {};
this.spriteConfigs = this.spriteConfigs ? [ ...this.spriteConfigs ] : []; this.spriteConfigs = this.spriteConfigs ? [ ...this.spriteConfigs ] : [];
// Default max is 1 for ROGUE encounters, 2 for others // Default max is 1 for ROGUE encounters, 2 for others
@ -528,6 +529,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]]; options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]];
enemyPartyConfigs: EnemyPartyConfig[] = []; enemyPartyConfigs: EnemyPartyConfig[] = [];
localizationKey: string = "";
dialogue: MysteryEncounterDialogue = {}; dialogue: MysteryEncounterDialogue = {};
requirements: EncounterSceneRequirement[] = []; requirements: EncounterSceneRequirement[] = [];
primaryPokemonRequirements: EncounterPokemonRequirement[] = []; primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
@ -632,6 +634,16 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
return this.withIntroSpriteConfigs(spriteConfigs).withIntroDialogue(dialogue); return this.withIntroSpriteConfigs(spriteConfigs).withIntroDialogue(dialogue);
} }
/**
* Sets the localization key used by the encounter
* @param localizationKey the string used as the key
* @returns `this`
*/
setLocalizationKey(localizationKey: string): this {
this.localizationKey = localizationKey;
return this;
}
/** /**
* OPTIONAL * OPTIONAL
*/ */

View File

@ -1,6 +1,6 @@
export enum EggTier { export enum EggTier {
COMMON, COMMON,
GREAT, RARE,
ULTRA, EPIC,
MASTER LEGENDARY
} }

View File

@ -50,7 +50,7 @@ export function getStatStageChangeDescriptionKey(stages: number, isIncrease: boo
return isIncrease ? "battle:statRose" : "battle:statFell"; return isIncrease ? "battle:statRose" : "battle:statFell";
} else if (stages === 2) { } else if (stages === 2) {
return isIncrease ? "battle:statSharplyRose" : "battle:statHarshlyFell"; return isIncrease ? "battle:statSharplyRose" : "battle:statHarshlyFell";
} else if (stages <= 6) { } else if (stages > 2 && stages <= 6) {
return isIncrease ? "battle:statRoseDrastically" : "battle:statSeverelyFell"; return isIncrease ? "battle:statRoseDrastically" : "battle:statSeverelyFell";
} }
return isIncrease ? "battle:statWontGoAnyHigher" : "battle:statWontGoAnyLower"; return isIncrease ? "battle:statWontGoAnyHigher" : "battle:statWontGoAnyLower";

View File

@ -983,7 +983,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder);
} }
statHolder.value = Utils.clampInt(statHolder.value, 1, Number.MAX_SAFE_INTEGER); statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER);
this.setStat(s, statHolder.value); this.setStat(s, statHolder.value);
} }
@ -1417,10 +1417,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns {boolean} Whether the ability is present and active * @returns {boolean} Whether the ability is present and active
*/ */
hasAbility(ability: Abilities, canApply: boolean = true, ignoreOverride?: boolean): boolean { hasAbility(ability: Abilities, canApply: boolean = true, ignoreOverride?: boolean): boolean {
if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).id === ability) { if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) {
return true; return true;
} }
if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().id === ability) { if (this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true))) {
return true; return true;
} }
return false; return false;
@ -2684,7 +2684,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
apply(source: Pokemon, move: Move): HitResult { apply(source: Pokemon, move: Move): HitResult {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (move.category === MoveCategory.STATUS) { const moveCategory = new Utils.NumberHolder(move.category);
applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, moveCategory);
if (moveCategory.value === MoveCategory.STATUS) {
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
const typeMultiplier = this.getMoveEffectiveness(source, move, false, false, cancelled); const typeMultiplier = this.getMoveEffectiveness(source, move, false, false, cancelled);

View File

@ -3084,11 +3084,12 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
* Steals an item from a set of target Pokemon. * Steals an item from a set of target Pokemon.
* This prioritizes high-tier held items when selecting the item to steal. * This prioritizes high-tier held items when selecting the item to steal.
* @param pokemon The {@linkcode Pokemon} holding this item * @param pokemon The {@linkcode Pokemon} holding this item
* @param target The {@linkcode Pokemon} to steal from (optional)
* @param _args N/A * @param _args N/A
* @returns `true` if an item was stolen; false otherwise. * @returns `true` if an item was stolen; false otherwise.
*/ */
override apply(pokemon: Pokemon, ..._args: unknown[]): boolean { override apply(pokemon: Pokemon, target?: Pokemon, ..._args: unknown[]): boolean {
const opponents = this.getTargets(pokemon); const opponents = this.getTargets(pokemon, target);
if (!opponents.length) { if (!opponents.length) {
return false; return false;
@ -3187,7 +3188,7 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier {
* @see {@linkcode HeldItemTransferModifier} * @see {@linkcode HeldItemTransferModifier}
*/ */
export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModifier { export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModifier {
private chance: number; public readonly chance: number;
constructor(type: ModifierType, pokemonId: number, chancePercent: number, stackCount?: number) { constructor(type: ModifierType, pokemonId: number, chancePercent: number, stackCount?: number) {
super(type, pokemonId, stackCount); super(type, pokemonId, stackCount);

View File

@ -8,10 +8,6 @@ import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms";
import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
import { getTerrainBlockMessage } from "#app/data/weather"; import { getTerrainBlockMessage } from "#app/data/weather";
import { Abilities } from "#app/enums/abilities";
import { BattlerTagType } from "#app/enums/battler-tag-type";
import { Moves } from "#app/enums/moves";
import { StatusEffect } from "#app/enums/status-effect";
import { MoveUsedEvent } from "#app/events/battle-scene"; import { MoveUsedEvent } from "#app/events/battle-scene";
import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon"; import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
@ -20,7 +16,11 @@ import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import * as Utils from "#app/utils"; import { BooleanHolder, NumberHolder } from "#app/utils";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next"; import i18next from "i18next";
export class MovePhase extends BattlePhase { export class MovePhase extends BattlePhase {
@ -89,7 +89,7 @@ export class MovePhase extends BattlePhase {
this.cancelled = true; this.cancelled = true;
} }
public start() { public start(): void {
super.start(); super.start();
console.log(Moves[this.move.moveId]); console.log(Moves[this.move.moveId]);
@ -140,7 +140,7 @@ export class MovePhase extends BattlePhase {
} }
/** Check for cancellation edge cases - no targets remaining, or {@linkcode Moves.NONE} is in the queue */ /** Check for cancellation edge cases - no targets remaining, or {@linkcode Moves.NONE} is in the queue */
protected resolveFinalPreMoveCancellationChecks() { protected resolveFinalPreMoveCancellationChecks(): void {
const targets = this.getActiveTargetPokemon(); const targets = this.getActiveTargetPokemon();
const moveQueue = this.pokemon.getMoveQueue(); const moveQueue = this.pokemon.getMoveQueue();
@ -150,14 +150,14 @@ export class MovePhase extends BattlePhase {
} }
} }
public getActiveTargetPokemon() { public getActiveTargetPokemon(): Pokemon[] {
return this.scene.getField(true).filter(p => this.targets.includes(p.getBattlerIndex())); return this.scene.getField(true).filter(p => this.targets.includes(p.getBattlerIndex()));
} }
/** /**
* Handles {@link StatusEffect.SLEEP Sleep}/{@link StatusEffect.PARALYSIS Paralysis}/{@link StatusEffect.FREEZE Freeze} rolls and side effects. * Handles {@link StatusEffect.SLEEP Sleep}/{@link StatusEffect.PARALYSIS Paralysis}/{@link StatusEffect.FREEZE Freeze} rolls and side effects.
*/ */
protected resolvePreMoveStatusEffects() { protected resolvePreMoveStatusEffects(): void {
if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) { if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) {
this.pokemon.status.incrementTurn(); this.pokemon.status.incrementTurn();
let activated = false; let activated = false;
@ -198,7 +198,7 @@ export class MovePhase extends BattlePhase {
* Lapse {@linkcode BattlerTagLapseType.PRE_MOVE PRE_MOVE} tags that trigger before a move is used, regardless of whether or not it failed. * Lapse {@linkcode BattlerTagLapseType.PRE_MOVE PRE_MOVE} tags that trigger before a move is used, regardless of whether or not it failed.
* Also lapse {@linkcode BattlerTagLapseType.MOVE MOVE} tags if the move should be successful. * Also lapse {@linkcode BattlerTagLapseType.MOVE MOVE} tags if the move should be successful.
*/ */
protected lapsePreMoveAndMoveTags() { protected lapsePreMoveAndMoveTags(): void {
this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE); this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE);
// TODO: does this intentionally happen before the no targets/Moves.NONE on queue cancellation case is checked? // TODO: does this intentionally happen before the no targets/Moves.NONE on queue cancellation case is checked?
@ -207,7 +207,7 @@ export class MovePhase extends BattlePhase {
} }
} }
protected useMove() { protected useMove(): void {
const targets = this.getActiveTargetPokemon(); const targets = this.getActiveTargetPokemon();
const moveQueue = this.pokemon.getMoveQueue(); const moveQueue = this.pokemon.getMoveQueue();
@ -217,7 +217,8 @@ export class MovePhase extends BattlePhase {
this.showMoveText(); this.showMoveText();
// TODO: Clean up implementation of two-turn moves. // TODO: Clean up implementation of two-turn moves.
if (moveQueue.length > 0) { // Using .shift here clears out two turn moves once they've been used if (moveQueue.length > 0) {
// Using .shift here clears out two turn moves once they've been used
this.ignorePp = moveQueue.shift()?.ignorePP ?? false; this.ignorePp = moveQueue.shift()?.ignorePP ?? false;
} }
@ -226,7 +227,7 @@ export class MovePhase extends BattlePhase {
const ppUsed = 1 + this.getPpIncreaseFromPressure(targets); const ppUsed = 1 + this.getPpIncreaseFromPressure(targets);
this.move.usePp(ppUsed); this.move.usePp(ppUsed);
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed)); this.scene.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. // Update the battle's "last move" pointer, unless we're currently mimicking a move.
@ -275,7 +276,7 @@ export class MovePhase extends BattlePhase {
this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual }); this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual });
let failedText: string | undefined; let failedText: string | undefined;
const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new Utils.BooleanHolder(false)); const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new BooleanHolder(false));
if (failureMessage) { if (failureMessage) {
failedText = failureMessage; failedText = failureMessage;
@ -299,7 +300,7 @@ export class MovePhase extends BattlePhase {
* Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`, * Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`,
* then ends the phase. * then ends the phase.
*/ */
public end() { public end(): void {
if (!this.followUp && this.canMove()) { if (!this.followUp && this.canMove()) {
this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex())); this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex()));
} }
@ -313,7 +314,7 @@ export class MovePhase extends BattlePhase {
* *
* TODO: This hardcodes the PP increase at 1 per opponent, rather than deferring to the ability. * TODO: This hardcodes the PP increase at 1 per opponent, rather than deferring to the ability.
*/ */
public getPpIncreaseFromPressure(targets: Pokemon[]) { public getPpIncreaseFromPressure(targets: Pokemon[]): number {
const foesWithPressure = this.pokemon.getOpponents().filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr)); const foesWithPressure = this.pokemon.getOpponents().filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr));
return foesWithPressure.length; return foesWithPressure.length;
} }
@ -323,10 +324,10 @@ export class MovePhase extends BattlePhase {
* - Move redirection abilities, effects, etc. * - Move redirection abilities, effects, etc.
* - Counterattacks, which pass a special value into the `targets` constructor param (`[`{@linkcode BattlerIndex.ATTACKER}`]`). * - Counterattacks, which pass a special value into the `targets` constructor param (`[`{@linkcode BattlerIndex.ATTACKER}`]`).
*/ */
protected resolveRedirectTarget() { protected resolveRedirectTarget(): void {
if (this.targets.length === 1) { if (this.targets.length === 1) {
const currentTarget = this.targets[0]; const currentTarget = this.targets[0];
const redirectTarget = new Utils.NumberHolder(currentTarget); const redirectTarget = new NumberHolder(currentTarget);
// check move redirection abilities of every pokemon *except* the user. // check move redirection abilities of every pokemon *except* the user.
this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget)); this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget));
@ -372,7 +373,7 @@ export class MovePhase extends BattlePhase {
* If there is no last attacker, or they are no longer on the field, a message is displayed and the * If there is no last attacker, or they are no longer on the field, a message is displayed and the
* move is marked for failure. * move is marked for failure.
*/ */
protected resolveCounterAttackTarget() { protected resolveCounterAttackTarget(): void {
if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) { if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) {
if (this.pokemon.turnData.attacksReceived.length) { if (this.pokemon.turnData.attacksReceived.length) {
this.targets[0] = this.pokemon.turnData.attacksReceived[0].sourceBattlerIndex; this.targets[0] = this.pokemon.turnData.attacksReceived[0].sourceBattlerIndex;
@ -411,7 +412,7 @@ export class MovePhase extends BattlePhase {
* *
* TODO: handle charge moves more gracefully * TODO: handle charge moves more gracefully
*/ */
protected handlePreMoveFailures() { protected handlePreMoveFailures(): void {
if (this.cancelled || this.failed) { if (this.cancelled || this.failed) {
if (this.failed) { if (this.failed) {
const ppUsed = this.ignorePp ? 0 : 1; const ppUsed = this.ignorePp ? 0 : 1;

View File

@ -196,7 +196,7 @@ export class TitlePhase extends Phase {
this.scene.gameMode = getGameMode(GameModes.DAILY); this.scene.gameMode = getGameMode(GameModes.DAILY);
this.scene.setSeed(seed); this.scene.setSeed(seed);
this.scene.resetSeed(1); this.scene.resetSeed(0);
this.scene.money = this.scene.gameMode.getStartingMoney(); this.scene.money = this.scene.gameMode.getStartingMoney();

View File

@ -158,7 +158,7 @@ export class TurnStartPhase extends FieldPhase {
if (!queuedMove) { if (!queuedMove) {
continue; continue;
} }
const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move);
if (move.getMove().hasAttr(MoveHeaderAttr)) { if (move.getMove().hasAttr(MoveHeaderAttr)) {
this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move));
} }

View File

@ -1,13 +1,13 @@
import { BattleStyle } from "#app/enums/battle-style"; import { BattleStyle } from "#app/enums/battle-style";
import { CommandPhase } from "#app/phases/command-phase"; import { CommandPhase } from "#app/phases/command-phase";
import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase";
import i18next, { initI18n } from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Ability Timing", () => { describe("Ability Timing", () => {
@ -32,11 +32,10 @@ describe("Ability Timing", () => {
.enemySpecies(Species.MAGIKARP) .enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.INTIMIDATE) .enemyAbility(Abilities.INTIMIDATE)
.ability(Abilities.BALL_FETCH); .ability(Abilities.BALL_FETCH);
vi.spyOn(i18next, "t");
}); });
it("should trigger after switch check", async () => { it("should trigger after switch check", async () => {
initI18n();
i18next.changeLanguage("en");
game.settings.battleStyle = BattleStyle.SWITCH; game.settings.battleStyle = BattleStyle.SWITCH;
await game.classicMode.runToSummon([ Species.EEVEE, Species.FEEBAS ]); await game.classicMode.runToSummon([ Species.EEVEE, Species.FEEBAS ]);
@ -46,7 +45,6 @@ describe("Ability Timing", () => {
}, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)); }, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase));
await game.phaseInterceptor.to("MessagePhase"); await game.phaseInterceptor.to("MessagePhase");
const message = game.textInterceptor.getLatestMessage(); expect(i18next.t).toHaveBeenCalledWith("battle:statFell", expect.objectContaining({ count: 1 }));
expect(message).toContain("battle:statFell");
}, 5000); }, 5000);
}); });

View File

@ -1,17 +1,19 @@
import { allMoves, MultiHitAttr, MultiHitType } from "#app/data/move";
import { Status, StatusEffect } from "#app/data/status-effect"; import { Status, StatusEffect } from "#app/data/status-effect";
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Abilities - BATTLE BOND", () => { describe("Abilities - BATTLE BOND", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
let game: GameManager; let game: GameManager;
const baseForm = 1;
const ashForm = 2;
beforeAll(() => { beforeAll(() => {
phaserGame = new Phaser.Game({ phaserGame = new Phaser.Game({
type: Phaser.HEADLESS, type: Phaser.HEADLESS,
@ -24,40 +26,68 @@ describe("Abilities - BATTLE BOND", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
const moveToUse = Moves.SPLASH; game.override.battleType("single")
game.override.battleType("single"); .startingWave(4) // Leads to arena reset on Wave 5 trainer battle
game.override.ability(Abilities.BATTLE_BOND); .ability(Abilities.BATTLE_BOND)
game.override.moveset([ moveToUse ]); .starterForms({ [Species.GRENINJA]: ashForm, })
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); .moveset([ Moves.SPLASH, Moves.WATER_SHURIKEN ])
.enemySpecies(Species.BULBASAUR)
.enemyMoveset(Moves.SPLASH)
.startingLevel(100) // Avoid levelling up
.enemyLevel(1000); // Avoid opponent dying before `doKillOpponents()`
}); });
test( it("check if fainted pokemon switches to base form on arena reset", async () => {
"check if fainted pokemon switches to base form on arena reset", await game.classicMode.startBattle([ Species.MAGIKARP, Species.GRENINJA ]);
async () => {
const baseForm = 1;
const ashForm = 2;
game.override.startingWave(4);
game.override.starterForms({
[Species.GRENINJA]: ashForm,
});
await game.startBattle([ Species.MAGIKARP, Species.GRENINJA ]); const greninja = game.scene.getParty()[1];
expect(greninja.formIndex).toBe(ashForm);
const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA); greninja.hp = 0;
expect(greninja).toBeDefined(); greninja.status = new Status(StatusEffect.FAINT);
expect(greninja!.formIndex).toBe(ashForm); expect(greninja.isFainted()).toBe(true);
greninja!.hp = 0; game.move.select(Moves.SPLASH);
greninja!.status = new Status(StatusEffect.FAINT); await game.doKillOpponents();
expect(greninja!.isFainted()).toBe(true); await game.phaseInterceptor.to("TurnEndPhase");
game.doSelectModifier();
await game.phaseInterceptor.to("QuietFormChangePhase");
game.move.select(Moves.SPLASH); expect(greninja.formIndex).toBe(baseForm);
await game.doKillOpponents(); });
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(greninja!.formIndex).toBe(baseForm); it("should not keep buffing Water Shuriken after Greninja switches to base form", async () => {
}, await game.classicMode.startBattle([ Species.GRENINJA ]);
);
const waterShuriken = allMoves[Moves.WATER_SHURIKEN];
vi.spyOn(waterShuriken, "calculateBattlePower");
let actualMultiHitType: MultiHitType | null = null;
const multiHitAttr = waterShuriken.getAttrs(MultiHitAttr)[0];
vi.spyOn(multiHitAttr, "getHitCount").mockImplementation(() => {
actualMultiHitType = multiHitAttr.getMultiHitType();
return 3;
});
// Wave 4: Use Water Shuriken in Ash form
let expectedBattlePower = 20;
let expectedMultiHitType = MultiHitType._3;
game.move.select(Moves.WATER_SHURIKEN);
await game.phaseInterceptor.to("BerryPhase", false);
expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower);
expect(actualMultiHitType).toBe(expectedMultiHitType);
await game.doKillOpponents();
await game.toNextWave();
// Wave 5: Use Water Shuriken in base form
expectedBattlePower = 15;
expectedMultiHitType = MultiHitType._2_TO_5;
game.move.select(Moves.WATER_SHURIKEN);
await game.phaseInterceptor.to("BerryPhase", false);
expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower);
expect(actualMultiHitType).toBe(expectedMultiHitType);
});
}); });

View File

@ -1,8 +1,9 @@
import { BattlerIndex } from "#app/battle";
import { StatusEffect } from "#app/data/status-effect";
import { toDmgValue } from "#app/utils"; import { toDmgValue } from "#app/utils";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { StatusEffect } from "#app/data/status-effect";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@ -222,4 +223,17 @@ describe("Abilities - Disguise", () => {
expect(mimikyu.formIndex).toBe(bustedForm); expect(mimikyu.formIndex).toBe(bustedForm);
expect(mimikyu.hp).toBe(maxHp - disguiseDamage); expect(mimikyu.hp).toBe(maxHp - disguiseDamage);
}); });
it("doesn't trigger if user is behind a substitute", async () => {
game.override
.enemyMoveset(Moves.SUBSTITUTE)
.moveset(Moves.POWER_TRIP);
await game.classicMode.startBattle();
game.move.select(Moves.POWER_TRIP);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(game.scene.getEnemyPokemon()!.formIndex).toBe(disguisedForm);
});
}); });

View File

@ -1,13 +1,14 @@
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerIndex } from "#app/battle";
import { StatusEffect } from "#enums/status-effect";
import Pokemon from "#app/field/pokemon"; import Pokemon from "#app/field/pokemon";
import GameManager from "#test/utils/gameManager";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { Stat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { Stat } from "#enums/stat";
describe("Abilities - Gulp Missile", () => { describe("Abilities - Gulp Missile", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -40,8 +41,9 @@ describe("Abilities - Gulp Missile", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.disableCrits()
.battleType("single") .battleType("single")
.moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH ]) .moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH, Moves.SUBSTITUTE ])
.enemySpecies(Species.SNORLAX) .enemySpecies(Species.SNORLAX)
.enemyAbility(Abilities.BALL_FETCH) .enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH) .enemyMoveset(Moves.SPLASH)
@ -234,6 +236,25 @@ describe("Abilities - Gulp Missile", () => {
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(-1); expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(-1);
}); });
it("doesn't trigger if user is behind a substitute", async () => {
game.override
.enemyAbility(Abilities.STURDY)
.enemyMoveset([ Moves.SPLASH, Moves.POWER_TRIP ]);
await game.classicMode.startBattle([ Species.CRAMORANT ]);
game.move.select(Moves.SURF);
await game.forceEnemyMove(Moves.SPLASH);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM);
game.move.select(Moves.SUBSTITUTE);
await game.forceEnemyMove(Moves.POWER_TRIP);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM);
});
it("cannot be suppressed", async () => { it("cannot be suppressed", async () => {
game.override.enemyMoveset(Moves.GASTRO_ACID); game.override.enemyMoveset(Moves.GASTRO_ACID);

View File

@ -1,3 +1,4 @@
import { BattlerIndex } from "#app/battle";
import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase";
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
@ -36,7 +37,7 @@ describe("Abilities - Ice Face", () => {
}); });
it("takes no damage from physical move and transforms to Noice", async () => { it("takes no damage from physical move and transforms to Noice", async () => {
await game.startBattle([ Species.HITMONLEE ]); await game.classicMode.startBattle([ Species.HITMONLEE ]);
game.move.select(Moves.TACKLE); game.move.select(Moves.TACKLE);
@ -52,7 +53,7 @@ describe("Abilities - Ice Face", () => {
it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => { it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => {
game.override.moveset([ Moves.SURGING_STRIKES ]); game.override.moveset([ Moves.SURGING_STRIKES ]);
game.override.enemyLevel(1); game.override.enemyLevel(1);
await game.startBattle([ Species.HITMONLEE ]); await game.classicMode.startBattle([ Species.HITMONLEE ]);
game.move.select(Moves.SURGING_STRIKES); game.move.select(Moves.SURGING_STRIKES);
@ -78,7 +79,7 @@ describe("Abilities - Ice Face", () => {
}); });
it("takes damage from special moves", async () => { it("takes damage from special moves", async () => {
await game.startBattle([ Species.MAGIKARP ]); await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.ICE_BEAM); game.move.select(Moves.ICE_BEAM);
@ -92,7 +93,7 @@ describe("Abilities - Ice Face", () => {
}); });
it("takes effects from status moves", async () => { it("takes effects from status moves", async () => {
await game.startBattle([ Species.MAGIKARP ]); await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.TOXIC_THREAD); game.move.select(Moves.TOXIC_THREAD);
@ -108,7 +109,7 @@ describe("Abilities - Ice Face", () => {
game.override.moveset([ Moves.QUICK_ATTACK ]); game.override.moveset([ Moves.QUICK_ATTACK ]);
game.override.enemyMoveset([ Moves.HAIL, Moves.HAIL, Moves.HAIL, Moves.HAIL ]); game.override.enemyMoveset([ Moves.HAIL, Moves.HAIL, Moves.HAIL, Moves.HAIL ]);
await game.startBattle([ Species.MAGIKARP ]); await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.QUICK_ATTACK); game.move.select(Moves.QUICK_ATTACK);
@ -130,7 +131,7 @@ describe("Abilities - Ice Face", () => {
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
game.override.moveset([ Moves.SNOWSCAPE ]); game.override.moveset([ Moves.SNOWSCAPE ]);
await game.startBattle([ Species.EISCUE, Species.NINJASK ]); await game.classicMode.startBattle([ Species.EISCUE, Species.NINJASK ]);
game.move.select(Moves.SNOWSCAPE); game.move.select(Moves.SNOWSCAPE);
@ -157,7 +158,7 @@ describe("Abilities - Ice Face", () => {
game.override.enemySpecies(Species.SHUCKLE); game.override.enemySpecies(Species.SHUCKLE);
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
await game.startBattle([ Species.EISCUE ]); await game.classicMode.startBattle([ Species.EISCUE ]);
game.move.select(Moves.HAIL); game.move.select(Moves.HAIL);
const eiscue = game.scene.getPlayerPokemon()!; const eiscue = game.scene.getPlayerPokemon()!;
@ -176,7 +177,7 @@ describe("Abilities - Ice Face", () => {
it("persists form change when switched out", async () => { it("persists form change when switched out", async () => {
game.override.enemyMoveset([ Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK ]); game.override.enemyMoveset([ Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK ]);
await game.startBattle([ Species.EISCUE, Species.MAGIKARP ]); await game.classicMode.startBattle([ Species.EISCUE, Species.MAGIKARP ]);
game.move.select(Moves.ICE_BEAM); game.move.select(Moves.ICE_BEAM);
@ -205,7 +206,7 @@ describe("Abilities - Ice Face", () => {
[Species.EISCUE]: noiceForm, [Species.EISCUE]: noiceForm,
}); });
await game.startBattle([ Species.EISCUE ]); await game.classicMode.startBattle([ Species.EISCUE ]);
const eiscue = game.scene.getPlayerPokemon()!; const eiscue = game.scene.getPlayerPokemon()!;
@ -222,10 +223,23 @@ describe("Abilities - Ice Face", () => {
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
}); });
it("doesn't trigger if user is behind a substitute", async () => {
game.override
.enemyMoveset(Moves.SUBSTITUTE)
.moveset(Moves.POWER_TRIP);
await game.classicMode.startBattle();
game.move.select(Moves.POWER_TRIP);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(game.scene.getEnemyPokemon()!.formIndex).toBe(icefaceForm);
});
it("cannot be suppressed", async () => { it("cannot be suppressed", async () => {
game.override.moveset([ Moves.GASTRO_ACID ]); game.override.moveset([ Moves.GASTRO_ACID ]);
await game.startBattle([ Species.MAGIKARP ]); await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.GASTRO_ACID); game.move.select(Moves.GASTRO_ACID);
@ -241,7 +255,7 @@ describe("Abilities - Ice Face", () => {
it("cannot be swapped with another ability", async () => { it("cannot be swapped with another ability", async () => {
game.override.moveset([ Moves.SKILL_SWAP ]); game.override.moveset([ Moves.SKILL_SWAP ]);
await game.startBattle([ Species.MAGIKARP ]); await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.SKILL_SWAP); game.move.select(Moves.SKILL_SWAP);
@ -257,7 +271,7 @@ describe("Abilities - Ice Face", () => {
it("cannot be copied", async () => { it("cannot be copied", async () => {
game.override.ability(Abilities.TRACE); game.override.ability(Abilities.TRACE);
await game.startBattle([ Species.MAGIKARP ]); await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.SIMPLE_BEAM); game.move.select(Moves.SIMPLE_BEAM);

View File

@ -9,6 +9,7 @@ import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { allMoves } from "#app/data/move";
describe("Abilities - Sheer Force", () => { describe("Abilities - Sheer Force", () => {
@ -174,5 +175,31 @@ describe("Abilities - Sheer Force", () => {
}, 20000); }, 20000);
it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => {
const moveToUse = Moves.CRUNCH;
game.override.enemyAbility(Abilities.COLOR_CHANGE)
.ability(Abilities.COLOR_CHANGE)
.moveset(moveToUse)
.enemyMoveset(moveToUse);
await game.classicMode.startBattle([
Species.PIDGEOT
]);
const pidgeot = game.scene.getParty()[0];
const onix = game.scene.getEnemyParty()[0];
pidgeot.stats[Stat.DEF] = 10000;
onix.stats[Stat.DEF] = 10000;
game.move.select(moveToUse);
await game.toNextTurn();
// Check that both Pokemon's Color Change activated
const expectedTypes = [ allMoves[moveToUse].type ];
expect(pidgeot.getTypes()).toStrictEqual(expectedTypes);
expect(onix.getTypes()).toStrictEqual(expectedTypes);
});
//TODO King's Rock Interaction Unit Test //TODO King's Rock Interaction Unit Test
}); });

View File

@ -55,7 +55,7 @@ describe("Egg Generation Tests", () => {
let gachaSpeciesCount = 0; let gachaSpeciesCount = 0;
for (let i = 0; i < EGG_HATCH_COUNT; i++) { for (let i = 0; i < EGG_HATCH_COUNT; i++) {
const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.MASTER }).generatePlayerPokemon(scene).species.speciesId; const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.LEGENDARY }).generatePlayerPokemon(scene).species.speciesId;
if (result === expectedSpecies) { if (result === expectedSpecies) {
gachaSpeciesCount++; gachaSpeciesCount++;
} }
@ -82,7 +82,7 @@ describe("Egg Generation Tests", () => {
}); });
it("should return an rare tier egg", () => { it("should return an rare tier egg", () => {
const scene = game.scene; const scene = game.scene;
const expectedTier = EggTier.GREAT; const expectedTier = EggTier.RARE;
const result = new Egg({ scene, tier: expectedTier }).tier; const result = new Egg({ scene, tier: expectedTier }).tier;
@ -90,7 +90,7 @@ describe("Egg Generation Tests", () => {
}); });
it("should return an epic tier egg", () => { it("should return an epic tier egg", () => {
const scene = game.scene; const scene = game.scene;
const expectedTier = EggTier.ULTRA; const expectedTier = EggTier.EPIC;
const result = new Egg({ scene, tier: expectedTier }).tier; const result = new Egg({ scene, tier: expectedTier }).tier;
@ -98,7 +98,7 @@ describe("Egg Generation Tests", () => {
}); });
it("should return an legendary tier egg", () => { it("should return an legendary tier egg", () => {
const scene = game.scene; const scene = game.scene;
const expectedTier = EggTier.MASTER; const expectedTier = EggTier.LEGENDARY;
const result = new Egg({ scene, tier: expectedTier }).tier; const result = new Egg({ scene, tier: expectedTier }).tier;
@ -200,7 +200,7 @@ describe("Egg Generation Tests", () => {
const scene = game.scene; const scene = game.scene;
const expectedEggTier = EggTier.COMMON; const expectedEggTier = EggTier.COMMON;
const result = new Egg({ scene, tier: EggTier.MASTER, species: Species.BULBASAUR }).tier; const result = new Egg({ scene, tier: EggTier.LEGENDARY, species: Species.BULBASAUR }).tier;
expect(result).toBe(expectedEggTier); expect(result).toBe(expectedEggTier);
}); });
@ -208,7 +208,7 @@ describe("Egg Generation Tests", () => {
const scene = game.scene; const scene = game.scene;
const expectedHatchWaves = 10; const expectedHatchWaves = 10;
const result = new Egg({ scene, tier: EggTier.MASTER, species: Species.BULBASAUR }).hatchWaves; const result = new Egg({ scene, tier: EggTier.LEGENDARY, species: Species.BULBASAUR }).hatchWaves;
expect(result).toBe(expectedHatchWaves); expect(result).toBe(expectedHatchWaves);
}); });
@ -229,7 +229,7 @@ describe("Egg Generation Tests", () => {
const result = new EggData(legacyEgg).toEgg(); const result = new EggData(legacyEgg).toEgg();
expect(result.tier).toBe(EggTier.GREAT); expect(result.tier).toBe(EggTier.RARE);
expect(result.id).toBe(legacyEgg.id); expect(result.id).toBe(legacyEgg.id);
expect(result.timestamp).toBe(legacyEgg.timestamp); expect(result.timestamp).toBe(legacyEgg.timestamp);
expect(result.hatchWaves).toBe(legacyEgg.hatchWaves); expect(result.hatchWaves).toBe(legacyEgg.hatchWaves);
@ -241,9 +241,9 @@ describe("Egg Generation Tests", () => {
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.COMMON }); new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.COMMON });
expect(scene.gameData.eggPity[EggTier.GREAT]).toBe(startPityValues[EggTier.GREAT] + 1); expect(scene.gameData.eggPity[EggTier.RARE]).toBe(startPityValues[EggTier.RARE] + 1);
expect(scene.gameData.eggPity[EggTier.ULTRA]).toBe(startPityValues[EggTier.ULTRA] + 1); expect(scene.gameData.eggPity[EggTier.EPIC]).toBe(startPityValues[EggTier.EPIC] + 1);
expect(scene.gameData.eggPity[EggTier.MASTER]).toBe(startPityValues[EggTier.MASTER] + 1); expect(scene.gameData.eggPity[EggTier.LEGENDARY]).toBe(startPityValues[EggTier.LEGENDARY] + 1);
}); });
it("should increase legendary egg pity by two", () => { it("should increase legendary egg pity by two", () => {
const scene = game.scene; const scene = game.scene;
@ -251,9 +251,9 @@ describe("Egg Generation Tests", () => {
new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true, tier: EggTier.COMMON }); new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true, tier: EggTier.COMMON });
expect(scene.gameData.eggPity[EggTier.GREAT]).toBe(startPityValues[EggTier.GREAT] + 1); expect(scene.gameData.eggPity[EggTier.RARE]).toBe(startPityValues[EggTier.RARE] + 1);
expect(scene.gameData.eggPity[EggTier.ULTRA]).toBe(startPityValues[EggTier.ULTRA] + 1); expect(scene.gameData.eggPity[EggTier.EPIC]).toBe(startPityValues[EggTier.EPIC] + 1);
expect(scene.gameData.eggPity[EggTier.MASTER]).toBe(startPityValues[EggTier.MASTER] + 2); expect(scene.gameData.eggPity[EggTier.LEGENDARY]).toBe(startPityValues[EggTier.LEGENDARY] + 2);
}); });
it("should not increase manaphy egg count if bulbasaurs are pulled", () => { it("should not increase manaphy egg count if bulbasaurs are pulled", () => {
const scene = game.scene; const scene = game.scene;
@ -277,7 +277,7 @@ describe("Egg Generation Tests", () => {
const scene = game.scene; const scene = game.scene;
const startingRareEggsPulled = scene.gameData.gameStats.rareEggsPulled; const startingRareEggsPulled = scene.gameData.gameStats.rareEggsPulled;
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.GREAT }); new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.RARE });
expect(scene.gameData.gameStats.rareEggsPulled).toBe(startingRareEggsPulled + 1); expect(scene.gameData.gameStats.rareEggsPulled).toBe(startingRareEggsPulled + 1);
}); });
@ -285,7 +285,7 @@ describe("Egg Generation Tests", () => {
const scene = game.scene; const scene = game.scene;
const startingEpicEggsPulled = scene.gameData.gameStats.epicEggsPulled; const startingEpicEggsPulled = scene.gameData.gameStats.epicEggsPulled;
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.ULTRA }); new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.EPIC });
expect(scene.gameData.gameStats.epicEggsPulled).toBe(startingEpicEggsPulled + 1); expect(scene.gameData.gameStats.epicEggsPulled).toBe(startingEpicEggsPulled + 1);
}); });
@ -293,7 +293,7 @@ describe("Egg Generation Tests", () => {
const scene = game.scene; const scene = game.scene;
const startingLegendaryEggsPulled = scene.gameData.gameStats.legendaryEggsPulled; const startingLegendaryEggsPulled = scene.gameData.gameStats.legendaryEggsPulled;
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.MASTER }); new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.LEGENDARY });
expect(scene.gameData.gameStats.legendaryEggsPulled).toBe(startingLegendaryEggsPulled + 1); expect(scene.gameData.gameStats.legendaryEggsPulled).toBe(startingLegendaryEggsPulled + 1);
}); });
@ -301,8 +301,8 @@ describe("Egg Generation Tests", () => {
vi.spyOn(Utils, "randInt").mockReturnValue(1); vi.spyOn(Utils, "randInt").mockReturnValue(1);
const scene = game.scene; const scene = game.scene;
const expectedTier1 = EggTier.MASTER; const expectedTier1 = EggTier.LEGENDARY;
const expectedTier2 = EggTier.ULTRA; const expectedTier2 = EggTier.EPIC;
const result1 = new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true }).tier; const result1 = new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true }).tier;
const result2 = new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true }).tier; const result2 = new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true }).tier;

View File

@ -1,16 +1,14 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { allMoves } from "#app/data/move"; import Pokemon from "#app/field/pokemon";
import { Abilities } from "#app/enums/abilities"; import { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier";
import { BerryType } from "#app/enums/berry-type"; import { Abilities } from "#enums/abilities";
import { Moves } from "#app/enums/moves"; import { BerryType } from "#enums/berry-type";
import { Species } from "#app/enums/species"; import { Moves } from "#enums/moves";
import { MoveEndPhase } from "#app/phases/move-end-phase"; import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phase from "phaser"; import Phase from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
// 20 seconds
describe("Items - Grip Claw", () => { describe("Items - Grip Claw", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
let game: GameManager; let game: GameManager;
@ -30,39 +28,85 @@ describe("Items - Grip Claw", () => {
game.override game.override
.battleType("double") .battleType("double")
.moveset([ Moves.POPULATION_BOMB, Moves.SPLASH ]) .moveset([ Moves.TACKLE, Moves.SPLASH, Moves.ATTRACT ])
.startingHeldItems([ .startingHeldItems([
{ name: "GRIP_CLAW", count: 5 }, // TODO: Find a way to mock the steal chance of grip claw { name: "GRIP_CLAW", count: 1 },
{ name: "MULTI_LENS", count: 3 },
]) ])
.enemySpecies(Species.SNORLAX) .enemySpecies(Species.SNORLAX)
.ability(Abilities.KLUTZ) .enemyAbility(Abilities.UNNERVE)
.ability(Abilities.UNNERVE)
.enemyMoveset(Moves.SPLASH) .enemyMoveset(Moves.SPLASH)
.enemyHeldItems([ .enemyHeldItems([
{ name: "BERRY", type: BerryType.SITRUS, count: 2 }, { name: "BERRY", type: BerryType.SITRUS, count: 2 },
{ name: "BERRY", type: BerryType.LUM, count: 2 }, { name: "BERRY", type: BerryType.LUM, count: 2 },
]) ])
.startingLevel(100)
.enemyLevel(100); .enemyLevel(100);
vi.spyOn(allMoves[Moves.POPULATION_BOMB], "accuracy", "get").mockReturnValue(100);
}); });
it( it("should steal items on contact and only from the attack target", async () => {
"should only steal items from the attack target", await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]);
async () => {
await game.startBattle([ Species.PANSEAR, Species.ROWLET ]);
const enemyPokemon = game.scene.getEnemyField(); const [ playerPokemon, ] = game.scene.getPlayerField();
const enemyHeldItemCt = enemyPokemon.map(p => p.getHeldItems.length); const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier;
vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100);
game.move.select(Moves.POPULATION_BOMB, 0, BattlerIndex.ENEMY); const enemyPokemon = game.scene.getEnemyField();
game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to(MoveEndPhase, false); const playerHeldItemCount = getHeldItemCount(playerPokemon);
const enemy1HeldItemCount = getHeldItemCount(enemyPokemon[0]);
const enemy2HeldItemCount = getHeldItemCount(enemyPokemon[1]);
expect(enemy2HeldItemCount).toBeGreaterThan(0);
expect(enemyPokemon[1].getHeldItems.length).toBe(enemyHeldItemCt[1]); game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY_2);
} game.move.select(Moves.SPLASH, 1);
);
await game.phaseInterceptor.to("BerryPhase", false);
const playerHeldItemCountAfter = getHeldItemCount(playerPokemon);
const enemy1HeldItemCountsAfter = getHeldItemCount(enemyPokemon[0]);
const enemy2HeldItemCountsAfter = getHeldItemCount(enemyPokemon[1]);
expect(playerHeldItemCountAfter).toBe(playerHeldItemCount + 1);
expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount);
expect(enemy2HeldItemCountsAfter).toBe(enemy2HeldItemCount - 1);
});
it("should not steal items when using a targetted, non attack move", async () => {
await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]);
const [ playerPokemon, ] = game.scene.getPlayerField();
const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier;
vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100);
const enemyPokemon = game.scene.getEnemyField();
const playerHeldItemCount = getHeldItemCount(playerPokemon);
const enemy1HeldItemCount = getHeldItemCount(enemyPokemon[0]);
const enemy2HeldItemCount = getHeldItemCount(enemyPokemon[1]);
expect(enemy2HeldItemCount).toBeGreaterThan(0);
game.move.select(Moves.ATTRACT, 0, BattlerIndex.ENEMY_2);
game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to("BerryPhase", false);
const playerHeldItemCountAfter = getHeldItemCount(playerPokemon);
const enemy1HeldItemCountsAfter = getHeldItemCount(enemyPokemon[0]);
const enemy2HeldItemCountsAfter = getHeldItemCount(enemyPokemon[1]);
expect(playerHeldItemCountAfter).toBe(playerHeldItemCount);
expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount);
expect(enemy2HeldItemCountsAfter).toBe(enemy2HeldItemCount);
});
}); });
/*
* Gets the total number of items a Pokemon holds
*/
function getHeldItemCount(pokemon: Pokemon) {
return pokemon.getHeldItems().reduce((currentTotal, item) => currentTotal + item.getStackCount(), 0);
}

View File

@ -2,13 +2,13 @@ import { StatusEffect } from "#app/data/status-effect";
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
import { MessagePhase } from "#app/phases/message-phase"; import { MessagePhase } from "#app/phases/message-phase";
import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase";
import i18next, { initI18n } from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Items - Toxic orb", () => { describe("Items - Toxic orb", () => {
@ -39,11 +39,11 @@ describe("Items - Toxic orb", () => {
game.override.startingHeldItems([{ game.override.startingHeldItems([{
name: "TOXIC_ORB", name: "TOXIC_ORB",
}]); }]);
vi.spyOn(i18next, "t");
}); });
it("TOXIC ORB", async () => { it("TOXIC ORB", async () => {
initI18n();
i18next.changeLanguage("en");
const moveToUse = Moves.GROWTH; const moveToUse = Moves.GROWTH;
await game.startBattle([ await game.startBattle([
Species.MIGHTYENA, Species.MIGHTYENA,
@ -57,11 +57,10 @@ describe("Items - Toxic orb", () => {
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase); await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
// Toxic orb should trigger here // Toxic orb should trigger here
await game.phaseInterceptor.run(MessagePhase); await game.phaseInterceptor.run(MessagePhase);
const message = game.textInterceptor.getLatestMessage(); expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.obtainSource", expect.anything());
expect(message).toContain("statusEffect:toxic.obtainSource");
await game.phaseInterceptor.run(MessagePhase); await game.phaseInterceptor.run(MessagePhase);
const message2 = game.textInterceptor.getLatestMessage(); expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.activation", expect.anything());
expect(message2).toBe("statusEffect:toxic.activation");
expect(game.scene.getParty()[0].status!.effect).toBe(StatusEffect.TOXIC); expect(game.scene.getParty()[0].status!.effect).toBe(StatusEffect.TOXIC);
}, 20000); }, 20000);
}); });

View File

@ -1,6 +1,7 @@
import { Moves } from "#app/enums/moves";
import { Stat } from "#app/enums/stat";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { Stat } from "#enums/stat";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@ -22,13 +23,15 @@ describe("Moves - Obstruct", () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.battleType("single") .battleType("single")
.enemySpecies(Species.MAGIKARP)
.enemyMoveset(Moves.TACKLE)
.enemyAbility(Abilities.BALL_FETCH) .enemyAbility(Abilities.BALL_FETCH)
.ability(Abilities.BALL_FETCH) .ability(Abilities.BALL_FETCH)
.moveset([ Moves.OBSTRUCT ]); .moveset([ Moves.OBSTRUCT ])
.starterSpecies(Species.FEEBAS);
}); });
it("protects from contact damaging moves and lowers the opponent's defense by 2 stages", async () => { it("protects from contact damaging moves and lowers the opponent's defense by 2 stages", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH));
await game.classicMode.startBattle(); await game.classicMode.startBattle();
game.move.select(Moves.OBSTRUCT); game.move.select(Moves.OBSTRUCT);
@ -42,7 +45,6 @@ describe("Moves - Obstruct", () => {
}); });
it("bypasses accuracy checks when applying protection and defense reduction", async () => { it("bypasses accuracy checks when applying protection and defense reduction", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH));
await game.classicMode.startBattle(); await game.classicMode.startBattle();
game.move.select(Moves.OBSTRUCT); game.move.select(Moves.OBSTRUCT);
@ -59,7 +61,7 @@ describe("Moves - Obstruct", () => {
); );
it("protects from non-contact damaging moves and doesn't lower the opponent's defense by 2 stages", async () => { it("protects from non-contact damaging moves and doesn't lower the opponent's defense by 2 stages", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.WATER_GUN)); game.override.enemyMoveset(Moves.WATER_GUN);
await game.classicMode.startBattle(); await game.classicMode.startBattle();
game.move.select(Moves.OBSTRUCT); game.move.select(Moves.OBSTRUCT);
@ -73,7 +75,7 @@ describe("Moves - Obstruct", () => {
}); });
it("doesn't protect from status moves", async () => { it("doesn't protect from status moves", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.GROWL)); game.override.enemyMoveset(Moves.GROWL);
await game.classicMode.startBattle(); await game.classicMode.startBattle();
game.move.select(Moves.OBSTRUCT); game.move.select(Moves.OBSTRUCT);
@ -83,4 +85,14 @@ describe("Moves - Obstruct", () => {
expect(player.getStatStage(Stat.ATK)).toBe(-1); expect(player.getStatStage(Stat.ATK)).toBe(-1);
}); });
it("doesn't reduce the stats of an opponent with Clear Body/etc", async () => {
game.override.enemyAbility(Abilities.CLEAR_BODY);
await game.classicMode.startBattle();
game.move.select(Moves.OBSTRUCT);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(0);
});
}); });

View File

@ -0,0 +1,53 @@
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { MoveResult } from "#app/field/pokemon";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Sketch", () => {
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
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.enemySpecies(Species.SHUCKLE)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("Sketch should not fail even if a previous Sketch failed to retrieve a valid move and ran out of PP", async () => {
game.override.moveset([ Moves.SKETCH, Moves.SKETCH ]);
await game.classicMode.startBattle([ Species.REGIELEKI ]);
const playerPokemon = game.scene.getPlayerPokemon();
game.move.select(Moves.SKETCH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
const moveSlot0 = playerPokemon?.getMoveset()[0];
expect(moveSlot0?.moveId).toBe(Moves.SKETCH);
expect(moveSlot0?.getPpRatio()).toBe(0);
await game.toNextTurn();
game.move.select(Moves.SKETCH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
// Can't verify if the player Pokemon's moveset was successfully changed because of overrides.
});
});

View File

@ -1,4 +1,3 @@
import { allMoves } from "#app/data/move";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
@ -7,7 +6,7 @@ import { Stat } from "#enums/stat";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - SYRUP BOMB", () => { describe("Moves - SYRUP BOMB", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -26,20 +25,21 @@ describe("Moves - SYRUP BOMB", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.starterSpecies(Species.MAGIKARP) .battleType("single")
.enemySpecies(Species.SNORLAX) .enemySpecies(Species.SNORLAX)
.enemyAbility(Abilities.BALL_FETCH)
.ability(Abilities.BALL_FETCH)
.startingLevel(30) .startingLevel(30)
.enemyLevel(100) .enemyLevel(100)
.moveset([ Moves.SYRUP_BOMB, Moves.SPLASH ]) .moveset([ Moves.SYRUP_BOMB, Moves.SPLASH ])
.enemyMoveset(Moves.SPLASH); .enemyMoveset(Moves.SPLASH);
vi.spyOn(allMoves[Moves.SYRUP_BOMB], "accuracy", "get").mockReturnValue(100);
}); });
//Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/syrup_bomb_(move) //Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/syrup_bomb_(move)
it("decreases the target Pokemon's speed stat once per turn for 3 turns", it("decreases the target Pokemon's speed stat once per turn for 3 turns",
async () => { async () => {
await game.startBattle([ Species.MAGIKARP ]); await game.classicMode.startBattle([ Species.MAGIKARP ]);
const targetPokemon = game.scene.getEnemyPokemon()!; const targetPokemon = game.scene.getEnemyPokemon()!;
expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0); expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0);
@ -66,7 +66,7 @@ describe("Moves - SYRUP BOMB", () => {
it("does not affect Pokemon with the ability Bulletproof", it("does not affect Pokemon with the ability Bulletproof",
async () => { async () => {
game.override.enemyAbility(Abilities.BULLETPROOF); game.override.enemyAbility(Abilities.BULLETPROOF);
await game.startBattle([ Species.MAGIKARP ]); await game.classicMode.startBattle([ Species.MAGIKARP ]);
const targetPokemon = game.scene.getEnemyPokemon()!; const targetPokemon = game.scene.getEnemyPokemon()!;
@ -79,4 +79,18 @@ describe("Moves - SYRUP BOMB", () => {
expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0); expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0);
} }
); );
it("stops lowering the target's speed if the user leaves the field", async () => {
await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]);
game.move.select(Moves.SYRUP_BOMB);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.move.forceHit();
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.toNextTurn();
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPD)).toBe(-1);
});
}); });

View File

@ -16,6 +16,7 @@ import { EggTier } from "#enums/egg-type";
import { CommandPhase } from "#app/phases/command-phase"; import { CommandPhase } from "#app/phases/command-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase";
import i18next from "i18next";
const namespace = "mysteryEncounters/aTrainersTest"; const namespace = "mysteryEncounters/aTrainersTest";
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
@ -106,7 +107,8 @@ describe("A Trainer's Test - Mystery Encounter", () => {
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.trainer).toBeDefined();
expect([ "trainerNames:buck", "trainerNames:cheryl", "trainerNames:marley", "trainerNames:mira", "trainerNames:riley" ].includes(scene.currentBattle.trainer!.config.name)).toBeTruthy(); expect([ i18next.t("trainerNames:buck"), i18next.t("trainerNames:cheryl"), i18next.t("trainerNames:marley"), i18next.t("trainerNames:mira"), i18next.t("trainerNames:riley") ]
.map(name => name.toLowerCase()).includes(scene.currentBattle.trainer!.config.name)).toBeTruthy();
expect(enemyField[0]).toBeDefined(); expect(enemyField[0]).toBeDefined();
}); });
@ -126,7 +128,7 @@ describe("A Trainer's Test - Mystery Encounter", () => {
expect(eggsAfter).toBeDefined(); expect(eggsAfter).toBeDefined();
expect(eggsBeforeLength + 1).toBe(eggsAfter.length); expect(eggsBeforeLength + 1).toBe(eggsAfter.length);
const eggTier = eggsAfter[eggsAfter.length - 1].tier; const eggTier = eggsAfter[eggsAfter.length - 1].tier;
expect(eggTier === EggTier.ULTRA || eggTier === EggTier.MASTER).toBeTruthy(); expect(eggTier === EggTier.EPIC || eggTier === EggTier.LEGENDARY).toBeTruthy();
}); });
}); });
@ -174,7 +176,7 @@ describe("A Trainer's Test - Mystery Encounter", () => {
expect(eggsAfter).toBeDefined(); expect(eggsAfter).toBeDefined();
expect(eggsBeforeLength + 1).toBe(eggsAfter.length); expect(eggsBeforeLength + 1).toBe(eggsAfter.length);
const eggTier = eggsAfter[eggsAfter.length - 1].tier; const eggTier = eggsAfter[eggsAfter.length - 1].tier;
expect(eggTier).toBe(EggTier.GREAT); expect(eggTier).toBe(EggTier.RARE);
}); });
it("should leave encounter without battle", async () => { it("should leave encounter without battle", async () => {

View File

@ -16,6 +16,7 @@ import { Moves } from "#enums/moves";
import { CommandPhase } from "#app/phases/command-phase"; import { CommandPhase } from "#app/phases/command-phase";
import { MovePhase } from "#app/phases/move-phase"; import { MovePhase } from "#app/phases/move-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import i18next from "i18next";
const namespace = "mysteryEncounters/absoluteAvarice"; const namespace = "mysteryEncounters/absoluteAvarice";
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
@ -146,7 +147,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
const pokemonId = partyPokemon.id; const pokemonId = partyPokemon.id;
const pokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier const pokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).pokemonId === pokemonId, true) as PokemonHeldItemModifier[]; && (m as PokemonHeldItemModifier).pokemonId === pokemonId, true) as PokemonHeldItemModifier[];
const revSeed = pokemonItems.find(i => i.type.name === "modifierType:ModifierType.REVIVER_SEED.name"); const revSeed = pokemonItems.find(i => i.type.name === i18next.t("modifierType:ModifierType.REVIVER_SEED.name"));
expect(revSeed).toBeDefined; expect(revSeed).toBeDefined;
expect(revSeed?.stackCount).toBe(1); expect(revSeed?.stackCount).toBe(1);
} }

View File

@ -17,6 +17,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { ShinyRateBoosterModifier } from "#app/modifier/modifier"; import { ShinyRateBoosterModifier } from "#app/modifier/modifier";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import i18next from "i18next";
const namespace = "mysteryEncounters/anOfferYouCantRefuse"; const namespace = "mysteryEncounters/anOfferYouCantRefuse";
/** Gyarados for Indimidate */ /** Gyarados for Indimidate */
@ -93,8 +94,8 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.strongestPokemon).toBeDefined(); expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.strongestPokemon).toBeDefined();
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.price).toBeDefined(); expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.price).toBeDefined();
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.option2PrimaryAbility).toBe("ability:intimidate.name"); expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.option2PrimaryAbility).toBe(i18next.t("ability:intimidate.name"));
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe("ability:intimidate.name"); expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe(i18next.t("ability:intimidate.name"));
expect(AnOfferYouCantRefuseEncounter.misc.pokemon instanceof PlayerPokemon).toBeTruthy(); expect(AnOfferYouCantRefuseEncounter.misc.pokemon instanceof PlayerPokemon).toBeTruthy();
expect(AnOfferYouCantRefuseEncounter.misc?.price?.toString()).toBe(AnOfferYouCantRefuseEncounter.dialogueTokens?.price); expect(AnOfferYouCantRefuseEncounter.misc?.price?.toString()).toBe(AnOfferYouCantRefuseEncounter.dialogueTokens?.price);
expect(onInitResult).toBe(true); expect(onInitResult).toBe(true);

View File

@ -103,11 +103,11 @@ describe("Field Trip - Mystery Encounter", () => {
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(5); expect(modifierSelectHandler.options.length).toEqual(5);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_attack"); expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_attack"));
expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_defense"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_defense"));
expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed"); expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed"));
expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.DIRE_HIT.name"); expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.DIRE_HIT.name"));
expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name"); expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name"));
}); });
it("should leave encounter without battle", async () => { it("should leave encounter without battle", async () => {
@ -150,11 +150,11 @@ describe("Field Trip - Mystery Encounter", () => {
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(5); expect(modifierSelectHandler.options.length).toEqual(5);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_sp_atk"); expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_sp_atk"));
expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_sp_def"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_sp_def"));
expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed"); expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed"));
expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.DIRE_HIT.name"); expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.DIRE_HIT.name"));
expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name"); expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name"));
}); });
it("should leave encounter without battle", async () => { it("should leave encounter without battle", async () => {
@ -198,12 +198,12 @@ describe("Field Trip - Mystery Encounter", () => {
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(5); expect(modifierSelectHandler.options.length).toEqual(5);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_accuracy"); expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_accuracy"));
expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed"));
expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:ModifierType.AddPokeballModifierType.name"); expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", { modifierCount: 5, pokeballName: i18next.t("pokeball:greatBall") }));
expect(i18next.t).toHaveBeenCalledWith("modifierType:ModifierType.AddPokeballModifierType.name", expect.objectContaining({ modifierCount: 5 })); expect(i18next.t).toHaveBeenCalledWith(("modifierType:ModifierType.AddPokeballModifierType.name"), expect.objectContaining({ modifierCount: 5 }));
expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.IV_SCANNER.name"); expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.IV_SCANNER.name"));
expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name"); expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name"));
}); });
it("should leave encounter without battle", async () => { it("should leave encounter without battle", async () => {

View File

@ -22,6 +22,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
import { CommandPhase } from "#app/phases/command-phase"; import { CommandPhase } from "#app/phases/command-phase";
import { MovePhase } from "#app/phases/move-phase"; import { MovePhase } from "#app/phases/move-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import i18next from "i18next";
const namespace = "mysteryEncounters/fieryFallout"; const namespace = "mysteryEncounters/fieryFallout";
/** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */ /** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */
@ -205,7 +206,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
const burnablePokemon = party.filter((pkm) => pkm.isAllowedInBattle() && !pkm.getTypes().includes(Type.FIRE)); const burnablePokemon = party.filter((pkm) => pkm.isAllowedInBattle() && !pkm.getTypes().includes(Type.FIRE));
const notBurnablePokemon = party.filter((pkm) => !pkm.isAllowedInBattle() || pkm.getTypes().includes(Type.FIRE)); const notBurnablePokemon = party.filter((pkm) => !pkm.isAllowedInBattle() || pkm.getTypes().includes(Type.FIRE));
expect(scene.currentBattle.mysteryEncounter?.dialogueTokens["burnedPokemon"]).toBe("pokemon:gengar"); expect(scene.currentBattle.mysteryEncounter?.dialogueTokens["burnedPokemon"]).toBe(i18next.t("pokemon:gengar"));
burnablePokemon.forEach((pkm) => { burnablePokemon.forEach((pkm) => {
expect(pkm.hp, `${pkm.name} should have received 20% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.2)); expect(pkm.hp, `${pkm.name} should have received 20% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.2));
}); });

View File

@ -14,6 +14,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { PartyExpPhase } from "#app/phases/party-exp-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase";
import i18next from "i18next";
const namespace = "mysteryEncounters/lostAtSea"; const namespace = "mysteryEncounters/lostAtSea";
@ -86,8 +87,8 @@ describe("Lost at Sea - Mystery Encounter", () => {
const onInitResult = onInit!(scene); const onInitResult = onInit!(scene);
expect(LostAtSeaEncounter.dialogueTokens?.damagePercentage).toBe("25"); expect(LostAtSeaEncounter.dialogueTokens?.damagePercentage).toBe("25");
expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe("move:surf.name"); expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe(i18next.t("move:surf.name"));
expect(LostAtSeaEncounter.dialogueTokens?.option2RequiredMove).toBe("move:fly.name"); expect(LostAtSeaEncounter.dialogueTokens?.option2RequiredMove).toBe(i18next.t("move:fly.name"));
expect(onInitResult).toBe(true); expect(onInitResult).toBe(true);
}); });

View File

@ -1,21 +1,22 @@
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import { Biome } from "#app/enums/biome";
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
import { Species } from "#app/enums/species";
import GameManager from "#app/test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter";
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import { Abilities } from "#enums/abilities";
import { Biome } from "#enums/biome";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import { CommandPhase } from "#app/phases/command-phase";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import GameManager from "#test/utils/gameManager";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import { Mode } from "#app/ui/ui";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils";
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import i18next from "i18next";
import { CommandPhase } from "#app/phases/command-phase"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import { Mode } from "#app/ui/ui";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import { Abilities } from "#app/enums/abilities";
const namespace = "mysteryEncounters/teleportingHijinks"; const namespace = "mysteryEncounters/teleportingHijinks";
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
@ -300,8 +301,8 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === "modifierType:AttackTypeBoosterItem.metal_coat")).toBe(true); expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.metal_coat"))).toBe(true);
expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === "modifierType:AttackTypeBoosterItem.magnet")).toBe(true); expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.magnet"))).toBe(true);
}); });
}); });
}); });

View File

@ -155,7 +155,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
expect(eggsAfter).toBeDefined(); expect(eggsAfter).toBeDefined();
expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length);
expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs);
expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs);
game.phaseInterceptor.superEndPhase(); game.phaseInterceptor.superEndPhase();
await game.phaseInterceptor.to(PostMysteryEncounterPhase); await game.phaseInterceptor.to(PostMysteryEncounterPhase);
@ -213,7 +213,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
expect(eggsAfter).toBeDefined(); expect(eggsAfter).toBeDefined();
expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length);
expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs);
expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs);
game.phaseInterceptor.superEndPhase(); game.phaseInterceptor.superEndPhase();
await game.phaseInterceptor.to(PostMysteryEncounterPhase); await game.phaseInterceptor.to(PostMysteryEncounterPhase);
@ -271,7 +271,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
expect(eggsAfter).toBeDefined(); expect(eggsAfter).toBeDefined();
expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length);
expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs);
expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs);
game.phaseInterceptor.superEndPhase(); game.phaseInterceptor.superEndPhase();
await game.phaseInterceptor.to(PostMysteryEncounterPhase); await game.phaseInterceptor.to(PostMysteryEncounterPhase);

View File

@ -9,6 +9,7 @@ import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import MessageUiHandler from "#app/ui/message-ui-handler"; import MessageUiHandler from "#app/ui/message-ui-handler";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import i18next from "i18next";
describe("Mystery Encounter Phases", () => { describe("Mystery Encounter Phases", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -78,9 +79,9 @@ describe("Mystery Encounter Phases", () => {
expect(ui.getMode()).toBe(Mode.MESSAGE); expect(ui.getMode()).toBe(Mode.MESSAGE);
expect(ui.showDialogue).toHaveBeenCalledTimes(1); expect(ui.showDialogue).toHaveBeenCalledTimes(1);
expect(ui.showText).toHaveBeenCalledTimes(2); expect(ui.showText).toHaveBeenCalledTimes(2);
expect(ui.showDialogue).toHaveBeenCalledWith("battle:mysteryEncounterAppeared", "???", null, expect.any(Function)); expect(ui.showDialogue).toHaveBeenCalledWith(i18next.t("battle:mysteryEncounterAppeared"), "???", null, expect.any(Function));
expect(ui.showText).toHaveBeenCalledWith("mysteryEncounters/mysteriousChallengers:intro", null, expect.any(Function), 750, true); expect(ui.showText).toHaveBeenCalledWith(i18next.t("mysteryEncounters/mysteriousChallengers:intro"), null, expect.any(Function), 750, true);
expect(ui.showText).toHaveBeenCalledWith("mysteryEncounters/mysteriousChallengers:option.selected", null, expect.any(Function), 300, true); expect(ui.showText).toHaveBeenCalledWith(i18next.t("mysteryEncounters/mysteriousChallengers:option.selected"), null, expect.any(Function), 300, true);
}); });
}); });

View File

@ -11,13 +11,15 @@ import * as account from "../../account";
const apiBase = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8001"; const apiBase = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8001";
export const server = setupServer(); /** We need a custom server. For some reasons I can't extend the listeners of {@linkcode global.i18nServer} with {@linkcode global.i18nServer.use} */
const server = setupServer();
describe("System - Game Data", () => { describe("System - Game Data", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
let game: GameManager; let game: GameManager;
beforeAll(() => { beforeAll(() => {
global.i18nServer.close();
server.listen(); server.listen();
phaserGame = new Phaser.Game({ phaserGame = new Phaser.Game({
type: Phaser.HEADLESS, type: Phaser.HEADLESS,
@ -26,6 +28,7 @@ describe("System - Game Data", () => {
afterAll(() => { afterAll(() => {
server.close(); server.close();
global.i18nServer.listen();
}); });
beforeEach(() => { beforeEach(() => {

View File

@ -14,6 +14,7 @@ import { Abilities } from "#enums/abilities";
import { Button } from "#enums/buttons"; import { Button } from "#enums/buttons";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import i18next from "i18next";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@ -66,11 +67,11 @@ describe("UI - Starter select", () => {
resolve(); resolve();
}); });
}); });
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
expect(options.some(option => option.label === "menu:cancel")).toBe(true); expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
optionSelectUiHandler?.processInput(Button.ACTION); optionSelectUiHandler?.processInput(Button.ACTION);
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -127,11 +128,11 @@ describe("UI - Starter select", () => {
resolve(); resolve();
}); });
}); });
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
expect(options.some(option => option.label === "menu:cancel")).toBe(true); expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
optionSelectUiHandler?.processInput(Button.ACTION); optionSelectUiHandler?.processInput(Button.ACTION);
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -191,11 +192,11 @@ describe("UI - Starter select", () => {
resolve(); resolve();
}); });
}); });
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
expect(options.some(option => option.label === "menu:cancel")).toBe(true); expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
optionSelectUiHandler?.processInput(Button.ACTION); optionSelectUiHandler?.processInput(Button.ACTION);
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -254,11 +255,11 @@ describe("UI - Starter select", () => {
resolve(); resolve();
}); });
}); });
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
expect(options.some(option => option.label === "menu:cancel")).toBe(true); expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
optionSelectUiHandler?.processInput(Button.ACTION); optionSelectUiHandler?.processInput(Button.ACTION);
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -315,11 +316,11 @@ describe("UI - Starter select", () => {
resolve(); resolve();
}); });
}); });
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
expect(options.some(option => option.label === "menu:cancel")).toBe(true); expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
optionSelectUiHandler?.processInput(Button.ACTION); optionSelectUiHandler?.processInput(Button.ACTION);
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -376,11 +377,11 @@ describe("UI - Starter select", () => {
resolve(); resolve();
}); });
}); });
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
expect(options.some(option => option.label === "menu:cancel")).toBe(true); expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
optionSelectUiHandler?.processInput(Button.ACTION); optionSelectUiHandler?.processInput(Button.ACTION);
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -436,11 +437,11 @@ describe("UI - Starter select", () => {
resolve(); resolve();
}); });
}); });
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
expect(options.some(option => option.label === "menu:cancel")).toBe(true); expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
optionSelectUiHandler?.processInput(Button.ACTION); optionSelectUiHandler?.processInput(Button.ACTION);
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -496,11 +497,11 @@ describe("UI - Starter select", () => {
resolve(); resolve();
}); });
}); });
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
expect(options.some(option => option.label === "menu:cancel")).toBe(true); expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
optionSelectUiHandler?.processInput(Button.ACTION); optionSelectUiHandler?.processInput(Button.ACTION);
let starterSelectUiHandler: StarterSelectUiHandler; let starterSelectUiHandler: StarterSelectUiHandler;
@ -561,11 +562,11 @@ describe("UI - Starter select", () => {
resolve(); resolve();
}); });
}); });
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
expect(options.some(option => option.label === "menu:cancel")).toBe(true); expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
optionSelectUiHandler?.processInput(Button.ACTION); optionSelectUiHandler?.processInput(Button.ACTION);
let starterSelectUiHandler: StarterSelectUiHandler | undefined; let starterSelectUiHandler: StarterSelectUiHandler | undefined;

View File

@ -7,7 +7,8 @@ import { Mode } from "#app/ui/ui";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import MockText from "../utils/mocks/mocksContainer/mockText"; import MockText from "#test/utils/mocks/mocksContainer/mockText";
import i18next from "i18next";
describe("UI - Type Hints", () => { describe("UI - Type Hints", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -53,7 +54,7 @@ describe("UI - Type Hints", () => {
const movesContainer = ui.getByName<Phaser.GameObjects.Container>(FightUiHandler.MOVES_CONTAINER_NAME); const movesContainer = ui.getByName<Phaser.GameObjects.Container>(FightUiHandler.MOVES_CONTAINER_NAME);
const dragonClawText = movesContainer const dragonClawText = movesContainer
.getAll<Phaser.GameObjects.Text>() .getAll<Phaser.GameObjects.Text>()
.find((text) => text.text === "move:dragonClaw.name")! as unknown as MockText; .find((text) => text.text === i18next.t("move:dragonClaw.name"))! as unknown as MockText;
expect.soft(dragonClawText.color).toBe("#929292"); expect.soft(dragonClawText.color).toBe("#929292");
ui.getHandler().processInput(Button.ACTION); ui.getHandler().processInput(Button.ACTION);
@ -78,7 +79,7 @@ describe("UI - Type Hints", () => {
const movesContainer = ui.getByName<Phaser.GameObjects.Container>(FightUiHandler.MOVES_CONTAINER_NAME); const movesContainer = ui.getByName<Phaser.GameObjects.Container>(FightUiHandler.MOVES_CONTAINER_NAME);
const growlText = movesContainer const growlText = movesContainer
.getAll<Phaser.GameObjects.Text>() .getAll<Phaser.GameObjects.Text>()
.find((text) => text.text === "move:growl.name")! as unknown as MockText; .find((text) => text.text === i18next.t("move:growl.name"))! as unknown as MockText;
expect.soft(growlText.color).toBe(undefined); expect.soft(growlText.color).toBe(undefined);
ui.getHandler().processInput(Button.ACTION); ui.getHandler().processInput(Button.ACTION);

View File

@ -86,7 +86,7 @@ export function waitUntil(truth) {
export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: Moves) { export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: Moves) {
const playerPokemon = scene.getPlayerField()[pokemonIndex]; const playerPokemon = scene.getPlayerField()[pokemonIndex];
const moveSet = playerPokemon.getMoveset(); const moveSet = playerPokemon.getMoveset();
const index = moveSet.findIndex((m) => m?.moveId === move); const index = moveSet.findIndex((m) => m?.moveId === move && m?.ppUsed < m?.getMovePp());
console.log(`Move position for ${Moves[move]} (=${move}):`, index); console.log(`Move position for ${Moves[move]} (=${move}):`, index);
return index; return index;
} }

View File

@ -53,6 +53,7 @@ import {
} from "#app/phases/mystery-encounter-phases"; } from "#app/phases/mystery-encounter-phases";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { PartyExpPhase } from "#app/phases/party-exp-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase";
import { ExpPhase } from "#app/phases/exp-phase";
export interface PromptHandler { export interface PromptHandler {
phaseTarget?: string; phaseTarget?: string;
@ -61,7 +62,114 @@ export interface PromptHandler {
expireFn?: () => void; expireFn?: () => void;
awaitingActionInput?: boolean; awaitingActionInput?: boolean;
} }
import { ExpPhase } from "#app/phases/exp-phase";
type PhaseClass =
| typeof LoginPhase
| typeof TitlePhase
| typeof SelectGenderPhase
| typeof EncounterPhase
| typeof NewBiomeEncounterPhase
| typeof SelectStarterPhase
| typeof PostSummonPhase
| typeof SummonPhase
| typeof ToggleDoublePositionPhase
| typeof CheckSwitchPhase
| typeof ShowAbilityPhase
| typeof MessagePhase
| typeof TurnInitPhase
| typeof CommandPhase
| typeof EnemyCommandPhase
| typeof TurnStartPhase
| typeof MovePhase
| typeof MoveEffectPhase
| typeof DamagePhase
| typeof FaintPhase
| typeof BerryPhase
| typeof TurnEndPhase
| typeof BattleEndPhase
| typeof EggLapsePhase
| typeof SelectModifierPhase
| typeof NextEncounterPhase
| typeof NewBattlePhase
| typeof VictoryPhase
| typeof LearnMovePhase
| typeof MoveEndPhase
| typeof StatStageChangePhase
| typeof ShinySparklePhase
| typeof SelectTargetPhase
| typeof UnavailablePhase
| typeof QuietFormChangePhase
| typeof SwitchPhase
| typeof SwitchSummonPhase
| typeof PartyHealPhase
| typeof EvolutionPhase
| typeof EndEvolutionPhase
| typeof LevelCapPhase
| typeof AttemptRunPhase
| typeof SelectBiomePhase
| typeof MysteryEncounterPhase
| typeof MysteryEncounterOptionSelectedPhase
| typeof MysteryEncounterBattlePhase
| typeof MysteryEncounterRewardsPhase
| typeof PostMysteryEncounterPhase
| typeof ModifierRewardPhase
| typeof PartyExpPhase
| typeof ExpPhase;
type PhaseString =
| "LoginPhase"
| "TitlePhase"
| "SelectGenderPhase"
| "EncounterPhase"
| "NewBiomeEncounterPhase"
| "SelectStarterPhase"
| "PostSummonPhase"
| "SummonPhase"
| "ToggleDoublePositionPhase"
| "CheckSwitchPhase"
| "ShowAbilityPhase"
| "MessagePhase"
| "TurnInitPhase"
| "CommandPhase"
| "EnemyCommandPhase"
| "TurnStartPhase"
| "MovePhase"
| "MoveEffectPhase"
| "DamagePhase"
| "FaintPhase"
| "BerryPhase"
| "TurnEndPhase"
| "BattleEndPhase"
| "EggLapsePhase"
| "SelectModifierPhase"
| "NextEncounterPhase"
| "NewBattlePhase"
| "VictoryPhase"
| "LearnMovePhase"
| "MoveEndPhase"
| "StatStageChangePhase"
| "ShinySparklePhase"
| "SelectTargetPhase"
| "UnavailablePhase"
| "QuietFormChangePhase"
| "SwitchPhase"
| "SwitchSummonPhase"
| "PartyHealPhase"
| "EvolutionPhase"
| "EndEvolutionPhase"
| "LevelCapPhase"
| "AttemptRunPhase"
| "SelectBiomePhase"
| "MysteryEncounterPhase"
| "MysteryEncounterOptionSelectedPhase"
| "MysteryEncounterBattlePhase"
| "MysteryEncounterRewardsPhase"
| "PostMysteryEncounterPhase"
| "ModifierRewardPhase"
| "PartyExpPhase"
| "ExpPhase";
type PhaseInterceptorPhase = PhaseClass | PhaseString;
export default class PhaseInterceptor { export default class PhaseInterceptor {
public scene; public scene;
@ -172,7 +280,7 @@ export default class PhaseInterceptor {
* @param phaseFrom - The phase to start from. * @param phaseFrom - The phase to start from.
* @returns The instance of the PhaseInterceptor. * @returns The instance of the PhaseInterceptor.
*/ */
runFrom(phaseFrom) { runFrom(phaseFrom: PhaseInterceptorPhase): PhaseInterceptor {
this.phaseFrom = phaseFrom; this.phaseFrom = phaseFrom;
return this; return this;
} }
@ -180,9 +288,10 @@ export default class PhaseInterceptor {
/** /**
* Method to transition to a target phase. * Method to transition to a target phase.
* @param phaseTo - The phase to transition to. * @param phaseTo - The phase to transition to.
* @param runTarget - Whether or not to run the target phase.
* @returns A promise that resolves when the transition is complete. * @returns A promise that resolves when the transition is complete.
*/ */
async to(phaseTo, runTarget: boolean = true): Promise<void> { async to(phaseTo: PhaseInterceptorPhase, runTarget: boolean = true): Promise<void> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
ErrorInterceptor.getInstance().add(this); ErrorInterceptor.getInstance().add(this);
if (this.phaseFrom) { if (this.phaseFrom) {
@ -219,7 +328,7 @@ export default class PhaseInterceptor {
* @param skipFn - Optional skip function. * @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is run. * @returns A promise that resolves when the phase is run.
*/ */
run(phaseTarget, skipFn?): Promise<void> { run(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise<void> {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
this.scene.moveAnimations = null; // Mandatory to avoid crash this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
@ -253,7 +362,7 @@ export default class PhaseInterceptor {
}); });
} }
whenAboutToRun(phaseTarget, skipFn?): Promise<void> { whenAboutToRun(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise<void> {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
this.scene.moveAnimations = null; // Mandatory to avoid crash this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
@ -311,7 +420,7 @@ export default class PhaseInterceptor {
* Method to start a phase and log it. * Method to start a phase and log it.
* @param phase - The phase to start. * @param phase - The phase to start.
*/ */
startPhase(phase) { startPhase(phase: PhaseClass) {
this.log.push(phase.name); this.log.push(phase.name);
const instance = this.scene.getCurrentPhase(); const instance = this.scene.getCurrentPhase();
this.onHold.push({ this.onHold.push({
@ -340,9 +449,10 @@ export default class PhaseInterceptor {
/** /**
* m2m to set mode. * m2m to set mode.
* @param phase - The phase to start. * @param mode - The {@linkcode Mode} to set.
* @param args - Additional arguments to pass to the original method.
*/ */
setMode(mode: Mode, ...args: any[]): Promise<void> { setMode(mode: Mode, ...args: unknown[]): Promise<void> {
const currentPhase = this.scene.getCurrentPhase(); const currentPhase = this.scene.getCurrentPhase();
const instance = this.scene.ui; const instance = this.scene.ui;
console.log("setMode", `${Mode[mode]} (=${mode})`, args); console.log("setMode", `${Mode[mode]} (=${mode})`, args);

View File

@ -4,16 +4,17 @@ import { initLoggedInUser } from "#app/account";
import { initAbilities } from "#app/data/ability"; import { initAbilities } from "#app/data/ability";
import { initBiomes } from "#app/data/balance/biomes"; import { initBiomes } from "#app/data/balance/biomes";
import { initEggMoves } from "#app/data/balance/egg-moves"; import { initEggMoves } from "#app/data/balance/egg-moves";
import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { initMoves } from "#app/data/move"; import { initMoves } from "#app/data/move";
import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters";
import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { initPokemonForms } from "#app/data/pokemon-forms"; import { initPokemonForms } from "#app/data/pokemon-forms";
import { initSpecies } from "#app/data/pokemon-species"; import { initSpecies } from "#app/data/pokemon-species";
import { initAchievements } from "#app/system/achv"; import { initAchievements } from "#app/system/achv";
import { initVouchers } from "#app/system/voucher"; import { initVouchers } from "#app/system/voucher";
import { initStatsKeys } from "#app/ui/game-stats-ui-handler"; import { initStatsKeys } from "#app/ui/game-stats-ui-handler";
import { beforeAll, vi } from "vitest"; import { afterAll, beforeAll, vi } from "vitest";
/** Set the timezone to UTC for tests. */
process.env.TZ = "UTC"; process.env.TZ = "UTC";
/** Mock the override import to always return default values, ignoring any custom overrides. */ /** Mock the override import to always return default values, ignoring any custom overrides. */
@ -26,26 +27,36 @@ vi.mock("#app/overrides", async (importOriginal) => {
} satisfies typeof import("#app/overrides"); } satisfies typeof import("#app/overrides");
}); });
vi.mock("i18next", () => ({ /**
default: { * This is a hacky way to mock the i18n backend requests (with the help of {@link https://mswjs.io/ | msw}).
use: () => {}, * The reason to put it inside of a mock is to elevate it.
t: (key: string) => key, * This is necessary because how our code is structured.
changeLanguage: () => Promise.resolve(), * Do NOT try to put any of this code into external functions, it won't work as it's elevated during runtime.
init: () => Promise.resolve(), */
resolvedLanguage: "en", vi.mock("i18next", async (importOriginal) => {
exists: () => true, console.log("Mocking i18next");
getDataByLanguage:() => ({ const { setupServer } = await import("msw/node");
en: { const { http, HttpResponse } = await import("msw");
keys: [ "foo" ]
}, global.i18nServer = setupServer(
}), http.get("/locales/en/*", async (req) => {
services: { const filename = req.params[0];
formatter: {
add: () => {}, try {
const json = await import(`../../public/locales/en/${req.params[0]}`);
console.log("Loaded locale", filename);
return HttpResponse.json(json);
} catch (err) {
console.log(`Failed to load locale ${filename}!`, err);
return HttpResponse.json({});
} }
}, })
}, );
})); global.i18nServer.listen({ onUnhandledRequest: "error" });
console.log("i18n MSW server listening!");
return await importOriginal();
});
initVouchers(); initVouchers();
initAchievements(); initAchievements();
@ -70,3 +81,8 @@ beforeAll(() => {
}, },
}); });
}); });
afterAll(() => {
global.i18nServer.close();
console.log("Closing i18n MSW server!");
});

View File

@ -593,7 +593,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
}; };
const updatePokemonHp = () => { const updatePokemonHp = () => {
let duration = !instant ? Utils.clampInt(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0; let duration = !instant ? Phaser.Math.Clamp(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0;
const speed = (this.scene as BattleScene).hpBarSpeed; const speed = (this.scene as BattleScene).hpBarSpeed;
if (speed) { if (speed) {
duration = speed >= 3 ? 0 : duration / Math.pow(2, speed); duration = speed >= 3 ? 0 : duration / Math.pow(2, speed);

View File

@ -471,9 +471,9 @@ export default class EggGachaUiHandler extends MessageUiHandler {
getGuaranteedEggTierFromPullCount(pullCount: number): EggTier { getGuaranteedEggTierFromPullCount(pullCount: number): EggTier {
switch (pullCount) { switch (pullCount) {
case 10: case 10:
return EggTier.GREAT; return EggTier.RARE;
case 25: case 25:
return EggTier.ULTRA; return EggTier.EPIC;
default: default:
return EggTier.COMMON; return EggTier.COMMON;
} }
@ -516,7 +516,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
const eggText = addTextObject(this.scene, 0, 14, egg.getEggDescriptor(), TextStyle.PARTY, { align: "center" }); const eggText = addTextObject(this.scene, 0, 14, egg.getEggDescriptor(), TextStyle.PARTY, { align: "center" });
eggText.setOrigin(0.5, 0); eggText.setOrigin(0.5, 0);
eggText.setTint(getEggTierTextTint(!egg.isManaphyEgg() ? egg.tier : EggTier.ULTRA)); eggText.setTint(getEggTierTextTint(!egg.isManaphyEgg() ? egg.tier : EggTier.EPIC));
ret.add(eggText); ret.add(eggText);
this.eggGachaSummaryContainer.addAt(ret, 0); this.eggGachaSummaryContainer.addAt(ret, 0);

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