Compare commits

..

No commits in common. "9c97e37c27e646bc738e0b82e1b6cebe84e2c16a" and "7c3ace7204950809b0a07f135cda08d744445828" have entirely different histories.

108 changed files with 610 additions and 5133 deletions

1
.env
View File

@ -1,3 +1,2 @@
VITE_BYPASS_LOGIN=0 VITE_BYPASS_LOGIN=0
VITE_BYPASS_TUTORIAL=0 VITE_BYPASS_TUTORIAL=0
VITE_SERVER_URL=http://localhost:8001

View File

@ -1,3 +1,2 @@
VITE_BYPASS_LOGIN=1 VITE_BYPASS_LOGIN=1
VITE_BYPASS_TUTORIAL=0 VITE_BYPASS_TUTORIAL=0
VITE_SERVER_URL=http://localhost:8001

View File

@ -46,14 +46,7 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to
- Keisuke Ito - Keisuke Ito
- Arata Iiyoshi - Arata Iiyoshi
- Atsuhiro Ishizuna - Atsuhiro Ishizuna
- Pokémon HeartGold/SoulSilver
- Pokémon Black/White 2 - Pokémon Black/White 2
- Pokémon X/Y
- Pokémon Omega Ruby/Alpha Sapphire
- Pokémon Sun/Moon
- Pokémon Ultra Sun/Ultra Moon
- Pokémon Sword/Shield
- Pokémon Scarlet/Violet
- Firel (Custom Metropolis and Laboratory biome music) - Firel (Custom Metropolis and Laboratory biome music)
- Lmz (Custom Jungle biome music) - Lmz (Custom Jungle biome music)

View File

@ -126,7 +126,6 @@ export default class BattleScene extends SceneBase {
public uiTheme: UiTheme = UiTheme.DEFAULT; public uiTheme: UiTheme = UiTheme.DEFAULT;
public windowType: integer = 0; public windowType: integer = 0;
public experimentalSprites: boolean = false; public experimentalSprites: boolean = false;
public musicPreference: integer = 0;
public moveAnimations: boolean = true; public moveAnimations: boolean = true;
public expGainsSpeed: integer = 0; public expGainsSpeed: integer = 0;
public skipSeenDialogues: boolean = false; public skipSeenDialogues: boolean = false;
@ -160,7 +159,7 @@ export default class BattleScene extends SceneBase {
public gameData: GameData; public gameData: GameData;
public sessionSlotId: integer; public sessionSlotId: integer;
public phaseQueue: Phase[]; private phaseQueue: Phase[];
private phaseQueuePrepend: Phase[]; private phaseQueuePrepend: Phase[];
private phaseQueuePrependSpliceIndex: integer; private phaseQueuePrependSpliceIndex: integer;
private nextCommandPhaseQueue: Phase[]; private nextCommandPhaseQueue: Phase[];
@ -202,7 +201,7 @@ export default class BattleScene extends SceneBase {
public arenaFlyout: ArenaFlyout; public arenaFlyout: ArenaFlyout;
private fieldOverlay: Phaser.GameObjects.Rectangle; private fieldOverlay: Phaser.GameObjects.Rectangle;
public modifiers: PersistentModifier[]; private modifiers: PersistentModifier[];
private enemyModifiers: PersistentModifier[]; private enemyModifiers: PersistentModifier[];
public uiContainer: Phaser.GameObjects.Container; public uiContainer: Phaser.GameObjects.Container;
public ui: UI; public ui: UI;
@ -301,8 +300,7 @@ export default class BattleScene extends SceneBase {
this.fieldSpritePipeline = new FieldSpritePipeline(this.game); this.fieldSpritePipeline = new FieldSpritePipeline(this.game);
(this.renderer as Phaser.Renderer.WebGL.WebGLRenderer).pipelines.add("FieldSprite", this.fieldSpritePipeline); (this.renderer as Phaser.Renderer.WebGL.WebGLRenderer).pipelines.add("FieldSprite", this.fieldSpritePipeline);
this.time.delayedCall(20, () => this.launchBattle());
this.launchBattle();
} }
update() { update() {
@ -949,8 +947,7 @@ export default class BattleScene extends SceneBase {
} }
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean): Battle { newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean): Battle {
const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave; const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (startingWave - 1)) + 1);
const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1);
let newDouble: boolean; let newDouble: boolean;
let newBattleType: BattleType; let newBattleType: BattleType;
let newTrainer: Trainer; let newTrainer: Trainer;
@ -1010,9 +1007,6 @@ export default class BattleScene extends SceneBase {
if (Overrides.DOUBLE_BATTLE_OVERRIDE) { if (Overrides.DOUBLE_BATTLE_OVERRIDE) {
newDouble = true; newDouble = true;
} }
if (Overrides.SINGLE_BATTLE_OVERRIDE) {
newDouble = false;
}
const lastBattle = this.currentBattle; const lastBattle = this.currentBattle;
@ -1382,7 +1376,8 @@ export default class BattleScene extends SceneBase {
if (this.money === undefined) { if (this.money === undefined) {
return; return;
} }
const formattedMoney = Utils.formatMoney(this.moneyFormat, this.money); const formattedMoney =
this.moneyFormat === MoneyFormat.ABBREVIATED ? Utils.formatFancyLargeNumber(this.money, 3) : this.money.toLocaleString();
this.moneyText.setText(`${formattedMoney}`); this.moneyText.setText(`${formattedMoney}`);
this.fieldUI.moveAbove(this.moneyText, this.luckText); this.fieldUI.moveAbove(this.moneyText, this.luckText);
if (forceVisible) { if (forceVisible) {
@ -1699,74 +1694,14 @@ export default class BattleScene extends SceneBase {
return 13.122; return 13.122;
case "battle_unova_gym": case "battle_unova_gym":
return 19.145; return 19.145;
case "battle_legendary_kanto": //XY Kanto Legendary Battle case "battle_legendary_regis": //B2W2 Legendary Titan Battle
return 32.966;
case "battle_legendary_raikou": //HGSS Raikou Battle
return 12.632;
case "battle_legendary_entei": //HGSS Entei Battle
return 2.905;
case "battle_legendary_suicune": //HGSS Suicune Battle
return 12.636;
case "battle_legendary_lugia": //HGSS Lugia Battle
return 19.770;
case "battle_legendary_ho_oh": //HGSS Ho-oh Battle
return 17.668;
case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle
return 49.500; return 49.500;
case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle
return 21.130;
case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle
return 10.547;
case "battle_legendary_rayquaza": //ORAS Rayquaza Battle
return 10.495;
case "battle_legendary_deoxys": //ORAS Deoxys Battle
return 13.333;
case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle
return 16.887;
case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle
return 22.770;
case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle
return 16.009;
case "battle_legendary_giratina": //ORAS Giratina Battle
return 10.451;
case "battle_legendary_arceus": //HGSS Arceus Battle
return 9.595;
case "battle_legendary_unova": //BW Unova Legendary Battle case "battle_legendary_unova": //BW Unova Legendary Battle
return 13.855; return 13.855;
case "battle_legendary_kyurem": //BW Kyurem Battle case "battle_legendary_kyurem": //BW Kyurem Battle
return 18.314; return 18.314;
case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle
return 18.329; return 18.329;
case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle
return 26.468;
case "battle_legendary_tapu": //SM Tapu Battle
return 0.000;
case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle
return 6.525;
case "battle_legendary_ub": //SM Ultra Beast Battle
return 9.818;
case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle
return 5.211;
case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle
return 10.344;
case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle
return 11.424;
case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle
return 12.503;
case "battle_legendary_calyrex": //SWSH Calyrex Battle
return 50.641;
case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle
return 0.175;
case "battle_legendary_ruinous": //SV Treasures of Ruin Battle
return 6.333;
case "battle_legendary_loyal_three": //SV Loyal Three Battle
return 6.500;
case "battle_legendary_ogerpon": //SV Ogerpon Battle
return 14.335;
case "battle_legendary_terapagos": //SV Terapagos Battle
return 24.377;
case "battle_legendary_pecharunt": //SV Pecharunt Battle
return 6.508;
case "battle_rival": case "battle_rival":
return 13.689; return 13.689;
case "battle_rival_2": case "battle_rival_2":

View File

@ -209,116 +209,22 @@ export default class Battle {
return "battle_final_encounter"; return "battle_final_encounter";
} }
if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) { if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) {
if (scene.musicPreference === 0) { if (pokemon.species.speciesId === Species.REGIROCK || pokemon.species.speciesId === Species.REGICE || pokemon.species.speciesId === Species.REGISTEEL || pokemon.species.speciesId === Species.REGIGIGAS || pokemon.species.speciesId === Species.REGIELEKI || pokemon.species.speciesId === Species.REGIDRAGO) {
if (pokemon.species.speciesId === Species.REGIROCK || pokemon.species.speciesId === Species.REGICE || pokemon.species.speciesId === Species.REGISTEEL || pokemon.species.speciesId === Species.REGIGIGAS || pokemon.species.speciesId === Species.REGIELEKI || pokemon.species.speciesId === Species.REGIDRAGO) { return "battle_legendary_regis";
return "battle_legendary_regis_g5"; }
} if (pokemon.species.speciesId === Species.COBALION || pokemon.species.speciesId === Species.TERRAKION || pokemon.species.speciesId === Species.VIRIZION || pokemon.species.speciesId === Species.TORNADUS || pokemon.species.speciesId === Species.THUNDURUS || pokemon.species.speciesId === Species.LANDORUS || pokemon.species.speciesId === Species.KELDEO || pokemon.species.speciesId === Species.MELOETTA || pokemon.species.speciesId === Species.GENESECT) {
if (pokemon.species.speciesId === Species.COBALION || pokemon.species.speciesId === Species.TERRAKION || pokemon.species.speciesId === Species.VIRIZION || pokemon.species.speciesId === Species.TORNADUS || pokemon.species.speciesId === Species.THUNDURUS || pokemon.species.speciesId === Species.LANDORUS || pokemon.species.speciesId === Species.KELDEO || pokemon.species.speciesId === Species.MELOETTA || pokemon.species.speciesId === Species.GENESECT) {
return "battle_legendary_unova";
}
if (pokemon.species.speciesId === Species.KYUREM) {
return "battle_legendary_kyurem";
}
if (pokemon.species.legendary) {
return "battle_legendary_res_zek";
}
return "battle_legendary_unova";
} else {
if (pokemon.species.speciesId === Species.ARTICUNO || pokemon.species.speciesId === Species.ZAPDOS || pokemon.species.speciesId === Species.MOLTRES || pokemon.species.speciesId === Species.MEWTWO || pokemon.species.speciesId === Species.MEW) {
return "battle_legendary_kanto";
}
if (pokemon.species.speciesId === Species.RAIKOU) {
return "battle_legendary_raikou";
}
if (pokemon.species.speciesId === Species.ENTEI) {
return "battle_legendary_entei";
}
if (pokemon.species.speciesId === Species.SUICUNE) {
return "battle_legendary_suicune";
}
if (pokemon.species.speciesId === Species.LUGIA) {
return "battle_legendary_lugia";
}
if (pokemon.species.speciesId === Species.HO_OH) {
return "battle_legendary_ho_oh";
}
if (pokemon.species.speciesId === Species.REGIROCK || pokemon.species.speciesId === Species.REGICE || pokemon.species.speciesId === Species.REGISTEEL || pokemon.species.speciesId === Species.REGIGIGAS || pokemon.species.speciesId === Species.REGIELEKI || pokemon.species.speciesId === Species.REGIDRAGO) {
return "battle_legendary_regis_g6";
}
if (pokemon.species.speciesId === Species.GROUDON || pokemon.species.speciesId === Species.KYOGRE) {
return "battle_legendary_gro_kyo";
}
if (pokemon.species.speciesId === Species.RAYQUAZA) {
return "battle_legendary_rayquaza";
}
if (pokemon.species.speciesId === Species.DEOXYS) {
return "battle_legendary_deoxys";
}
if (pokemon.species.speciesId === Species.UXIE || pokemon.species.speciesId === Species.MESPRIT || pokemon.species.speciesId === Species.AZELF) {
return "battle_legendary_lake_trio";
}
if (pokemon.species.speciesId === Species.HEATRAN || pokemon.species.speciesId === Species.CRESSELIA || pokemon.species.speciesId === Species.DARKRAI || pokemon.species.speciesId === Species.SHAYMIN) {
return "battle_legendary_sinnoh";
}
if (pokemon.species.speciesId === Species.DIALGA || pokemon.species.speciesId === Species.PALKIA) {
return "battle_legendary_dia_pal";
}
if (pokemon.species.speciesId === Species.GIRATINA) {
return "battle_legendary_giratina";
}
if (pokemon.species.speciesId === Species.ARCEUS) {
return "battle_legendary_arceus";
}
if (pokemon.species.speciesId === Species.COBALION || pokemon.species.speciesId === Species.TERRAKION || pokemon.species.speciesId === Species.VIRIZION || pokemon.species.speciesId === Species.TORNADUS || pokemon.species.speciesId === Species.THUNDURUS || pokemon.species.speciesId === Species.LANDORUS || pokemon.species.speciesId === Species.KELDEO || pokemon.species.speciesId === Species.MELOETTA || pokemon.species.speciesId === Species.GENESECT) {
return "battle_legendary_unova";
}
if (pokemon.species.speciesId === Species.KYUREM) {
return "battle_legendary_kyurem";
}
if (pokemon.species.speciesId === Species.XERNEAS || pokemon.species.speciesId === Species.YVELTAL || pokemon.species.speciesId === Species.ZYGARDE) {
return "battle_legendary_xern_yvel";
}
if (pokemon.species.speciesId === Species.TAPU_KOKO || pokemon.species.speciesId === Species.TAPU_LELE || pokemon.species.speciesId === Species.TAPU_BULU || pokemon.species.speciesId === Species.TAPU_FINI) {
return "battle_legendary_tapu";
}
if (pokemon.species.speciesId === Species.COSMOG || pokemon.species.speciesId === Species.COSMOEM || pokemon.species.speciesId === Species.SOLGALEO || pokemon.species.speciesId === Species.LUNALA || pokemon.species.speciesId === Species.NECROZMA) {
return "battle_legendary_sol_lun";
}
if (pokemon.species.speciesId === Species.NIHILEGO || pokemon.species.speciesId === Species.BUZZWOLE || pokemon.species.speciesId === Species.PHEROMOSA || pokemon.species.speciesId === Species.XURKITREE || pokemon.species.speciesId === Species.CELESTEELA || pokemon.species.speciesId === Species.KARTANA || pokemon.species.speciesId === Species.GUZZLORD || pokemon.species.speciesId === Species.POIPOLE || pokemon.species.speciesId === Species.NAGANADEL || pokemon.species.speciesId === Species.STAKATAKA || pokemon.species.speciesId === Species.BLACEPHALON) {
return "battle_legendary_ub";
}
if (pokemon.species.speciesId === Species.ZACIAN || pokemon.species.speciesId === Species.ZAMAZENTA) {
return "battle_legendary_zac_zam";
}
if (pokemon.species.speciesId === Species.GLASTRIER || pokemon.species.speciesId === Species.SPECTRIER) {
return "battle_legendary_glas_spec";
}
if (pokemon.species.speciesId === Species.CALYREX) {
return "battle_legendary_calyrex";
}
if (pokemon.species.speciesId === Species.GALAR_ARTICUNO || pokemon.species.speciesId === Species.GALAR_ZAPDOS || pokemon.species.speciesId === Species.GALAR_MOLTRES) {
return "battle_legendary_birds_galar";
}
if (pokemon.species.speciesId === Species.WO_CHIEN || pokemon.species.speciesId === Species.CHIEN_PAO || pokemon.species.speciesId === Species.TING_LU || pokemon.species.speciesId === Species.CHI_YU) {
return "battle_legendary_ruinous";
}
if (pokemon.species.speciesId === Species.OKIDOGI || pokemon.species.speciesId === Species.MUNKIDORI || pokemon.species.speciesId === Species.FEZANDIPITI) {
return "battle_legendary_loyal_three";
}
if (pokemon.species.speciesId === Species.OGERPON) {
return "battle_legendary_ogerpon";
}
if (pokemon.species.speciesId === Species.TERAPAGOS) {
return "battle_legendary_terapagos";
}
if (pokemon.species.speciesId === Species.PECHARUNT) {
return "battle_legendary_pecharunt";
}
if (pokemon.species.legendary) {
return "battle_legendary_res_zek";
}
return "battle_legendary_unova"; return "battle_legendary_unova";
} }
if (pokemon.species.speciesId === Species.RESHIRAM || pokemon.species.speciesId === Species.ZEKROM) {
return "battle_legendary_res_zek";
}
if (pokemon.species.speciesId === Species.KYUREM) {
return "battle_legendary_kyurem";
}
if (pokemon.species.legendary) {
return "battle_legendary_res_zek";
}
return "battle_legendary_unova";
} }
} }

View File

