Skip to content

Commit

Permalink
[Bug] Prevent battle skip with Wimp Out (#4931)
Browse files Browse the repository at this point in the history
Co-authored-by: NightKev <[email protected]>
Co-authored-by: Mumble <[email protected]>
Co-authored-by: PigeonBar <[email protected]>
Co-authored-by: Moka <[email protected]>
  • Loading branch information
5 people authored Nov 30, 2024
1 parent d1294ca commit 5af2bcd
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 25 deletions.
12 changes: 6 additions & 6 deletions src/data/ability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3720,16 +3720,16 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr {

/**
* Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1)
* @param {Pokemon} pokemon Pokemon that has this ability
* @param {boolean} passive N/A
* @param {boolean} simulated true if applying in a simulated call.
* @param {any[]} args N/A
* @returns {boolean} true if any opponents are sleeping
* @param pokemon Pokemon that has this ability
* @param passive N/A
* @param simulated `true` if applying in a simulated call.
* @param args N/A
* @returns `true` if any opponents are sleeping
*/
applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
let hadEffect: boolean = false;
for (const opp of pokemon.getOpponents()) {
if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) {
if (!simulated) {
opp.damageAndUpdate(Utils.toDmgValue(opp.getMaxHp() / 8), HitResult.OTHER);
pokemon.scene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) }));
Expand Down
2 changes: 1 addition & 1 deletion src/data/arena-tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1144,7 +1144,7 @@ class FireGrassPledgeTag extends ArenaTag {
? arena.scene.getPlayerField()
: arena.scene.getEnemyField();

field.filter(pokemon => !pokemon.isOfType(Type.FIRE)).forEach(pokemon => {
field.filter(pokemon => !pokemon.isOfType(Type.FIRE) && !pokemon.switchOutStatus).forEach(pokemon => {
// "{pokemonNameWithAffix} was hurt by the sea of fire!"
pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
// TODO: Replace this with a proper animation
Expand Down
2 changes: 1 addition & 1 deletion src/data/move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1867,7 +1867,7 @@ export class FlameBurstAttr extends MoveEffectAttr {
applyAbAttrs(BlockNonDirectDamageAbAttr, targetAlly, cancelled);
}

if (cancelled.value || !targetAlly) {
if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) {
return false;
}

Expand Down
3 changes: 3 additions & 0 deletions src/field/pokemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3007,6 +3007,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
damageAndUpdate(damage: integer, result?: DamageResult, critical: boolean = false, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false, source?: Pokemon): integer {
const damagePhase = new DamageAnimPhase(this.scene, this.getBattlerIndex(), damage, result as DamageResult, critical);
this.scene.unshiftPhase(damagePhase);
if (this.switchOutStatus && source) {
damage = 0;
}
damage = this.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase);
// Damage amount may have changed, but needed to be queued before calling damage function
damagePhase.updateAmount(damage);
Expand Down
6 changes: 4 additions & 2 deletions src/phases/move-effect-phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,11 @@ export class MoveEffectPhase extends PokemonPhase {
* If the move missed a target, stop all future hits against that target
* and move on to the next target (if there is one).
*/
if (isCommanding || (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()])) {
if (target.switchOutStatus || isCommanding || (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()])) {
this.stopMultiHit(target);
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
if (!target.switchOutStatus) {
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
}
if (moveHistoryEntry.result === MoveResult.PENDING) {
moveHistoryEntry.result = MoveResult.MISS;
}
Expand Down
2 changes: 1 addition & 1 deletion src/phases/post-turn-status-effect-phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {

start() {
const pokemon = this.getPokemon();
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) {
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) {
pokemon.status.incrementTurn();
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
Expand Down
24 changes: 13 additions & 11 deletions src/phases/turn-end-phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,23 @@ export class TurnEndPhase extends FieldPhase {
this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn));

const handlePokemon = (pokemon: Pokemon) => {
pokemon.lapseTags(BattlerTagLapseType.TURN_END);
if (!pokemon.switchOutStatus) {
pokemon.lapseTags(BattlerTagLapseType.TURN_END);

this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);
this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);

if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(),
Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true));
}
if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(),
Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true));
}

if (!pokemon.isPlayer()) {
this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon);
this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon);
}
if (!pokemon.isPlayer()) {
this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon);
this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon);
}

applyPostTurnAbAttrs(PostTurnAbAttr, pokemon);
applyPostTurnAbAttrs(PostTurnAbAttr, pokemon);
}

this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon);

Expand Down
10 changes: 7 additions & 3 deletions src/phases/weather-effect-phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,20 @@ export class WeatherEffectPhase extends CommonAnimPhase {
};

this.executeForAll((pokemon: Pokemon) => {
const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length;
const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length || pokemon.switchOutStatus;
if (!immune) {
inflictDamage(pokemon);
}
});
}
}

this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType)!, null, () => { // TODO: is this bang correct?
this.executeForAll((pokemon: Pokemon) => applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather));
this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => {
this.executeForAll((pokemon: Pokemon) => {
if (!pokemon.switchOutStatus) {
applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather);
}
});

super.start();
});
Expand Down
30 changes: 30 additions & 0 deletions src/test/abilities/wimp_out.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,4 +632,34 @@ describe("Abilities - Wimp Out", () => {
const hasFled = enemyPokemon.switchOutStatus;
expect(isVisible && !hasFled).toBe(true);
});
it("wimp out will not skip battles when triggered in a double battle", async () => {
const wave = 2;
game.override
.enemyMoveset(Moves.SPLASH)
.enemySpecies(Species.WIMPOD)
.enemyAbility(Abilities.WIMP_OUT)
.moveset([ Moves.MATCHA_GOTCHA, Moves.FALSE_SWIPE ])
.startingLevel(50)
.enemyLevel(1)
.battleType("double")
.startingWave(wave);
await game.classicMode.startBattle([
Species.RAICHU,
Species.PIKACHU
]);
const [ wimpod0, wimpod1 ] = game.scene.getEnemyField();

game.move.select(Moves.FALSE_SWIPE, 0, BattlerIndex.ENEMY);
game.move.select(Moves.MATCHA_GOTCHA, 1);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]);
await game.phaseInterceptor.to("TurnEndPhase");

expect(wimpod0.hp).toBeGreaterThan(0);
expect(wimpod0.switchOutStatus).toBe(true);
expect(wimpod0.isFainted()).toBe(false);
expect(wimpod1.isFainted()).toBe(true);

await game.toNextWave();
expect(game.scene.currentBattle.waveIndex).toBe(wave + 1);
});
});

0 comments on commit 5af2bcd

Please sign in to comment.