mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-02-24 04:57:34 +00:00
Merge branch 'pr-illusion' of https://github.com/PyGaVS/pokerogue into pr-illusion
This commit is contained in:
commit
2557a28796
@ -634,15 +634,15 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr {
|
||||
* Examples include: Absorb, Draining Kiss, Bitter Blade, etc.
|
||||
* Also displays a message to show this ability was activated.
|
||||
* @param pokemon {@linkcode Pokemon} with this ability
|
||||
* @param passive N/A
|
||||
* @param _passive N/A
|
||||
* @param attacker {@linkcode Pokemon} that is attacking this Pokemon
|
||||
* @param move {@linkcode PokemonMove} that is being used
|
||||
* @param hitResult N/A
|
||||
* @args N/A
|
||||
* @param _hitResult N/A
|
||||
* @param _args N/A
|
||||
* @returns true if healing should be reversed on a healing move, false otherwise.
|
||||
*/
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.hasAttr(HitHealAttr)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (move.hasAttr(HitHealAttr) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (!simulated) {
|
||||
pokemon.scene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) }));
|
||||
}
|
||||
@ -669,8 +669,8 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
this.allOthers = allOthers;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (simulated) {
|
||||
return true;
|
||||
}
|
||||
@ -707,13 +707,13 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
this.selfTarget = selfTarget;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate);
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate);
|
||||
const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1];
|
||||
const damageReceived = lastAttackReceived?.damage || 0;
|
||||
|
||||
if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat)) {
|
||||
if (!simulated ) {
|
||||
if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (!simulated) {
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages));
|
||||
}
|
||||
return true;
|
||||
@ -734,8 +734,8 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr {
|
||||
this.tagType = tagType;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag;
|
||||
if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) {
|
||||
if (!simulated) {
|
||||
@ -758,8 +758,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
|
||||
this.tagType = tagType;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (!pokemon.getTag(this.tagType) && !simulated) {
|
||||
pokemon.addTag(this.tagType, undefined, undefined, pokemon.id);
|
||||
pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }));
|
||||
@ -771,8 +771,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
|
||||
}
|
||||
|
||||
export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (hitResult < HitResult.NO_EFFECT) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean {
|
||||
if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (simulated) {
|
||||
return true;
|
||||
}
|
||||
@ -787,7 +787,7 @@ export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
||||
return i18next.t("abilityTriggers:postDefendTypeChange", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
abilityName,
|
||||
@ -805,8 +805,8 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
|
||||
this.terrainType = terrainType;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (hitResult < HitResult.NO_EFFECT) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean {
|
||||
if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (simulated) {
|
||||
return pokemon.scene.arena.terrain?.terrainType !== (this.terrainType || undefined);
|
||||
} else {
|
||||
@ -829,8 +829,9 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
|
||||
this.effects = effects;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status
|
||||
&& (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
|
||||
if (simulated) {
|
||||
return attacker.canSetStatus(effect, true, false, pokemon);
|
||||
@ -869,8 +870,8 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
|
||||
this.turnCount = turnCount;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (simulated) {
|
||||
return attacker.canAddTag(this.tagType);
|
||||
} else {
|
||||
@ -893,7 +894,11 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
this.stages = stages;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (move.hitsSubstitute(attacker, pokemon)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!simulated) {
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages));
|
||||
}
|
||||
@ -901,7 +906,7 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
return true;
|
||||
}
|
||||
|
||||
getCondition(): AbAttrCondition {
|
||||
override getCondition(): AbAttrCondition {
|
||||
return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length !== 0 && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical;
|
||||
}
|
||||
}
|
||||
@ -915,8 +920,9 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
|
||||
this.damageRatio = damageRatio;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)
|
||||
&& !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
|
||||
attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio));
|
||||
return true;
|
||||
@ -925,7 +931,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
||||
return i18next.t("abilityTriggers:postDefendContactDamage", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
abilityName
|
||||
@ -948,8 +954,8 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
|
||||
this.turns = turns;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) {
|
||||
return false;
|
||||
} else {
|
||||
@ -963,24 +969,24 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
||||
return i18next.t("abilityTriggers:perishBody", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName });
|
||||
}
|
||||
}
|
||||
|
||||
export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr {
|
||||
private weatherType: WeatherType;
|
||||
protected condition: PokemonDefendCondition | null;
|
||||
protected condition?: PokemonDefendCondition;
|
||||
|
||||
constructor(weatherType: WeatherType, condition?: PokemonDefendCondition) {
|
||||
super();
|
||||
|
||||
this.weatherType = weatherType;
|
||||
this.condition = condition ?? null;
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (this.condition !== null && !this.condition(pokemon, attacker, move)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon)) {
|
||||
return false;
|
||||
}
|
||||
if (!pokemon.scene.arena.weather?.isImmutable()) {
|
||||
@ -999,8 +1005,9 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
|
||||
super();
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)
|
||||
&& !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (!simulated) {
|
||||
const tempAbilityId = attacker.getAbility().id;
|
||||
attacker.summonData.ability = pokemon.getAbility().id;
|
||||
@ -1012,7 +1019,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
|
||||
return i18next.t("abilityTriggers:postDefendAbilitySwap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
|
||||
}
|
||||
}
|
||||
@ -1025,8 +1032,9 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
|
||||
this.ability = ability;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr)
|
||||
&& !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (!simulated) {
|
||||
attacker.summonData.ability = this.ability;
|
||||
}
|
||||
@ -1037,7 +1045,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
||||
return i18next.t("abilityTriggers:postDefendAbilityGive", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
abilityName
|
||||
@ -1056,8 +1064,8 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
|
||||
this.chance = chance;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (attacker.getTag(BattlerTagType.DISABLED) === null) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) {
|
||||
if (simulated) {
|
||||
return true;
|
||||
@ -1724,17 +1732,17 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
|
||||
}
|
||||
|
||||
export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
||||
private condition: PokemonDefendCondition | null;
|
||||
private condition?: PokemonDefendCondition;
|
||||
|
||||
constructor(condition?: PokemonDefendCondition) {
|
||||
super();
|
||||
|
||||
this.condition = condition ?? null;
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) {
|
||||
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move)) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable);
|
||||
if (heldItems.length) {
|
||||
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
|
||||
@ -4489,7 +4497,7 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC
|
||||
export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
|
||||
private multiplier: number;
|
||||
private tagType: BattlerTagType;
|
||||
private recoilDamageFunc: ((pokemon: Pokemon) => number) | undefined;
|
||||
private recoilDamageFunc?: ((pokemon: Pokemon) => number);
|
||||
private triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string;
|
||||
|
||||
constructor(condition: PokemonDefendCondition, multiplier: number, tagType: BattlerTagType, triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string, recoilDamageFunc?: (pokemon: Pokemon) => number) {
|
||||
@ -4505,16 +4513,16 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
|
||||
* Applies the pre-defense ability to the Pokémon.
|
||||
* Removes the appropriate `BattlerTagType` when hit by an attack and is in its defense form.
|
||||
*
|
||||
* @param {Pokemon} pokemon The Pokémon with the ability.
|
||||
* @param {boolean} passive n/a
|
||||
* @param {Pokemon} attacker The attacking Pokémon.
|
||||
* @param {PokemonMove} move The move being used.
|
||||
* @param {Utils.BooleanHolder} cancelled n/a
|
||||
* @param {any[]} args Additional arguments.
|
||||
* @returns {boolean} Whether the immunity was applied.
|
||||
* @param pokemon The Pokémon with the ability.
|
||||
* @param _passive n/a
|
||||
* @param attacker The attacking Pokémon.
|
||||
* @param move The move being used.
|
||||
* @param _cancelled n/a
|
||||
* @param args Additional arguments.
|
||||
* @returns `true` if the immunity was applied.
|
||||
*/
|
||||
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move)) {
|
||||
override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (!simulated) {
|
||||
(args[0] as Utils.NumberHolder).value = this.multiplier;
|
||||
pokemon.removeTag(this.tagType);
|
||||
@ -4530,12 +4538,12 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
|
||||
|
||||
/**
|
||||
* Gets the message triggered when the Pokémon avoids damage using the form-changing ability.
|
||||
* @param {Pokemon} pokemon The Pokémon with the ability.
|
||||
* @param {string} abilityName The name of the ability.
|
||||
* @param {...any} args n/a
|
||||
* @returns {string} The trigger message.
|
||||
* @param pokemon The Pokémon with the ability.
|
||||
* @param abilityName The name of the ability.
|
||||
* @param _args n/a
|
||||
* @returns The trigger message.
|
||||
*/
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
||||
return this.triggerMessageFunc(pokemon, abilityName);
|
||||
}
|
||||
}
|
||||
@ -5622,7 +5630,8 @@ export function initAbilities() {
|
||||
.attr(NoFusionAbilityAbAttr)
|
||||
// Add BattlerTagType.DISGUISE if the pokemon is in its disguised form
|
||||
.conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false)
|
||||
.attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE,
|
||||
.attr(FormBlockDamageAbAttr,
|
||||
(target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE,
|
||||
(pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }),
|
||||
(pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8))
|
||||
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
||||
@ -5784,7 +5793,8 @@ export function initAbilities() {
|
||||
.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
|
||||
.attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW)
|
||||
.attr(FormBlockDamageAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE,
|
||||
.attr(FormBlockDamageAbAttr,
|
||||
(target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE,
|
||||
(pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }))
|
||||
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
||||
.bypassFaint()
|
||||
|
@ -1376,7 +1376,7 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag {
|
||||
const effectPhase = pokemon.scene.getCurrentPhase();
|
||||
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
|
||||
const attacker = effectPhase.getPokemon();
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels));
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ this.stat ], this.levels));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2139,6 +2139,10 @@ export class GulpMissileTag extends BattlerTag {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (moveEffectPhase.move.getMove().hitsSubstitute(attacker, pokemon)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
|
||||
|
||||
|
@ -1938,12 +1938,21 @@ export class IncrementMovePriorityAttr extends MoveAttr {
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class MultiHitAttr extends MoveAttr {
|
||||
/** This move's intrinsic multi-hit type. It should never be modified. */
|
||||
private readonly intrinsicMultiHitType: MultiHitType;
|
||||
/** This move's current multi-hit type. It may be temporarily modified by abilities (e.g., Battle Bond). */
|
||||
private multiHitType: MultiHitType;
|
||||
|
||||
constructor(multiHitType?: MultiHitType) {
|
||||
super();
|
||||
|
||||
this.multiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5;
|
||||
this.intrinsicMultiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5;
|
||||
this.multiHitType = this.intrinsicMultiHitType;
|
||||
}
|
||||
|
||||
// Currently used by `battle_bond.test.ts`
|
||||
getMultiHitType(): MultiHitType {
|
||||
return this.multiHitType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1957,7 +1966,7 @@ export class MultiHitAttr extends MoveAttr {
|
||||
* @returns True
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const hitType = new Utils.NumberHolder(this.multiHitType);
|
||||
const hitType = new Utils.NumberHolder(this.intrinsicMultiHitType);
|
||||
applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType);
|
||||
this.multiHitType = hitType.value;
|
||||
|
||||
@ -4107,11 +4116,11 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr {
|
||||
* @param user {@linkcode Pokemon} using the move
|
||||
* @param target {@linkcode Pokemon} target of the move
|
||||
* @param move {@linkcode Move} with this attribute
|
||||
* @param args [0] {@linkcode Utils.IntegerHolder} The category of the move
|
||||
* @param args [0] {@linkcode Utils.NumberHolder} The category of the move
|
||||
* @returns true if the function succeeds
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const category = (args[0] as Utils.IntegerHolder);
|
||||
const category = (args[0] as Utils.NumberHolder);
|
||||
|
||||
if (user.getAlly() === target) {
|
||||
category.value = MoveCategory.STATUS;
|
||||
@ -4835,14 +4844,14 @@ export class GulpMissileTagAttr extends MoveEffectAttr {
|
||||
|
||||
/**
|
||||
* Adds BattlerTagType from GulpMissileTag based on the Pokemon's HP ratio.
|
||||
* @param {Pokemon} user The Pokemon using the move.
|
||||
* @param {Pokemon} target The Pokemon being targeted by the move.
|
||||
* @param {Move} move The move being used.
|
||||
* @param {any[]} args Additional arguments, if any.
|
||||
* @param user The Pokemon using the move.
|
||||
* @param _target N/A
|
||||
* @param move The move being used.
|
||||
* @param _args N/A
|
||||
* @returns Whether the BattlerTag is applied.
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
apply(user: Pokemon, _target: Pokemon, move: Move, _args: any[]): boolean {
|
||||
if (!super.apply(user, _target, move, _args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1616,10 +1616,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @returns {boolean} Whether the ability is present and active
|
||||
*/
|
||||
hasAbility(ability: Abilities, canApply: boolean = true, ignoreOverride?: boolean): boolean {
|
||||
if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).id === ability) {
|
||||
if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) {
|
||||
return true;
|
||||
}
|
||||
if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().id === ability) {
|
||||
if (this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -2886,7 +2886,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
*/
|
||||
apply(source: Pokemon, move: Move): HitResult {
|
||||
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
if (move.category === MoveCategory.STATUS) {
|
||||
const moveCategory = new Utils.NumberHolder(move.category);
|
||||
applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, moveCategory);
|
||||
if (moveCategory.value === MoveCategory.STATUS) {
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
const typeMultiplier = this.getMoveEffectiveness(source, move, false, false, cancelled);
|
||||
|
||||
|
@ -8,10 +8,6 @@ import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms";
|
||||
import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect";
|
||||
import { Type } from "#app/data/type";
|
||||
import { getTerrainBlockMessage } from "#app/data/weather";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { StatusEffect } from "#app/enums/status-effect";
|
||||
import { MoveUsedEvent } from "#app/events/battle-scene";
|
||||
import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
@ -20,7 +16,11 @@ import { CommonAnimPhase } from "#app/phases/common-anim-phase";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
||||
import * as Utils from "#app/utils";
|
||||
import { BooleanHolder, NumberHolder } from "#app/utils";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import i18next from "i18next";
|
||||
|
||||
export class MovePhase extends BattlePhase {
|
||||
@ -89,7 +89,7 @@ export class MovePhase extends BattlePhase {
|
||||
this.cancelled = true;
|
||||
}
|
||||
|
||||
public start() {
|
||||
public start(): void {
|
||||
super.start();
|
||||
|
||||
console.log(Moves[this.move.moveId]);
|
||||
@ -140,7 +140,7 @@ export class MovePhase extends BattlePhase {
|
||||
}
|
||||
|
||||
/** Check for cancellation edge cases - no targets remaining, or {@linkcode Moves.NONE} is in the queue */
|
||||
protected resolveFinalPreMoveCancellationChecks() {
|
||||
protected resolveFinalPreMoveCancellationChecks(): void {
|
||||
const targets = this.getActiveTargetPokemon();
|
||||
const moveQueue = this.pokemon.getMoveQueue();
|
||||
|
||||
@ -150,14 +150,14 @@ export class MovePhase extends BattlePhase {
|
||||
}
|
||||
}
|
||||
|
||||
public getActiveTargetPokemon() {
|
||||
public getActiveTargetPokemon(): Pokemon[] {
|
||||
return this.scene.getField(true).filter(p => this.targets.includes(p.getBattlerIndex()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles {@link StatusEffect.SLEEP Sleep}/{@link StatusEffect.PARALYSIS Paralysis}/{@link StatusEffect.FREEZE Freeze} rolls and side effects.
|
||||
*/
|
||||
protected resolvePreMoveStatusEffects() {
|
||||
protected resolvePreMoveStatusEffects(): void {
|
||||
if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) {
|
||||
this.pokemon.status.incrementTurn();
|
||||
let activated = false;
|
||||
@ -198,7 +198,7 @@ export class MovePhase extends BattlePhase {
|
||||
* Lapse {@linkcode BattlerTagLapseType.PRE_MOVE PRE_MOVE} tags that trigger before a move is used, regardless of whether or not it failed.
|
||||
* Also lapse {@linkcode BattlerTagLapseType.MOVE MOVE} tags if the move should be successful.
|
||||
*/
|
||||
protected lapsePreMoveAndMoveTags() {
|
||||
protected lapsePreMoveAndMoveTags(): void {
|
||||
this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE);
|
||||
|
||||
// TODO: does this intentionally happen before the no targets/Moves.NONE on queue cancellation case is checked?
|
||||
@ -207,7 +207,7 @@ export class MovePhase extends BattlePhase {
|
||||
}
|
||||
}
|
||||
|
||||
protected useMove() {
|
||||
protected useMove(): void {
|
||||
const targets = this.getActiveTargetPokemon();
|
||||
const moveQueue = this.pokemon.getMoveQueue();
|
||||
|
||||
@ -217,7 +217,8 @@ export class MovePhase extends BattlePhase {
|
||||
this.showMoveText();
|
||||
|
||||
// TODO: Clean up implementation of two-turn moves.
|
||||
if (moveQueue.length > 0) { // Using .shift here clears out two turn moves once they've been used
|
||||
if (moveQueue.length > 0) {
|
||||
// Using .shift here clears out two turn moves once they've been used
|
||||
this.ignorePp = moveQueue.shift()?.ignorePP ?? false;
|
||||
}
|
||||
|
||||
@ -226,7 +227,7 @@ export class MovePhase extends BattlePhase {
|
||||
const ppUsed = 1 + this.getPpIncreaseFromPressure(targets);
|
||||
|
||||
this.move.usePp(ppUsed);
|
||||
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed));
|
||||
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed));
|
||||
}
|
||||
|
||||
// Update the battle's "last move" pointer, unless we're currently mimicking a move.
|
||||
@ -275,7 +276,7 @@ export class MovePhase extends BattlePhase {
|
||||
this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual });
|
||||
|
||||
let failedText: string | undefined;
|
||||
const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new Utils.BooleanHolder(false));
|
||||
const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new BooleanHolder(false));
|
||||
|
||||
if (failureMessage) {
|
||||
failedText = failureMessage;
|
||||
@ -299,7 +300,7 @@ export class MovePhase extends BattlePhase {
|
||||
* Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`,
|
||||
* then ends the phase.
|
||||
*/
|
||||
public end() {
|
||||
public end(): void {
|
||||
if (!this.followUp && this.canMove()) {
|
||||
this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex()));
|
||||
}
|
||||
@ -313,7 +314,7 @@ export class MovePhase extends BattlePhase {
|
||||
*
|
||||
* TODO: This hardcodes the PP increase at 1 per opponent, rather than deferring to the ability.
|
||||
*/
|
||||
public getPpIncreaseFromPressure(targets: Pokemon[]) {
|
||||
public getPpIncreaseFromPressure(targets: Pokemon[]): number {
|
||||
const foesWithPressure = this.pokemon.getOpponents().filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr));
|
||||
return foesWithPressure.length;
|
||||
}
|
||||
@ -323,10 +324,10 @@ export class MovePhase extends BattlePhase {
|
||||
* - Move redirection abilities, effects, etc.
|
||||
* - Counterattacks, which pass a special value into the `targets` constructor param (`[`{@linkcode BattlerIndex.ATTACKER}`]`).
|
||||
*/
|
||||
protected resolveRedirectTarget() {
|
||||
protected resolveRedirectTarget(): void {
|
||||
if (this.targets.length === 1) {
|
||||
const currentTarget = this.targets[0];
|
||||
const redirectTarget = new Utils.NumberHolder(currentTarget);
|
||||
const redirectTarget = new NumberHolder(currentTarget);
|
||||
|
||||
// check move redirection abilities of every pokemon *except* the user.
|
||||
this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget));
|
||||
@ -372,7 +373,7 @@ export class MovePhase extends BattlePhase {
|
||||
* If there is no last attacker, or they are no longer on the field, a message is displayed and the
|
||||
* move is marked for failure.
|
||||
*/
|
||||
protected resolveCounterAttackTarget() {
|
||||
protected resolveCounterAttackTarget(): void {
|
||||
if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) {
|
||||
if (this.pokemon.turnData.attacksReceived.length) {
|
||||
this.targets[0] = this.pokemon.turnData.attacksReceived[0].sourceBattlerIndex;
|
||||
@ -411,7 +412,7 @@ export class MovePhase extends BattlePhase {
|
||||
*
|
||||
* TODO: handle charge moves more gracefully
|
||||
*/
|
||||
protected handlePreMoveFailures() {
|
||||
protected handlePreMoveFailures(): void {
|
||||
if (this.cancelled || this.failed) {
|
||||
if (this.failed) {
|
||||
const ppUsed = this.ignorePp ? 0 : 1;
|
||||
|
@ -158,7 +158,7 @@ export class TurnStartPhase extends FieldPhase {
|
||||
if (!queuedMove) {
|
||||
continue;
|
||||
}
|
||||
const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move) || new PokemonMove(queuedMove.move);
|
||||
const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move);
|
||||
if (move.getMove().hasAttr(MoveHeaderAttr)) {
|
||||
this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move));
|
||||
}
|
||||
|
@ -1,17 +1,19 @@
|
||||
import { allMoves, MultiHitAttr, MultiHitType } from "#app/data/move";
|
||||
import { Status, StatusEffect } from "#app/data/status-effect";
|
||||
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
|
||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
|
||||
describe("Abilities - BATTLE BOND", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
const baseForm = 1;
|
||||
const ashForm = 2;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
@ -24,40 +26,68 @@ describe("Abilities - BATTLE BOND", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
const moveToUse = Moves.SPLASH;
|
||||
game.override.battleType("single");
|
||||
game.override.ability(Abilities.BATTLE_BOND);
|
||||
game.override.moveset([ moveToUse ]);
|
||||
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
|
||||
game.override.battleType("single")
|
||||
.startingWave(4) // Leads to arena reset on Wave 5 trainer battle
|
||||
.ability(Abilities.BATTLE_BOND)
|
||||
.starterForms({ [Species.GRENINJA]: ashForm, })
|
||||
.moveset([ Moves.SPLASH, Moves.WATER_SHURIKEN ])
|
||||
.enemySpecies(Species.BULBASAUR)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.startingLevel(100) // Avoid levelling up
|
||||
.enemyLevel(1000); // Avoid opponent dying before `doKillOpponents()`
|
||||
});
|
||||
|
||||
test(
|
||||
"check if fainted pokemon switches to base form on arena reset",
|
||||
async () => {
|
||||
const baseForm = 1;
|
||||
const ashForm = 2;
|
||||
game.override.startingWave(4);
|
||||
game.override.starterForms({
|
||||
[Species.GRENINJA]: ashForm,
|
||||
});
|
||||
it("check if fainted pokemon switches to base form on arena reset", async () => {
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP, Species.GRENINJA ]);
|
||||
|
||||
await game.startBattle([ Species.MAGIKARP, Species.GRENINJA ]);
|
||||
const greninja = game.scene.getParty()[1];
|
||||
expect(greninja.formIndex).toBe(ashForm);
|
||||
|
||||
const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA);
|
||||
expect(greninja).toBeDefined();
|
||||
expect(greninja!.formIndex).toBe(ashForm);
|
||||
greninja.hp = 0;
|
||||
greninja.status = new Status(StatusEffect.FAINT);
|
||||
expect(greninja.isFainted()).toBe(true);
|
||||
|
||||
greninja!.hp = 0;
|
||||
greninja!.status = new Status(StatusEffect.FAINT);
|
||||
expect(greninja!.isFainted()).toBe(true);
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.doKillOpponents();
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.doSelectModifier();
|
||||
await game.phaseInterceptor.to("QuietFormChangePhase");
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.doKillOpponents();
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
game.doSelectModifier();
|
||||
await game.phaseInterceptor.to(QuietFormChangePhase);
|
||||
expect(greninja.formIndex).toBe(baseForm);
|
||||
});
|
||||
|
||||
expect(greninja!.formIndex).toBe(baseForm);
|
||||
},
|
||||
);
|
||||
it("should not keep buffing Water Shuriken after Greninja switches to base form", async () => {
|
||||
await game.classicMode.startBattle([ Species.GRENINJA ]);
|
||||
|
||||
const waterShuriken = allMoves[Moves.WATER_SHURIKEN];
|
||||
vi.spyOn(waterShuriken, "calculateBattlePower");
|
||||
|
||||
let actualMultiHitType: MultiHitType | null = null;
|
||||
const multiHitAttr = waterShuriken.getAttrs(MultiHitAttr)[0];
|
||||
vi.spyOn(multiHitAttr, "getHitCount").mockImplementation(() => {
|
||||
actualMultiHitType = multiHitAttr.getMultiHitType();
|
||||
return 3;
|
||||
});
|
||||
|
||||
// Wave 4: Use Water Shuriken in Ash form
|
||||
let expectedBattlePower = 20;
|
||||
let expectedMultiHitType = MultiHitType._3;
|
||||
|
||||
game.move.select(Moves.WATER_SHURIKEN);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower);
|
||||
expect(actualMultiHitType).toBe(expectedMultiHitType);
|
||||
|
||||
await game.doKillOpponents();
|
||||
await game.toNextWave();
|
||||
|
||||
// Wave 5: Use Water Shuriken in base form
|
||||
expectedBattlePower = 15;
|
||||
expectedMultiHitType = MultiHitType._2_TO_5;
|
||||
|
||||
game.move.select(Moves.WATER_SHURIKEN);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower);
|
||||
expect(actualMultiHitType).toBe(expectedMultiHitType);
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import { toDmgValue } from "#app/utils";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import { Stat } from "#enums/stat";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
@ -222,4 +223,17 @@ describe("Abilities - Disguise", () => {
|
||||
expect(mimikyu.formIndex).toBe(bustedForm);
|
||||
expect(mimikyu.hp).toBe(maxHp - disguiseDamage);
|
||||
});
|
||||
|
||||
it("doesn't trigger if user is behind a substitute", async () => {
|
||||
game.override
|
||||
.enemyMoveset(Moves.SUBSTITUTE)
|
||||
.moveset(Moves.POWER_TRIP);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.POWER_TRIP);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getEnemyPokemon()!.formIndex).toBe(disguisedForm);
|
||||
});
|
||||
});
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import Pokemon from "#app/field/pokemon";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { Stat } from "#enums/stat";
|
||||
|
||||
describe("Abilities - Gulp Missile", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -40,8 +41,9 @@ describe("Abilities - Gulp Missile", () => {
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.disableCrits()
|
||||
.battleType("single")
|
||||
.moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH ])
|
||||
.moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH, Moves.SUBSTITUTE ])
|
||||
.enemySpecies(Species.SNORLAX)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
@ -234,6 +236,25 @@ describe("Abilities - Gulp Missile", () => {
|
||||
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(-1);
|
||||
});
|
||||
|
||||
it("doesn't trigger if user is behind a substitute", async () => {
|
||||
game.override
|
||||
.enemyAbility(Abilities.STURDY)
|
||||
.enemyMoveset([ Moves.SPLASH, Moves.POWER_TRIP ]);
|
||||
await game.classicMode.startBattle([ Species.CRAMORANT ]);
|
||||
|
||||
game.move.select(Moves.SURF);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM);
|
||||
|
||||
game.move.select(Moves.SUBSTITUTE);
|
||||
await game.forceEnemyMove(Moves.POWER_TRIP);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM);
|
||||
});
|
||||
|
||||
it("cannot be suppressed", async () => {
|
||||
game.override.enemyMoveset(Moves.GASTRO_ACID);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
|
||||
@ -36,7 +37,7 @@ describe("Abilities - Ice Face", () => {
|
||||
});
|
||||
|
||||
it("takes no damage from physical move and transforms to Noice", async () => {
|
||||
await game.startBattle([ Species.HITMONLEE ]);
|
||||
await game.classicMode.startBattle([ Species.HITMONLEE ]);
|
||||
|
||||
game.move.select(Moves.TACKLE);
|
||||
|
||||
@ -52,7 +53,7 @@ describe("Abilities - Ice Face", () => {
|
||||
it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => {
|
||||
game.override.moveset([ Moves.SURGING_STRIKES ]);
|
||||
game.override.enemyLevel(1);
|
||||
await game.startBattle([ Species.HITMONLEE ]);
|
||||
await game.classicMode.startBattle([ Species.HITMONLEE ]);
|
||||
|
||||
game.move.select(Moves.SURGING_STRIKES);
|
||||
|
||||
@ -78,7 +79,7 @@ describe("Abilities - Ice Face", () => {
|
||||
});
|
||||
|
||||
it("takes damage from special moves", async () => {
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.ICE_BEAM);
|
||||
|
||||
@ -92,7 +93,7 @@ describe("Abilities - Ice Face", () => {
|
||||
});
|
||||
|
||||
it("takes effects from status moves", async () => {
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.TOXIC_THREAD);
|
||||
|
||||
@ -108,7 +109,7 @@ describe("Abilities - Ice Face", () => {
|
||||
game.override.moveset([ Moves.QUICK_ATTACK ]);
|
||||
game.override.enemyMoveset([ Moves.HAIL, Moves.HAIL, Moves.HAIL, Moves.HAIL ]);
|
||||
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.QUICK_ATTACK);
|
||||
|
||||
@ -130,7 +131,7 @@ describe("Abilities - Ice Face", () => {
|
||||
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
|
||||
game.override.moveset([ Moves.SNOWSCAPE ]);
|
||||
|
||||
await game.startBattle([ Species.EISCUE, Species.NINJASK ]);
|
||||
await game.classicMode.startBattle([ Species.EISCUE, Species.NINJASK ]);
|
||||
|
||||
game.move.select(Moves.SNOWSCAPE);
|
||||
|
||||
@ -157,7 +158,7 @@ describe("Abilities - Ice Face", () => {
|
||||
game.override.enemySpecies(Species.SHUCKLE);
|
||||
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
|
||||
|
||||
await game.startBattle([ Species.EISCUE ]);
|
||||
await game.classicMode.startBattle([ Species.EISCUE ]);
|
||||
|
||||
game.move.select(Moves.HAIL);
|
||||
const eiscue = game.scene.getPlayerPokemon()!;
|
||||
@ -176,7 +177,7 @@ describe("Abilities - Ice Face", () => {
|
||||
it("persists form change when switched out", async () => {
|
||||
game.override.enemyMoveset([ Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK ]);
|
||||
|
||||
await game.startBattle([ Species.EISCUE, Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.EISCUE, Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.ICE_BEAM);
|
||||
|
||||
@ -205,7 +206,7 @@ describe("Abilities - Ice Face", () => {
|
||||
[Species.EISCUE]: noiceForm,
|
||||
});
|
||||
|
||||
await game.startBattle([ Species.EISCUE ]);
|
||||
await game.classicMode.startBattle([ Species.EISCUE ]);
|
||||
|
||||
const eiscue = game.scene.getPlayerPokemon()!;
|
||||
|
||||
@ -222,10 +223,23 @@ describe("Abilities - Ice Face", () => {
|
||||
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
|
||||
});
|
||||
|
||||
it("doesn't trigger if user is behind a substitute", async () => {
|
||||
game.override
|
||||
.enemyMoveset(Moves.SUBSTITUTE)
|
||||
.moveset(Moves.POWER_TRIP);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.POWER_TRIP);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getEnemyPokemon()!.formIndex).toBe(icefaceForm);
|
||||
});
|
||||
|
||||
it("cannot be suppressed", async () => {
|
||||
game.override.moveset([ Moves.GASTRO_ACID ]);
|
||||
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.GASTRO_ACID);
|
||||
|
||||
@ -241,7 +255,7 @@ describe("Abilities - Ice Face", () => {
|
||||
it("cannot be swapped with another ability", async () => {
|
||||
game.override.moveset([ Moves.SKILL_SWAP ]);
|
||||
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.SKILL_SWAP);
|
||||
|
||||
@ -257,7 +271,7 @@ describe("Abilities - Ice Face", () => {
|
||||
it("cannot be copied", async () => {
|
||||
game.override.ability(Abilities.TRACE);
|
||||
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.SIMPLE_BEAM);
|
||||
|
||||
|
@ -9,6 +9,7 @@ import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { allMoves } from "#app/data/move";
|
||||
|
||||
|
||||
describe("Abilities - Sheer Force", () => {
|
||||
@ -174,5 +175,31 @@ describe("Abilities - Sheer Force", () => {
|
||||
|
||||
}, 20000);
|
||||
|
||||
it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => {
|
||||
const moveToUse = Moves.CRUNCH;
|
||||
game.override.enemyAbility(Abilities.COLOR_CHANGE)
|
||||
.ability(Abilities.COLOR_CHANGE)
|
||||
.moveset(moveToUse)
|
||||
.enemyMoveset(moveToUse);
|
||||
|
||||
await game.classicMode.startBattle([
|
||||
Species.PIDGEOT
|
||||
]);
|
||||
|
||||
const pidgeot = game.scene.getParty()[0];
|
||||
const onix = game.scene.getEnemyParty()[0];
|
||||
|
||||
pidgeot.stats[Stat.DEF] = 10000;
|
||||
onix.stats[Stat.DEF] = 10000;
|
||||
|
||||
game.move.select(moveToUse);
|
||||
await game.toNextTurn();
|
||||
|
||||
// Check that both Pokemon's Color Change activated
|
||||
const expectedTypes = [ allMoves[moveToUse].type ];
|
||||
expect(pidgeot.getTypes()).toStrictEqual(expectedTypes);
|
||||
expect(onix.getTypes()).toStrictEqual(expectedTypes);
|
||||
});
|
||||
|
||||
//TODO King's Rock Interaction Unit Test
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Stat } from "#app/enums/stat";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Stat } from "#enums/stat";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
@ -22,13 +23,15 @@ describe("Moves - Obstruct", () => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyMoveset(Moves.TACKLE)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.moveset([ Moves.OBSTRUCT ]);
|
||||
.moveset([ Moves.OBSTRUCT ])
|
||||
.starterSpecies(Species.FEEBAS);
|
||||
});
|
||||
|
||||
it("protects from contact damaging moves and lowers the opponent's defense by 2 stages", async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH));
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.OBSTRUCT);
|
||||
@ -42,7 +45,6 @@ describe("Moves - Obstruct", () => {
|
||||
});
|
||||
|
||||
it("bypasses accuracy checks when applying protection and defense reduction", async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH));
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.OBSTRUCT);
|
||||
@ -59,7 +61,7 @@ describe("Moves - Obstruct", () => {
|
||||
);
|
||||
|
||||
it("protects from non-contact damaging moves and doesn't lower the opponent's defense by 2 stages", async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.WATER_GUN));
|
||||
game.override.enemyMoveset(Moves.WATER_GUN);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.OBSTRUCT);
|
||||
@ -73,7 +75,7 @@ describe("Moves - Obstruct", () => {
|
||||
});
|
||||
|
||||
it("doesn't protect from status moves", async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.GROWL));
|
||||
game.override.enemyMoveset(Moves.GROWL);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.OBSTRUCT);
|
||||
@ -83,4 +85,14 @@ describe("Moves - Obstruct", () => {
|
||||
|
||||
expect(player.getStatStage(Stat.ATK)).toBe(-1);
|
||||
});
|
||||
|
||||
it("doesn't reduce the stats of an opponent with Clear Body/etc", async () => {
|
||||
game.override.enemyAbility(Abilities.CLEAR_BODY);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.OBSTRUCT);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
53
src/test/moves/sketch.test.ts
Normal file
53
src/test/moves/sketch.test.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Moves - Sketch", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.SHUCKLE)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("Sketch should not fail even if a previous Sketch failed to retrieve a valid move and ran out of PP", async () => {
|
||||
game.override.moveset([ Moves.SKETCH, Moves.SKETCH ]);
|
||||
|
||||
await game.classicMode.startBattle([ Species.REGIELEKI ]);
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
|
||||
game.move.select(Moves.SKETCH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
const moveSlot0 = playerPokemon?.getMoveset()[0];
|
||||
expect(moveSlot0?.moveId).toBe(Moves.SKETCH);
|
||||
expect(moveSlot0?.getPpRatio()).toBe(0);
|
||||
|
||||
await game.toNextTurn();
|
||||
game.move.select(Moves.SKETCH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||
// Can't verify if the player Pokemon's moveset was successfully changed because of overrides.
|
||||
});
|
||||
});
|
@ -86,7 +86,7 @@ export function waitUntil(truth) {
|
||||
export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: Moves) {
|
||||
const playerPokemon = scene.getPlayerField()[pokemonIndex];
|
||||
const moveSet = playerPokemon.getMoveset();
|
||||
const index = moveSet.findIndex((m) => m?.moveId === move);
|
||||
const index = moveSet.findIndex((m) => m?.moveId === move && m?.ppUsed < m?.getMovePp());
|
||||
console.log(`Move position for ${Moves[move]} (=${move}):`, index);
|
||||
return index;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user