@ -235,10 +235,10 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr {
} }
} }
type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean; type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: PokemonMove) => boolean;
export class PreDefendAbAttr extends AbAttr { export class PreDefendAbAttr extends AbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
@ -252,7 +252,7 @@ export class PreDefendFormChangeAbAttr extends PreDefendAbAttr {
this.formFunc = formFunc; this.formFunc = formFunc;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const formIndex = this.formFunc(pokemon); const formIndex = this.formFunc(pokemon);
if (formIndex !== pokemon.formIndex) { if (formIndex !== pokemon.formIndex) {
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
@ -263,7 +263,7 @@ export class PreDefendFormChangeAbAttr extends PreDefendAbAttr {
} }
} }
export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (pokemon.hp === pokemon.getMaxHp() && if (pokemon.hp === pokemon.getMaxHp() &&
pokemon.getMaxHp() > 1 && //Checks if pokemon has wonder_guard (which forces 1hp) pokemon.getMaxHp() > 1 && //Checks if pokemon has wonder_guard (which forces 1hp)
(args[0] as Utils.NumberHolder).value >= pokemon.hp) { //Damage >= hp (args[0] as Utils.NumberHolder).value >= pokemon.hp) { //Damage >= hp
@ -308,8 +308,8 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr {
this.powerMultiplier = powerMultiplier; this.powerMultiplier = powerMultiplier;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) { if (this.condition(pokemon, attacker, move.getMove())) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier; (args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
return true; return true;
} }
@ -329,8 +329,8 @@ export class PreDefendMovePowerToOneAbAttr extends ReceivedMoveDamageMultiplierA
super(condition, 1); super(condition, 1);
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) { if (this.condition(pokemon, attacker, move.getMove())) {
(args[0] as Utils.NumberHolder).value = 1; (args[0] as Utils.NumberHolder).value = 1;
return true; return true;
} }
@ -339,12 +339,6 @@ export class PreDefendMovePowerToOneAbAttr extends ReceivedMoveDamageMultiplierA
} }
} }
/**
* Determines whether a Pokemon is immune to a move because of an ability.
* @extends PreDefendAbAttr
* @see {@linkcode applyPreDefend}
* @see {@linkcode getCondition}
*/
export class TypeImmunityAbAttr extends PreDefendAbAttr { export class TypeImmunityAbAttr extends PreDefendAbAttr {
private immuneType: Type; private immuneType: Type;
private condition: AbAttrCondition; private condition: AbAttrCondition;
@ -356,17 +350,8 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
this.condition = condition; this.condition = condition;
} }
/** applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
* @param pokemon {@linkcode Pokemon} the defending Pokemon if ((move.getMove() instanceof AttackMove || move.getMove().getAttrs(StatusMoveTypeImmunityAttr).find(attr => attr.immuneType === this.immuneType)) && move.getMove().type === this.immuneType) {
* @param passive N/A
* @param attacker {@linkcode Pokemon} the attacking Pokemon
* @param move {@linkcode Move} the attacking move
* @param cancelled N/A
* @param args [0] {@linkcode Utils.NumberHolder} gets set to 0 if move is immuned by an ability.
* @param args [1] {@linkcode Utils.NumberHolder} type of move being defended against in case it has changed from default type
*/
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if ((move instanceof AttackMove || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => attr.immuneType === this.immuneType)) && move.type === this.immuneType) {
(args[0] as Utils.NumberHolder).value = 0; (args[0] as Utils.NumberHolder).value = 0;
return true; return true;
} }
@ -384,7 +369,7 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
super(immuneType); super(immuneType);
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (ret) { if (ret) {
@ -414,7 +399,7 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr {
this.levels = levels; this.levels = levels;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (ret) { if (ret) {
@ -440,7 +425,7 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr {
this.turnCount = turnCount; this.turnCount = turnCount;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (ret) { if (ret) {
@ -460,8 +445,8 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
super(null, condition); super(null, condition);
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.type, attacker) < 2) { if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, attacker) < 2) {
cancelled.value = true; cancelled.value = true;
(args[0] as Utils.NumberHolder).value = 0; (args[0] as Utils.NumberHolder).value = 0;
return true; return true;
@ -476,15 +461,15 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
} }
export class PostDefendAbAttr extends AbAttr { export class PostDefendAbAttr extends AbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
export class PostDefendDisguiseAbAttr extends PostDefendAbAttr { export class PostDefendDisguiseAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (pokemon.formIndex === 0 && pokemon.battleData.hitCount !== 0 && (move.category === MoveCategory.SPECIAL || move.category === MoveCategory.PHYSICAL)) { if (pokemon.formIndex === 0 && pokemon.battleData.hitCount !== 0 && (move.getMove().category === MoveCategory.SPECIAL || move.getMove().category === MoveCategory.PHYSICAL)) {
const recoilDamage = Math.ceil((pokemon.getMaxHp() / 8) - attacker.turnData.damageDealt); const recoilDamage = Math.ceil((pokemon.getMaxHp() / 8) - attacker.turnData.damageDealt);
if (!recoilDamage) { if (!recoilDamage) {
@ -509,7 +494,7 @@ export class PostDefendFormChangeAbAttr extends PostDefendAbAttr {
this.formFunc = formFunc; this.formFunc = formFunc;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
const formIndex = this.formFunc(pokemon); const formIndex = this.formFunc(pokemon);
if (formIndex !== pokemon.formIndex) { if (formIndex !== pokemon.formIndex) {
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
@ -521,16 +506,16 @@ export class PostDefendFormChangeAbAttr extends PostDefendAbAttr {
} }
export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const attackPriority = new Utils.IntegerHolder(move.priority); const attackPriority = new Utils.IntegerHolder(move.getMove().priority);
applyMoveAttrs(IncrementMovePriorityAttr,attacker,null,move,attackPriority); applyMoveAttrs(IncrementMovePriorityAttr,attacker,null,move.getMove(),attackPriority);
applyAbAttrs(IncrementMovePriorityAbAttr, attacker, null, move, attackPriority); applyAbAttrs(IncrementMovePriorityAbAttr, attacker, null, move.getMove(), attackPriority);
if (move.moveTarget===MoveTarget.USER || move.moveTarget===MoveTarget.NEAR_ALLY) { if (move.getMove().moveTarget===MoveTarget.USER || move.getMove().moveTarget===MoveTarget.NEAR_ALLY) {
return false; return false;
} }
if (attackPriority.value > 0 && !move.isMultiTarget()) { if (attackPriority.value > 0 && !move.getMove().isMultiTarget()) {
cancelled.value = true; cancelled.value = true;
return true; return true;
} }
@ -554,7 +539,7 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr {
this.immuneCondition = immuneCondition; this.immuneCondition = immuneCondition;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.immuneCondition(pokemon, attacker, move)) { if (this.immuneCondition(pokemon, attacker, move)) {
cancelled.value = true; cancelled.value = true;
return true; return true;
@ -578,7 +563,7 @@ export class MoveImmunityStatChangeAbAttr extends MoveImmunityAbAttr {
this.levels = levels; this.levels = levels;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (ret) { if (ret) {
const simulated = args.length > 1 && args[1]; const simulated = args.length > 1 && args[1];
@ -608,8 +593,8 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr {
* @args N/A * @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, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.hasAttr(HitHealAttr)) { if (move.getMove().hasAttr(HitHealAttr)) {
pokemon.scene.queueMessage(getPokemonMessage(attacker, " sucked up the liquid ooze!")); pokemon.scene.queueMessage(getPokemonMessage(attacker, " sucked up the liquid ooze!"));
return true; return true;
} }
@ -634,8 +619,8 @@ export class PostDefendStatChangeAbAttr extends PostDefendAbAttr {
this.allOthers = allOthers; this.allOthers = allOthers;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) { if (this.condition(pokemon, attacker, move.getMove())) {
if (this.allOthers) { if (this.allOthers) {
const otherPokemon = pokemon.getAlly() ? pokemon.getOpponents().concat([ pokemon.getAlly() ]) : pokemon.getOpponents(); const otherPokemon = pokemon.getAlly() ? pokemon.getOpponents().concat([ pokemon.getAlly() ]) : pokemon.getOpponents();
for (const other of otherPokemon) { for (const other of otherPokemon) {
@ -668,10 +653,10 @@ export class PostDefendHpGatedStatChangeAbAttr extends PostDefendAbAttr {
this.selfTarget = selfTarget; this.selfTarget = selfTarget;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate); const hpGateFlat: integer = 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];
if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + lastAttackReceived.damage) > hpGateFlat)) { if (this.condition(pokemon, attacker, move.getMove()) && (pokemon.hp <= hpGateFlat && (pokemon.hp + lastAttackReceived.damage) > hpGateFlat)) {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.levels)); pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.levels));
return true; return true;
} }
@ -691,8 +676,8 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr {
this.tagType = tagType; this.tagType = tagType;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) { if (this.condition(pokemon, attacker, move.getMove())) {
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) {
pokemon.scene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); pokemon.scene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER);
@ -713,11 +698,11 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
this.tagType = tagType; this.tagType = tagType;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) { if (this.condition(pokemon, attacker, move.getMove())) {
if (!pokemon.getTag(this.tagType)) { if (!pokemon.getTag(this.tagType)) {
pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); pokemon.addTag(this.tagType, undefined, undefined, pokemon.id);
pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: pokemon.name, moveName: move.name })); pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: pokemon.name, moveName: move.getName() }));
} }
return true; return true;
} }
@ -726,9 +711,9 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
} }
export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT) { if (hitResult < HitResult.NO_EFFECT) {
const type = move.type; const type = move.getMove().type;
const pokemonTypes = pokemon.getTypes(true); const pokemonTypes = pokemon.getTypes(true);
if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) { if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) {
pokemon.summonData.types = [ type ]; pokemon.summonData.types = [ type ];
@ -753,7 +738,7 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
this.terrainType = terrainType; this.terrainType = terrainType;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT) { if (hitResult < HitResult.NO_EFFECT) {
return pokemon.scene.arena.trySetTerrain(this.terrainType, true); return pokemon.scene.arena.trySetTerrain(this.terrainType, true);
} }
@ -773,8 +758,8 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
this.effects = effects; this.effects = effects;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, 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.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) {
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)];
return attacker.trySetStatus(effect, true, pokemon); return attacker.trySetStatus(effect, true, pokemon);
} }
@ -788,7 +773,7 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr
super(10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP); super(10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP);
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (attacker.hasAbility(Abilities.OVERCOAT) || attacker.isOfType(Type.GRASS)) { if (attacker.hasAbility(Abilities.OVERCOAT) || attacker.isOfType(Type.GRASS)) {
return false; return false;
} }
@ -809,9 +794,9 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
this.turnCount = turnCount; this.turnCount = turnCount;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) { if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) {
return attacker.addTag(this.tagType, this.turnCount, move.id, attacker.id); return attacker.addTag(this.tagType, this.turnCount, move.moveId, attacker.id);
} }
return false; return false;
@ -829,7 +814,7 @@ export class PostDefendCritStatChangeAbAttr extends PostDefendAbAttr {
this.levels = levels; this.levels = levels;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels)); pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels));
return true; return true;
@ -849,8 +834,8 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
this.damageRatio = damageRatio; this.damageRatio = damageRatio;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
attacker.turnData.damageTaken += Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)); attacker.turnData.damageTaken += Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio));
return true; return true;
@ -879,8 +864,8 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
this.turns = turns; this.turns = turns;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, 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 {
@ -906,7 +891,7 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr {
this.weatherType = weatherType; this.weatherType = weatherType;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (!pokemon.scene.arena.weather?.isImmutable()) { if (!pokemon.scene.arena.weather?.isImmutable()) {
return pokemon.scene.arena.trySetWeather(this.weatherType, true); return pokemon.scene.arena.trySetWeather(this.weatherType, true);
} }
@ -920,8 +905,8 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
super(); super();
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) { if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) {
const tempAbilityId = attacker.getAbility().id; const tempAbilityId = attacker.getAbility().id;
attacker.summonData.ability = pokemon.getAbility().id; attacker.summonData.ability = pokemon.getAbility().id;
pokemon.summonData.ability = tempAbilityId; pokemon.summonData.ability = tempAbilityId;
@ -944,8 +929,8 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
this.ability = ability; this.ability = ability;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) { if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) {
attacker.summonData.ability = this.ability; attacker.summonData.ability = this.ability;
return true; return true;
@ -962,7 +947,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
private chance: integer; private chance: integer;
private attacker: Pokemon; private attacker: Pokemon;
private move: Move; private move: PokemonMove;
constructor(chance: integer) { constructor(chance: integer) {
super(); super();
@ -970,13 +955,13 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
this.chance = chance; this.chance = chance;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (!attacker.summonData.disabledMove) { if (!attacker.summonData.disabledMove) {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !attacker.isMax()) { if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !attacker.isMax()) {
this.attacker = attacker; this.attacker = attacker;
this.move = move; this.move = move;
attacker.summonData.disabledMove = move.id; attacker.summonData.disabledMove = move.moveId;
attacker.summonData.disabledTurns = 4; attacker.summonData.disabledTurns = 4;
return true; return true;
} }
@ -985,7 +970,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
} }
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
return getPokemonMessage(this.attacker, `'s ${this.move.name}\nwas disabled!`); return getPokemonMessage(this.attacker, `'s ${this.move.getName()}\nwas disabled!`);
} }
} }
@ -1013,18 +998,49 @@ export class PostStatChangeStatChangeAbAttr extends PostStatChangeAbAttr {
} }
export class PreAttackAbAttr extends AbAttr { export class PreAttackAbAttr extends AbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
export class VariableMovePowerAbAttr extends PreAttackAbAttr { export class VariableMovePowerAbAttr extends PreAttackAbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
//const power = args[0] as Utils.NumberHolder; //const power = args[0] as Utils.NumberHolder;
return false; return false;
} }
} }
export class VariableMoveTypeAbAttr extends AbAttr {
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
//const power = args[0] as Utils.IntegerHolder;
return false;
}
}
export class MoveTypeChangePowerMultiplierAbAttr extends VariableMoveTypeAbAttr {
private matchType: Type;
private newType: Type;
private powerMultiplier: number;
constructor(matchType: Type, newType: Type, powerMultiplier: number) {
super(true);
this.matchType = matchType;
this.newType = newType;
this.powerMultiplier = powerMultiplier;
}
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const type = (args[0] as Utils.IntegerHolder);
if (type.value === this.matchType) {
type.value = this.newType;
(args[1] as Utils.NumberHolder).value *= this.powerMultiplier;
return true;
}
return false;
}
}
export class FieldPreventExplosiveMovesAbAttr extends AbAttr { export class FieldPreventExplosiveMovesAbAttr extends AbAttr {
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> { apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
cancelled.value = true; cancelled.value = true;
@ -1044,10 +1060,11 @@ export class MoveTypeChangeAttr extends PreAttackAbAttr {
this.condition = condition; this.condition = condition;
} }
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) { if (this.condition(pokemon, defender, move.getMove())) {
move.type = this.newType; const type = (args[0] as Utils.IntegerHolder);
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier; type.value = this.newType;
(args[1] as Utils.NumberHolder).value *= this.powerMultiplier;
return true; return true;
} }
@ -1080,8 +1097,8 @@ export class DamageBoostAbAttr extends PreAttackAbAttr {
* @param args Utils.NumberHolder as damage * @param args Utils.NumberHolder as damage
* @returns true if the function succeeds * @returns true if the function succeeds
*/ */
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) { if (this.condition(pokemon, defender, move.getMove())) {
const power = args[0] as Utils.NumberHolder; const power = args[0] as Utils.NumberHolder;
power.value = Math.floor(power.value * this.damageMultiplier); power.value = Math.floor(power.value * this.damageMultiplier);
return true; return true;
@ -1101,8 +1118,8 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr {
this.powerMultiplier = powerMultiplier; this.powerMultiplier = powerMultiplier;
} }
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) { if (this.condition(pokemon, defender, move.getMove())) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier; (args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
return true; return true;
@ -1148,8 +1165,8 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr {
/** /**
* @override * @override
*/ */
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
const multiplier = this.mult(pokemon, defender, move); const multiplier = this.mult(pokemon, defender, move.getMove());
if (multiplier !== 1) { if (multiplier !== 1) {
(args[0] as Utils.NumberHolder).value *= multiplier; (args[0] as Utils.NumberHolder).value *= multiplier;
return true; return true;
@ -1160,7 +1177,7 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr {
} }
export class FieldVariableMovePowerAbAttr extends AbAttr { export class FieldVariableMovePowerAbAttr extends AbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
//const power = args[0] as Utils.NumberHolder; //const power = args[0] as Utils.NumberHolder;
return false; return false;
} }
@ -1176,8 +1193,8 @@ export class FieldMovePowerBoostAbAttr extends FieldVariableMovePowerAbAttr {
this.powerMultiplier = powerMultiplier; this.powerMultiplier = powerMultiplier;
} }
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) { if (this.condition(pokemon, defender, move.getMove())) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier; (args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
return true; return true;
@ -1218,7 +1235,7 @@ export class BattleStatMultiplierAbAttr extends AbAttr {
} }
export class PostAttackAbAttr extends AbAttr { export class PostAttackAbAttr extends AbAttr {
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> { applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
@ -1232,9 +1249,9 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
this.condition = condition; this.condition = condition;
} }
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> { applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, defender, move))) { if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, defender, move.getMove()))) {
const heldItems = this.getTargetHeldItems(defender).filter(i => i.getTransferrable(false)); const heldItems = this.getTargetHeldItems(defender).filter(i => i.getTransferrable(false));
if (heldItems.length) { if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
@ -1270,8 +1287,8 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
this.effects = effects; this.effects = effects;
} }
applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) { if (pokemon !== attacker && (!this.contactRequired || move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) {
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)];
return attacker.trySetStatus(effect, true, pokemon); return attacker.trySetStatus(effect, true, pokemon);
} }
@ -1288,11 +1305,11 @@ export class PostAttackContactApplyStatusEffectAbAttr extends PostAttackApplySta
export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
private contactRequired: boolean; private contactRequired: boolean;
private chance: (user: Pokemon, target: Pokemon, move: Move) => integer; private chance: (user: Pokemon, target: Pokemon, move: PokemonMove) => integer;
private effects: BattlerTagType[]; private effects: BattlerTagType[];
constructor(contactRequired: boolean, chance: (user: Pokemon, target: Pokemon, move: Move) => integer, ...effects: BattlerTagType[]) { constructor(contactRequired: boolean, chance: (user: Pokemon, target: Pokemon, move: PokemonMove) => integer, ...effects: BattlerTagType[]) {
super(); super();
this.contactRequired = contactRequired; this.contactRequired = contactRequired;
@ -1300,8 +1317,8 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
this.effects = effects; this.effects = effects;
} }
applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) { if (pokemon !== attacker && (!this.contactRequired || move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) {
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)];
@ -1321,9 +1338,9 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
this.condition = condition; this.condition = condition;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move.getMove()))) {
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.getTransferrable(false)); const heldItems = this.getTargetHeldItems(attacker).filter(i => i.getTransferrable(false));
if (heldItems.length) { if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
@ -1985,9 +2002,9 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
* @param args [0] {@linkcode StatusEffect} applied by move * @param args [0] {@linkcode StatusEffect} applied by move
* @returns true if defender is confused * @returns true if defender is confused
*/ */
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (this.effects.indexOf(args[0]) > -1 && !defender.isFainted()) { if (this.effects.indexOf(args[0]) > -1 && !defender.isFainted()) {
return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3,2), move.id, defender.id); return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3,2), move.moveId, defender.id);
} }
return false; return false;
} }
@ -2590,7 +2607,7 @@ export class PostTurnLootAbAttr extends PostTurnAbAttr {
if (!berryModifier) { if (!berryModifier) {
pokemon.scene.addModifier(new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1)); pokemon.scene.addModifier(new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1));
} else if (berryModifier.stackCount < berryModifier.getMaxHeldItemCount(pokemon)) { } else {
berryModifier.stackCount++; berryModifier.stackCount++;
} }
@ -3011,7 +3028,7 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr {
} }
export class PostFaintAbAttr extends AbAttr { export class PostFaintAbAttr extends AbAttr {
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
return false; return false;
} }
} }
@ -3030,7 +3047,7 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr {
* @param args N/A * @param args N/A
* @returns {boolean} Returns true if the weather clears, otherwise false. * @returns {boolean} Returns true if the weather clears, otherwise false.
*/ */
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
const weatherType = pokemon.scene.arena.weather.weatherType; const weatherType = pokemon.scene.arena.weather.weatherType;
let turnOffWeather = false; let turnOffWeather = false;
@ -3074,8 +3091,8 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr {
this.damageRatio = damageRatio; this.damageRatio = damageRatio;
} }
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
pokemon.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)); pokemon.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled));
if (cancelled.value) { if (cancelled.value) {
@ -3102,7 +3119,7 @@ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr {
super (); super ();
} }
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
const damage = pokemon.turnData.attacksReceived[0].damage; const damage = pokemon.turnData.attacksReceived[0].damage;
attacker.damageAndUpdate((damage), HitResult.OTHER); attacker.damageAndUpdate((damage), HitResult.OTHER);
attacker.turnData.damageTaken += damage; attacker.turnData.damageTaken += damage;
@ -3439,7 +3456,7 @@ export class IceFaceMoveImmunityAbAttr extends MoveImmunityAbAttr {
* @param {any[]} args - Additional arguments. * @param {any[]} args - Additional arguments.
* @returns {boolean} - Whether the immunity was applied. * @returns {boolean} - Whether the immunity was applied.
*/ */
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const isImmune = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); const isImmune = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (isImmune) { if (isImmune) {
@ -3549,13 +3566,13 @@ export function applyPostBattleInitAbAttrs(attrType: { new(...args: any[]): Post
} }
export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefendAbAttr }, export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefendAbAttr },
pokemon: Pokemon, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> { pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
const simulated = args.length > 1 && args[1]; const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, false, simulated); return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, false, simulated);
} }
export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr }, export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr },
pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> { pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args); return applyAbAttrsInternal<PostDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args);
} }
@ -3570,12 +3587,12 @@ export function applyBattleStatMultiplierAbAttrs(attrType: { new(...args: any[])
} }
export function applyPreAttackAbAttrs(attrType: { new(...args: any[]): PreAttackAbAttr }, export function applyPreAttackAbAttrs(attrType: { new(...args: any[]): PreAttackAbAttr },
pokemon: Pokemon, defender: Pokemon, move: Move, ...args: any[]): Promise<void> { pokemon: Pokemon, defender: Pokemon, move: PokemonMove, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args); return applyAbAttrsInternal<PreAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args);
} }
export function applyPostAttackAbAttrs(attrType: { new(...args: any[]): PostAttackAbAttr }, export function applyPostAttackAbAttrs(attrType: { new(...args: any[]): PostAttackAbAttr },
pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> { pokemon: Pokemon, defender: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, defender, move, hitResult, args), args); return applyAbAttrsInternal<PostAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, defender, move, hitResult, args), args);
} }
@ -3656,7 +3673,7 @@ export function applyPostBattleAbAttrs(attrType: { new(...args: any[]): PostBatt
} }
export function applyPostFaintAbAttrs(attrType: { new(...args: any[]): PostFaintAbAttr }, export function applyPostFaintAbAttrs(attrType: { new(...args: any[]): PostFaintAbAttr },
pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> { pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostFaintAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, attacker, move, hitResult, args), args); return applyAbAttrsInternal<PostFaintAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, attacker, move, hitResult, args), args);
} }
@ -3675,7 +3692,7 @@ export const allAbilities = [ new Ability(Abilities.NONE, 3) ];
export function initAbilities() { export function initAbilities() {
allAbilities.push( allAbilities.push(
new Ability(Abilities.STENCH, 3) new Ability(Abilities.STENCH, 3)
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.category !== MoveCategory.STATUS && !move.hasAttr(FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED), .attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.getMove().category !== MoveCategory.STATUS && !move.getMove().hasAttr(FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED),
new Ability(Abilities.DRIZZLE, 3) new Ability(Abilities.DRIZZLE, 3)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN) .attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN), .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN),
@ -3812,7 +3829,7 @@ export function initAbilities() {
return false; return false;
}), }),
new Ability(Abilities.SOUNDPROOF, 3) new Ability(Abilities.SOUNDPROOF, 3)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.SOUND_BASED)) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.SOUND_BASED))
.ignorable(), .ignorable(),
new Ability(Abilities.RAIN_DISH, 3) new Ability(Abilities.RAIN_DISH, 3)
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN) .attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
@ -4098,13 +4115,13 @@ export function initAbilities() {
) )
.partial(), .partial(),
new Ability(Abilities.TELEPATHY, 5) new Ability(Abilities.TELEPATHY, 5)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move instanceof AttackMove) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move.getMove() instanceof AttackMove)
.ignorable(), .ignorable(),
new Ability(Abilities.MOODY, 5) new Ability(Abilities.MOODY, 5)
.attr(MoodyAbAttr), .attr(MoodyAbAttr),
new Ability(Abilities.OVERCOAT, 5) new Ability(Abilities.OVERCOAT, 5)
.attr(BlockWeatherDamageAttr) .attr(BlockWeatherDamageAttr)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.POWDER_MOVE)) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.POWDER_MOVE))
.ignorable(), .ignorable(),
new Ability(Abilities.POISON_TOUCH, 5) new Ability(Abilities.POISON_TOUCH, 5)
.attr(PostAttackContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON), .attr(PostAttackContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON),
@ -4193,14 +4210,14 @@ export function initAbilities() {
new Ability(Abilities.MAGICIAN, 6) new Ability(Abilities.MAGICIAN, 6)
.attr(PostAttackStealHeldItemAbAttr), .attr(PostAttackStealHeldItemAbAttr),
new Ability(Abilities.BULLETPROOF, 6) new Ability(Abilities.BULLETPROOF, 6)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.BALLBOMB_MOVE)) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.BALLBOMB_MOVE))
.ignorable(), .ignorable(),
new Ability(Abilities.COMPETITIVE, 6) new Ability(Abilities.COMPETITIVE, 6)
.attr(PostStatChangeStatChangeAbAttr, (target, statsChanged, levels) => levels < 0, [BattleStat.SPATK], 2), .attr(PostStatChangeStatChangeAbAttr, (target, statsChanged, levels) => levels < 0, [BattleStat.SPATK], 2),
new Ability(Abilities.STRONG_JAW, 6) new Ability(Abilities.STRONG_JAW, 6)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5), .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5),
new Ability(Abilities.REFRIGERATE, 6) new Ability(Abilities.REFRIGERATE, 6)
.attr(MoveTypeChangeAttr, Type.ICE, 1.2, (user, target, move) => move.type === Type.NORMAL), .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.ICE, 1.2),
new Ability(Abilities.SWEET_VEIL, 6) new Ability(Abilities.SWEET_VEIL, 6)
.attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
@ -4223,11 +4240,11 @@ export function initAbilities() {
new Ability(Abilities.TOUGH_CLAWS, 6) new Ability(Abilities.TOUGH_CLAWS, 6)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3), .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3),
new Ability(Abilities.PIXILATE, 6) new Ability(Abilities.PIXILATE, 6)
.attr(MoveTypeChangeAttr, Type.FAIRY, 1.2, (user, target, move) => move.type === Type.NORMAL), .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.FAIRY, 1.2),
new Ability(Abilities.GOOEY, 6) new Ability(Abilities.GOOEY, 6)
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), BattleStat.SPD, -1, false), .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), BattleStat.SPD, -1, false),
new Ability(Abilities.AERILATE, 6) new Ability(Abilities.AERILATE, 6)
.attr(MoveTypeChangeAttr, Type.FLYING, 1.2, (user, target, move) => move.type === Type.NORMAL), .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.FLYING, 1.2),
new Ability(Abilities.PARENTAL_BOND, 6) new Ability(Abilities.PARENTAL_BOND, 6)
.unimplemented(), .unimplemented(),
new Ability(Abilities.DARK_AURA, 6) new Ability(Abilities.DARK_AURA, 6)
@ -4297,7 +4314,7 @@ export function initAbilities() {
new Ability(Abilities.TRIAGE, 7) new Ability(Abilities.TRIAGE, 7)
.attr(IncrementMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3), .attr(IncrementMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3),
new Ability(Abilities.GALVANIZE, 7) new Ability(Abilities.GALVANIZE, 7)
.attr(MoveTypeChangeAttr, Type.ELECTRIC, 1.2, (user, target, move) => move.type === Type.NORMAL), .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.ELECTRIC, 1.2),
new Ability(Abilities.SURGE_SURFER, 7) new Ability(Abilities.SURGE_SURFER, 7)
.conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), BattleStatMultiplierAbAttr, BattleStat.SPD, 2), .conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), BattleStatMultiplierAbAttr, BattleStat.SPD, 2),
new Ability(Abilities.SCHOOLING, 7) new Ability(Abilities.SCHOOLING, 7)
@ -4465,7 +4482,7 @@ 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(IceFaceMoveImmunityAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE)) .attr(IceFaceMoveImmunityAbAttr, (target, user, move) => move.getMove().category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE))
.ignorable(), .ignorable(),
new Ability(Abilities.POWER_SPOT, 8) new Ability(Abilities.POWER_SPOT, 8)
.unimplemented(), .unimplemented(),
@ -4550,7 +4567,7 @@ export function initAbilities() {
.attr(TypeImmunityStatChangeAbAttr, Type.FIRE, BattleStat.DEF, 2) .attr(TypeImmunityStatChangeAbAttr, Type.FIRE, BattleStat.DEF, 2)
.ignorable(), .ignorable(),
new Ability(Abilities.WIND_RIDER, 9) new Ability(Abilities.WIND_RIDER, 9)
.attr(MoveImmunityStatChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.WIND_MOVE) && move.category !== MoveCategory.STATUS, BattleStat.ATK, 1) .attr(MoveImmunityStatChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.WIND_MOVE) && move.getMove().category !== MoveCategory.STATUS, BattleStat.ATK, 1)
.attr(PostSummonStatChangeOnArenaAbAttr, ArenaTagType.TAILWIND) .attr(PostSummonStatChangeOnArenaAbAttr, ArenaTagType.TAILWIND)
.ignorable(), .ignorable(),
new Ability(Abilities.GUARD_DOG, 9) new Ability(Abilities.GUARD_DOG, 9)
@ -4590,7 +4607,7 @@ export function initAbilities() {
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr)
.partial(), // While setting the tag, the getbattlestat should ignore all modifiers to stats except stat stages .partial(), // While setting the tag, the getbattlestat should ignore all modifiers to stats except stat stages
new Ability(Abilities.GOOD_AS_GOLD, 9) new Ability(Abilities.GOOD_AS_GOLD, 9)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.category === MoveCategory.STATUS) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().category === MoveCategory.STATUS)
.ignorable() .ignorable()
.partial(), .partial(),
new Ability(Abilities.VESSEL_OF_RUIN, 9) new Ability(Abilities.VESSEL_OF_RUIN, 9)

View File

@ -99,7 +99,6 @@ export default class Move implements Localizable {
public id: Moves; public id: Moves;
public name: string; public name: string;
public type: Type; public type: Type;
public defaultType: Type;
public category: MoveCategory; public category: MoveCategory;
public moveTarget: MoveTarget; public moveTarget: MoveTarget;
public power: integer; public power: integer;
@ -119,7 +118,6 @@ export default class Move implements Localizable {
this.nameAppend = ""; this.nameAppend = "";
this.type = type; this.type = type;
this.defaultType = type;
this.category = category; this.category = category;
this.moveTarget = defaultMoveTarget; this.moveTarget = defaultMoveTarget;
this.power = power; this.power = power;
@ -1631,7 +1629,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
} }
if ((!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0)) if ((!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0))
&& pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) { && pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null,this.effect); applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, new PokemonMove(move.id), null,this.effect);
return true; return true;
} }
} }
@ -3325,22 +3323,23 @@ export class TechnoBlastTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.GENESECT)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.GENESECT)) {
const form = user.species.speciesId === Species.GENESECT ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.GENESECT ? user.formIndex : user.fusionSpecies.formIndex;
const type = (args[0] as Utils.IntegerHolder);
switch (form) { switch (form) {
case 1: // Shock Drive case 1: // Shock Drive
move.type = Type.ELECTRIC; type.value = Type.ELECTRIC;
break; break;
case 2: // Burn Drive case 2: // Burn Drive
move.type = Type.FIRE; type.value = Type.FIRE;
break; break;
case 3: // Chill Drive case 3: // Chill Drive
move.type = Type.ICE; type.value = Type.ICE;
break; break;
case 4: // Douse Drive case 4: // Douse Drive
move.type = Type.WATER; type.value = Type.WATER;
break; break;
default: default:
move.type = Type.NORMAL; type.value = Type.NORMAL;
break; break;
} }
return true; return true;
@ -3354,13 +3353,14 @@ export class AuraWheelTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)) {
const form = user.species.speciesId === Species.MORPEKO ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.MORPEKO ? user.formIndex : user.fusionSpecies.formIndex;
const type = (args[0] as Utils.IntegerHolder);
switch (form) { switch (form) {
case 1: // Hangry Mode case 1: // Hangry Mode
move.type = Type.DARK; type.value = Type.DARK;
break; break;
default: // Full Belly Mode default: // Full Belly Mode
move.type = Type.ELECTRIC; type.value = Type.ELECTRIC;
break; break;
} }
return true; return true;
@ -3374,16 +3374,17 @@ export class RagingBullTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.PALDEA_TAUROS)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.PALDEA_TAUROS)) {
const form = user.species.speciesId === Species.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies.formIndex;
const type = (args[0] as Utils.IntegerHolder);
switch (form) { switch (form) {
case 1: // Blaze breed case 1: // Blaze breed
move.type = Type.FIRE; type.value = Type.FIRE;
break; break;
case 2: // Aqua breed case 2: // Aqua breed
move.type = Type.WATER; type.value = Type.WATER;
break; break;
default: default:
move.type = Type.FIGHTING; type.value = Type.FIGHTING;
break; break;
} }
return true; return true;
@ -3397,31 +3398,32 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.OGERPON)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.OGERPON)) {
const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies.formIndex;
const type = (args[0] as Utils.IntegerHolder);
switch (form) { switch (form) {
case 1: // Wellspring Mask case 1: // Wellspring Mask
move.type = Type.WATER; type.value = Type.WATER;
break; break;
case 2: // Hearthflame Mask case 2: // Hearthflame Mask
move.type = Type.FIRE; type.value = Type.FIRE;
break; break;
case 3: // Cornerstone Mask case 3: // Cornerstone Mask
move.type = Type.ROCK; type.value = Type.ROCK;
break; break;
case 4: // Teal Mask Tera case 4: // Teal Mask Tera
move.type = Type.GRASS; type.value = Type.GRASS;
break; break;
case 5: // Wellspring Mask Tera case 5: // Wellspring Mask Tera
move.type = Type.WATER; type.value = Type.WATER;
break; break;
case 6: // Hearthflame Mask Tera case 6: // Hearthflame Mask Tera
move.type = Type.FIRE; type.value = Type.FIRE;
break; break;
case 7: // Cornerstone Mask Tera case 7: // Cornerstone Mask Tera
move.type = Type.ROCK; type.value = Type.ROCK;
break; break;
default: default:
move.type = Type.GRASS; type.value = Type.GRASS;
break; break;
} }
return true; return true;
@ -3434,21 +3436,23 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr {
export class WeatherBallTypeAttr extends VariableMoveTypeAttr { export class WeatherBallTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) {
const type = (args[0] as Utils.IntegerHolder);
switch (user.scene.arena.weather?.weatherType) { switch (user.scene.arena.weather?.weatherType) {
case WeatherType.SUNNY: case WeatherType.SUNNY:
case WeatherType.HARSH_SUN: case WeatherType.HARSH_SUN:
move.type = Type.FIRE; type.value = Type.FIRE;
break; break;
case WeatherType.RAIN: case WeatherType.RAIN:
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
move.type = Type.WATER; type.value = Type.WATER;
break; break;
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
move.type = Type.ROCK; type.value = Type.ROCK;
break; break;
case WeatherType.HAIL: case WeatherType.HAIL:
case WeatherType.SNOW: case WeatherType.SNOW:
move.type = Type.ICE; type.value = Type.ICE;
break; break;
default: default:
return false; return false;
@ -3480,18 +3484,20 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr {
} }
const currentTerrain = user.scene.arena.getTerrainType(); const currentTerrain = user.scene.arena.getTerrainType();
const type = (args[0] as Utils.IntegerHolder);
switch (currentTerrain) { switch (currentTerrain) {
case TerrainType.MISTY: case TerrainType.MISTY:
move.type = Type.FAIRY; type.value = Type.FAIRY;
break; break;
case TerrainType.ELECTRIC: case TerrainType.ELECTRIC:
move.type = Type.ELECTRIC; type.value = Type.ELECTRIC;
break; break;
case TerrainType.GRASSY: case TerrainType.GRASSY:
move.type = Type.GRASS; type.value = Type.GRASS;
break; break;
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
move.type = Type.PSYCHIC; type.value = Type.PSYCHIC;
break; break;
default: default:
return false; return false;
@ -3502,6 +3508,8 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr {
export class HiddenPowerTypeAttr extends VariableMoveTypeAttr { export class HiddenPowerTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const type = (args[0] as Utils.IntegerHolder);
const iv_val = Math.floor(((user.ivs[Stat.HP] & 1) const iv_val = Math.floor(((user.ivs[Stat.HP] & 1)
+(user.ivs[Stat.ATK] & 1) * 2 +(user.ivs[Stat.ATK] & 1) * 2
+(user.ivs[Stat.DEF] & 1) * 4 +(user.ivs[Stat.DEF] & 1) * 4
@ -3509,7 +3517,7 @@ export class HiddenPowerTypeAttr extends VariableMoveTypeAttr {
+(user.ivs[Stat.SPATK] & 1) * 16 +(user.ivs[Stat.SPATK] & 1) * 16
+(user.ivs[Stat.SPDEF] & 1) * 32) * 15/63); +(user.ivs[Stat.SPDEF] & 1) * 32) * 15/63);
move.type = [ type.value = [
Type.FIGHTING, Type.FLYING, Type.POISON, Type.GROUND, Type.FIGHTING, Type.FLYING, Type.POISON, Type.GROUND,
Type.ROCK, Type.BUG, Type.GHOST, Type.STEEL, Type.ROCK, Type.BUG, Type.GHOST, Type.STEEL,
Type.FIRE, Type.WATER, Type.GRASS, Type.ELECTRIC, Type.FIRE, Type.WATER, Type.GRASS, Type.ELECTRIC,
@ -3521,14 +3529,16 @@ export class HiddenPowerTypeAttr extends VariableMoveTypeAttr {
export class MatchUserTypeAttr extends VariableMoveTypeAttr { export class MatchUserTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const type = (args[0] as Utils.IntegerHolder);
const userTypes = user.getTypes(true); const userTypes = user.getTypes(true);
if (userTypes.includes(Type.STELLAR)) { // will not change to stellar type if (userTypes.includes(Type.STELLAR)) { // will not change to stellar type
const nonTeraTypes = user.getTypes(); const nonTeraTypes = user.getTypes();
move.type = nonTeraTypes[0]; type.value = nonTeraTypes[0];
return true; return true;
} else if (userTypes.length > 0) { } else if (userTypes.length > 0) {
move.type = userTypes[0]; type.value = userTypes[0];
return true; return true;
} else { } else {
return false; return false;
@ -4335,7 +4345,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
// Check if the move category is not STATUS or if the switch out condition is not met // Check if the move category is not STATUS or if the switch out condition is not met
if (!this.getSwitchOutCondition()(user, target, move)) { if (!this.getSwitchOutCondition()(user, target, move)) {
//Apply effects before switch out i.e. poison point, flame body, etc //Apply effects before switch out i.e. poison point, flame body, etc
applyPostDefendAbAttrs(PostDefendContactApplyStatusEffectAbAttr, target, user, move, null); applyPostDefendAbAttrs(PostDefendContactApplyStatusEffectAbAttr, target, user, new PokemonMove(move.id), null);
return resolve(false); return resolve(false);
} }
@ -6005,7 +6015,7 @@ export function initMoves() {
], true) ], true)
.attr(RemoveArenaTrapAttr), .attr(RemoveArenaTrapAttr),
new StatusMove(Moves.SWEET_SCENT, Type.NORMAL, 100, 20, -1, 0, 2) new StatusMove(Moves.SWEET_SCENT, Type.NORMAL, 100, 20, -1, 0, 2)
.attr(StatChangeAttr, BattleStat.EVA, -2) .attr(StatChangeAttr, BattleStat.EVA, -1)
.target(MoveTarget.ALL_NEAR_ENEMIES), .target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.IRON_TAIL, Type.STEEL, MoveCategory.PHYSICAL, 100, 75, 15, 30, 0, 2) new AttackMove(Moves.IRON_TAIL, Type.STEEL, MoveCategory.PHYSICAL, 100, 75, 15, 30, 0, 2)
.attr(StatChangeAttr, BattleStat.DEF, -1), .attr(StatChangeAttr, BattleStat.DEF, -1),

View File

@ -363,8 +363,7 @@ export const pokemonFormChanges: PokemonFormChanges = {
new SpeciesFormChange(Species.PIDGEOT, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.PIDGEOTITE)) new SpeciesFormChange(Species.PIDGEOT, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.PIDGEOTITE))
], ],
[Species.PIKACHU]: [ [Species.PIKACHU]: [
new SpeciesFormChange(Species.PIKACHU, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)), new SpeciesFormChange(Species.PIKACHU, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))
new SpeciesFormChange(Species.PIKACHU, "partner", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))
], ],
[Species.MEOWTH]: [ [Species.MEOWTH]: [
new SpeciesFormChange(Species.MEOWTH, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) new SpeciesFormChange(Species.MEOWTH, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))
@ -398,8 +397,7 @@ export const pokemonFormChanges: PokemonFormChanges = {
new SpeciesFormChange(Species.LAPRAS, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) new SpeciesFormChange(Species.LAPRAS, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))
], ],
[Species.EEVEE]: [ [Species.EEVEE]: [
new SpeciesFormChange(Species.EEVEE, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)), new SpeciesFormChange(Species.EEVEE, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))
new SpeciesFormChange(Species.EEVEE, "partner", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))
], ],
[Species.SNORLAX]: [ [Species.SNORLAX]: [
new SpeciesFormChange(Species.SNORLAX, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) new SpeciesFormChange(Species.SNORLAX, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))

View File

@ -78,7 +78,7 @@ export const trainerNamePools = {
[TrainerType.ACE_TRAINER]: [["Aaron","Allen","Blake","Brian","Gaven","Jake","Kevin","Mike","Nick","Paul","Ryan","Sean","Darin","Albert","Berke","Clyde","Edgar","George","Leroy","Owen","Parker","Randall","Ruben","Samuel","Vincent","Warren","Wilton","Zane","Alfred","Braxton","Felix","Gerald","Jonathan","Leonel","Marcel","Mitchell","Quincy","Roderick","Colby","Rolando","Yuji","Abel","Anton","Arthur","Cesar","Dalton","Dennis","Ernest","Garrett","Graham","Henry","Isaiah","Jonah","Jose","Keenan","Micah","Omar","Quinn","Rodolfo","Saul","Sergio","Skylar","Stefan","Zachery","Alton","Arabella","Bonita","Cal","Cody","French","Kobe","Paulo","Shaye","Austin","Beckett","Charlie","Corky","David","Dwayne","Elmer","Jesse","Jared","Johan","Jordan","Kipp","Lou","Terry","Tom","Webster","Billy","Doyle","Enzio","Geoff","Grant","Kelsey","Miguel","Pierce","Ray","Santino","Shel","Adelbert","Bence","Emil","Evan","Mathis","Maxim","Neil","Rico","Robbie","Theo","Viktor","Benedict","Cornelius","Hisato","Leopold","Neville","Vito","Chase","Cole","Hiroshi","Jackson","Jim","Kekoa","Makana","Yuki","Elwood","Seth","Alvin","Arjun","Arnold","Cameron","Carl","Carlton","Christopher","Dave","Dax","Dominic","Edmund","Finn","Fred","Garret","Grayson","Jace","Jaxson","Jay","Jirard","Johnson","Kayden","Kite","Louis","Mac","Marty","Percy","Raymond","Ronnie","Satch","Tim","Zach","Conner","Vince","Bedro","Boda","Botan","Daras","Dury","Herton","Rewn","Stum","Tock","Trilo","Berki","Cruik","Dazon","Desid","Dillot","Farfin","Forgon","Hebel","Morfon","Moril","Shadd","Vanhub","Bardo","Carben","Degin","Gorps","Klept","Lask","Malex","Mopar","Niled","Noxon","Teslor","Tetil"],["Beth","Carol","Cybil","Emma","Fran","Gwen","Irene","Jenn","Joyce","Kate","Kelly","Lois","Lola","Megan","Quinn","Reena","Cara","Alexa","Brooke","Caroline","Elaine","Hope","Jennifer","Jody","Julie","Lori","Mary","Michelle","Shannon","Wendy","Alexia","Alicia","Athena","Carolina","Cristin","Darcy","Dianne","Halle","Jazmyn","Katelynn","Keira","Marley","Allyson","Kathleen","Naomi","Alyssa","Ariana","Brandi","Breanna","Brenda","Brenna","Catherine","Clarice","Dana","Deanna","Destiny","Jamie","Jasmin","Kassandra","Laura","Maria","Mariah","Maya","Meagan","Mikayla","Monique","Natasha","Olivia","Sandra","Savannah","Sydney","Moira","Piper","Salma","Allison","Beverly","Cathy","Cheyenne","Clara","Dara","Eileen","Glinda","Junko","Lena","Lucille","Mariana","Olwen","Shanta","Stella","Angi","Belle","Chandra","Cora","Eve","Jacqueline","Jeanne","Juliet","Kathrine","Layla","Lucca","Melina","Miki","Nina","Sable","Shelly","Summer","Trish","Vicki","Alanza","Cordelia","Hilde","Imelda","Michele","Mireille","Claudia","Constance","Harriet","Honor","Melba","Portia","Alexis","Angela","Karla","Lindsey","Tori","Sheri","Jada","Kailee","Amanda","Annie","Kindra","Kyla","Sofia","Yvette","Becky","Flora","Gloria","Buna","Ferda","Lehan","Liqui","Lomen","Neira","Atilo","Detta","Gilly","Gosney","Levens","Moden","Rask","Rateis","Rosno","Tynan","Veron","Zoel","Cida","Dibsin","Dodin","Ebson","Equin","Flostin","Gabsen","Halsion","Hileon","Quelor","Rapeel","Roze","Tensin"]], [TrainerType.ACE_TRAINER]: [["Aaron","Allen","Blake","Brian","Gaven","Jake","Kevin","Mike","Nick","Paul","Ryan","Sean","Darin","Albert","Berke","Clyde","Edgar","George","Leroy","Owen","Parker","Randall","Ruben","Samuel","Vincent","Warren","Wilton","Zane","Alfred","Braxton","Felix","Gerald","Jonathan","Leonel","Marcel","Mitchell","Quincy","Roderick","Colby","Rolando","Yuji","Abel","Anton","Arthur","Cesar","Dalton","Dennis","Ernest","Garrett","Graham","Henry","Isaiah","Jonah","Jose","Keenan","Micah","Omar","Quinn","Rodolfo","Saul","Sergio","Skylar","Stefan","Zachery","Alton","Arabella","Bonita","Cal","Cody","French","Kobe","Paulo","Shaye","Austin","Beckett","Charlie","Corky","David","Dwayne","Elmer","Jesse","Jared","Johan","Jordan","Kipp","Lou","Terry","Tom","Webster","Billy","Doyle","Enzio","Geoff","Grant","Kelsey","Miguel","Pierce","Ray","Santino","Shel","Adelbert","Bence","Emil","Evan","Mathis","Maxim","Neil","Rico","Robbie","Theo","Viktor","Benedict","Cornelius","Hisato","Leopold","Neville","Vito","Chase","Cole","Hiroshi","Jackson","Jim","Kekoa","Makana","Yuki","Elwood","Seth","Alvin","Arjun","Arnold","Cameron","Carl","Carlton","Christopher","Dave","Dax","Dominic","Edmund","Finn","Fred","Garret","Grayson","Jace","Jaxson","Jay","Jirard","Johnson","Kayden","Kite","Louis","Mac","Marty","Percy","Raymond","Ronnie","Satch","Tim","Zach","Conner","Vince","Bedro","Boda","Botan","Daras","Dury","Herton","Rewn","Stum","Tock","Trilo","Berki","Cruik","Dazon","Desid","Dillot","Farfin","Forgon","Hebel","Morfon","Moril","Shadd","Vanhub","Bardo","Carben","Degin","Gorps","Klept","Lask","Malex","Mopar","Niled","Noxon","Teslor","Tetil"],["Beth","Carol","Cybil","Emma","Fran","Gwen","Irene","Jenn","Joyce","Kate","Kelly","Lois","Lola","Megan","Quinn","Reena","Cara","Alexa","Brooke","Caroline","Elaine","Hope","Jennifer","Jody","Julie","Lori","Mary","Michelle","Shannon","Wendy","Alexia","Alicia","Athena","Carolina","Cristin","Darcy","Dianne","Halle","Jazmyn","Katelynn","Keira","Marley","Allyson","Kathleen","Naomi","Alyssa","Ariana","Brandi","Breanna","Brenda","Brenna","Catherine","Clarice","Dana","Deanna","Destiny","Jamie","Jasmin","Kassandra","Laura","Maria","Mariah","Maya","Meagan","Mikayla","Monique","Natasha","Olivia","Sandra","Savannah","Sydney","Moira","Piper","Salma","Allison","Beverly","Cathy","Cheyenne","Clara","Dara","Eileen","Glinda","Junko","Lena","Lucille","Mariana","Olwen","Shanta","Stella","Angi","Belle","Chandra","Cora","Eve","Jacqueline","Jeanne","Juliet","Kathrine","Layla","Lucca","Melina","Miki","Nina","Sable","Shelly","Summer","Trish","Vicki","Alanza","Cordelia","Hilde","Imelda","Michele","Mireille","Claudia","Constance","Harriet","Honor","Melba","Portia","Alexis","Angela","Karla","Lindsey","Tori","Sheri","Jada","Kailee","Amanda","Annie","Kindra","Kyla","Sofia","Yvette","Becky","Flora","Gloria","Buna","Ferda","Lehan","Liqui","Lomen","Neira","Atilo","Detta","Gilly","Gosney","Levens","Moden","Rask","Rateis","Rosno","Tynan","Veron","Zoel","Cida","Dibsin","Dodin","Ebson","Equin","Flostin","Gabsen","Halsion","Hileon","Quelor","Rapeel","Roze","Tensin"]],
[TrainerType.ARTIST]: [["Ismael","William","Horton","Pierre","Zach","Gough","Salvador","Vincent","Duncan"],["Georgia"]], [TrainerType.ARTIST]: [["Ismael","William","Horton","Pierre","Zach","Gough","Salvador","Vincent","Duncan"],["Georgia"]],
[TrainerType.BACKERS]: [["Alf & Fred","Hawk & Dar","Joe & Ross","Les & Web","Masa & Yas","Stu & Art"],["Ai & Ciel","Ami & Eira","Cam & Abby","Fey & Sue","Kat & Phae","Kay & Ali","Ava & Aya","Cleo & Rio","May & Mal"]], [TrainerType.BACKERS]: [["Alf & Fred","Hawk & Dar","Joe & Ross","Les & Web","Masa & Yas","Stu & Art"],["Ai & Ciel","Ami & Eira","Cam & Abby","Fey & Sue","Kat & Phae","Kay & Ali","Ava & Aya","Cleo & Rio","May & Mal"]],
[TrainerType.BACKPACKER]: [["Alexander","Carlos","Herman","Jerome","Keane","Kelsey","Kiyo","Michael","Nate","Peter","Sam","Stephen","Talon","Terrance","Toru","Waylon","Boone","Clifford","Ivan","Kendall","Lowell","Randall","Reece","Roland","Shane","Walt","Farid","Heike","Joren","Lane","Roderick","Darnell","Deon","Emory","Graeme","Grayson","Aitor","Alex","Arturo","Asier","Jaime","Jonathan","Julio","Kevin","Kosuke","Lander","Markel","Mateo","Nil","Pau","Samuel"],["Anna","Corin","Elaine","Emi","Jill","Kumiko","Liz","Lois","Lora","Molly","Patty","Ruth","Vicki","Annie","Blossom","Clara","Eileen","Mae","Myra","Rachel","Tami","Ashley","Mikiko","Kiana","Perdy","Maria","Yuho","Peren","Barbara","Diane"]], [TrainerType.BACKPACKER]: [["Alexander","Carlos","Herman","Jerome","Keane","Kelsey","Kiyo","Michael","Nate","Peter","Sam","Stephen","Talon","Terrance","Toru","Waylon","Boone","Clifford","Ivan","Kendall","Lowell","Randall","Reece","Roland","Shane","Walt","Farid","Heike","Joren","Lane","Roderick","Darnell","Deon","Emory","Graeme","Grayson","Ashley","Mikiko","Kiana","Perdy","Maria","Yuho","Peren","Barbara","Diane","Ruth","Aitor","Alex","Arturo","Asier","Jaime","Jonathan","Julio","Kevin","Kosuke","Lander","Markel","Mateo","Nil","Pau","Samuel"],["Anna","Corin","Elaine","Emi","Jill","Kumiko","Liz","Lois","Lora","Molly","Patty","Ruth","Vicki","Annie","Blossom","Clara","Eileen","Mae","Myra","Rachel","Tami"]],
[TrainerType.BAKER]: ["Chris","Jenn","Lilly"], [TrainerType.BAKER]: ["Chris","Jenn","Lilly"],
[TrainerType.BEAUTY]: ["Cassie","Julia","Olivia","Samantha","Valerie","Victoria","Bridget","Connie","Jessica","Johanna","Melissa","Sheila","Shirley","Tiffany","Namiko","Thalia","Grace","Lola","Lori","Maura","Tamia","Cyndy","Devon","Gabriella","Harley","Lindsay","Nicola","Callie","Charlotte","Kassandra","December","Fleming","Nikola","Aimee","Anais","Brigitte","Cassandra","Andrea","Brittney","Carolyn","Krystal","Alexis","Alice","Aina","Anya","Arianna","Aubrey","Beverly","Camille","Beauty","Evette","Hansol","Haruka","Jill","Jo","Lana","Lois","Lucy","Mai","Nickie","Nicole","Prita","Rose","Shelly","Suzy","Tessa","Anita","Alissa","Rita","Cudsy","Eloff","Miru","Minot","Nevah","Niven","Ogoin"], [TrainerType.BEAUTY]: ["Cassie","Julia","Olivia","Samantha","Valerie","Victoria","Bridget","Connie","Jessica","Johanna","Melissa","Sheila","Shirley","Tiffany","Namiko","Thalia","Grace","Lola","Lori","Maura","Tamia","Cyndy","Devon","Gabriella","Harley","Lindsay","Nicola","Callie","Charlotte","Kassandra","December","Fleming","Nikola","Aimee","Anais","Brigitte","Cassandra","Andrea","Brittney","Carolyn","Krystal","Alexis","Alice","Aina","Anya","Arianna","Aubrey","Beverly","Camille","Beauty","Evette","Hansol","Haruka","Jill","Jo","Lana","Lois","Lucy","Mai","Nickie","Nicole","Prita","Rose","Shelly","Suzy","Tessa","Anita","Alissa","Rita","Cudsy","Eloff","Miru","Minot","Nevah","Niven","Ogoin"],
[TrainerType.BIKER]: ["Charles","Dwayne","Glenn","Harris","Joel","Riley","Zeke","Alex","Billy","Ernest","Gerald","Hideo","Isaac","Jared","Jaren","Jaxon","Jordy","Lao","Lukas","Malik","Nikolas","Ricardo","Ruben","Virgil","William","Aiden","Dale","Dan","Jacob","Markey","Reese","Teddy","Theron","Jeremy","Morgann","Phillip","Philip","Stanley","Dillon"], [TrainerType.BIKER]: ["Charles","Dwayne","Glenn","Harris","Joel","Riley","Zeke","Alex","Billy","Ernest","Gerald","Hideo","Isaac","Jared","Jaren","Jaxon","Jordy","Lao","Lukas","Malik","Nikolas","Ricardo","Ruben","Virgil","William","Aiden","Dale","Dan","Jacob","Markey","Reese","Teddy","Theron","Jeremy","Morgann","Phillip","Philip","Stanley","Dillon"],
@ -93,7 +93,7 @@ export const trainerNamePools = {
[TrainerType.FISHERMAN]: ["Andre","Arnold","Barney","Chris","Edgar","Henry","Jonah","Justin","Kyle","Martin","Marvin","Ralph","Raymond","Scott","Stephen","Wilton","Tully","Andrew","Barny","Carter","Claude","Dale","Elliot","Eugene","Ivan","Ned","Nolan","Roger","Ronald","Wade","Wayne","Darian","Kai","Chip","Hank","Kaden","Tommy","Tylor","Alec","Brett","Cameron","Cody","Cole","Cory","Erick","George","Joseph","Juan","Kenneth","Luc","Miguel","Travis","Walter","Zachary","Josh","Gideon","Kyler","Liam","Murphy","Bruce","Damon","Devon","Hubert","Jones","Lydon","Mick","Pete","Sean","Sid","Vince","Bucky","Dean","Eustace","Kenzo","Leroy","Mack","Ryder","Ewan","Finn","Murray","Seward","Shad","Wharton","Finley","Fisher","Fisk","River","Sheaffer","Timin","Carl","Ernest","Hal","Herbert","Hisato","Mike","Vernon","Harriet","Marina","Chase"], [TrainerType.FISHERMAN]: ["Andre","Arnold","Barney","Chris","Edgar","Henry","Jonah","Justin","Kyle","Martin","Marvin","Ralph","Raymond","Scott","Stephen","Wilton","Tully","Andrew","Barny","Carter","Claude","Dale","Elliot","Eugene","Ivan","Ned","Nolan","Roger","Ronald","Wade","Wayne","Darian","Kai","Chip","Hank","Kaden","Tommy","Tylor","Alec","Brett","Cameron","Cody","Cole","Cory","Erick","George","Joseph","Juan","Kenneth","Luc","Miguel","Travis","Walter","Zachary","Josh","Gideon","Kyler","Liam","Murphy","Bruce","Damon","Devon","Hubert","Jones","Lydon","Mick","Pete","Sean","Sid","Vince","Bucky","Dean","Eustace","Kenzo","Leroy","Mack","Ryder","Ewan","Finn","Murray","Seward","Shad","Wharton","Finley","Fisher","Fisk","River","Sheaffer","Timin","Carl","Ernest","Hal","Herbert","Hisato","Mike","Vernon","Harriet","Marina","Chase"],
[TrainerType.GUITARIST]: ["Anna","Beverly","January","Tina","Alicia","Claudia","Julia","Lidia","Mireia","Noelia","Sara","Sheila","Tatiana"], [TrainerType.GUITARIST]: ["Anna","Beverly","January","Tina","Alicia","Claudia","Julia","Lidia","Mireia","Noelia","Sara","Sheila","Tatiana"],
[TrainerType.HARLEQUIN]: ["Charley","Ian","Jack","Kerry","Louis","Pat","Paul","Rick","Anders","Clarence","Gary"], [TrainerType.HARLEQUIN]: ["Charley","Ian","Jack","Kerry","Louis","Pat","Paul","Rick","Anders","Clarence","Gary"],
[TrainerType.HIKER]: ["Anthony","Bailey","Benjamin","Daniel","Erik","Jim","Kenny","Leonard","Michael","Parry","Phillip","Russell","Sidney","Tim","Timothy","Alan","Brice","Clark","Eric","Lenny","Lucas","Mike","Trent","Devan","Eli","Marc","Sawyer","Allen","Daryl","Dudley","Earl","Franklin","Jeremy","Marcos","Nob","Oliver","Wayne","Alexander","Damon","Jonathan","Justin","Kevin","Lorenzo","Louis","Maurice","Nicholas","Reginald","Robert","Theodore","Bruce","Clarke","Devin","Dwight","Edwin","Eoin","Noland","Russel","Andy","Bret","Darrell","Gene","Hardy","Hugh","Jebediah","Jeremiah","Kit","Neil","Terrell","Don","Doug","Hunter","Jared","Jerome","Keith","Manuel","Markus","Otto","Shelby","Stephen","Teppei","Tobias","Wade","Zaiem","Aaron","Alain","Bergin","Bernard","Brent","Corwin","Craig","Delmon","Dunstan","Orestes","Ross","Davian","Calhoun","David","Gabriel","Ryan","Thomas","Travis","Zachary","Anuhea","Barnaby","Claus","Collin","Colson","Dexter","Dillan","Eugine","Farkas","Hisato","Julius","Kenji","Irwin","Lionel","Paul","Richter","Valentino","Donald","Douglas","Kevyn","Chester"], //["Angela","Carla","Celia","Daniela","Estela","Fatima","Helena","Leire","Lucia","Luna","Manuela","Mar","Marina","Miyu","Nancy","Nerea","Paula","Rocio","Yanira"] [TrainerType.HIKER]: ["Anthony","Bailey","Benjamin","Daniel","Erik","Jim","Kenny","Leonard","Michael","Parry","Phillip","Russell","Sidney","Tim","Timothy","Alan","Brice","Clark","Eric","Lenny","Lucas","Mike","Trent","Devan","Eli","Marc","Sawyer","Allen","Daryl","Dudley","Earl","Franklin","Jeremy","Marcos","Nob","Oliver","Wayne","Alexander","Damon","Jonathan","Justin","Kevin","Lorenzo","Louis","Maurice","Nicholas","Reginald","Robert","Theodore","Bruce","Clarke","Devin","Dwight","Edwin","Eoin","Noland","Russel","Andy","Bret","Darrell","Gene","Hardy","Hugh","Jebediah","Jeremiah","Kit","Neil","Terrell","Don","Doug","Hunter","Jared","Jerome","Keith","Manuel","Markus","Otto","Shelby","Stephen","Teppei","Tobias","Wade","Zaiem","Aaron","Alain","Bergin","Bernard","Brent","Corwin","Craig","Delmon","Dunstan","Orestes","Ross","Davian","Calhoun","David","Gabriel","Ryan","Thomas","Travis","Zachary","Anuhea","Barnaby","Claus","Collin","Colson","Dexter","Dillan","Eugine","Farkas","Hisato","Julius","Kenji","Irwin","Lionel","Paul","Richter","Valentino","Donald","Douglas","Kevyn","Angela","Carla","Celia","Daniela","Estela","Fatima","Helena","Leire","Lucia","Luna","Manuela","Mar","Marina","Miyu","Nancy","Nerea","Paula","Rocio","Yanira","Chester"],
[TrainerType.HOOLIGANS]: ["Jim & Cas","Rob & Sal"], [TrainerType.HOOLIGANS]: ["Jim & Cas","Rob & Sal"],
[TrainerType.HOOPSTER]: ["Bobby","John","Lamarcus","Derrick","Nicolas"], [TrainerType.HOOPSTER]: ["Bobby","John","Lamarcus","Derrick","Nicolas"],
[TrainerType.INFIELDER]: ["Alex","Connor","Todd"], [TrainerType.INFIELDER]: ["Alex","Connor","Todd"],

View File

@ -4,7 +4,7 @@ import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
import { variantData } from "#app/data/variant"; import { variantData } from "#app/data/variant";
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info";
import { Moves } from "../data/enums/moves"; import { Moves } from "../data/enums/moves";
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, VariableMoveTypeAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags } from "../data/move"; import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags } from "../data/move";
import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type"; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
@ -27,7 +27,7 @@ import { TempBattleStat } from "../data/temp-battle-stat";
import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "../data/arena-tag"; import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "../data/arena-tag";
import { ArenaTagType } from "../data/enums/arena-tag-type"; import { ArenaTagType } from "../data/enums/arena-tag-type";
import { Biome } from "../data/enums/biome"; import { Biome } from "../data/enums/biome";
import { Ability, AbAttr, BattleStatMultiplierAbAttr, MoveTypeChangeAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr } from "../data/ability"; import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr } from "../data/ability";
import { Abilities } from "#app/data/enums/abilities"; import { Abilities } from "#app/data/enums/abilities";
import PokemonData from "../system/pokemon-data"; import PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
@ -1057,10 +1057,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return !this.isOfType(Type.FLYING, true) && !this.hasAbility(Abilities.LEVITATE); return !this.isOfType(Type.FLYING, true) && !this.hasAbility(Abilities.LEVITATE);
} }
getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove): TypeDamageMultiplier { getAttackMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier {
const move = pokemonMove.getMove(); const typeless = move.getMove().hasAttr(TypelessAttr);
const typeless = move.hasAttr(TypelessAttr); const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.getMove().type, source));
const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.type, source));
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
if (!typeless) { if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true); applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true);
@ -1621,8 +1620,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return (this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.getFieldIndex() ? 0 : 1]; return (this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.getFieldIndex() ? 0 : 1];
} }
apply(source: Pokemon, move: Move): HitResult { apply(source: Pokemon, battlerMove: PokemonMove): HitResult {
let result: HitResult; let result: HitResult;
const move = battlerMove.getMove();
const damage = new Utils.NumberHolder(0); const damage = new Utils.NumberHolder(0);
const defendingSidePlayField = this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField(); const defendingSidePlayField = this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField();
@ -1630,15 +1630,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory); applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory);
const moveCategory = variableCategory.value as MoveCategory; const moveCategory = variableCategory.value as MoveCategory;
const variableType = new Utils.IntegerHolder(move.type);
const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1);
applyMoveAttrs(VariableMoveTypeAttr, source, this, move); applyMoveAttrs(VariableMoveTypeAttr, source, this, move, variableType);
applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, move, typeChangeMovePowerMultiplier); // 2nd argument is for MoveTypeChangePowerMultiplierAbAttr
applyAbAttrs(VariableMoveTypeAbAttr, source, null, variableType, typeChangeMovePowerMultiplier);
applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, battlerMove, variableType, typeChangeMovePowerMultiplier);
const type = variableType.value as Type;
const types = this.getTypes(true, true); const types = this.getTypes(true, true);
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
const typeless = move.hasAttr(TypelessAttr); const typeless = move.hasAttr(TypelessAttr);
const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType))) const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType)))
? this.getAttackTypeEffectiveness(move.type, source) ? this.getAttackTypeEffectiveness(type, source)
: 1); : 1);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
if (typeless) { if (typeless) {
@ -1663,44 +1667,44 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const isPhysical = moveCategory === MoveCategory.PHYSICAL; const isPhysical = moveCategory === MoveCategory.PHYSICAL;
const power = new Utils.NumberHolder(move.power); const power = new Utils.NumberHolder(move.power);
const sourceTeraType = source.getTeraType(); const sourceTeraType = source.getTeraType();
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
power.value = 60; power.value = 60;
} }
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, move, power); applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power);
this.scene.getField(true).map(p => applyPreAttackAbAttrs(FieldVariableMovePowerAbAttr, this, source, move, power)); this.scene.getField(true).map(p => applyPreAttackAbAttrs(FieldVariableMovePowerAbAttr, this, source, battlerMove, power));
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, power); applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, battlerMove, cancelled, power);
power.value *= typeChangeMovePowerMultiplier.value; power.value *= typeChangeMovePowerMultiplier.value;
if (!typeless) { if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier); applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
} }
if (!cancelled.value) { if (!cancelled.value) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier); applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, typeMultiplier)); defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier));
} }
if (cancelled.value) { if (cancelled.value) {
result = HitResult.NO_EFFECT; result = HitResult.NO_EFFECT;
} else { } else {
const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === move.type) as TypeBoostTag; const typeBoost = source.findTag(t => t instanceof TypeBoostTag && (t as TypeBoostTag).boostedType === type) as TypeBoostTag;
if (typeBoost) { if (typeBoost) {
power.value *= typeBoost.boostValue; power.value *= typeBoost.boostValue;
if (typeBoost.oneUse) { if (typeBoost.oneUse) {
source.removeTag(typeBoost.tagType); source.removeTag(typeBoost.tagType);
} }
} }
const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(move.type, source.isGrounded())); const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(type, source.isGrounded()));
applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier);
if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && this.isGrounded() && move.type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) { if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && this.isGrounded() && type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) {
power.value /= 2; power.value /= 2;
} }
applyMoveAttrs(VariablePowerAttr, source, this, move, power); applyMoveAttrs(VariablePowerAttr, source, this, move, power);
this.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power); this.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power);
if (!typeless) { if (!typeless) {
this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power); this.scene.arena.applyTags(WeakenMoveTypeTag, type, power);
this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, move.type, power); this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, type, power);
} }
if (source.getTag(HelpingHandTag)) { if (source.getTag(HelpingHandTag)) {
power.value *= 1.5; power.value *= 1.5;
@ -1727,9 +1731,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))]; const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))];
isCritical = !source.getTag(BattlerTagType.NO_CRIT) && (critChance === 1 || !this.scene.randBattleSeedInt(critChance)); isCritical = !source.getTag(BattlerTagType.NO_CRIT) && (critChance === 1 || !this.scene.randBattleSeedInt(critChance));
if (Overrides.NEVER_CRIT_OVERRIDE) {
isCritical = false;
}
} }
if (isCritical) { if (isCritical) {
const blockCrit = new Utils.BooleanHolder(false); const blockCrit = new Utils.BooleanHolder(false);
@ -1748,11 +1749,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0; const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0;
const sourceTypes = source.getTypes(); const sourceTypes = source.getTypes();
const matchesSourceType = sourceTypes[0] === move.type || (sourceTypes.length > 1 && sourceTypes[1] === move.type); const matchesSourceType = sourceTypes[0] === type || (sourceTypes.length > 1 && sourceTypes[1] === type);
const stabMultiplier = new Utils.NumberHolder(1); const stabMultiplier = new Utils.NumberHolder(1);
if (sourceTeraType === Type.UNKNOWN && matchesSourceType) { if (sourceTeraType === Type.UNKNOWN && matchesSourceType) {
stabMultiplier.value += 0.5; stabMultiplier.value += 0.5;
} else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type) { } else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type) {
stabMultiplier.value += 0.5; stabMultiplier.value += 0.5;
} }
@ -1777,7 +1778,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, move, damage); applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, battlerMove, damage);
/** /**
* For each {@link HitsTagAttr} the move has, doubles the damage of the move if: * For each {@link HitsTagAttr} the move has, doubles the damage of the move if:
@ -1792,7 +1793,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}); });
} }
if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && move.type === Type.DRAGON) { if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && type === Type.DRAGON) {
damage.value = Math.floor(damage.value / 2); damage.value = Math.floor(damage.value / 2);
} }
@ -1847,7 +1848,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const oneHitKo = result === HitResult.ONE_HIT_KO; const oneHitKo = result === HitResult.ONE_HIT_KO;
if (damage.value) { if (damage.value) {
if (this.getHpRatio() === 1) { if (this.getHpRatio() === 1) {
applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, damage); applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, battlerMove, cancelled, damage);
} else if (!this.isPlayer() && damage.value >= this.hp) { } else if (!this.isPlayer() && damage.value >= this.hp) {
this.scene.applyModifiers(EnemyEndureChanceModifier, false, this); this.scene.applyModifiers(EnemyEndureChanceModifier, false, this);
} }
@ -1912,11 +1913,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
break; break;
case MoveCategory.STATUS: case MoveCategory.STATUS:
if (!typeless) { if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier); applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
} }
if (!cancelled.value) { if (!cancelled.value) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier); applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, typeMultiplier)); defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier));
} }
if (!typeMultiplier.value) { if (!typeMultiplier.value) {
this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: this.name })); this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: this.name }));
@ -3380,6 +3381,10 @@ export class EnemyPokemon extends Pokemon {
const pokemonMove = movePool[m]; const pokemonMove = movePool[m];
const move = pokemonMove.getMove(); const move = pokemonMove.getMove();
const variableType = new Utils.IntegerHolder(move.type);
applyAbAttrs(VariableMoveTypeAbAttr, this, null, variableType);
const moveType = variableType.value as Type;
let moveScore = moveScores[m]; let moveScore = moveScores[m];
const targetScores: integer[] = []; const targetScores: integer[] = [];
@ -3397,12 +3402,12 @@ export class EnemyPokemon extends Pokemon {
const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove); const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove);
if (target.isPlayer() !== this.isPlayer()) { if (target.isPlayer() !== this.isPlayer()) {
targetScore *= effectiveness; targetScore *= effectiveness;
if (this.isOfType(move.type)) { if (this.isOfType(moveType)) {
targetScore *= 1.5; targetScore *= 1.5;
} }
} else if (effectiveness) { } else if (effectiveness) {
targetScore /= effectiveness; targetScore /= effectiveness;
if (this.isOfType(move.type)) { if (this.isOfType(moveType)) {
targetScore /= 1.5; targetScore /= 1.5;
} }
} }
@ -3784,19 +3789,6 @@ export enum HitResult {
export type DamageResult = HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE | HitResult.NOT_VERY_EFFECTIVE | HitResult.ONE_HIT_KO | HitResult.OTHER; export type DamageResult = HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE | HitResult.NOT_VERY_EFFECTIVE | HitResult.ONE_HIT_KO | HitResult.OTHER;
/**
* Wrapper class for the {@linkcode Move} class for Pokemon to interact with.
* These are the moves assigned to a {@linkcode Pokemon} object.
* It links to {@linkcode Move} class via the move ID.
* Compared to {@linkcode Move}, this class also tracks if a move has received.
* PP Ups, amount of PP used, and things like that.
* @see {@linkcode isUsable} - checks if move is disabled, out of PP, or not implemented.
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
* @see {@linkcode usePp} - removes a point of PP from the move.
* @see {@linkcode getMovePp} - returns amount of PP a move currently has.
* @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount.
* @see {@linkcode getName} - returns name of {@linkcode Move}.
**/
export class PokemonMove { export class PokemonMove {
public moveId: Moves; public moveId: Moves;
public ppUsed: integer; public ppUsed: integer;

View File

@ -94,6 +94,7 @@ export class InputsController {
private buttonLock: Button; private buttonLock: Button;
private interactions: Map<Button, Map<string, boolean>> = new Map(); private interactions: Map<Button, Map<string, boolean>> = new Map();
private time: Phaser.Time.Clock;
private configs: Map<string, InterfaceConfig> = new Map(); private configs: Map<string, InterfaceConfig> = new Map();
public gamepadSupport: boolean = true; public gamepadSupport: boolean = true;
@ -120,6 +121,7 @@ export class InputsController {
constructor(scene: BattleScene) { constructor(scene: BattleScene) {
this.scene = scene; this.scene = scene;
this.time = this.scene.time;
this.selectedDevice = { this.selectedDevice = {
[Device.GAMEPAD]: null, [Device.GAMEPAD]: null,
[Device.KEYBOARD]: "default" [Device.KEYBOARD]: "default"
@ -244,9 +246,6 @@ export class InputsController {
* If an interaction is valid and should be processed, it emits an 'input_down' event with details of the interaction. * If an interaction is valid and should be processed, it emits an 'input_down' event with details of the interaction.
*/ */
update(): void { update(): void {
if (this.pauseUpdate) {
return;
}
for (const b of Utils.getEnumValues(Button).reverse()) { for (const b of Utils.getEnumValues(Button).reverse()) {
if ( if (
this.interactions.hasOwnProperty(b) && this.interactions.hasOwnProperty(b) &&
@ -257,7 +256,8 @@ export class InputsController {
if ( if (
(!this.gamepadSupport && this.interactions[b].source === "gamepad") || (!this.gamepadSupport && this.interactions[b].source === "gamepad") ||
(this.interactions[b].source === "gamepad" && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.GAMEPAD]) || (this.interactions[b].source === "gamepad" && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.GAMEPAD]) ||
(this.interactions[b].source === "keyboard" && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.KEYBOARD]) (this.interactions[b].source === "keyboard" && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.KEYBOARD]) ||
this.pauseUpdate
) { ) {
// Deletes the last interaction for a button if gamepad is disabled. // Deletes the last interaction for a button if gamepad is disabled.
this.delLastProcessedMovementTime(b as Button); this.delLastProcessedMovementTime(b as Button);
@ -548,8 +548,7 @@ export class InputsController {
if (!this.isButtonLocked(button)) { if (!this.isButtonLocked(button)) {
return false; return false;
} }
const duration = Date.now() - this.interactions[button].pressTime; if (this.time.now - this.interactions[button].pressTime >= repeatInputDelayMillis) {
if (duration >= repeatInputDelayMillis) {
return true; return true;
} }
} }
@ -574,7 +573,7 @@ export class InputsController {
return; return;
} }
this.setButtonLock(button); this.setButtonLock(button);
this.interactions[button].pressTime = Date.now(); this.interactions[button].pressTime = this.time.now;
this.interactions[button].isPressed = true; this.interactions[button].isPressed = true;
this.interactions[button].source = source; this.interactions[button].source = source;
this.interactions[button].sourceName = sourceName.toLowerCase(); this.interactions[button].sourceName = sourceName.toLowerCase();
@ -634,7 +633,7 @@ export class InputsController {
this.interactions[b].sourceName = null; this.interactions[b].sourceName = null;
} }
} }
this.pauseUpdate = false; setTimeout(() => this.pauseUpdate = false, 500);
} }
/** /**

View File

@ -16,11 +16,9 @@ import {initPokemonForms} from "#app/data/pokemon-forms";
import {initSpecies} from "#app/data/pokemon-species"; import {initSpecies} from "#app/data/pokemon-species";
import {initMoves} from "#app/data/move"; import {initMoves} from "#app/data/move";
import {initAbilities} from "#app/data/ability"; import {initAbilities} from "#app/data/ability";
import {initAchievements} from "#app/system/achv";
import {initTrainerTypeDialogue} from "#app/data/dialogue"; import {initTrainerTypeDialogue} from "#app/data/dialogue";
import i18next from "i18next"; import i18next from "i18next";
import { initStatsKeys } from "./ui/game-stats-ui-handler"; import { initStatsKeys } from "./ui/game-stats-ui-handler";
import { initVouchers } from "./system/voucher";
export class LoadingScene extends SceneBase { export class LoadingScene extends SceneBase {
constructor() { constructor() {
@ -137,6 +135,7 @@ export class LoadingScene extends SceneBase {
this.loadImage("summary_stats_overlay_exp", "ui"); this.loadImage("summary_stats_overlay_exp", "ui");
this.loadImage("summary_moves", "ui"); this.loadImage("summary_moves", "ui");
this.loadImage("summary_moves_effect", "ui"); this.loadImage("summary_moves_effect", "ui");
this.loadImage("summary_moves_effect_type", "ui");
this.loadImage("summary_moves_overlay_row", "ui"); this.loadImage("summary_moves_overlay_row", "ui");
this.loadImage("summary_moves_overlay_pp", "ui"); this.loadImage("summary_moves_overlay_pp", "ui");
this.loadAtlas("summary_moves_cursor", "ui"); this.loadAtlas("summary_moves_cursor", "ui");
@ -330,8 +329,6 @@ export class LoadingScene extends SceneBase {
this.loadLoadingScreen(); this.loadLoadingScreen();
initVouchers();
initAchievements();
initStatsKeys(); initStatsKeys();
initPokemonPrevolutions(); initPokemonPrevolutions();
initBiomes(); initBiomes();

View File

@ -369,26 +369,26 @@ export const PGMdialogue: DialogueTranslationEntries = {
}, },
"firebreather": { "firebreather": {
"encounter": { "encounter": {
1: "Meine Flammen werden dich verschlingen!", 1: "My flames shall devour you!",
2: "Meine Seele hat Feuer gefangen. Ich werde dir zeigen, wie heiß sie brennt!", 2: "My soul is on fire. I'll show you how hot it burns!",
3: "Komm näher und sieh dir meine Flammen an!" 3: "Step right up and take a look!"
}, },
"victory": { "victory": {
1: "Verbrannt bis zur Asche...", 1: "I burned down to ashes...",
2: "Yow! Das ist heiß!", 2: "Yow! That's hot!",
3: "Auuu! Ich habe mir die Nasenspitze verbrannt!" 3: "Ow! I scorched the tip of my nose!"
}, },
}, },
"sailor": { "sailor": {
"encounter": { "encounter": {
1: "Matrose, du gehst über Bord, wenn du verlierst!", 1: "Matey, you're walking the plank if you lose!",
2: "Komm schon! Mein Stolz als Seemann steht auf dem Spiel!", 2: "Come on then! My sailor's pride is at stake!",
3: "Ahoj! Bist du seekrank?" 3: "Ahoy there! Are you seasick?"
}, },
"victory": { "victory": {
1: "Argh! Von einem Kind besiegt!", 1: "Argh! Beaten by a kid!",
2: "Dein Geist hat mich versenkt!", 2: "Your spirit sank me!",
3: "Ich glaube, ich bin der der seekrank ist..." 3: "I think it's me that's seasick..."
}, },
}, },
"brock": { "brock": {

View File

@ -283,7 +283,7 @@ export const pokemon: SimpleTranslationEntries = {
"ralts": "Trasla", "ralts": "Trasla",
"kirlia": "Kirlia", "kirlia": "Kirlia",
"gardevoir": "Gardevoir", "gardevoir": "Gardevoir",
"surskit": "Gehweiher", "surskit": "Geweiher",
"masquerain": "Maskeregen", "masquerain": "Maskeregen",
"shroomish": "Knilz", "shroomish": "Knilz",
"breloom": "Kapilz", "breloom": "Kapilz",

View File

@ -243,64 +243,64 @@ export const PGMdialogue: DialogueTranslationEntries = {
}, },
"scientist": { "scientist": {
"encounter": { "encounter": {
1: "제 연구는 이 세상을 평화와 기쁨으로 이끌 겁니다.", 1: "My research will lead this world to peace and joy.",
}, },
"victory": { "victory": {
1: "전 천재니까… 당신 같은 사람에게 질 수 없는데…", 1: "I am a genius… I am not supposed to lose against someone like you…",
}, },
}, },
"school_kid": { "school_kid": {
"encounter": { "encounter": {
1: "…헤헷. 계산과 분석에는 자신 있어.", 1: "…Heehee. I'm confident in my calculations and analysis.",
2: "언젠가 체육관 관장이 되고 싶어서, 최대한 많은 경험을 쌓고 있어." 2: "I'm gaining as much experience as I can because I want to be a Gym Leader someday."
}, },
"victory": { "victory": {
1: "으아아… 이번에는 아마 계산과 분석이 빗나간 것 같아…", 1: "Ohhhh… Calculation and analysis are perhaps no match for chance…",
2: "내가 보기엔, 어렵고 힘든 경험도 나름의 의미가 있는 것 같아." 2: "Even difficult, trying experiences have their purpose, I suppose."
} }
}, },
"artist": { "artist": {
"encounter": { "encounter": {
1: "예전엔 인기가 많았지만, 지금은 모두 사라졌다네.", 1: "I used to be popular, but now I am all washed up.",
}, },
"victory": { "victory": {
1: "시대가 변하면, 가치관도 변하지. 난 그걸 너무 늦게 깨달았어.", 1: "As times change, values also change. I realized that too late.",
}, },
}, },
"guitarist": { "guitarist": {
"encounter": { "encounter": {
1: "패배의 리듬을 느낄 준비는 됐겠지? 내가 승리할 거니까!", 1: "Get ready to feel the rhythm of defeat as I strum my way to victory!",
}, },
"victory": { "victory": {
1: "지금은 조용하지만, 회복의 멜로디를 연주할 거야.", 1: "Silenced for now, but my melody of resilience will play on.",
}, },
}, },
"worker": { "worker": {
"encounter": { "encounter": {
1: "사람들이 저를 오해하는 게 신경 쓰여요. 전 생각보다 훨씬 깨끗하답니다.", 1: "It bothers me that people always misunderstand me. I'm a lot more pure than everyone thinks.",
}, },
"victory": { "victory": {
1: "피부가 타는 게 싫어서, 일하는 동안엔 그늘에 머물고 싶어요.", 1: "I really don't want my skin to burn, so I want to stay in the shade while I work.",
}, },
}, },
"worker_female": { "worker_female": {
"encounter": { "encounter": {
1: `사람들이 나를 오해하는 게 신경 쓰여. 1: `It bothers me that people always misunderstand me.
$나는 .` $I'm a lot more pure than everyone thinks.`
}, },
"victory": { "victory": {
1: "피부가 타는 게 싫어서, 일하는 동안엔 그늘에 머물고 싶어." 1: "I really don't want my skin to burn, so I want to stay in the shade while I work."
}, },
"defeat": { "defeat": {
1: "생각처럼 몸이 잘 안따라주네." 1: "My body and mind aren't necessarily always in sync."
} }
}, },
"worker_double": { "worker_double": {
"encounter": { "encounter": {
1: "너를 무너뜨릴 수 있다는 것을 보여줄게. 우리는 실전 경험이 있거든!", 1: "I'll show you we can break you. We've been training in the field!",
}, },
"victory": { "victory": {
1: "이상하네… 어떻게 이럴 수 있지… 힘으로 압도할 수 없다니.", 1: "How strange… How could this be… I shouldn't have been outmuscled.",
}, },
}, },
"hex_maniac": { "hex_maniac": {
@ -697,19 +697,19 @@ export const PGMdialogue: DialogueTranslationEntries = {
}, },
"falkner": { "falkner": {
"encounter": { "encounter": {
1: "넓은 하늘을 화려하게 나는 새 포켓몬의 진정한 강함을 알게 해주겠다!", 1: "I'll show you the real power of the magnificent bird Pokémon!",
2: "바람이여, 나에게 오라!", 2: "Winds, stay with me!",
3: "아버지, 내 시합을 하늘에서도 봐줘!" 3: "Dad! I hope you're watching me battle from above!"
}, },
"victory": { "victory": {
1: "알았다… 미련없이 땅에 내려가지.", 1: "I understand… I'll bow out gracefully.",
2: "패배는 패배니까. 넌 정말 강하군.", 2: "A defeat is a defeat. You are strong indeed.",
3: "…큭! 그래, 내가 졌다." 3: "…Shoot! Yeah, I lost."
}, },
"defeat": { "defeat": {
1: "아버지! 소중히 여기던 새 포켓몬으로 이겼어…", 1: "Dad! I won with your cherished bird Pokémon…",
2: "언제나 새 포켓몬이 최강이다!", 2: "Bird Pokémon are the best after all!",
3: "아버지를 따라 잡은 기분이군!" 3: "Feels like I'm catching up to my dad!"
} }
}, },
"nessa": { "nessa": {
@ -874,34 +874,34 @@ export const PGMdialogue: DialogueTranslationEntries = {
}, },
"morty": { "morty": {
"encounter": { "encounter": {
1: `조금만 더 노력하면, 내가 전설의 포켓몬을 만나는 미래가 보여. 1: `With a little more, I could see a future in which I meet the legendary Pokémon.
$내가 !`, $You're going to help me reach that level!`,
2: `커다란 무지개색 포켓몬은 진정한 강함을 가진 트레이너 앞에 나타난다는 이야기가 있어. 2: `It's said that a rainbow-hued Pokémon will come down to appear before a truly powerful Trainer.
$ 믿, . , . $I believed that tale, so I have secretly trained here all my life. As a result, I can now see what others cannot.
$내겐 . $I see a shadow of the person who will make the Pokémon appear.
$ 믿! !`, $I believe that person is me! You're going to help me reach that level!`,
3: "네가 믿든 믿지 않든, 불가사의한 힘은 존재해.", 3: "Whether you choose to believe or not, mystic power does exist.",
4: "넌 내 수련의 결실을 보게 될 거야.", 4: "You can bear witness to the fruits of my training.",
5: "포켓몬과 너의 영혼을 하나로 만들어야 해. 가능하겠어?", 5: "You must make your soul one with that of Pokémon. Can you do this?",
6: "저기, 너 내 수행의 일부분이 되고 싶은거지?" 6: "Say, do you want to be part of my training?"
}, },
"victory": { "victory": {
1: "나는 아직 멀었구나…", 1: "I'm not good enough yet…",
2: `그래… 여행으로 먼 곳을 돌아다니면서, 나보다 훨씬 많은 것을 봐왔구나. 2: `I see… Your journey has taken you to far-away places and you have witnessed much more than I.
$네가 `, $I envy you for that`,
3: "이게 어떻게 가능한 거지…", 3: "How is this possible…",
4: `우리의 잠재력은 그렇게 다르진 않은 것 같아. 4: `I don't think our potentials are so different.
$그치만 .`, $But you seem to have something more than that So be it.`,
5: "수련이 더 필요하겠군.", 5: "Guess I need more training.",
6: "안타깝게 됐네." 6: "That's a shame."
}, },
"defeat": { "defeat": {
1: "내가…다시 한 발짝 앞섰어.", 1: "I moved… one step ahead again.",
2: "후후훗…", 2: "Fufufu…",
3: "뭐-뭐야?! 이럴 수가! 그것도 부족해?", 3: "Wh-what?! It can't be! Even that wasn't enough?",
4: "정말 단단한 바위를 뚫고 나온 기분인데!", 4: "I feel like I just smashed through a really stubborn boulder!",
5: "아하하하하!", 5: "Ahahahah!",
6: "내가 이길 줄 알았어!" 6: "I knew I would win!"
} }
}, },
"crispin": { "crispin": {
@ -1146,26 +1146,26 @@ export const PGMdialogue: DialogueTranslationEntries = {
}, },
"lorelei": { "lorelei": {
"encounter": { "encounter": {
1: `얼음포켓몬을 내보내면 대적할 사람이 없지! 상대를 얼린다는 건 매우 강력한 공격이야. 1: `No one can best me when it comes to icy Pokémon! Freezing moves are powerful!
$ ! ! ?`, $Your Pokémon will be at my mercy when they are frozen solid! Hahaha! Are you ready?`,
}, },
"victory": { "victory": {
1: "어떻게 감히!" 1: "How dare you!"
}, },
"defeat": { "defeat": {
1: "얼어붙은 넌 아무것도 할 수 없어." 1: "There's nothing you can do once you're frozen."
} }
}, },
"will": { "will": {
"encounter": { "encounter": {
1: `나는 전세계를 돌아다니며, 강한 에스퍼 포켓몬을 만들도록 수행해왔다. 1: `I have trained all around the world, making my psychic Pokémon powerful.
$계속 ! !`, $I can only keep getting better! Losing is not an option!`,
}, },
"victory": { "victory": {
1: "이… 내가… 믿을수 없어…" 1: "I… I can't… believe it…"
}, },
"defeat": { "defeat": {
1: "근소한 차이였다. 네게 부족한 것이 무엇인지 궁금하군." 1: "That was close. I wonder what it is that you lack."
} }
}, },
"malva": { "malva": {
@ -1216,35 +1216,35 @@ export const PGMdialogue: DialogueTranslationEntries = {
}, },
"bruno": { "bruno": {
"encounter": { "encounter": {
1: "우월한 힘으로 너를 부숴주지! 우! 하~앗!" 1: "We will grind you down with our superior power! Hoo hah!"
}, },
"victory": { "victory": {
1: "하? 어떻게 내가 진 거지?" 1: "Why? How could I lose?"
}, },
"defeat": { "defeat": {
1: "얼마든지 내게 도전 할 수 있지만, 결과는 절대 바뀌지 않을 거다!" 1: "You can challenge me all you like, but the results will never change!"
} }
}, },
"bugsy": { "bugsy": {
"encounter": { "encounter": {
1: "내 이름은 호일! 벌레 포켓몬에 대해서라면 누구에게도 지지 않아!" 1: "I'm Bugsy! I never lose when it comes to bug Pokémon!"
}, },
"victory": { "victory": {
1: "우와, 대단해! 넌 포켓몬 전문가구나!\n내 연구는 아직 안 끝났네. 응, 네가 이겼어." 1: "Whoa, amazing! You're an expert on Pokémon!\nMy research isn't complete yet. OK, you win."
}, },
"defeat": { "defeat": {
1: "고마워! 방금 승부 덕분에, 내 연구도 진전을 이룬 것 같아!" 1: "Thanks! Thanks to our battle, I was also able to make progress in my research!"
} }
}, },
"koga": { "koga": {
"encounter": { "encounter": {
1: "후하하하! 포켓몬은 딘순히 강한 것만이 아니다--곧 알려주지!" 1: "Fwahahahaha! Pokémon are not merely about brute force--you shall see soon enough!"
}, },
"victory": { "victory": {
1: "하! 스스로 증명해냈군!" 1: "Ah! You've proven your worth!"
}, },
"defeat": { "defeat": {
1: "인술을 피하는 방법을 배워보겠나?" 1: "Have you learned to fear the techniques of the ninja?"
} }
}, },
"bertha": { "bertha": {
@ -1319,13 +1319,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
}, },
"agatha": { "agatha": {
"encounter": { "encounter": {
1: "포켓몬은 싸우게 하려고 있는 것이야! 진정한 싸움이라는 것을 보여주겠다!" 1: "Pokémon are for battling! I'll show you how a real Trainer battles!"
}, },
"victory": { "victory": {
1: "이런! 넌 무언가 특별하구나, 꼬마야!" 1: "Oh my! You're something special, child!"
}, },
"defeat": { "defeat": {
1: "바하하하. 제대로 된 승부는 이렇게 하는거다!" 1: "Bahaha. That's how a proper battle's done!"
} }
}, },
"flint": { "flint": {
@ -1414,33 +1414,33 @@ export const PGMdialogue: DialogueTranslationEntries = {
}, },
"lance": { "lance": {
"encounter": { "encounter": {
1: "널 기다리고 있었다. 그 실력을 시험해보겠어.", 1: "I've been waiting for you. Allow me to test your skill.",
2: "여기까지 올 수 있을거라고 생각했다. 슬슬 시작해볼까." 2: "I thought that you would be able to get this far. Let's get this started."
}, },
"victory": { "victory": {
1: "날 따라잡았군. 훌륭해!", 1: "You got me. You are magnificent!",
2: "다른 트레이너가 날 이길 거라곤 생각 못했는데… 놀랍군." 2: "I never expected another trainer to beat me… I'm surprised."
}, },
"defeat": { "defeat": {
1: "근소하군. 다시 해볼까?", 1: "That was close. Want to try again?",
2: "네가 약해서가 아니다. 신경쓰지 말도록." 2: "It's not that you are weak. Don't let it bother you."
} }
}, },
"karen": { "karen": {
"encounter": { "encounter": {
1: "난 카렌! 내 악 타입 포켓몬과의 승부를 원하니?", 1: "I am Karen. Would you care for a showdown with my Dark-type Pokémon?",
2: "난 네가 이전에 만났던 트레이너들과는 달라.", 2: "I am unlike those you've already met.",
3: "강한 포켓몬, 약한 포켓몬, 그런 건 사람이 멋대로 정하는 것." 3: "You've assembled a charming team. Our battle should be a good one."
}, },
"victory": { "victory": {
1: "좋아하는 마음이 전해진다면 포켓몬도 답할거야. 그렇게 강해지는 거지", 1: "No! I can't win. How did you become so strong?",
2: "난 내가 선택한 길을 걸어갈거야.", 2: "I will not stray from my chosen path.",
3: "챔피언이 너를 기다리고 있어." 3: "The Champion is looking forward to meeting you."
}, },
"defeat": { "defeat": {
1: "정말 강한 트레이너라면 좋아하는 포켓몬으로 이길 수 있도록 열심히 해야 해.", 1: "That's about what I expected.",
2: "뭐, 비교적 재밌었어.", 2: "Well, that was relatively entertaining.",
3: "언제라도 다시 찾아와, 상대해줄게." 3: "Come visit me anytime."
} }
}, },
"milo": { "milo": {
@ -1507,13 +1507,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
}, },
"blue": { "blue": {
"encounter": { "encounter": {
1: "여기까지 왔다니, 실력이 꽤 봐줄만 할 것 같은데." 1: "You must be pretty good to get this far."
}, },
"victory": { "victory": {
1: "그 녀석한테만 지는 줄 알았는데… 누구냐고? 하, 하…" 1: "I've only lost to him and now to you… Him? Hee, hee…"
}, },
"defeat": { "defeat": {
1: "봤지? 여기까지 온 내 실력." 1: "See? My power is what got me here."
} }
}, },
"piers": { "piers": {
@ -1540,24 +1540,24 @@ export const PGMdialogue: DialogueTranslationEntries = {
}, },
"jasmine": { "jasmine": {
"encounter": { "encounter": {
1: "와… 당신의 포켓몬은 인상적이네요. 재미있을 것 같아요." 1: "Oh… Your Pokémon are impressive. I think I will enjoy this."
}, },
"victory": { "victory": {
1: "당신은 정말 강하네요. 저도 더 열심히 노력해야겠어요." 1: "You are truly strong. I'll have to try much harder, too."
}, },
"defeat": { "defeat": {
1: "이길 줄은 몰랐어요." 1: "I never expected to win."
} }
}, },
"lance_champion": { "lance_champion": {
"encounter": { "encounter": {
1: "여전히 난 챔피언이다. 더이상 주저할 게 없군." 1: "I am still the Champion. I won't hold anything back."
}, },
"victory": { "victory": {
1: "새로운 챔피언의 등장이군." 1: "This is the emergence of a new Champion."
}, },
"defeat": { "defeat": {
1: "성공적으로 챔피언 자리를 지켜냈다." 1: "I successfully defended my Championship."
} }
}, },
"steven": { "steven": {
@ -1652,24 +1652,24 @@ export const PGMdialogue: DialogueTranslationEntries = {
}, },
"whitney": { "whitney": {
"encounter": { "encounter": {
1: "있지! 포켓몬들 말이야, 정말 너무 귀엽지?" 1: "Hey! Don't you think Pokémon are, like, super cute?"
}, },
"victory": { "victory": {
1: "흑! 으아앙! 너무해!" 1: "Waaah! Waaah! You're so mean!"
}, },
"defeat": { "defeat": {
1: "이걸로 끝!" 1: "And that's that!"
} }
}, },
"chuck": { "chuck": {
"encounter": { "encounter": {
1: "하! 나에게 도전하겠다고? 용감한 거냐, 아니면 그냥 무모한 거냐?" 1: "Hah! You want to challenge me? Are you brave or just ignorant?"
}, },
"victory": { "victory": {
1: "자네 강하군! 나를 제자로 삼아주겠나?" 1: "You're strong! Would you please make me your apprentice?"
}, },
"defeat": { "defeat": {
1: "자. 내가 자네보다 얼마나 더 강력한지 깨달았겠지?" 1: "There. Do you realize how much more powerful I am than you?"
} }
}, },
"katy": { "katy": {
@ -1685,24 +1685,24 @@ export const PGMdialogue: DialogueTranslationEntries = {
}, },
"pryce": { "pryce": {
"encounter": { "encounter": {
1: "젊음만으로는 승리를 보장할 수 없다! 중요한 것은 경험이다." 1: "Youth alone does not ensure victory! Experience is what counts."
}, },
"victory": { "victory": {
1: "특출하군! 완벽해. 지금 이 느낌을 잊지 말도록." 1: "Outstanding! That was perfect. Try not to forget what you feel now."
}, },
"defeat": { "defeat": {
1: "내가 예상했던 그대로군." 1: "Just as I envisioned."
} }
}, },
"clair": { "clair": {
"encounter": { "encounter": {
1: "내가 누군지 알지? 그런데도 감히 내게 도전해?" 1: "Do you know who I am? And you still dare to challenge me?"
}, },
"victory": { "victory": {
1: "네 실력이 어디까지 올라갈 수 있는지 궁금하네. 아주 흥미진진하겠어." 1: "I wonder how far you can get with your skill level. This should be fascinating."
}, },
"defeat": { "defeat": {
1: "끝이다." 1: "That's that."
} }
}, },
"maylene": { "maylene": {

View File

@ -25,7 +25,6 @@ import i18next from "#app/plugins/i18n";
import { getModifierTierTextTint } from "#app/ui/text"; import { getModifierTierTextTint } from "#app/ui/text";
import { BattlerTagType } from "#app/data/enums/battler-tag-type.js"; import { BattlerTagType } from "#app/data/enums/battler-tag-type.js";
import * as Overrides from "../overrides"; import * as Overrides from "../overrides";
import { MoneyMultiplierModifier } from "./modifier";
const outputModifierData = false; const outputModifierData = false;
const useMaxWeightForOutput = false; const useMaxWeightForOutput = false;
@ -632,13 +631,9 @@ export class MoneyRewardModifierType extends ModifierType {
} }
getDescription(scene: BattleScene): string { getDescription(scene: BattleScene): string {
const moneyAmount = new Utils.IntegerHolder(scene.getWaveMoneyAmount(this.moneyMultiplier));
scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount);
const formattedMoney = Utils.formatMoney(scene.moneyFormat, moneyAmount.value);
return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", { return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", {
moneyMultiplier: i18next.t(this.moneyMultiplierDescriptorKey as any), moneyMultiplier: i18next.t(this.moneyMultiplierDescriptorKey as any),
moneyAmount: formattedMoney, moneyAmount: scene.getWaveMoneyAmount(this.moneyMultiplier).toLocaleString("en-US"),
}); });
} }
} }

View File

@ -29,7 +29,6 @@ import { modifierTypes } from "./modifier/modifier-type";
export const SEED_OVERRIDE: string = ""; export const SEED_OVERRIDE: string = "";
export const WEATHER_OVERRIDE: WeatherType = WeatherType.NONE; export const WEATHER_OVERRIDE: WeatherType = WeatherType.NONE;
export const DOUBLE_BATTLE_OVERRIDE: boolean = false; export const DOUBLE_BATTLE_OVERRIDE: boolean = false;
export const SINGLE_BATTLE_OVERRIDE: boolean = false;
export const STARTING_WAVE_OVERRIDE: integer = 0; export const STARTING_WAVE_OVERRIDE: integer = 0;
export const STARTING_BIOME_OVERRIDE: Biome = Biome.TOWN; export const STARTING_BIOME_OVERRIDE: Biome = Biome.TOWN;
export const ARENA_TINT_OVERRIDE: TimeOfDay = null; export const ARENA_TINT_OVERRIDE: TimeOfDay = null;
@ -111,7 +110,6 @@ export const OPP_MODIFIER_OVERRIDE: Array<ModifierOverride> = [];
export const STARTING_HELD_ITEMS_OVERRIDE: Array<ModifierOverride> = []; export const STARTING_HELD_ITEMS_OVERRIDE: Array<ModifierOverride> = [];
export const OPP_HELD_ITEMS_OVERRIDE: Array<ModifierOverride> = []; export const OPP_HELD_ITEMS_OVERRIDE: Array<ModifierOverride> = [];
export const NEVER_CRIT_OVERRIDE: boolean = false;
/** /**
* An array of items by keys as defined in the "modifierTypes" object in the "modifier/modifier-type.ts" file. * An array of items by keys as defined in the "modifierTypes" object in the "modifier/modifier-type.ts" file.

View File

@ -149,7 +149,7 @@ export class LoginPhase extends Phase {
export class TitlePhase extends Phase { export class TitlePhase extends Phase {
private loaded: boolean; private loaded: boolean;
private lastSessionData: SessionSaveData; private lastSessionData: SessionSaveData;
public gameMode: GameModes; private gameMode: GameModes;
constructor(scene: BattleScene) { constructor(scene: BattleScene) {
super(scene); super(scene);
@ -527,63 +527,60 @@ export class SelectStarterPhase extends Phase {
return this.end(); return this.end();
} }
this.scene.sessionSlotId = slotId; this.scene.sessionSlotId = slotId;
this.initBattle(starters);
const party = this.scene.getParty();
const loadPokemonAssets: Promise<void>[] = [];
starters.forEach((starter: Starter, i: integer) => {
if (!i && Overrides.STARTER_SPECIES_OVERRIDE) {
starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species);
}
const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr);
let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0));
if (!i && Overrides.STARTER_SPECIES_OVERRIDE) {
starterFormIndex = Overrides.STARTER_FORM_OVERRIDE;
}
let starterGender = starter.species.malePercent !== null
? !starterProps.female ? Gender.MALE : Gender.FEMALE
: Gender.GENDERLESS;
if (Overrides.GENDER_OVERRIDE !== null) {
starterGender = Overrides.GENDER_OVERRIDE;
}
const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0);
const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature);
starterPokemon.tryPopulateMoveset(starter.moveset);
if (starter.passive) {
starterPokemon.passive = true;
}
starterPokemon.luck = this.scene.gameData.getDexAttrLuck(this.scene.gameData.dexData[starter.species.speciesId].caughtAttr);
if (starter.pokerus) {
starterPokemon.pokerus = true;
}
if (this.scene.gameMode.isSplicedOnly) {
starterPokemon.generateFusionSpecies(true);
}
starterPokemon.setVisible(false);
party.push(starterPokemon);
loadPokemonAssets.push(starterPokemon.loadAssets());
});
overrideModifiers(this.scene);
overrideHeldItems(this.scene, party[0]);
Promise.all(loadPokemonAssets).then(() => {
SoundFade.fadeOut(this.scene, this.scene.sound.get("menu"), 500, true);
this.scene.time.delayedCall(500, () => this.scene.playBgm());
if (this.scene.gameMode.isClassic) {
this.scene.gameData.gameStats.classicSessionsPlayed++;
} else {
this.scene.gameData.gameStats.endlessSessionsPlayed++;
}
this.scene.newBattle();
this.scene.arena.init();
this.scene.sessionPlayTime = 0;
this.scene.lastSavePlayTime = 0;
this.end();
});
}); });
}, this.gameMode); }, this.gameMode);
} }
initBattle(starters: Starter[]) {
const party = this.scene.getParty();
const loadPokemonAssets: Promise<void>[] = [];
starters.forEach((starter: Starter, i: integer) => {
if (!i && Overrides.STARTER_SPECIES_OVERRIDE) {
starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species);
}
const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr);
let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0));
if (!i && Overrides.STARTER_SPECIES_OVERRIDE) {
starterFormIndex = Overrides.STARTER_FORM_OVERRIDE;
}
let starterGender = starter.species.malePercent !== null
? !starterProps.female ? Gender.MALE : Gender.FEMALE
: Gender.GENDERLESS;
if (Overrides.GENDER_OVERRIDE !== null) {
starterGender = Overrides.GENDER_OVERRIDE;
}
const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0);
const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature);
starterPokemon.tryPopulateMoveset(starter.moveset);
if (starter.passive) {
starterPokemon.passive = true;
}
starterPokemon.luck = this.scene.gameData.getDexAttrLuck(this.scene.gameData.dexData[starter.species.speciesId].caughtAttr);
if (starter.pokerus) {
starterPokemon.pokerus = true;
}
if (this.scene.gameMode.isSplicedOnly) {
starterPokemon.generateFusionSpecies(true);
}
starterPokemon.setVisible(false);
party.push(starterPokemon);
loadPokemonAssets.push(starterPokemon.loadAssets());
});
overrideModifiers(this.scene);
overrideHeldItems(this.scene, party[0]);
Promise.all(loadPokemonAssets).then(() => {
SoundFade.fadeOut(this.scene, this.scene.sound.get("menu"), 500, true);
this.scene.time.delayedCall(500, () => this.scene.playBgm());
if (this.scene.gameMode.isClassic) {
this.scene.gameData.gameStats.classicSessionsPlayed++;
} else {
this.scene.gameData.gameStats.endlessSessionsPlayed++;
}
this.scene.newBattle();
this.scene.arena.init();
this.scene.sessionPlayTime = 0;
this.scene.lastSavePlayTime = 0;
this.end();
});
}
} }
export class BattlePhase extends Phase { export class BattlePhase extends Phase {
@ -2713,10 +2710,9 @@ export class MoveEffectPhase extends PokemonPhase {
} }
const overridden = new Utils.BooleanHolder(false); const overridden = new Utils.BooleanHolder(false);
const move = this.move.getMove();
// Assume single target for override // Assume single target for override
applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), move, overridden, this.move.virtual).then(() => { applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), this.move.getMove(), overridden, this.move.virtual).then(() => {
if (overridden.value) { if (overridden.value) {
return this.end(); return this.end();
@ -2727,8 +2723,8 @@ export class MoveEffectPhase extends PokemonPhase {
if (user.turnData.hitsLeft === undefined) { if (user.turnData.hitsLeft === undefined) {
const hitCount = new Utils.IntegerHolder(1); const hitCount = new Utils.IntegerHolder(1);
// Assume single target for multi hit // Assume single target for multi hit
applyMoveAttrs(MultiHitAttr, user, this.getTarget(), move, hitCount); applyMoveAttrs(MultiHitAttr, user, this.getTarget(), this.move.getMove(), hitCount);
if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) { if (this.move.getMove() instanceof AttackMove && !this.move.getMove().hasAttr(FixedDamageAttr)) {
this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0)); this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0));
} }
user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value; user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value;
@ -2739,13 +2735,13 @@ export class MoveEffectPhase extends PokemonPhase {
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
const activeTargets = targets.map(t => t.isActive(true)); const activeTargets = targets.map(t => t.isActive(true));
if (!activeTargets.length || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) { if (!activeTargets.length || (!this.move.getMove().hasAttr(VariableTargetAttr) && !this.move.getMove().isMultiTarget() && !targetHitChecks[this.targets[0]])) {
user.turnData.hitCount = 1; user.turnData.hitCount = 1;
user.turnData.hitsLeft = 1; user.turnData.hitsLeft = 1;
if (activeTargets.length) { if (activeTargets.length) {
this.scene.queueMessage(getPokemonMessage(user, "'s\nattack missed!")); this.scene.queueMessage(getPokemonMessage(user, "'s\nattack missed!"));
moveHistoryEntry.result = MoveResult.MISS; moveHistoryEntry.result = MoveResult.MISS;
applyMoveAttrs(MissEffectAttr, user, null, move); applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove());
} else { } else {
this.scene.queueMessage(i18next.t("battle:attackFailed")); this.scene.queueMessage(i18next.t("battle:attackFailed"));
moveHistoryEntry.result = MoveResult.FAIL; moveHistoryEntry.result = MoveResult.FAIL;
@ -2756,7 +2752,7 @@ export class MoveEffectPhase extends PokemonPhase {
const applyAttrs: Promise<void>[] = []; const applyAttrs: Promise<void>[] = [];
// Move animation only needs one target // Move animation only needs one target
new MoveAnim(move.id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => { new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => {
for (const target of targets) { for (const target of targets) {
if (!targetHitChecks[target.getBattlerIndex()]) { if (!targetHitChecks[target.getBattlerIndex()]) {
user.turnData.hitCount = 1; user.turnData.hitCount = 1;
@ -2765,31 +2761,31 @@ export class MoveEffectPhase extends PokemonPhase {
if (moveHistoryEntry.result === MoveResult.PENDING) { if (moveHistoryEntry.result === MoveResult.PENDING) {
moveHistoryEntry.result = MoveResult.MISS; moveHistoryEntry.result = MoveResult.MISS;
} }
applyMoveAttrs(MissEffectAttr, user, null, move); applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove());
continue; continue;
} }
const isProtected = !move.hasFlag(MoveFlags.IGNORE_PROTECT) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)); const isProtected = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType));
const firstHit = moveHistoryEntry.result !== MoveResult.SUCCESS; const firstHit = moveHistoryEntry.result !== MoveResult.SUCCESS;
moveHistoryEntry.result = MoveResult.SUCCESS; moveHistoryEntry.result = MoveResult.SUCCESS;
const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT; const hitResult = !isProtected ? target.apply(user, this.move) : HitResult.NO_EFFECT;
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
applyAttrs.push(new Promise(resolve => { applyAttrs.push(new Promise(resolve => {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit), applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit),
user, target, move).then(() => { user, target, this.move.getMove()).then(() => {
if (hitResult !== HitResult.FAIL) { if (hitResult !== HitResult.FAIL) {
const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), move)); const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), this.move.getMove()));
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present // Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move)).then(() => { && (attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove())).then(() => {
if (hitResult !== HitResult.NO_EFFECT) { if (hitResult !== HitResult.NO_EFFECT) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& !attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move).then(() => { && !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()).then(() => {
if (hitResult < HitResult.NO_EFFECT) { if (hitResult < HitResult.NO_EFFECT) {
const flinched = new Utils.BooleanHolder(false); const flinched = new Utils.BooleanHolder(false);
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
@ -2797,15 +2793,15 @@ export class MoveEffectPhase extends PokemonPhase {
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id); target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
} }
} }
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit), Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit),
user, target, move).then(() => { user, target, this.move.getMove()).then(() => {
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, move, hitResult).then(() => { return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult).then(() => {
if (!user.isPlayer() && move instanceof AttackMove) { if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
} }
})).then(() => { })).then(() => {
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, move, hitResult).then(() => { applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult).then(() => {
if (move instanceof AttackMove) { if (this.move.getMove() instanceof AttackMove) {
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex()); this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex());
} }
resolve(); resolve();
@ -2815,7 +2811,7 @@ export class MoveEffectPhase extends PokemonPhase {
).then(() => resolve()); ).then(() => resolve());
}); });
} else { } else {
applyMoveAttrs(NoEffectAttr, user, null, move).then(() => resolve()); applyMoveAttrs(NoEffectAttr, user, null, this.move.getMove()).then(() => resolve());
} }
}); });
} else { } else {
@ -2825,8 +2821,8 @@ export class MoveEffectPhase extends PokemonPhase {
})); }));
} }
// Trigger effect which should only apply one time after all targeted effects have already applied // Trigger effect which should only apply one time after all targeted effects have already applied
const postTarget = applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, const postTarget = applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_TARGET,
user, null, move); user, null, this.move.getMove());
if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after
applyAttrs[applyAttrs.length - 1]?.then(() => postTarget); applyAttrs[applyAttrs.length - 1]?.then(() => postTarget);
@ -2840,8 +2836,6 @@ export class MoveEffectPhase extends PokemonPhase {
} }
end() { end() {
const move = this.move.getMove();
move.type = move.defaultType;
const user = this.getUserPokemon(); const user = this.getUserPokemon();
if (user) { if (user) {
if (--user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) { if (--user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) {
@ -3548,7 +3542,7 @@ export class FaintPhase extends PokemonPhase {
if (pokemon.turnData?.attacksReceived?.length) { if (pokemon.turnData?.attacksReceived?.length) {
const lastAttack = pokemon.turnData.attacksReceived[0]; const lastAttack = pokemon.turnData.attacksReceived[0];
applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId), new PokemonMove(lastAttack.move).getMove(), lastAttack.result); applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId), new PokemonMove(lastAttack.move), lastAttack.result);
} }
const alivePlayField = this.scene.getField(true); const alivePlayField = this.scene.getField(true);

View File

@ -40,10 +40,6 @@ export class Achv {
return i18next.t(`achv:${this.localizationKey}.name`); return i18next.t(`achv:${this.localizationKey}.name`);
} }
getDescription(): string {
return this.description;
}
getIconImage(): string { getIconImage(): string {
return this.iconImage; return this.iconImage;
} }
@ -263,12 +259,14 @@ export const achvs = {
CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY","", "CLASSIC_VICTORY.description", "relic_crown", 150), CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY","", "CLASSIC_VICTORY.description", "relic_crown", 150),
}; };
export function initAchievements() { {
const achvKeys = Object.keys(achvs); (function() {
achvKeys.forEach((a: string, i: integer) => { const achvKeys = Object.keys(achvs);
achvs[a].id = a; achvKeys.forEach((a: string, i: integer) => {
if (achvs[a].hasParent) { achvs[a].id = a;
achvs[a].parentId = achvKeys[i - 1]; if (achvs[a].hasParent) {
} achvs[a].parentId = achvKeys[i - 1];
}); }
});
})();
} }

View File

@ -60,13 +60,13 @@ export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): str
} }
} }
export function encrypt(data: string, bypassLogin: boolean): string { function encrypt(data: string, bypassLogin: boolean): string {
return (bypassLogin return (bypassLogin
? (data: string) => btoa(data) ? (data: string) => btoa(data)
: (data: string) => AES.encrypt(data, saveKey))(data); : (data: string) => AES.encrypt(data, saveKey))(data);
} }
export function decrypt(data: string, bypassLogin: boolean): string { function decrypt(data: string, bypassLogin: boolean): string {
return (bypassLogin return (bypassLogin
? (data: string) => atob(data) ? (data: string) => atob(data)
: (data: string) => AES.decrypt(data, saveKey).toString(enc.Utf8))(data); : (data: string) => AES.decrypt(data, saveKey).toString(enc.Utf8))(data);
@ -493,7 +493,7 @@ export class GameData {
}); });
} }
parseSystemData(dataStr: string): SystemSaveData { private parseSystemData(dataStr: string): SystemSaveData {
return JSON.parse(dataStr, (k: string, v: any) => { return JSON.parse(dataStr, (k: string, v: any) => {
if (k === "gameStats") { if (k === "gameStats") {
return new GameStats(v); return new GameStats(v);
@ -512,7 +512,7 @@ export class GameData {
}) as SystemSaveData; }) as SystemSaveData;
} }
convertSystemDataStr(dataStr: string, shorten: boolean = false): string { private convertSystemDataStr(dataStr: string, shorten: boolean = false): string {
if (!shorten) { if (!shorten) {
// Account for past key oversight // Account for past key oversight
dataStr = dataStr.replace(/\$pAttr/g, "$pa"); dataStr = dataStr.replace(/\$pAttr/g, "$pa");

View File

@ -66,8 +66,7 @@ export const SettingKeys = {
Player_Gender: "PLAYER_GENDER", Player_Gender: "PLAYER_GENDER",
Master_Volume: "MASTER_VOLUME", Master_Volume: "MASTER_VOLUME",
BGM_Volume: "BGM_VOLUME", BGM_Volume: "BGM_VOLUME",
SE_Volume: "SE_VOLUME", SE_Volume: "SE_VOLUME"
Music_Preference: "MUSIC_PREFERENCE"
}; };
/** /**
@ -288,14 +287,6 @@ export const Setting: Array<Setting> = [
options: VOLUME_OPTIONS, options: VOLUME_OPTIONS,
default: 10, default: 10,
type: SettingType.AUDIO type: SettingType.AUDIO
},
{
key: SettingKeys.Music_Preference,
label: "Music Preference",
options: ["Consistent", "Mixed"],
default: 0,
type: SettingType.AUDIO,
requireReload: true
} }
]; ];
@ -344,9 +335,6 @@ export function setSetting(scene: BattleScene, setting: string, value: integer):
scene.seVolume = value ? parseInt(Setting[index].options[value]) * 0.01 : 0; scene.seVolume = value ? parseInt(Setting[index].options[value]) * 0.01 : 0;
scene.updateSoundVolume(); scene.updateSoundVolume();
break; break;
case SettingKeys.Music_Preference:
scene.musicPreference = value;
break;
case SettingKeys.Damage_Numbers: case SettingKeys.Damage_Numbers:
scene.damageNumbersMode = value; scene.damageNumbersMode = value;
break; break;

View File

@ -83,40 +83,42 @@ export const vouchers: Vouchers = {};
const voucherAchvs: Achv[] = [ achvs.CLASSIC_VICTORY ]; const voucherAchvs: Achv[] = [ achvs.CLASSIC_VICTORY ];
export function initVouchers() { {
import("../data/trainer-config").then(tc => { (function() {
const trainerConfigs = tc.trainerConfigs; import("../data/trainer-config").then(tc => {
const trainerConfigs = tc.trainerConfigs;
for (const achv of voucherAchvs) { for (const achv of voucherAchvs) {
const voucherType = achv.score >= 150 const voucherType = achv.score >= 150
? VoucherType.GOLDEN ? VoucherType.GOLDEN
: achv.score >= 100 : achv.score >= 100
? VoucherType.PREMIUM ? VoucherType.PREMIUM
: achv.score >= 75 : achv.score >= 75
? VoucherType.PLUS ? VoucherType.PLUS
: VoucherType.REGULAR; : VoucherType.REGULAR;
vouchers[achv.id] = new Voucher(voucherType, getAchievementDescription(achv.localizationKey)); vouchers[achv.id] = new Voucher(voucherType, getAchievementDescription(achv.localizationKey));
} }
const bossTrainerTypes = Object.keys(trainerConfigs) const bossTrainerTypes = Object.keys(trainerConfigs)
.filter(tt => trainerConfigs[tt].isBoss && trainerConfigs[tt].getDerivedType() !== TrainerType.RIVAL); .filter(tt => trainerConfigs[tt].isBoss && trainerConfigs[tt].getDerivedType() !== TrainerType.RIVAL);
for (const trainerType of bossTrainerTypes) { for (const trainerType of bossTrainerTypes) {
const voucherType = trainerConfigs[trainerType].moneyMultiplier < 10 const voucherType = trainerConfigs[trainerType].moneyMultiplier < 10
? VoucherType.PLUS ? VoucherType.PLUS
: VoucherType.PREMIUM; : VoucherType.PREMIUM;
const key = TrainerType[trainerType]; const key = TrainerType[trainerType];
const trainerName = trainerConfigs[trainerType].name; const trainerName = trainerConfigs[trainerType].name;
const trainer = trainerConfigs[trainerType]; const trainer = trainerConfigs[trainerType];
const title = trainer.title ? ` (${trainer.title})` : ""; const title = trainer.title ? ` (${trainer.title})` : "";
vouchers[key] = new Voucher( vouchers[key] = new Voucher(
voucherType, voucherType,
`${i18next.t("voucher:defeatTrainer", { trainerName })} ${title}`, `${i18next.t("voucher:defeatTrainer", { trainerName })} ${title}`,
); );
} }
const voucherKeys = Object.keys(vouchers); const voucherKeys = Object.keys(vouchers);
for (const k of voucherKeys) { for (const k of voucherKeys) {
vouchers[k].id = k; vouchers[k].id = k;
} }
}); });
})();
} }

View File

@ -1,83 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {
CheckSwitchPhase, CommandPhase, MessagePhase,
PostSummonPhase,
ShinySparklePhase,
ShowAbilityPhase,
StatChangePhase,
SummonPhase,
ToggleDoublePositionPhase, TurnInitPhase
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {BattleStat} from "#app/data/battle-stat";
describe("Abilities - Intimidate", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MIGHTYENA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INTIMIDATE);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INTIMIDATE);
});
it("INTIMIDATE", async() => {
await game.runToSummon([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
await game.phaseInterceptor.run(PostSummonPhase);
expect(game.scene.getParty()[0].summonData).not.toBeUndefined();
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
await game.phaseInterceptor.run(ShowAbilityPhase);
await game.phaseInterceptor.run(StatChangePhase);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
await game.phaseInterceptor.run(SummonPhase);
await game.phaseInterceptor.run(ShinySparklePhase, () => game.isCurrentPhase(ToggleDoublePositionPhase));
await game.phaseInterceptor.run(ToggleDoublePositionPhase);
game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
game.setMode(Mode.MESSAGE);
game.endPhase();
});
await game.phaseInterceptor.run(CheckSwitchPhase);
await game.phaseInterceptor.run(PostSummonPhase);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
await game.phaseInterceptor.run(ShowAbilityPhase);
game.scene.moveAnimations = null; // Mandatory to avoid crash
await game.phaseInterceptor.run(StatChangePhase);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(TurnInitPhase);
await game.phaseInterceptor.run(CommandPhase);
}, 20000);
});

View File

@ -1,65 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {
MessagePhase,
PostSummonPhase,
ShowAbilityPhase,
StatChangePhase,
ToggleDoublePositionPhase
} from "#app/phases";
import {BattleStat} from "#app/data/battle-stat";
describe("Abilities - Intrepid Sword", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.ZACIAN);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INTREPID_SWORD);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INTREPID_SWORD);
});
it("INTREPID SWORD on player", async() => {
await game.runToSummon([
Species.ZACIAN,
]);
await game.phaseInterceptor.runFrom(PostSummonPhase).to(PostSummonPhase);
expect(game.scene.getParty()[0].summonData).not.toBeUndefined();
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
await game.phaseInterceptor.mustRun(ShowAbilityPhase).catch((error) => expect(error).toBe(ShowAbilityPhase));
await game.phaseInterceptor.mustRun(StatChangePhase).catch((error) => expect(error).toBe(StatChangePhase));
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(1);
}, 20000);
it("INTREPID SWORD on opponent", async() => {
await game.runToSummon([
Species.ZACIAN,
]);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
await game.phaseInterceptor.runFrom(PostSummonPhase).to(ToggleDoublePositionPhase);
await game.phaseInterceptor.mustRun(StatChangePhase).catch((error) => expect(error).toBe(StatChangePhase));
await game.phaseInterceptor.whenAboutToRun(MessagePhase);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(1);
}, 20000);
});

View File

@ -1,67 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {
CommandPhase,
EnemyCommandPhase,
VictoryPhase
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {Stat} from "#app/data/pokemon-stat";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import {BattleStat} from "#app/data/battle-stat";
describe("Abilities - Moxie", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.AERIAL_ACE;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.MOXIE);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.MOXIE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
});
it("MOXIE", async() => {
const moveToUse = Moves.AERIAL_ACE;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[Stat.ATK]).toBe(0);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(VictoryPhase);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(1);
}, 20000);
});

View File

@ -0,0 +1,9 @@
import { MoneyAchv } from "#app/system/achv";
import { describe, expect, it } from "vitest";
describe("check some Achievement related stuff", () => {
it ("should check Achievement creation", () => {
const ach = new MoneyAchv("", "Achievement", 1000, null, 100);
expect(ach.name).toBe("Achievement");
});
});

View File

@ -1,274 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import {MoneyAchv, Achv, AchvTier, RibbonAchv, DamageAchv, HealAchv, LevelAchv, ModifierAchv, achvs} from "#app/system/achv";
import BattleScene from "../../battle-scene";
import { IntegerHolder, NumberHolder } from "#app/utils.js";
import { TurnHeldItemTransferModifier } from "#app/modifier/modifier.js";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
describe("check some Achievement related stuff", () => {
it ("should check Achievement creation", () => {
const ach = new MoneyAchv("", "Achievement", 1000, null, 100);
expect(ach.name).toBe("Achievement");
});
});
describe("Achv", () => {
let achv: Achv;
beforeEach(() => {
achv = new Achv("", "Test Achievement", "This is a test achievement", "test_icon", 10);
});
it("should have the correct name", () => {
expect(achv.getDescription()).toBe("This is a test achievement");
});
it("should have the correct icon image", () => {
expect(achv.getIconImage()).toBe("test_icon");
});
it("should set the achievement as secret", () => {
achv.setSecret();
expect(achv.secret).toBe(true);
expect(achv.hasParent).toBe(false);
achv.setSecret(true);
expect(achv.secret).toBe(true);
expect(achv.hasParent).toBe(true);
achv.setSecret(false);
expect(achv.secret).toBe(true);
expect(achv.hasParent).toBe(false);
});
it("should return the correct tier based on the score", () => {
const achv1 = new Achv("", "Test Achievement 1", "Test Description", "test_icon", 10);
const achv2 = new Achv("", "Test Achievement 2", "Test Description", "test_icon", 25);
const achv3 = new Achv("", "Test Achievement 3", "Test Description", "test_icon", 50);
const achv4 = new Achv("", "Test Achievement 4", "Test Description", "test_icon", 75);
const achv5 = new Achv("", "Test Achievement 5", "Test Description", "test_icon", 100);
expect(achv1.getTier()).toBe(AchvTier.COMMON);
expect(achv2.getTier()).toBe(AchvTier.GREAT);
expect(achv3.getTier()).toBe(AchvTier.ULTRA);
expect(achv4.getTier()).toBe(AchvTier.ROGUE);
expect(achv5.getTier()).toBe(AchvTier.MASTER);
});
it("should validate the achievement based on the condition function", () => {
const conditionFunc = jest.fn((scene: BattleScene, args: any[]) => args[0] === 10);
const achv = new Achv("", "Test Achievement", "Test Description", "test_icon", 10, conditionFunc);
expect(achv.validate(new BattleScene(), [5])).toBe(false);
expect(achv.validate(new BattleScene(), [10])).toBe(true);
expect(conditionFunc).toHaveBeenCalledTimes(2);
});
});
describe("MoneyAchv", () => {
it("should create an instance of MoneyAchv", () => {
const moneyAchv = new MoneyAchv("", "Test Money Achievement", 10000, "money_icon", 10);
expect(moneyAchv).toBeInstanceOf(MoneyAchv);
expect(moneyAchv instanceof Achv).toBe(true);
});
it("should validate the achievement based on the money amount", () => {
const moneyAchv = new MoneyAchv("", "Test Money Achievement", 10000, "money_icon", 10);
const scene = new BattleScene();
scene.money = 5000;
expect(moneyAchv.validate(scene, [])).toBe(false);
scene.money = 15000;
expect(moneyAchv.validate(scene, [])).toBe(true);
});
});
describe("RibbonAchv", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let scene: BattleScene;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(0);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(0);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(0);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(0);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([]);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([]);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
game = new GameManager(phaserGame);
scene = game.scene;
});
it("should create an instance of RibbonAchv", () => {
const ribbonAchv = new RibbonAchv("", "Test Ribbon Achievement", 10, "ribbon_icon", 10);
expect(ribbonAchv).toBeInstanceOf(RibbonAchv);
expect(ribbonAchv instanceof Achv).toBe(true);
});
it("should validate the achievement based on the ribbon amount", () => {
const ribbonAchv = new RibbonAchv("", "Test Ribbon Achievement", 10, "ribbon_icon", 10);
scene.gameData.gameStats.ribbonsOwned = 5;
expect(ribbonAchv.validate(scene, [])).toBe(false);
scene.gameData.gameStats.ribbonsOwned = 15;
expect(ribbonAchv.validate(scene, [])).toBe(true);
});
});
describe("DamageAchv", () => {
it("should create an instance of DamageAchv", () => {
const damageAchv = new DamageAchv("", "Test Damage Achievement", 250, "damage_icon", 10);
expect(damageAchv).toBeInstanceOf(DamageAchv);
expect(damageAchv instanceof Achv).toBe(true);
});
it("should validate the achievement based on the damage amount", () => {
const damageAchv = new DamageAchv("", "Test Damage Achievement", 250, "damage_icon", 10);
const scene = new BattleScene();
const numberHolder = new NumberHolder(200);
expect(damageAchv.validate(scene, [numberHolder])).toBe(false);
numberHolder.value = 300;
expect(damageAchv.validate(scene, [numberHolder])).toBe(true);
});
});
describe("HealAchv", () => {
it("should create an instance of HealAchv", () => {
const healAchv = new HealAchv("", "Test Heal Achievement", 250, "heal_icon", 10);
expect(healAchv).toBeInstanceOf(HealAchv);
expect(healAchv instanceof Achv).toBe(true);
});
it("should validate the achievement based on the heal amount", () => {
const healAchv = new HealAchv("", "Test Heal Achievement", 250, "heal_icon", 10);
const scene = new BattleScene();
const numberHolder = new NumberHolder(200);
expect(healAchv.validate(scene, [numberHolder])).toBe(false);
numberHolder.value = 300;
expect(healAchv.validate(scene, [numberHolder])).toBe(true);
});
});
describe("LevelAchv", () => {
it("should create an instance of LevelAchv", () => {
const levelAchv = new LevelAchv("", "Test Level Achievement", 100, "level_icon", 10);
expect(levelAchv).toBeInstanceOf(LevelAchv);
expect(levelAchv instanceof Achv).toBe(true);
});
it("should validate the achievement based on the level", () => {
const levelAchv = new LevelAchv("", "Test Level Achievement", 100, "level_icon", 10);
const scene = new BattleScene();
const integerHolder = new IntegerHolder(50);
expect(levelAchv.validate(scene, [integerHolder])).toBe(false);
integerHolder.value = 150;
expect(levelAchv.validate(scene, [integerHolder])).toBe(true);
});
});
describe("ModifierAchv", () => {
it("should create an instance of ModifierAchv", () => {
const modifierAchv = new ModifierAchv("", "Test Modifier Achievement", "Test Description", "modifier_icon", 10, () => true);
expect(modifierAchv).toBeInstanceOf(ModifierAchv);
expect(modifierAchv instanceof Achv).toBe(true);
});
it("should validate the achievement based on the modifier function", () => {
const modifierAchv = new ModifierAchv("", "Test Modifier Achievement", "Test Description", "modifier_icon", 10, () => true);
const scene = new BattleScene();
const modifier = new TurnHeldItemTransferModifier(null, 3, 1);
expect(modifierAchv.validate(scene, [modifier])).toBe(true);
});
});
describe("achvs", () => {
it("should contain the predefined achievements", () => {
expect(achvs._10K_MONEY).toBeInstanceOf(MoneyAchv);
expect(achvs._100K_MONEY).toBeInstanceOf(MoneyAchv);
expect(achvs._1M_MONEY).toBeInstanceOf(MoneyAchv);
expect(achvs._10M_MONEY).toBeInstanceOf(MoneyAchv);
expect(achvs._250_DMG).toBeInstanceOf(DamageAchv);
expect(achvs._1000_DMG).toBeInstanceOf(DamageAchv);
expect(achvs._2500_DMG).toBeInstanceOf(DamageAchv);
expect(achvs._10000_DMG).toBeInstanceOf(DamageAchv);
expect(achvs._250_HEAL).toBeInstanceOf(HealAchv);
expect(achvs._1000_HEAL).toBeInstanceOf(HealAchv);
expect(achvs._2500_HEAL).toBeInstanceOf(HealAchv);
expect(achvs._10000_HEAL).toBeInstanceOf(HealAchv);
expect(achvs.LV_100).toBeInstanceOf(LevelAchv);
expect(achvs.LV_250).toBeInstanceOf(LevelAchv);
expect(achvs.LV_1000).toBeInstanceOf(LevelAchv);
expect(achvs._10_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._25_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._50_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._75_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._100_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs.TRANSFER_MAX_BATTLE_STAT).toBeInstanceOf(Achv);
expect(achvs.MAX_FRIENDSHIP).toBeInstanceOf(Achv);
expect(achvs.MEGA_EVOLVE).toBeInstanceOf(Achv);
expect(achvs.GIGANTAMAX).toBeInstanceOf(Achv);
expect(achvs.TERASTALLIZE).toBeInstanceOf(Achv);
expect(achvs.STELLAR_TERASTALLIZE).toBeInstanceOf(Achv);
expect(achvs.SPLICE).toBeInstanceOf(Achv);
expect(achvs.MINI_BLACK_HOLE).toBeInstanceOf(ModifierAchv);
expect(achvs.CATCH_MYTHICAL).toBeInstanceOf(Achv);
expect(achvs.CATCH_SUB_LEGENDARY).toBeInstanceOf(Achv);
expect(achvs.CATCH_LEGENDARY).toBeInstanceOf(Achv);
expect(achvs.SEE_SHINY).toBeInstanceOf(Achv);
expect(achvs.SHINY_PARTY).toBeInstanceOf(Achv);
expect(achvs.HATCH_MYTHICAL).toBeInstanceOf(Achv);
expect(achvs.HATCH_SUB_LEGENDARY).toBeInstanceOf(Achv);
expect(achvs.HATCH_LEGENDARY).toBeInstanceOf(Achv);
expect(achvs.HATCH_SHINY).toBeInstanceOf(Achv);
expect(achvs.HIDDEN_ABILITY).toBeInstanceOf(Achv);
expect(achvs.PERFECT_IVS).toBeInstanceOf(Achv);
expect(achvs.CLASSIC_VICTORY).toBeInstanceOf(Achv);
});
it("should initialize the achievements with IDs and parent IDs", () => {
expect(achvs._10K_MONEY.id).toBe("_10K_MONEY");
expect(achvs._10K_MONEY.hasParent).toBe(undefined);
expect(achvs._100K_MONEY.id).toBe("_100K_MONEY");
expect(achvs._100K_MONEY.hasParent).toBe(true);
expect(achvs._100K_MONEY.parentId).toBe("_10K_MONEY");
expect(achvs._1M_MONEY.id).toBe("_1M_MONEY");
expect(achvs._1M_MONEY.hasParent).toBe(true);
expect(achvs._1M_MONEY.parentId).toBe("_100K_MONEY");
expect(achvs._10M_MONEY.id).toBe("_10M_MONEY");
expect(achvs._10M_MONEY.hasParent).toBe(true);
expect(achvs._10M_MONEY.parentId).toBe("_1M_MONEY");
expect(achvs.LV_100.id).toBe("LV_100");
expect(achvs.LV_100.hasParent).toBe(false);
expect(achvs.LV_250.id).toBe("LV_250");
expect(achvs.LV_250.hasParent).toBe(true);
expect(achvs.LV_250.parentId).toBe("LV_100");
expect(achvs.LV_1000.id).toBe("LV_1000");
expect(achvs.LV_1000.hasParent).toBe(true);
expect(achvs.LV_1000.parentId).toBe("LV_250");
});
});

View File

@ -1,220 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {
CommandPhase, EnemyCommandPhase,
TurnStartPhase
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Moves} from "#app/data/enums/moves";
import {Command} from "#app/ui/command-ui-handler";
import {Stat} from "#app/data/pokemon-stat";
import TargetSelectUiHandler from "#app/ui/target-select-ui-handler";
import {Button} from "#app/enums/buttons";
describe("Battle order", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
});
it("opponent faster than player 50 vs 150", async() => {
await game.startBattle([
Species.BULBASAUR,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 50;
game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 150;
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order[0]).toBe(2);
expect(order[1]).toBe(0);
}, 20000);
it("Player faster than opponent 150 vs 50", async() => {
await game.startBattle([
Species.BULBASAUR,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 150;
game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 50;
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order[0]).toBe(0);
expect(order[1]).toBe(2);
}, 20000);
it("double - both opponents faster than player 50/50 vs 150/150", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle([
Species.BULBASAUR,
Species.BLASTOISE,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 50;
game.scene.getParty()[1].stats[Stat.SPD] = 50;
game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 150;
game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 150;
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order.indexOf(0)).toBeGreaterThan(order.indexOf(2));
expect(order.indexOf(0)).toBeGreaterThan(order.indexOf(3));
expect(order.indexOf(1)).toBeGreaterThan(order.indexOf(2));
expect(order.indexOf(1)).toBeGreaterThan(order.indexOf(3));
}, 20000);
it("double - speed tie except 1 - 100/100 vs 100/150", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle([
Species.BULBASAUR,
Species.BLASTOISE,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 100;
game.scene.getParty()[1].stats[Stat.SPD] = 100;
game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 100;
game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 150;
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order.indexOf(3)).toBeLessThan(order.indexOf(0));
expect(order.indexOf(3)).toBeLessThan(order.indexOf(1));
expect(order.indexOf(3)).toBeLessThan(order.indexOf(2));
}, 20000);
it("double - speed tie 100/150 vs 100/150", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle([
Species.BULBASAUR,
Species.BLASTOISE,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 100;
game.scene.getParty()[1].stats[Stat.SPD] = 150;
game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 100;
game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 150;
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order.indexOf(1)).toBeLessThan(order.indexOf(0));
expect(order.indexOf(1)).toBeLessThan(order.indexOf(2));
expect(order.indexOf(3)).toBeLessThan(order.indexOf(0));
expect(order.indexOf(3)).toBeLessThan(order.indexOf(2));
}, 20000);
});

View File

@ -1,248 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import {generateStarter, getMovePosition, waitUntil,} from "#app/test/utils/gameManagerUtils";
import {Mode} from "#app/ui/ui";
import {GameModes} from "#app/game-mode";
import {Species} from "#app/data/enums/species";
import * as overrides from "../../overrides";
import {Command} from "#app/ui/command-ui-handler";
import {
BattleEndPhase,
BerryPhase,
CommandPhase,
DamagePhase,
EggLapsePhase,
EncounterPhase,
EnemyCommandPhase,
FaintPhase,
LoginPhase,
MessagePhase,
MoveEffectPhase,
MoveEndPhase,
MovePhase,
PostSummonPhase,
SelectGenderPhase,
SelectModifierPhase,
SelectStarterPhase,
StatChangePhase,
TitlePhase,
TurnEndPhase,
TurnInitPhase,
TurnStartPhase,
VictoryPhase,
} from "#app/phases";
import {Moves} from "#app/data/enums/moves";
import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser";
import {allSpecies} from "#app/data/pokemon-species";
import {PlayerGender} from "#app/data/enums/player-gender";
describe("Test Battle Phase", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
});
it("test phase interceptor with remove", async() => {
await game.phaseInterceptor.run(LoginPhase);
await game.phaseInterceptor.run(LoginPhase, () => {
return game.phaseInterceptor.log.includes("LoginPhase");
});
game.scene.gameData.gender = PlayerGender.MALE;
await game.phaseInterceptor.remove(SelectGenderPhase, () => game.isCurrentPhase(TitlePhase));
await game.phaseInterceptor.run(TitlePhase);
await waitUntil(() => game.scene.ui?.getMode() === Mode.TITLE);
expect(game.scene.ui?.getMode()).toBe(Mode.TITLE);
}, 100000);
it("test phase interceptor with prompt", async() => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
});
await game.phaseInterceptor.run(SelectGenderPhase);
await game.phaseInterceptor.run(TitlePhase);
await game.waitMode(Mode.TITLE);
expect(game.scene.ui?.getMode()).toBe(Mode.TITLE);
expect(game.scene.gameData.gender).toBe(PlayerGender.MALE);
}, 100000);
it("test phase interceptor with prompt with preparation for a future prompt", async() => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
});
game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
game.setMode(Mode.MESSAGE);
game.endPhase();
});
await game.phaseInterceptor.run(SelectGenderPhase);
await game.phaseInterceptor.run(TitlePhase);
await game.waitMode(Mode.TITLE);
expect(game.scene.ui?.getMode()).toBe(Mode.TITLE);
expect(game.scene.gameData.gender).toBe(PlayerGender.MALE);
}, 100000);
it("newGame one-liner", async() => {
await game.startBattle();
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 100000);
it("do attack wave 3 - single battle - regular - OHKO", async() => {
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle();
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
await game.phaseInterceptor.run(MovePhase);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(MoveEffectPhase);
await game.phaseInterceptor.run(DamagePhase);
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(FaintPhase));
await game.phaseInterceptor.run(FaintPhase);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(VictoryPhase);
await game.phaseInterceptor.run(MoveEndPhase);
await game.phaseInterceptor.run(MovePhase);
await game.phaseInterceptor.run(BerryPhase);
await game.phaseInterceptor.run(TurnEndPhase);
await game.phaseInterceptor.run(BattleEndPhase);
await game.phaseInterceptor.run(EggLapsePhase);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(game.scene.ui?.getMode()).toBe(Mode.MODIFIER_SELECT);
expect(game.scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
}, 100000);
it("do attack wave 3 - single battle - regular - NO OHKO with opponent using non damage attack", async() => {
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(5);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TAIL_WHIP]);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle();
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
await game.phaseInterceptor.run(MovePhase);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(MoveEffectPhase);
await game.phaseInterceptor.run(DamagePhase);
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEndPhase));
await game.phaseInterceptor.run(MoveEndPhase);
await game.phaseInterceptor.run(MovePhase);
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEffectPhase));
await game.phaseInterceptor.run(MoveEffectPhase);
game.scene.moveAnimations = null; // Mandatory to avoid the crash
await game.phaseInterceptor.run(StatChangePhase, () => game.isCurrentPhase(MessagePhase) || game.isCurrentPhase(MoveEndPhase) || game.isCurrentPhase(DamagePhase));
await game.phaseInterceptor.run(DamagePhase, () => game.isCurrentPhase(MessagePhase) || game.isCurrentPhase(MoveEndPhase));
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEndPhase));
await game.phaseInterceptor.run(MoveEndPhase);
await game.phaseInterceptor.run(BerryPhase);
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(TurnEndPhase));
await game.phaseInterceptor.run(TurnEndPhase);
await game.phaseInterceptor.run(TurnInitPhase);
await game.phaseInterceptor.run(CommandPhase);
await waitUntil(() => game.scene.ui?.getMode() === Mode.COMMAND);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 100000);
it("load 100% data file", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
}, 50000);
it("start battle with selected team", async() => {
await game.startBattle([
Species.CHARIZARD,
Species.CHANSEY,
Species.MEW
]);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.CHARIZARD);
expect(game.scene.getParty()[1].species.speciesId).toBe(Species.CHANSEY);
expect(game.scene.getParty()[2].species.speciesId).toBe(Species.MEW);
}, 50000);
it("assert next phase", async() => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
}, () => game.isCurrentPhase(TitlePhase));
await game.phaseInterceptor.mustRun(SelectGenderPhase).catch((error) => expect(error).toBe(SelectGenderPhase));
await game.phaseInterceptor.mustRun(TitlePhase).catch((error) => expect(error).toBe(TitlePhase));
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const starters = generateStarter(game.scene);
const selectStarterPhase = new SelectStarterPhase(game.scene, GameModes.CLASSIC);
game.scene.pushPhase(new EncounterPhase(game.scene, false));
selectStarterPhase.initBattle(starters);
});
await game.phaseInterceptor.mustRun(EncounterPhase).catch((error) => expect(error).toBe(EncounterPhase));
await game.phaseInterceptor.mustRun(PostSummonPhase).catch((error) => expect(error).toBe(PostSummonPhase));
}, 50000);
it("test remove random battle seed int", async() => {
for (let i=0; i<10; i++) {
const rand = game.scene.randBattleSeedInt(15);
expect(rand).toBe(14);
}
});
});

View File

@ -1,33 +0,0 @@
import {beforeAll, describe, expect, it} from "vitest";
import BattleScene from "../../battle-scene";
import { getLegendaryGachaSpeciesForTimestamp } from "#app/data/egg.js";
import { Species } from "#app/data/enums/species.js";
import Phaser from "phaser";
describe("getLegendaryGachaSpeciesForTimestamp", () => {
beforeAll(() => {
new Phaser.Game({
type: Phaser.HEADLESS,
});
});
it("should return Arceus for the 10th of June", () => {
const scene = new BattleScene();
const timestamp = new Date(2024, 5, 10, 15, 0, 0, 0).getTime();
const expectedSpecies = Species.ARCEUS;
const result = getLegendaryGachaSpeciesForTimestamp(scene, timestamp);
expect(result).toBe(expectedSpecies);
});
it("should return Arceus for the 10th of July", () => {
const scene = new BattleScene();
const timestamp = new Date(2024, 6, 10, 15, 0, 0, 0).getTime();
const expectedSpecies = Species.ARCEUS;
const result = getLegendaryGachaSpeciesForTimestamp(scene, timestamp);
expect(result).toBe(expectedSpecies);
});
});

View File

@ -1,105 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import pad_xbox360 from "#app/configs/inputs/pad_xbox360";
import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty";
import InputsHandler from "#app/test/utils/inputsHandler";
describe("Inputs", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let originalDocument: Document;
beforeAll(() => {
originalDocument = window.document;
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
Object.defineProperty(window, "document", {
value: originalDocument,
configurable: true,
writable: true,
});
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.inputsHandler = new InputsHandler(game.scene);
});
it("Mobile - test touch holding for 1ms - 1 input", async () => {
await game.inputsHandler.pressTouch("dpadUp", 1);
expect(game.inputsHandler.log.length).toBe(1);
});
it("Mobile - test touch holding for 200ms - 1 input", async () => {
await game.inputsHandler.pressTouch("dpadUp", 200);
expect(game.inputsHandler.log.length).toBe(1);
});
it("Mobile - test touch holding for 300ms - 2 input", async () => {
await game.inputsHandler.pressTouch("dpadUp", 300);
expect(game.inputsHandler.log.length).toBe(2);
});
it("Mobile - test touch holding for 1000ms - 4 input", async () => {
await game.inputsHandler.pressTouch("dpadUp", 1000);
expect(game.inputsHandler.log.length).toBe(4);
});
it("keyboard - test input holding for 1ms - 1 input", async() => {
await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 1);
expect(game.inputsHandler.log.length).toBe(1);
});
it("keyboard - test input holding for 200ms - 1 input", async() => {
await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 200);
expect(game.inputsHandler.log.length).toBe(1);
});
it("keyboard - test input holding for 300ms - 2 input", async() => {
await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 300);
expect(game.inputsHandler.log.length).toBe(2);
});
it("keyboard - test input holding for 1000ms - 4 input", async() => {
await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 1000);
expect(game.inputsHandler.log.length).toBe(4);
});
it("keyboard - test input holding for 2000ms - 8 input", async() => {
await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 2000);
expect(game.inputsHandler.log.length).toBe(8);
});
it("gamepad - test input holding for 1ms - 1 input", async() => {
await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 1);
expect(game.inputsHandler.log.length).toBe(1);
});
it("gamepad - test input holding for 200ms - 1 input", async() => {
await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 200);
expect(game.inputsHandler.log.length).toBe(1);
});
it("gamepad - test input holding for 300ms - 2 input", async() => {
await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 300);
expect(game.inputsHandler.log.length).toBe(2);
});
it("gamepad - test input holding for 1000ms - 4 input", async() => {
await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 1000);
expect(game.inputsHandler.log.length).toBe(4);
});
it("gamepad - test input holding for 2000ms - 8 input", async() => {
await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 2000);
expect(game.inputsHandler.log.length).toBe(8);
});
});

View File

@ -1,80 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {
CommandPhase,
EnemyCommandPhase,
MessagePhase,
TurnEndPhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import {StatusEffect} from "#app/data/status-effect";
describe("Items - Toxic orb", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.GROWTH;
const oppMoveToUse = Moves.TACKLE;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([oppMoveToUse, oppMoveToUse, oppMoveToUse, oppMoveToUse]);
vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{
name: "TOXIC_ORB",
}]);
});
it("TOXIC ORB", async() => {
const moveToUse = Moves.GROWTH;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
expect(game.scene.modifiers[0].type.id).toBe("TOXIC_ORB");
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
// Select Attack
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
// Select Move Growth
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
// will run the 13 phase from enemyCommandPhase to TurnEndPhase
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
// Toxic orb should trigger here
await game.phaseInterceptor.run(MessagePhase);
const message = game.textInterceptor.getLatestMessage();
expect(message).toContain("was badly poisoned by Toxic Orb");
await game.phaseInterceptor.run(MessagePhase);
const message2 = game.textInterceptor.getLatestMessage();
expect(message2).toContain("is hurt");
expect(message2).toContain("by poison");
expect(game.scene.getParty()[0].status.effect).toBe(StatusEffect.TOXIC);
}, 20000);
});

View File

@ -1,42 +0,0 @@
import {afterEach, beforeAll, describe, expect, it} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import {Species} from "#app/data/enums/species";
import i18next from "i18next";
import {initI18n} from "#app/plugins/i18n";
describe("Lokalization - french", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
initI18n();
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
it("test bulbasaur name english", async () => {
game = new GameManager(phaserGame);
await game.startBattle([
Species.BULBASAUR,
]);
expect(game.scene.getParty()[0].name).toBe("Bulbasaur");
}, 20000);
it("test bulbasaure name french", async () => {
const locale = "fr";
i18next.changeLanguage(locale);
localStorage.setItem("prLang", locale);
game = new GameManager(phaserGame);
await game.startBattle([
Species.BULBASAUR,
]);
expect(game.scene.getParty()[0].name).toBe("Bulbizarre");
}, 20000);
});

View File

@ -1,69 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {
CommandPhase,
EnemyCommandPhase,
TurnInitPhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {Stat} from "#app/data/pokemon-stat";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import {BattleStat} from "#app/data/battle-stat";
describe("Moves - Growth", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.GROWTH;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.MOXIE);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
});
it("GROWTH", async() => {
const moveToUse = Moves.GROWTH;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[Stat.SPATK]).toBe(0);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.SPATK]).toBe(0);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.SPATK]).toBe(1);
}, 20000);
});

View File

@ -1,85 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Species} from "#app/data/enums/species";
import {
CommandPhase,
EnemyCommandPhase, TurnEndPhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import {Stat} from "#app/data/pokemon-stat";
describe("Moves - Tackle", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.TACKLE;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MAGIKARP);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(1);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(97);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.GROWTH,Moves.GROWTH,Moves.GROWTH,Moves.GROWTH]);
vi.spyOn(overrides, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true);
});
it("TACKLE against ghost", async() => {
const moveToUse = Moves.TACKLE;
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.GENGAR);
await game.startBattle([
Species.MIGHTYENA,
]);
const hpOpponent = game.scene.currentBattle.enemyParty[0].hp;
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
const hpLost = hpOpponent - game.scene.currentBattle.enemyParty[0].hp;
expect(hpLost).toBe(0);
}, 20000);
it("TACKLE against not resistant", async() => {
const moveToUse = Moves.TACKLE;
await game.startBattle([
Species.MIGHTYENA,
]);
game.scene.currentBattle.enemyParty[0].stats[Stat.DEF] = 50;
game.scene.getParty()[0].stats[Stat.ATK] = 50;
const hpOpponent = game.scene.currentBattle.enemyParty[0].hp;
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
const hpLost = hpOpponent - game.scene.currentBattle.enemyParty[0].hp;
expect(hpLost).toBeGreaterThan(0);
expect(hpLost).toBe(4);
}, 20000);
});

View File

@ -1,66 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {
CommandPhase,
EnemyCommandPhase,
TurnInitPhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import {BattleStat} from "#app/data/battle-stat";
describe("Moves - Tail whip", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.TAIL_WHIP;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
});
it("TAIL_WHIP", async() => {
const moveToUse = Moves.TAIL_WHIP;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.DEF]).toBe(0);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.DEF]).toBe(-1);
}, 20000);
});

5
src/test/phaser.setup.ts Normal file
View File

@ -0,0 +1,5 @@
import Phaser from "phaser";
export default new Phaser.Game({
type: Phaser.HEADLESS,
});

View File

@ -1,51 +0,0 @@
import BattleScene from "#app/battle-scene.js";
import { LoginPhase, TitlePhase, UnavailablePhase } from "#app/phases.js";
import { Mode } from "#app/ui/ui.js";
import {afterEach, beforeAll, beforeEach, describe, expect, it} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
describe("Phases", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let scene: BattleScene;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
scene = game.scene;
});
describe("LoginPhase", () => {
it("should start the login phase", async () => {
const loginPhase = new LoginPhase(scene);
loginPhase.start();
expect(scene.ui.getMode()).to.equal(Mode.MESSAGE);
});
});
describe("TitlePhase", () => {
it("should start the title phase", async () => {
const titlePhase = new TitlePhase(scene);
titlePhase.start();
expect(scene.ui.getMode()).to.equal(Mode.MESSAGE);
});
});
describe("UnavailablePhase", () => {
it("should start the unavailable phase", async () => {
const unavailablePhase = new UnavailablePhase(scene);
unavailablePhase.start();
expect(scene.ui.getMode()).to.equal(Mode.UNAVAILABLE);
});
});
});

57
src/test/pokemon.test.ts Normal file
View File

@ -0,0 +1,57 @@
import {describe, expect, it} from "vitest";
import {getPokemonSpecies} from "#app/data/pokemon-species";
import {PokemonMove} from "#app/field/pokemon";
import {Species} from "#app/data/enums/species";
import {Moves} from "#app/data/enums/moves";
import PokemonData from "#app/system/pokemon-data";
describe("some tests related to PokemonData and Species", () => {
it("should create a species", () => {
const species = getPokemonSpecies(Species.MEW);
expect(species).not.toBeNull();
});
it("should create a pokemon", () => {
const pokemon = new PokemonData({
species: Species.MEW,
level: 1,
});
expect(pokemon).not.toBeNull();
expect(pokemon.level).toEqual(1);
expect(pokemon.species).toEqual(Species.MEW);
});
it("should generate a moveset", () => {
const pokemon = new PokemonData({
species: Species.MEW,
level: 1,
});
expect(pokemon.moveset[0].moveId).toBe(Moves.TACKLE);
expect(pokemon.moveset[1].moveId).toBe(Moves.GROWL);
});
it("should create an ennemypokemon", () => {
const ennemyPokemon = new PokemonData({
species: Species.MEWTWO,
level: 100,
});
expect(ennemyPokemon).not.toBeNull();
expect(ennemyPokemon.level).toEqual(100);
expect(ennemyPokemon.species).toEqual(Species.MEWTWO);
});
it("should create an ennemypokemon with specified moveset", () => {
const ennemyPokemon = new PokemonData({
species: Species.MEWTWO,
level: 100,
moveset: [
new PokemonMove(Moves.ACID),
new PokemonMove(Moves.ACROBATICS),
new PokemonMove(Moves.FOCUS_ENERGY),
]
});
expect(ennemyPokemon.moveset[0].moveId).toBe(Moves.ACID);
expect(ennemyPokemon.moveset[1].moveId).toBe(Moves.ACROBATICS);
expect(ennemyPokemon.moveset[2].moveId).toBe(Moves.FOCUS_ENERGY);
});
});

View File

@ -1,8 +1,8 @@
import {beforeAll, describe, expect, it} from "vitest"; import {beforeAll, describe, expect, it} from "vitest";
import _masterlist from "../../../public/images/pokemon/variant/_masterlist.json"; import _masterlist from "../../public/images/pokemon/variant/_masterlist.json";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import {getAppRootDir} from "#app/test/sprites/spritesUtils"; import {getAppRootDir} from "#app/test/testUtils";
const deepCopy = (data) => { const deepCopy = (data) => {
return JSON.parse(JSON.stringify(data)); return JSON.parse(JSON.stringify(data));

View File

@ -5,8 +5,8 @@ import {
getKeyWithKeycode, getKeyWithKeycode,
getKeyWithSettingName, getKeyWithSettingName,
} from "#app/configs/inputs/configHandler"; } from "#app/configs/inputs/configHandler";
import {MenuManip} from "#app/test/settingMenu/helpers/menuManip"; import {MenuManip} from "#app/test/helpers/menuManip";
import {InGameManip} from "#app/test/settingMenu/helpers/inGameManip"; import {InGameManip} from "#app/test/helpers/inGameManip";
import {Device} from "#app/enums/devices"; import {Device} from "#app/enums/devices";
import {InterfaceConfig} from "#app/inputs-controller"; import {InterfaceConfig} from "#app/inputs-controller";
import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty"; import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty";

View File

@ -1,612 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import {Species} from "#app/data/enums/species";
import {
EncounterPhase,
SelectStarterPhase,
TitlePhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {GameModes} from "#app/game-mode";
import StarterSelectUiHandler from "#app/ui/starter-select-ui-handler";
import {Button} from "#app/enums/buttons";
import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler";
import SaveSlotSelectUiHandler from "#app/ui/save-slot-select-ui-handler";
import {OptionSelectItem} from "#app/ui/abstact-option-select-ui-handler";
import {Gender} from "#app/data/gender";
import {Nature} from "#app/data/nature";
import {Abilities} from "#app/data/enums/abilities";
import {allSpecies} from "#app/data/pokemon-species";
describe("UI - Starter select", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
});
it("Bulbasaur - shiny - variant 2 male", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(2);
expect(game.scene.getParty()[0].gender).toBe(Gender.MALE);
}, 20000);
it("Bulbasaur - shiny - variant 2 female hardy overgrow", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(2);
expect(game.scene.getParty()[0].nature).toBe(Nature.HARDY);
expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.OVERGROW);
}, 20000);
it("Bulbasaur - shiny - variant 2 female lonely cholorophyl", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.CYCLE_NATURE);
handler.processInput(Button.CYCLE_ABILITY);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(2);
expect(game.scene.getParty()[0].nature).toBe(Nature.LONELY);
expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.CHLOROPHYLL);
}, 20000);
it("Bulbasaur - shiny - variant 2 female", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(2);
expect(game.scene.getParty()[0].gender).toBe(Gender.FEMALE);
}, 20000);
it("Bulbasaur - not shiny", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_SHINY);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(false);
expect(game.scene.getParty()[0].variant).toBe(0);
}, 20000);
it("Bulbasaur - shiny - variant 0", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.V);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(0);
}, 20000);
it("Bulbasaur - shiny - variant 1", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.V);
handler.processInput(Button.V);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(1);
}, 20000);
it("Bulbasaur - shiny - variant 1", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.V);
handler.processInput(Button.V);
handler.processInput(Button.V);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(2);
}, 20000);
it("Check if first pokemon in party is caterpie from gen 1 and 1rd row, 3rd column ", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
let starterSelectUiHandler: StarterSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
starterSelectUiHandler = game.scene.ui.getHandler() as StarterSelectUiHandler;
starterSelectUiHandler.processInput(Button.SUBMIT);
resolve();
});
});
expect(starterSelectUiHandler.starterGens[0]).toBe(0);
expect(starterSelectUiHandler.starterCursors[0]).toBe(3);
expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18);
expect(starterSelectUiHandler.cursorObj.y).toBe(10);
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.CATERPIE);
}, 20000);
it("Check if first pokemon in party is nidoran_m from gen 1 and 2nd row, 4th column (cursor (9+4)-1) ", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.DOWN);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
let starterSelectUiHandler: StarterSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
starterSelectUiHandler = game.scene.ui.getHandler() as StarterSelectUiHandler;
starterSelectUiHandler.processInput(Button.SUBMIT);
resolve();
});
});
expect(starterSelectUiHandler.starterGens[0]).toBe(0);
expect(starterSelectUiHandler.starterCursors[0]).toBe(12);
expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18);
expect(starterSelectUiHandler.cursorObj.y).toBe(28);
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.NIDORAN_M);
}, 20000);
});

View File

@ -1,16 +0,0 @@
export default class TextInterceptor {
private scene;
private logs = [];
constructor(scene) {
this.scene = scene;
scene.messageWrapper = this;
}
showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer): void {
this.logs.push(text);
}
getLatestMessage(): string {
return this.logs[this.logs.length - 1];
}
}

View File

@ -1,50 +0,0 @@
<!DOCTYPE html><body>
<div id="touchControls">
<div id="dpad" class="unselectable">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72">
<path id="dpadUp" data-key="UP" d="M48,5.8C48,2.5,45.4,0,42,0H29.9C26.6,0,24,2.4,24,5.8V24h24V5.8z" />
<path id="dpadRight" data-key="RIGHT" d="M66.2,24H48v24h18.2c3.3,0,5.8-2.7,5.8-6V29.9C72,26.5,69.5,24,66.2,24z" />
<path id="dpadDown" data-key="DOWN" d="M24,66.3c0,3.3,2.6,5.7,5.9,5.7H42c3.3,0,6-2.4,6-5.7V48H24V66.3z" />
<path id="dpadLeft" data-key="LEFT" d="M5.7,24C2.4,24,0,26.5,0,29.9V42c0,3.3,2.3,6,5.7,6H24V24H5.7z" />
<rect id="dpadCenter" x="24" y="24" width="24" height="24" />
</svg>
</div>
<div id="apad" class="unselectable">
<div id="apadAction" class="apadCircBtn apadBtn" data-key="ACTION">
<text id="apadLabelAction" class="apadLabel">A</text>
</div>
<div id="apadCancel" class="apadCircBtn apadBtn" data-key="CANCEL">
<text id="apadLabelCancel" class="apadLabel">B</text>
</div>
<div class="apadBtnContainer apadRectBtnContainer">
<div id="apadCycleShiny" class="apadSqBtn apadBtn" data-key="CYCLE_SHINY">
<text class="apadLabel apadLabelSmall">R</text>
</div>
<div id="apadCycleVariant" class="apadSqBtn apadBtn" data-key="CYCLE_VARIANT">
<text class="apadLabel apadLabelSmall">V</text>
</div>
<div id="apadStats" class="apadRectBtn apadBtn" data-key="STATS">
<text class="apadLabel apadLabelSmall">C</text>
</div>
<div id="apadMenu" class="apadRectBtn apadBtn" data-key="MENU">
<text class="apadLabel apadLabelSmall">Menu</text>
</div>
</div>
<div class="apadBtnContainer apadSqBtnContainer">
<div id="apadCycleForm" class="apadSqBtn apadBtn" data-key="CYCLE_FORM">
<text class="apadLabel apadLabelSmall">F</text>
</div>
<div id="apadCycleGender" class="apadSqBtn apadBtn" data-key="CYCLE_GENDER">
<text class="apadLabel apadLabelSmall">G</text>
</div>
<div id="apadCycleAbility" class="apadSqBtn apadBtn" data-key="CYCLE_ABILITY">
<text class="apadLabel apadLabelSmall">E</text>
</div>
<div id="apadCycleNature" class="apadSqBtn apadBtn" data-key="CYCLE_NATURE">
<text class="apadLabel apadLabelSmall">N</text>
</div>
</div>
</div>
</div>
</body>

View File

@ -1,223 +0,0 @@
import GameWrapper from "#app/test/utils/gameWrapper";
import {Mode} from "#app/ui/ui";
import {generateStarter, waitUntil} from "#app/test/utils/gameManagerUtils";
import {
CheckSwitchPhase,
CommandPhase,
EncounterPhase,
LoginPhase,
PostSummonPhase,
SelectGenderPhase,
SelectStarterPhase,
SummonPhase,
TitlePhase,
ToggleDoublePositionPhase,
} from "#app/phases";
import BattleScene from "#app/battle-scene.js";
import PhaseInterceptor from "#app/test/utils/phaseInterceptor";
import TextInterceptor from "#app/test/utils/TextInterceptor";
import {expect} from "vitest";
import {GameModes} from "#app/game-mode";
import fs from "fs";
import { AES, enc } from "crypto-js";
import {updateUserInfo} from "#app/account";
import {Species} from "#app/data/enums/species";
import {PlayerGender} from "#app/data/enums/player-gender";
import {GameDataType} from "#app/data/enums/game-data-type";
import InputsHandler from "#app/test/utils/inputsHandler";
import {ExpNotification} from "#app/enums/exp-notification";
/**
* Class to manage the game state and transitions between phases.
*/
export default class GameManager {
public gameWrapper: GameWrapper;
public scene: BattleScene;
public phaseInterceptor: PhaseInterceptor;
public textInterceptor: TextInterceptor;
public inputsHandler: InputsHandler;
/**
* Creates an instance of GameManager.
* @param phaserGame - The Phaser game instance.
* @param bypassLogin - Whether to bypass the login phase.
*/
constructor(phaserGame: Phaser.Game, bypassLogin: boolean = true) {
BattleScene.prototype.randBattleSeedInt = (arg) => arg-1;
this.gameWrapper = new GameWrapper(phaserGame, bypassLogin);
this.scene = new BattleScene();
this.phaseInterceptor = new PhaseInterceptor(this.scene);
this.textInterceptor = new TextInterceptor(this.scene);
this.gameWrapper.setScene(this.scene);
}
/**
* Sets the game mode.
* @param mode - The mode to set.
*/
setMode(mode: Mode) {
this.scene.ui?.setMode(mode);
}
/**
* Waits until the specified mode is set.
* @param mode - The mode to wait for.
* @returns A promise that resolves when the mode is set.
*/
waitMode(mode: Mode): Promise<void> {
return new Promise(async (resolve) => {
await waitUntil(() => this.scene.ui?.getMode() === mode);
return resolve();
});
}
/**
* Ends the current phase.
*/
endPhase() {
this.scene.getCurrentPhase().end();
}
/**
* Adds an action to be executed on the next prompt.
* @param phaseTarget - The target phase.
* @param mode - The mode to wait for.
* @param callback - The callback to execute.
* @param expireFn - Optional function to determine if the prompt has expired.
*/
onNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn?: () => void) {
this.phaseInterceptor.addToNextPrompt(phaseTarget, mode, callback, expireFn);
}
/**
* Runs the game to the title phase.
* @returns A promise that resolves when the title phase is reached.
*/
runToTitle(): Promise<void> {
return new Promise(async(resolve) => {
await this.phaseInterceptor.run(LoginPhase);
this.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
this.scene.gameData.gender = PlayerGender.MALE;
this.endPhase();
}, () => this.isCurrentPhase(TitlePhase));
await this.phaseInterceptor.run(SelectGenderPhase, () => this.isCurrentPhase(TitlePhase));
await this.phaseInterceptor.run(TitlePhase);
this.scene.gameSpeed = 5;
this.scene.moveAnimations = false;
this.scene.showLevelUpStats = false;
this.scene.expGainsSpeed = 3;
this.scene.expParty = ExpNotification.SKIP;
this.scene.hpBarSpeed = 3;
resolve();
});
}
/**
* Runs the game to the summon phase.
* @param species - Optional array of species to summon.
* @returns A promise that resolves when the summon phase is reached.
*/
runToSummon(species?: Species[]): Promise<void> {
return new Promise(async(resolve) => {
await this.runToTitle();
this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const starters = generateStarter(this.scene, species);
const selectStarterPhase = new SelectStarterPhase(this.scene, GameModes.CLASSIC);
this.scene.pushPhase(new EncounterPhase(this.scene, false));
selectStarterPhase.initBattle(starters);
});
await this.phaseInterceptor.run(EncounterPhase);
resolve();
});
}
/**
* Starts a battle.
* @param species - Optional array of species to start the battle with.
* @returns A promise that resolves when the battle is started.
*/
startBattle(species?: Species[]): Promise<void> {
return new Promise(async(resolve) => {
await this.runToSummon(species);
await this.phaseInterceptor.runFrom(PostSummonPhase).to(ToggleDoublePositionPhase);
await this.phaseInterceptor.run(SummonPhase, () => this.isCurrentPhase(CheckSwitchPhase) || this.isCurrentPhase(PostSummonPhase));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(PostSummonPhase));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(PostSummonPhase));
await this.phaseInterceptor.run(CheckSwitchPhase, () => this.isCurrentPhase(PostSummonPhase));
await this.phaseInterceptor.run(CheckSwitchPhase, () => this.isCurrentPhase(PostSummonPhase));
await this.phaseInterceptor.runFrom(PostSummonPhase).to(CommandPhase);
await waitUntil(() => this.scene.ui?.getMode() === Mode.COMMAND);
console.log("==================[New Turn]==================");
expect(this.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(this.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
return resolve();
});
}
/**
* Checks if the player has won the battle.
* @returns True if the player has won, otherwise false.
*/
isVictory() {
return this.scene.currentBattle.enemyParty.every(pokemon => pokemon.isFainted());
}
/**
* Checks if the current phase matches the target phase.
* @param phaseTarget - The target phase.
* @returns True if the current phase matches the target phase, otherwise false.
*/
isCurrentPhase(phaseTarget) {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
return this.scene.getCurrentPhase().constructor.name === targetName;
}
/**
* Checks if the current mode matches the target mode.
* @param mode - The target mode.
* @returns True if the current mode matches the target mode, otherwise false.
*/
isCurrentMode(mode: Mode) {
return this.scene.ui?.getMode() === mode;
}
/**
* Exports the save data to import it in a test game.
* @returns A promise that resolves with the exported save data.
*/
exportSaveToTest(): Promise<string> {
return new Promise(async (resolve) => {
await this.scene.gameData.saveAll(this.scene, true, true, true, true);
this.scene.reset(true);
await waitUntil(() => this.scene.ui?.getMode() === Mode.TITLE);
await this.scene.gameData.tryExportData(GameDataType.SESSION, 0);
await waitUntil(() => localStorage.hasOwnProperty("toExport"));
return resolve(localStorage.getItem("toExport"));
});
}
/**
* Imports game data from a file.
* @param path - The path to the data file.
* @returns A promise that resolves with a tuple containing a boolean indicating success and an integer status code.
*/
async importData(path): Promise<[boolean, integer]> {
const saveKey = "x0i2O7WRiANTqPmZ";
const dataRaw = fs.readFileSync(path, {encoding: "utf8", flag: "r"});
let dataStr = AES.decrypt(dataRaw, saveKey).toString(enc.Utf8);
dataStr = this.scene.gameData.convertSystemDataStr(dataStr);
const systemData = this.scene.gameData.parseSystemData(dataStr);
const valid = !!systemData.dexData && !!systemData.timestamp;
if (valid) {
await updateUserInfo();
await this.scene.gameData.initSystem(dataStr);
}
return updateUserInfo();
}
}

View File

@ -1,87 +0,0 @@
// Function to convert Blob to string
import {getDailyRunStarters} from "#app/data/daily-run";
import {Gender} from "#app/data/gender";
import {Species} from "#app/data/enums/species";
import {Starter} from "#app/ui/starter-select-ui-handler";
import {GameModes, gameModes} from "#app/game-mode";
import {getPokemonSpecies, getPokemonSpeciesForm} from "#app/data/pokemon-species";
import {PlayerPokemon} from "#app/field/pokemon";
export function blobToString(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(reader.result);
};
reader.onerror = () => {
reject(new Error("Error reading Blob as string"));
};
reader.readAsText(blob);
});
}
export function holdOn(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export function generateStarter(scene, species?: Species[]) {
const seed = "test";
const starters = getTestRunStarters(scene, seed, species);
const startingLevel = scene.gameMode.getStartingLevel();
for (const starter of starters) {
const starterProps = scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr);
const starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0));
const starterGender = starter.species.malePercent !== null
? !starterProps.female ? Gender.MALE : Gender.FEMALE
: Gender.GENDERLESS;
const starterPokemon = scene.addPlayerPokemon(starter.species, startingLevel, starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, undefined, starter.nature);
starter.moveset = starterPokemon.moveset;
}
return starters;
}
function getTestRunStarters(scene, seed, species) {
if (!species) {
return getDailyRunStarters(scene, seed);
}
const starters: Starter[] = [];
const startingLevel = gameModes[GameModes.CLASSIC].getStartingLevel();
for (const specie of species) {
const starterSpeciesForm = getPokemonSpeciesForm(specie, 0);
const starterSpecies = getPokemonSpecies(starterSpeciesForm.speciesId);
const pokemon = new PlayerPokemon(scene, starterSpecies, startingLevel, undefined, 0, undefined, undefined, undefined, undefined, undefined, undefined);
const starter: Starter = {
species: starterSpecies,
dexAttr: pokemon.getDexAttr(),
abilityIndex: pokemon.abilityIndex,
passive: false,
nature: pokemon.getNature(),
pokerus: pokemon.pokerus
};
starters.push(starter);
}
return starters;
}
export function waitUntil(truth) {
return new Promise(resolve => {
const interval = setInterval(() => {
if (truth()) {
clearInterval(interval);
resolve(true);
}
}, 1000);
});
}
export function getMovePosition(scene, pokemonIndex, moveIndex) {
const playerPokemon = scene.getPlayerField()[pokemonIndex];
const moveSet = playerPokemon.getMoveset();
const index = moveSet.findIndex((move) => move.moveId === moveIndex);
return index;
}

View File

@ -1,252 +0,0 @@
/* eslint-disable */
// @ts-nocheck
import * as main from "#app/main";
import fs from "fs";
import InputManager = Phaser.Input.InputManager;
import KeyboardManager = Phaser.Input.Keyboard.KeyboardManager;
import KeyboardPlugin = Phaser.Input.Keyboard.KeyboardPlugin;
import GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin;
import EventEmitter = Phaser.Events.EventEmitter;
import UpdateList = Phaser.GameObjects.UpdateList;
import MockGraphics from "#app/test/utils/mocks/mocksContainer/mockGraphics";
import MockTextureManager from "#app/test/utils/mocks/mockTextureManager";
import Phaser from "phaser";
import {blobToString} from "#app/test/utils/gameManagerUtils";
import {vi} from "vitest";
import mockLocalStorage from "#app/test/utils/mocks/mockLocalStorage";
import mockConsoleLog from "#app/test/utils/mocks/mockConsoleLog";
import MockLoader from "#app/test/utils/mocks/mockLoader";
import {MockFetch} from "#app/test/utils/mocks/mockFetch";
import * as Utils from "#app/utils";
import InputText from "phaser3-rex-plugins/plugins/inputtext";
import {MockClock} from "#app/test/utils/mocks/mockClock";
import BattleScene from "#app/battle-scene.js";
import {MoveAnim} from "#app/data/battle-anims";
import Pokemon from "#app/field/pokemon";
import * as battleScene from "#app/battle-scene";
Object.defineProperty(window, "localStorage", {
value: mockLocalStorage(),
});
Object.defineProperty(window, "console", {
value: mockConsoleLog(false),
});
InputText.prototype.setElement = () => null;
InputText.prototype.resize = () => null;
window.URL.createObjectURL = (blob: Blob) => {
blobToString(blob).then((data: string) => {
localStorage.setItem("toExport", data);
})
return null;
};
navigator.getGamepads = vi.fn().mockReturnValue([]);
global.fetch = vi.fn(MockFetch);
Utils.setCookie(Utils.sessionIdKey, 'fake_token');
window.matchMedia = () => ({
matches: false,
});
/**
* Sets this object's position relative to another object with a given offset
* @param guideObject {@linkcode Phaser.GameObjects.GameObject} to base the position off of
* @param x The relative x position
* @param y The relative y position
*/
const setPositionRelative = function (guideObject: any, x: number, y: number) {
const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX));
const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY));
this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y);
};
Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Sprite.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Image.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.NineSlice.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Text.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative;
export default class GameWrapper {
public game: Phaser.Game;
public scene: BattleScene;
constructor(phaserGame: Phaser.Game, bypassLogin: boolean) {
Phaser.Math.RND.sow([ 'test' ]);
vi.spyOn(Utils, "apiFetch", "get").mockReturnValue(fetch);
if (bypassLogin) {
vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(true);
}
this.game = phaserGame;
MoveAnim.prototype.getAnim = () => ({
frames: {},
});
Pokemon.prototype.enableMask = () => null;
localStorage.clear();
}
setScene(scene: BattleScene) {
this.scene = scene;
this.injectMandatory();
this.scene.preload && this.scene.preload();
this.scene.create();
}
injectMandatory() {
this.game.config = {
seed: ["test"],
}
this.scene.game = this.game;
this.game.renderer = {
maxTextures: -1,
gl: {},
deleteTexture: () => null,
canvasToTexture: () => ({}),
createCanvasTexture: () => ({}),
pipelines: {
add: () => null,
},
};
this.scene.renderer = this.game.renderer;
this.scene.children = {
removeAll: () => null,
};
this.scene.sound = {
play: () => null,
pause: () => null,
setRate: () => null,
add: () => this.scene.sound,
get: () => this.scene.sound,
getAllPlaying: () => [],
manager: {
game: this.game,
},
setVolume: () => null,
stopByKey: () => null,
on: (evt, callback) => callback(),
key: "",
};
this.scene.tweens = {
add: (data) => {
if (data.onComplete) {
data.onComplete();
}
},
getTweensOf: () => ([]),
killTweensOf: () => ([]),
chain: () => null,
addCounter: (data) => {
if (data.onComplete) {
data.onComplete();
}
},
};
this.scene.anims = this.game.anims;
this.scene.cache = this.game.cache;
this.scene.plugins = this.game.plugins;
this.scene.registry = this.game.registry;
this.scene.scale = this.game.scale;
this.scene.textures = this.game.textures;
this.scene.events = this.game.events;
this.scene.manager = new InputManager(this.game, {});
this.scene.manager.keyboard = new KeyboardManager(this.scene);
this.scene.pluginEvents = new EventEmitter();
this.scene.domContainer = {} as HTMLDivElement;
this.scene.spritePipeline = {};
this.scene.fieldSpritePipeline = {};
this.scene.load = new MockLoader(this.scene);
this.scene.sys = {
queueDepthSort: () => null,
anims: this.game.anims,
game: this.game,
textures: {
addCanvas: () => ({
get: () => ({ // this.frame in Text.js
source: {},
setSize: () => null,
glTexture: () => ({
spectorMetadata: {},
}),
}),
})
},
cache: this.scene.load.cacheManager,
scale: this.game.scale,
// _scene.sys.scale = new ScaleManager(_scene);
// events: {
// on: () => null,
// },
events: new EventEmitter(),
settings: {
loader: {
key: 'battle',
}
},
input: this.game.input,
};
const mockTextureManager = new MockTextureManager(this.scene);
this.scene.add = mockTextureManager.add;
this.scene.sys.displayList = this.scene.add.displayList;
this.scene.sys.updateList = new UpdateList(this.scene);
this.scene.systems = this.scene.sys;
this.scene.input = this.game.input;
this.scene.scene = this.scene;
this.scene.input.keyboard = new KeyboardPlugin(this.scene);
this.scene.input.gamepad = new GamepadPlugin(this.scene);
this.scene.cachedFetch = (url, init) => {
return new Promise((resolve) => {
// need to remove that if later we want to test battle-anims
const newUrl = url.includes('./battle-anims/') ? prependPath('./battle-anims/tackle.json') : prependPath(url);
let raw;
try {
raw = fs.readFileSync(newUrl, {encoding: "utf8", flag: "r"});
} catch(e) {
return resolve(createFetchBadResponse({}));
}
const data = JSON.parse(raw);
const response = createFetchResponse(data);
return resolve(response);
});
};
this.scene.make = {
graphics: (config) => new MockGraphics(mockTextureManager, config),
rexTransitionImagePack: () => ({
transit: () => null,
}),
};
this.scene.time = new MockClock(this.scene);
}
}
function prependPath(originalPath) {
const prefix = "public";
if (originalPath.startsWith("./")) {
return originalPath.replace("./", `${prefix}/`);
}
return originalPath;
}
// Simulate fetch response
function createFetchResponse(data) {
return {
ok: true,
status: 200,
json: () => Promise.resolve(data),
text: () => Promise.resolve(JSON.stringify(data)),
};
}
// Simulate fetch response
function createFetchBadResponse(data) {
return {
ok: false,
status: 404,
json: () => Promise.resolve(data),
text: () => Promise.resolve(JSON.stringify(data)),
};
}

View File

@ -1,115 +0,0 @@
import BattleScene from "#app/battle-scene";
import Phaser from "phaser";
import {InputsController} from "#app/inputs-controller";
import pad_xbox360 from "#app/configs/inputs/pad_xbox360";
import {holdOn} from "#app/test/utils/gameManagerUtils";
import {initTouchControls} from "#app/touch-controls";
import { JSDOM } from "jsdom";
import fs from "fs";
export default class InputsHandler {
private scene: BattleScene;
private events: Phaser.Events.EventEmitter;
private inputController: InputsController;
public log = [];
public logUp = [];
private fakePad: Fakepad;
private fakeMobile: FakeMobile;
constructor(scene: BattleScene) {
this.scene = scene;
this.inputController = this.scene.inputController;
this.fakePad = new Fakepad(pad_xbox360);
this.fakeMobile = new FakeMobile();
this.scene.input.gamepad.gamepads.push(this.fakePad);
this.init();
}
pressTouch(button: string, duration: integer): Promise<void> {
return new Promise(async (resolve) => {
this.fakeMobile.touchDown(button);
await holdOn(duration);
this.fakeMobile.touchUp(button);
resolve();
});
}
pressGamepadButton(button: integer, duration: integer): Promise<void> {
return new Promise(async (resolve) => {
this.scene.input.gamepad.emit("down", this.fakePad, {index: button});
await holdOn(duration);
this.scene.input.gamepad.emit("up", this.fakePad, {index: button});
resolve();
});
}
pressKeyboardKey(key: integer, duration: integer): Promise<void> {
return new Promise(async (resolve) => {
this.scene.input.keyboard.emit("keydown", {keyCode: key});
await holdOn(duration);
this.scene.input.keyboard.emit("keyup", {keyCode: key});
resolve();
});
}
init(): void {
setInterval(() => {
this.inputController.update();
});
initTouchControls(this.inputController.events);
this.events = this.inputController.events;
this.scene.input.gamepad.emit("connected", this.fakePad);
this.listenInputs();
}
listenInputs(): void {
this.events.on("input_down", (event) => {
this.log.push({type: "input_down", button: event.button});
}, this);
this.events.on("input_up", (event) => {
this.logUp.push({type: "input_up", button: event.button});
}, this);
}
}
class Fakepad extends Phaser.Input.Gamepad.Gamepad {
public id: string;
public index: number;
constructor(pad) {
super(undefined, {...pad, buttons: pad.deviceMapping, axes: []});
this.id = "xbox_360_fakepad";
this.index = 0;
}
}
class FakeMobile {
constructor() {
const fakeMobilePage = fs.readFileSync("./src/test/utils/fakeMobile.html", {encoding: "utf8", flag: "r"});
const dom = new JSDOM(fakeMobilePage);
Object.defineProperty(window, "document", {
value: dom.window.document,
configurable: true,
});
}
touchDown(button: string) {
const node = document.querySelector(`[data-key][id='${button}']`);
if (!node) {
return;
}
const event = new Event("touchstart");
node.dispatchEvent(event);
}
touchUp(button: string) {
const node = document.querySelector(`[data-key][id='${button}']`);
if (!node) {
return;
}
const event = new Event("touchend");
node.dispatchEvent(event);
}
}

View File

@ -1,79 +0,0 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import {apiFetch} from "#app/utils";
import {waitUntil} from "#app/test/utils/gameManagerUtils";
describe("Test misc", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
});
it("test fetch mock async", async () => {
const spy = vi.fn();
await fetch("https://localhost:8080/account/info").then(response => {
expect(response.status).toBe(200);
expect(response.ok).toBe(true);
return response.json();
}).then(data => {
spy(); // Call the spy function
expect(data).toEqual({"username":"greenlamp","lastSessionSlot":0});
});
expect(spy).toHaveBeenCalled();
});
it("test apifetch mock async", async () => {
const spy = vi.fn();
await apiFetch("https://localhost:8080/account/info").then(response => {
expect(response.status).toBe(200);
expect(response.ok).toBe(true);
return response.json();
}).then(data => {
spy(); // Call the spy function
expect(data).toEqual({"username":"greenlamp","lastSessionSlot":0});
});
expect(spy).toHaveBeenCalled();
});
it("test fetch mock sync", async () => {
const response = await fetch("https://localhost:8080/account/info");
const data = await response.json();
expect(response.ok).toBe(true);
expect(response.status).toBe(200);
expect(data).toEqual({"username":"greenlamp","lastSessionSlot":0});
});
it("test apifetch mock sync", async () => {
const data = await game.scene.cachedFetch("./battle-anims/splishy-splash.json");
expect(data).not.toBeUndefined();
});
it("testing wait phase queue", async () => {
const fakeScene = {
phaseQueue: [1, 2, 3] // Initially not empty
};
setTimeout(() => {
fakeScene.phaseQueue = [];
}, 500);
const spy = vi.fn();
await waitUntil(() => fakeScene.phaseQueue.length === 0).then(result => {
expect(result).toBe(true);
spy(); // Call the spy function
});
expect(spy).toHaveBeenCalled();
});
});

View File

@ -1,17 +0,0 @@
import Clock = Phaser.Time.Clock;
export class MockClock extends Clock {
constructor(scene) {
super(scene);
setInterval(() => {
/*
To simulate frame update
eventEmitter.on(SceneEvents.PRE_UPDATE, this.preUpdate, this);
eventEmitter.on(SceneEvents.UPDATE, this.update, this);
*/
this.preUpdate(this.systems.game.loop.time, 100);
this.update(this.systems.game.loop.time, 100);
}, 100);
}
}

View File

@ -1,82 +0,0 @@
const MockConsoleLog = (_logDisabled= false, _phaseText=false) => {
let logs = [];
const logDisabled: boolean = _logDisabled;
const phaseText: boolean = _phaseText;
const originalLog = console.log;
const originalError = console.error;
const originalDebug = console.debug;
const originalWarn = console.warn;
const notified = [];
const blacklist = ["Phaser", "variant icon does not exist", "Texture \"%s\" not found"];
const whitelist = ["Phase"];
return ({
log(...args) {
const argsStr = this.getStr(args);
logs.push(argsStr);
if (logDisabled && (!phaseText)) {
return;
}
if ((phaseText && !whitelist.some((b) => argsStr.includes(b))) || blacklist.some((b) => argsStr.includes(b))) {
return;
}
originalLog(args);
},
error(...args) {
const argsStr = this.getStr(args);
logs.push(argsStr);
originalError(args); // Appelle le console.error originel
},
debug(...args) {
const argsStr = this.getStr(args);
logs.push(argsStr);
if (logDisabled && (!phaseText)) {
return;
}
if (!whitelist.some((b) => argsStr.includes(b)) || blacklist.some((b) => argsStr.includes(b))) {
return;
}
originalDebug(args);
},
warn(...args) {
const argsStr = this.getStr(args);
logs.push(args);
if (logDisabled && (!phaseText)) {
return;
}
if (!whitelist.some((b) => argsStr.includes(b)) || blacklist.some((b) => argsStr.includes(b))) {
return;
}
originalWarn(args);
},
notify(msg) {
originalLog(msg);
notified.push(msg);
},
getLogs() {
return logs;
},
clearLogs() {
logs = [];
},
getStr(...args) {
return args.map(arg => {
if (typeof arg === "object" && arg !== null) {
// Handle objects including arrays
return JSON.stringify(arg, (key, value) =>
typeof value === "bigint" ? value.toString() : value
);
} else if (typeof arg === "bigint") {
// Handle BigInt values
return arg.toString();
} else {
// Handle all other types
return arg.toString();
}
}).join(";");
},
});
};
export default MockConsoleLog;

View File

@ -1,32 +0,0 @@
export const MockFetch = (input, init) => {
const url = typeof input === "string" ? input : input.url;
let responseHandler;
let responseText;
const handlers = {
"account/info": {"username":"greenlamp","lastSessionSlot":0},
"savedata/session": {},
"savedata/system": {},
"savedata/updateall": "",
"daily/rankingpagecount": { data: 0 },
"game/titlestats": {"playerCount":0,"battleCount":5},
"daily/rankings": [],
};
for (const key of Object.keys(handlers)) {
if (url.includes(key)) {
responseHandler = async() => handlers[key];
responseText = async() => handlers[key] ? JSON.stringify(handlers[key]) : handlers[key];
break;
}
}
return Promise.resolve({
ok: true,
status: 200,
json: responseHandler,
text: responseText,
});
};

View File

@ -1,42 +0,0 @@
import CacheManager = Phaser.Cache.CacheManager;
export default class MockLoader {
public cacheManager;
constructor(scene) {
this.cacheManager = new CacheManager(scene);
}
once(event, callback) {
callback();
}
setBaseURL(url) {
return null;
}
video() {
return null;
}
spritesheet(key, url, frameConfig) {
}
audio(key, url) {
}
isLoading() {
return false;
}
start() {
}
image() {
}
atlas(key, textureUrl, atlasUrl) {
}
}

View File

@ -1,27 +0,0 @@
const mockLocalStorage = (() => {
let store = {} as Storage;
return {
getItem(key: string) {
return store[key];
},
setItem(key: string, value: string) {
store[key] = value;
},
hasOwnProperty(key: string) {
return store.hasOwnProperty(key);
},
removeItem(key: string) {
delete store[key];
},
clear() {
store = {} as Storage;
},
};
});
export default mockLocalStorage;

View File

@ -1,85 +0,0 @@
import MockContainer from "#app/test/utils/mocks/mocksContainer/mockContainer";
import MockSprite from "#app/test/utils/mocks/mocksContainer/mockSprite";
import MockRectangle from "#app/test/utils/mocks/mocksContainer/mockRectangle";
import MockNineslice from "#app/test/utils/mocks/mocksContainer/mockNineslice";
import MockImage from "#app/test/utils/mocks/mocksContainer/mockImage";
import MockText from "#app/test/utils/mocks/mocksContainer/mockText";
import MockPolygon from "#app/test/utils/mocks/mocksContainer/mockPolygon";
export default class MockTextureManager {
private textures: Map<string, any>;
private scene;
public add;
public displayList;
public list = [];
constructor(scene) {
this.scene = scene;
this.textures = new Map();
this.displayList = new Phaser.GameObjects.DisplayList(scene);
this.add = {
container: this.container.bind(this),
sprite: this.sprite.bind(this),
tileSprite: this.sprite.bind(this),
existing: this.existing.bind(this),
rectangle: this.rectangle.bind(this),
nineslice: this.nineslice.bind(this),
image: this.image.bind(this),
polygon: this.polygon.bind(this),
text: this.text.bind(this),
bitmapText: this.text.bind(this),
displayList: this.displayList,
};
}
container(x, y) {
const container = new MockContainer(this, x, y);
this.list.push(container);
return container;
}
sprite(x,y, texture) {
const sprite = new MockSprite(this, x, y, texture);
this.list.push(sprite);
return sprite;
}
existing(obj) {
// const whitelist = ["ArenaBase", "PlayerPokemon", "EnemyPokemon"];
// const key = obj.constructor.name;
// if (whitelist.includes(key) || obj.texture?.key?.includes("trainer_")) {
// this.containers.push(obj);
// }
}
rectangle(x, y, width, height, fillColor) {
const rectangle = new MockRectangle(this, x, y, width, height, fillColor);
this.list.push(rectangle);
return rectangle;
}
nineslice(x, y, texture, frame, width, height, leftWidth, rightWidth, topHeight, bottomHeight) {
const nineSlice = new MockNineslice(this, x, y, texture, frame, width, height, leftWidth, rightWidth, topHeight, bottomHeight);
this.list.push(nineSlice);
return nineSlice;
}
image(x, y, texture) {
const image = new MockImage(this, x, y, texture);
this.list.push(image);
return image;
}
text(x, y, content, styleOptions) {
const text = new MockText(this, x, y, content, styleOptions);
this.list.push(text);
return text;
}
polygon(x, y, content, fillColor, fillAlpha) {
const polygon = new MockPolygon(this, x, y, content, fillColor, fillAlpha);
this.list.push(polygon);
return polygon;
}
}

View File

@ -1,207 +0,0 @@
import MockTextureManager from "#app/test/utils/mocks/mockTextureManager";
export default class MockContainer {
protected x;
protected y;
protected scene;
protected width;
protected height;
protected visible;
private alpha;
private style;
public frame;
protected textureManager;
public list = [];
constructor(textureManager: MockTextureManager, x, y) {
this.x = x;
this.y = y;
this.frame = {};
this.textureManager = textureManager;
}
setVisible(visible) {
this.visible = visible;
}
once(event, callback, source) {
}
off(event, callback, source) {
}
removeFromDisplayList() {
// same as remove or destroy
}
addedToScene() {
// This callback is invoked when this Game Object is added to a Scene.
}
setSize(width, height) {
// Sets the size of this Game Object.
}
setMask() {
/// Sets the mask that this Game Object will use to render with.
}
setPositionRelative(source, x, y) {
/// Sets the position of this Game Object to be a relative position from the source Game Object.
}
setInteractive(hitArea?, callback?, dropZone?) {
/// Sets the InteractiveObject to be a drop zone for a drag and drop operation.
}
setOrigin(x, y) {
this.x = x;
this.y = y;
}
setAlpha(alpha) {
this.alpha = alpha;
}
setFrame(frame, updateSize?: boolean, updateOrigin?: boolean) {
// Sets the frame this Game Object will use to render with.
}
setScale(scale) {
// Sets the scale of this Game Object.
}
setPosition(x, y) {
this.x = x;
this.y = y;
}
setX(x) {
this.x = x;
}
setY(y) {
this.y = y;
}
destroy() {
this.list = [];
}
setShadow(shadowXpos, shadowYpos, shadowColor) {
// Sets the shadow settings for this Game Object.
}
setLineSpacing(lineSpacing) {
// Sets the line spacing value of this Game Object.
}
setText(text) {
// Sets the text this Game Object will display.
}
setAngle(angle) {
// Sets the angle of this Game Object.
}
setShadowOffset(offsetX, offsetY) {
// Sets the shadow offset values.
}
setWordWrapWidth(width) {
// Sets the width (in pixels) to use for wrapping lines.
}
setFontSize(fontSize) {
// Sets the font size of this Game Object.
}
getBounds() {
return { width: this.width, height: this.height };
}
setColor(color) {
// Sets the tint of this Game Object.
}
setShadowColor(color) {
// Sets the shadow color.
}
setTint(color) {
// Sets the tint of this Game Object.
}
setStrokeStyle(thickness, color) {
// Sets the stroke style for the graphics.
return this;
}
setDepth(depth) {
// Sets the depth of this Game Object.
}
setTexture(texture) {
// Sets the texture this Game Object will use to render with.
}
clearTint() {
// Clears any previously set tint.
}
sendToBack() {
// Sends this Game Object to the back of its parent's display list.
}
moveAbove(obj) {
// Moves this Game Object to be above the given Game Object in the display list.
}
moveBelow(obj) {
// Moves this Game Object to be below the given Game Object in the display list.
}
setName(name) {
// return this.phaserSprite.setName(name);
}
bringToTop(obj) {
// Brings this Game Object to the top of its parents display list.
}
on(event, callback, source) {
}
add(obj) {
// Adds a child to this Game Object.
this.list.push(obj);
}
removeAll() {
// Removes all Game Objects from this Container.
this.list = [];
}
addAt(obj, index) {
// Adds a Game Object to this Container at the given index.
this.list.splice(index, 0, obj);
}
remove(obj) {
const index = this.list.indexOf(obj);
if (index !== -1) {
this.list.splice(index, 1);
}
}
getIndex(obj) {
const index = this.list.indexOf(obj);
return index || -1;
}
getAt(index) {
return this.list[index];
}
getAll() {
return this.list;
}
}

View File

@ -1,96 +0,0 @@
export default class MockGraphics {
private scene;
public list = [];
constructor(textureManager, config) {
this.scene = textureManager.scene;
}
fillStyle(color) {
// Sets the fill style to be used by the fill methods.
}
beginPath() {
// Starts a new path by emptying the list of sub-paths. Call this method when you want to create a new path.
}
fillRect(x, y, width, height) {
// Adds a rectangle shape to the path which is filled when you call fill().
}
createGeometryMask() {
// Creates a geometry mask.
}
setOrigin(x, y) {
}
setAlpha(alpha) {
}
setVisible(visible) {
}
setName(name) {
}
once(event, callback, source) {
}
removeFromDisplayList() {
// same as remove or destroy
}
addedToScene() {
// This callback is invoked when this Game Object is added to a Scene.
}
setPositionRelative(source, x, y) {
/// Sets the position of this Game Object to be a relative position from the source Game Object.
}
destroy() {
this.list = [];
}
setScale(scale) {
// Sets the scale of this Game Object.
}
off(event, callback, source) {
}
add(obj) {
// Adds a child to this Game Object.
this.list.push(obj);
}
removeAll() {
// Removes all Game Objects from this Container.
this.list = [];
}
addAt(obj, index) {
// Adds a Game Object to this Container at the given index.
this.list.splice(index, 0, obj);
}
remove(obj) {
const index = this.list.indexOf(obj);
if (index !== -1) {
this.list.splice(index, 1);
}
}
getIndex(obj) {
const index = this.list.indexOf(obj);
return index || -1;
}
getAt(index) {
return this.list[index];
}
getAll() {
return this.list;
}
}

View File

@ -1,11 +0,0 @@
import MockContainer from "#app/test/utils/mocks/mocksContainer/mockContainer";
export default class MockImage extends MockContainer {
private texture;
constructor(textureManager, x, y, texture) {
super(textureManager, x, y);
this.texture = texture;
}
}

View File

@ -1,20 +0,0 @@
import MockContainer from "#app/test/utils/mocks/mocksContainer/mockContainer";
export default class MockNineslice extends MockContainer {
private texture;
private leftWidth;
private rightWidth;
private topHeight;
private bottomHeight;
constructor(textureManager, x, y, texture, frame, width, height, leftWidth, rightWidth, topHeight, bottomHeight) {
super(textureManager, x, y);
this.texture = texture;
this.frame = frame;
this.leftWidth = leftWidth;
this.rightWidth = rightWidth;
this.topHeight = topHeight;
this.bottomHeight = bottomHeight;
}
}

View File

@ -1,9 +0,0 @@
import MockContainer from "#app/test/utils/mocks/mocksContainer/mockContainer";
export default class MockPolygon extends MockContainer {
constructor(textureManager, x, y, content, fillColor, fillAlpha) {
super(textureManager, x, y);
}
}

View File

@ -1,74 +0,0 @@
export default class MockRectangle {
private fillColor;
private scene;
public list = [];
constructor(textureManager, x, y, width, height, fillColor) {
this.fillColor = fillColor;
this.scene = textureManager.scene;
}
setOrigin(x, y) {
}
setAlpha(alpha) {
}
setVisible(visible) {
}
setName(name) {
}
once(event, callback, source) {
}
removeFromDisplayList() {
// same as remove or destroy
}
addedToScene() {
// This callback is invoked when this Game Object is added to a Scene.
}
setPositionRelative(source, x, y) {
/// Sets the position of this Game Object to be a relative position from the source Game Object.
}
destroy() {
this.list = [];
}
add(obj) {
// Adds a child to this Game Object.
this.list.push(obj);
}
removeAll() {
// Removes all Game Objects from this Container.
this.list = [];
}
addAt(obj, index) {
// Adds a Game Object to this Container at the given index.
this.list.splice(index, 0, obj);
}
remove(obj) {
const index = this.list.indexOf(obj);
if (index !== -1) {
this.list.splice(index, 1);
}
}
getIndex(obj) {
const index = this.list.indexOf(obj);
return index || -1;
}
getAt(index) {
return this.list[index];
}
getAll() {
return this.list;
}
}

View File

@ -1,205 +0,0 @@
import Sprite = Phaser.GameObjects.Sprite;
import Frame = Phaser.Textures.Frame;
import Phaser from "phaser";
export default class MockSprite {
private phaserSprite;
public pipelineData;
public texture;
public key;
public frame;
public textureManager;
public scene;
public anims;
public list = [];
constructor(textureManager, x, y, texture) {
this.textureManager = textureManager;
this.scene = textureManager.scene;
Phaser.GameObjects.Sprite.prototype.setInteractive = this.setInteractive;
// @ts-ignore
Phaser.GameObjects.Sprite.prototype.setTexture = this.setTexture;
Phaser.GameObjects.Sprite.prototype.setSizeToFrame = this.setSizeToFrame;
Phaser.GameObjects.Sprite.prototype.setFrame = this.setFrame;
// Phaser.GameObjects.Sprite.prototype.disable = this.disable;
// Phaser.GameObjects.Sprite.prototype.texture = { frameTotal: 1, get: () => null };
this.phaserSprite = new Phaser.GameObjects.Sprite(textureManager.scene, x, y, texture);
this.pipelineData = {};
this.texture = {
key: texture || "",
};
this.anims = {
pause: () => null,
};
}
setTexture(key: string, frame?: string | number) {
return this;
}
setSizeToFrame(frame?: boolean | Frame): Sprite {
return {} as Sprite;
}
setPipeline(obj) {
// Sets the pipeline of this Game Object.
return this.phaserSprite.setPipeline(obj);
}
off(event, callback, source) {
}
setTintFill(color) {
// Sets the tint fill color.
return this.phaserSprite.setTintFill(color);
}
setScale(scale) {
return this.phaserSprite.setScale(scale);
}
setOrigin(x, y) {
return this.phaserSprite.setOrigin(x, y);
}
setSize(width, height) {
// Sets the size of this Game Object.
return this.phaserSprite.setSize(width, height);
}
once(event, callback, source) {
return this.phaserSprite.once(event, callback, source);
}
removeFromDisplayList() {
// same as remove or destroy
return this.phaserSprite.removeFromDisplayList();
}
addedToScene() {
// This callback is invoked when this Game Object is added to a Scene.
return this.phaserSprite.addedToScene();
}
setVisible(visible) {
return this.phaserSprite.setVisible(visible);
}
setPosition(x, y) {
return this.phaserSprite.setPosition(x, y);
}
stop() {
return this.phaserSprite.stop();
}
setInteractive(hitArea, hitAreaCallback, dropZone) {
return null;
}
on(event, callback, source) {
return this.phaserSprite.on(event, callback, source);
}
setAlpha(alpha) {
return this.phaserSprite.setAlpha(alpha);
}
setTint(color) {
// Sets the tint of this Game Object.
return this.phaserSprite.setTint(color);
}
setFrame(frame, updateSize?: boolean, updateOrigin?: boolean) {
// Sets the frame this Game Object will use to render with.
this.frame = frame;
return frame;
}
setPositionRelative(source, x, y) {
/// Sets the position of this Game Object to be a relative position from the source Game Object.
return this.phaserSprite.setPositionRelative(source, x, y);
}
setCrop(x, y, width, height) {
// Sets the crop size of this Game Object.
return this.phaserSprite.setCrop(x, y, width, height);
}
clearTint() {
// Clears any previously set tint.
return this.phaserSprite.clearTint();
}
disableInteractive() {
// Disables Interactive features of this Game Object.
return null;
}
apply() {
return this.phaserSprite.apply();
}
play() {
// return this.phaserSprite.play();
}
setPipelineData(key, value) {
this.pipelineData[key] = value;
}
destroy() {
return this.phaserSprite.destroy();
}
setName(name) {
return this.phaserSprite.setName(name);
}
setAngle(angle) {
return this.phaserSprite.setAngle(angle);
}
setMask() {
}
add(obj) {
// Adds a child to this Game Object.
this.list.push(obj);
}
removeAll() {
// Removes all Game Objects from this Container.
this.list = [];
}
addAt(obj, index) {
// Adds a Game Object to this Container at the given index.
this.list.splice(index, 0, obj);
}
remove(obj) {
const index = this.list.indexOf(obj);
if (index !== -1) {
this.list.splice(index, 1);
}
}
getIndex(obj) {
const index = this.list.indexOf(obj);
return index || -1;
}
getAt(index) {
return this.list[index];
}
getAll() {
return this.list;
}
}

View File

@ -1,265 +0,0 @@
import UI from "#app/ui/ui";
export default class MockText {
private phaserText;
private wordWrapWidth;
private splitRegExp;
private scene;
private textureManager;
public list = [];
constructor(textureManager, x, y, content, styleOptions) {
this.scene = textureManager.scene;
this.textureManager = textureManager;
// Phaser.GameObjects.TextStyle.prototype.setStyle = () => null;
// Phaser.GameObjects.Text.prototype.updateText = () => null;
// Phaser.Textures.TextureManager.prototype.addCanvas = () => {};
UI.prototype.showText = this.showText;
// super(scene, x, y);
// this.phaserText = new Phaser.GameObjects.Text(scene, x, y, content, styleOptions);
}
runWordWrap(text) {
if (!text) {
return "";
}
let result = "";
this.splitRegExp = /(?:\r\n|\r|\n)/;
const lines = text.split(this.splitRegExp);
const lastLineIndex = lines.length - 1;
const whiteSpaceWidth = 2;
for (let i = 0; i <= lastLineIndex; i++) {
let spaceLeft = this.wordWrapWidth;
const words = lines[i].split(" ");
const lastWordIndex = words.length - 1;
for (let j = 0; j <= lastWordIndex; j++) {
const word = words[j];
const wordWidth = word.length * 2;
let wordWidthWithSpace = wordWidth;
if (j < lastWordIndex) {
wordWidthWithSpace += whiteSpaceWidth;
}
if (wordWidthWithSpace > spaceLeft) {
// Skip printing the newline if it's the first word of the line that is greater
// than the word wrap width.
if (j > 0) {
result += "\n";
spaceLeft = this.wordWrapWidth;
}
}
result += word;
if (j < lastWordIndex) {
result += " ";
spaceLeft -= wordWidthWithSpace;
} else {
spaceLeft -= wordWidth;
}
}
if (i < lastLineIndex) {
result += "\n";
}
}
return result;
}
showText(text, delay, callback, callbackDelay, prompt, promptDelay) {
this.scene.messageWrapper.showText(text, delay, callback, callbackDelay, prompt, promptDelay);
if (callback) {
callback();
}
}
setScale(scale) {
// return this.phaserText.setScale(scale);
}
setShadow(shadowXpos, shadowYpos, shadowColor) {
// Sets the shadow settings for this Game Object.
// return this.phaserText.setShadow(shadowXpos, shadowYpos, shadowColor);
}
setLineSpacing(lineSpacing) {
// Sets the line spacing value of this Game Object.
// return this.phaserText.setLineSpacing(lineSpacing);
}
setOrigin(x, y) {
// return this.phaserText.setOrigin(x, y);
}
once(event, callback, source) {
// return this.phaserText.once(event, callback, source);
}
off(event, callback, obj) {
}
removedFromScene() {
}
addToDisplayList() {
}
setStroke(color, thickness) {
// Sets the stroke color and thickness.
// return this.phaserText.setStroke(color, thickness);
}
removeFromDisplayList() {
// same as remove or destroy
// return this.phaserText.removeFromDisplayList();
}
addedToScene() {
// This callback is invoked when this Game Object is added to a Scene.
// return this.phaserText.addedToScene();
}
setVisible(visible) {
// return this.phaserText.setVisible(visible);
}
setY(y) {
// return this.phaserText.setY(y);
}
setX(x) {
// return this.phaserText.setX(x);
}
setText(text) {
// Sets the text this Game Object will display.
// return this.phaserText.setText(text);
}
setAngle(angle) {
// Sets the angle of this Game Object.
// return this.phaserText.setAngle(angle);
}
setPositionRelative(source, x, y) {
/// Sets the position of this Game Object to be a relative position from the source Game Object.
// return this.phaserText.setPositionRelative(source, x, y);
}
setShadowOffset(offsetX, offsetY) {
// Sets the shadow offset values.
// return this.phaserText.setShadowOffset(offsetX, offsetY);
}
setWordWrapWidth(width) {
// Sets the width (in pixels) to use for wrapping lines.
this.wordWrapWidth = width;
}
setFontSize(fontSize) {
// Sets the font size of this Game Object.
// return this.phaserText.setFontSize(fontSize);
}
getBounds() {
// return this.phaserText.getBounds();
return {
width: 1,
};
}
setColor(color) {
// Sets the tint of this Game Object.
// return this.phaserText.setColor(color);
}
setShadowColor(color) {
// Sets the shadow color.
// return this.phaserText.setShadowColor(color);
}
setTint(color) {
// Sets the tint of this Game Object.
// return this.phaserText.setTint(color);
}
setStrokeStyle(thickness, color) {
// Sets the stroke style for the graphics.
// return this.phaserText.setStrokeStyle(thickness, color);
}
destroy() {
// return this.phaserText.destroy();
this.list = [];
}
setAlpha(alpha) {
// return this.phaserText.setAlpha(alpha);
}
setName(name) {
// return this.phaserText.setName(name);
}
setAlign(align) {
// return this.phaserText.setAlign(align);
}
setMask() {
/// Sets the mask that this Game Object will use to render with.
}
getBottomLeft() {
return {
x: 0,
y: 0,
};
}
getTopLeft() {
return {
x: 0,
y: 0,
};
}
add(obj) {
// Adds a child to this Game Object.
this.list.push(obj);
}
removeAll() {
// Removes all Game Objects from this Container.
this.list = [];
}
addAt(obj, index) {
// Adds a Game Object to this Container at the given index.
this.list.splice(index, 0, obj);
}
remove(obj) {
const index = this.list.indexOf(obj);
if (index !== -1) {
this.list.splice(index, 1);
}
}
getIndex(obj) {
const index = this.list.indexOf(obj);
return index || -1;
}
getAt(index) {
return this.list[index];
}
getAll() {
return this.list;
}
}

View File

@ -1,279 +0,0 @@
import {
BattleEndPhase,
BerryPhase,
CheckSwitchPhase, CommandPhase, DamagePhase, EggLapsePhase,
EncounterPhase, EnemyCommandPhase, FaintPhase,
LoginPhase, MessagePhase, MoveEffectPhase, MoveEndPhase, MovePhase, NewBattlePhase, NextEncounterPhase,
PostSummonPhase,
SelectGenderPhase, SelectModifierPhase,
SelectStarterPhase, ShinySparklePhase, ShowAbilityPhase, StatChangePhase, SummonPhase,
TitlePhase, ToggleDoublePositionPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase, VictoryPhase
} from "#app/phases";
import {Mode} from "#app/ui/ui";
export default class PhaseInterceptor {
public scene;
public phases = {};
public log;
private onHold;
private interval;
private promptInterval;
private intervalRun;
private prompts;
private phaseFrom;
/**
* List of phases with their corresponding start methods.
*/
private PHASES = [
[LoginPhase, this.startPhase],
[TitlePhase, this.startPhase],
[SelectGenderPhase, this.startPhase],
[EncounterPhase, this.startPhase],
[SelectStarterPhase, this.startPhase],
[PostSummonPhase, this.startPhase],
[SummonPhase, this.startPhase],
[ToggleDoublePositionPhase, this.startPhase],
[CheckSwitchPhase, this.startPhase],
[ShowAbilityPhase, this.startPhase],
[MessagePhase, this.startPhase],
[TurnInitPhase, this.startPhase],
[CommandPhase, this.startPhase],
[EnemyCommandPhase, this.startPhase],
[TurnStartPhase, this.startPhase],
[MovePhase, this.startPhase],
[MoveEffectPhase, this.startPhase],
[DamagePhase, this.startPhase],
[FaintPhase, this.startPhase],
[BerryPhase, this.startPhase],
[TurnEndPhase, this.startPhase],
[BattleEndPhase, this.startPhase],
[EggLapsePhase, this.startPhase],
[SelectModifierPhase, this.startPhase],
[NextEncounterPhase, this.startPhase],
[NewBattlePhase, this.startPhase],
[VictoryPhase, this.startPhase],
[MoveEndPhase, this.startPhase],
[StatChangePhase, this.startPhase],
[ShinySparklePhase, this.startPhase],
];
/**
* Constructor to initialize the scene and properties, and to start the phase handling.
* @param scene - The scene to be managed.
*/
constructor(scene) {
this.scene = scene;
this.log = [];
this.onHold = [];
this.prompts = [];
this.initPhases();
this.startPromptHander();
}
/**
* Method to set the starting phase.
* @param phaseFrom - The phase to start from.
* @returns The instance of the PhaseInterceptor.
*/
runFrom(phaseFrom) {
this.phaseFrom = phaseFrom;
return this;
}
/**
* Method to transition to a target phase.
* @param phaseTo - The phase to transition to.
* @returns A promise that resolves when the transition is complete.
*/
async to(phaseTo): Promise<void> {
return new Promise(async (resolve) => {
await this.run(this.phaseFrom);
this.phaseFrom = null;
const targetName = typeof phaseTo === "string" ? phaseTo : phaseTo.name;
this.intervalRun = setInterval(async () => {
const currentPhase = this.onHold?.length && this.onHold[0];
if (currentPhase && currentPhase.name !== targetName) {
await this.run(currentPhase.name);
} else if (currentPhase.name === targetName) {
await this.run(currentPhase.name);
clearInterval(this.intervalRun);
return resolve();
}
});
});
}
/**
* Method to run a phase with an optional skip function.
* @param phaseTarget - The phase to run.
* @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is run.
*/
run(phaseTarget, skipFn?): Promise<void> {
this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve) => {
this.waitUntil(phaseTarget, skipFn).then(() => {
const currentPhase = this.onHold.shift();
currentPhase.call();
resolve();
}).catch(() => {
resolve();
});
});
}
/**
* Method to ensure a phase is run, to throw error on test if not.
* @param phaseTarget - The phase to run.
* @returns A promise that resolves when the phase is run.
*/
mustRun(phaseTarget): Promise<void> {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve, reject) => {
const interval = setInterval(async () => {
const currentPhase = this.onHold?.length && this.onHold[0];
if (currentPhase && currentPhase.name !== targetName) {
reject(currentPhase);
} else if (currentPhase && currentPhase.name === targetName) {
clearInterval(interval);
await this.run(phaseTarget);
resolve();
}
});
});
}
/**
* Method to execute actions when about to run a phase. Does not run the phase, stop right before.
* @param phaseTarget - The phase to run.
* @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is about to run.
*/
whenAboutToRun(phaseTarget, skipFn?): Promise<void> {
return new Promise(async (resolve) => {
this.waitUntil(phaseTarget, skipFn).then(() => {
resolve();
}).catch(() => {
resolve();
});
});
}
/**
* Method to remove a phase from the list.
* @param phaseTarget - The phase to remove.
* @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is removed.
*/
remove(phaseTarget, skipFn?): Promise<void> {
return new Promise(async (resolve) => {
this.waitUntil(phaseTarget, skipFn).then(() => {
this.onHold.shift();
this.scene.getCurrentPhase().end();
resolve();
}).catch(() => {
resolve();
});
});
}
/**
* Method to wait until a specific phase is reached.
* @param phaseTarget - The phase to wait for.
* @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is reached.
*/
waitUntil(phaseTarget, skipFn?): Promise<void> {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
return new Promise((resolve, reject) => {
this.interval = setInterval(() => {
const currentPhase = this.onHold?.length && this.onHold[0] && this.onHold[0].name;
// if the currentPhase here is not filled, it means it's a phase we haven't added to the list
if (currentPhase === targetName) {
clearInterval(this.interval);
return resolve();
} else if (skipFn && skipFn()) {
clearInterval(this.interval);
return reject("Skipped phase");
}
});
});
}
/**
* Method to initialize phases and their corresponding methods.
*/
initPhases() {
for (const [phase, method] of this.PHASES) {
const originalStart = phase.prototype.start;
this.phases[phase.name] = originalStart;
phase.prototype.start = () => method.call(this, phase);
}
}
/**
* Method to start a phase and log it.
* @param phase - The phase to start.
*/
startPhase(phase) {
this.log.push(phase.name);
const instance = this.scene.getCurrentPhase();
this.onHold.push({
name: phase.name,
call: () => {
this.phases[phase.name].apply(instance);
}
});
}
/**
* Method to start the prompt handler.
*/
startPromptHander() {
this.promptInterval = setInterval(() => {
if (this.prompts.length) {
const actionForNextPrompt = this.prompts[0];
const expireFn = actionForNextPrompt.expireFn && actionForNextPrompt.expireFn();
const currentMode = this.scene.ui.getMode();
const currentPhase = this.scene.getCurrentPhase().constructor.name;
if (expireFn) {
this.prompts.shift();
} else if (currentMode === actionForNextPrompt.mode && currentPhase === actionForNextPrompt.phaseTarget) {
this.prompts.shift().callback();
}
}
});
}
/**
* Method to add an action to the next prompt.
* @param phaseTarget - The target phase for the prompt.
* @param mode - The mode of the UI.
* @param callback - The callback function to execute.
* @param expireFn - The function to determine if the prompt has expired.
*/
addToNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn: () => void) {
this.prompts.push({
phaseTarget,
mode,
callback,
expireFn
});
}
/**
* Restores the original state of phases and clears intervals.
*
* This function iterates through all phases and resets their `start` method to the original
* function stored in `this.phases`. Additionally, it clears the `promptInterval` and `interval`.
*/
restoreOg() {
for (const [phase] of this.PHASES) {
phase.prototype.start = this.phases[phase.name];
}
clearInterval(this.promptInterval);
clearInterval(this.interval);
}
}

File diff suppressed because one or more lines are too long

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