From fb9f5dad111b8ef598d36ad4985f696ae9602357 Mon Sep 17 00:00:00 2001 From: LaukkaE <73663099+LaukkaE@users.noreply.github.com> Date: Mon, 8 Apr 2024 00:20:24 +0300 Subject: [PATCH] Properly implement Soul-Heart, Fix Opponent Stage boosts on faints (#53) * Properly implement Soul-Heart, Fix Opponent Stage boosts on faints add phases.ts remove unused import spacing fix * simplify alivePlayField --- src/data/ability.ts | 36 ++++++++++++++++++++++++++++++++++-- src/phases.ts | 19 ++++++++++--------- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 2be51626e..7250ee2d9 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -795,6 +795,33 @@ class PostVictoryStatChangeAbAttr extends PostVictoryAbAttr { } } +export class PostKnockOutAbAttr extends AbAttr { + applyPostKnockOut(pokemon: Pokemon, args: any[]): boolean | Promise { + return false; + } +} + +export class PostKnockOutStatChangeAbAttr extends PostKnockOutAbAttr { + private stat: BattleStat | ((p: Pokemon) => BattleStat); + private levels: integer; + + constructor(stat: BattleStat | ((p: Pokemon) => BattleStat), levels: integer) { + super(); + + this.stat = stat; + this.levels = levels; + } + + applyPostKnockOut(pokemon: Pokemon, args: any[]): boolean | Promise { + const stat = typeof this.stat === 'function' + ? this.stat(pokemon) + : this.stat; + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], this.levels)); + + return true; + } +} + export class IgnoreOpponentStatChangesAbAttr extends AbAttr { constructor() { super(false); @@ -1696,6 +1723,11 @@ export function applyPostAttackAbAttrs(attrType: { new(...args: any[]): PostAtta return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPostAttack(pokemon, defender, move, hitResult, args), args); } +export function applyPostKnockOutAbAttrs(attrType: { new(...args: any[]): PostKnockOutAbAttr }, + pokemon: Pokemon, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPostKnockOut(pokemon, args), args); +} + export function applyPostVictoryAbAttrs(attrType: { new(...args: any[]): PostVictoryAbAttr }, pokemon: Pokemon, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPostVictory(pokemon, args), args); @@ -2571,8 +2603,8 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.DAZZLING, "Dazzling (N)", "Surprises the opposing Pokémon, making it unable to attack using priority moves.", 7) .ignorable(), - new Ability(Abilities.SOUL_HEART, "Soul-Heart (P)", "Boosts its Sp. Atk stat every time a Pokémon faints.", 7) - .attr(PostVictoryStatChangeAbAttr, BattleStat.SPATK, 1), + new Ability(Abilities.SOUL_HEART, "Soul-Heart", "Boosts its Sp. Atk stat every time a Pokémon faints.", 7) + .attr(PostKnockOutStatChangeAbAttr, BattleStat.SPATK, 1), new Ability(Abilities.TANGLING_HAIR, "Tangling Hair", "Contact with the Pokémon lowers the attacker's Speed stat.", 7) .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), BattleStat.SPD, -1, false), new Ability(Abilities.RECEIVER, "Receiver (N)", "The Pokémon copies the Ability of a defeated ally.", 7), diff --git a/src/phases.ts b/src/phases.ts index cdf1c9b30..59fb90b1a 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -30,7 +30,7 @@ import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, get import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTagType } from "./data/enums/arena-tag-type"; -import { Abilities, CheckTrappedAbAttr, MoveAbilityBypassAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, applyPostBattleInitAbAttrs, PostBattleInitAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr } from "./data/ability"; +import { Abilities, CheckTrappedAbAttr, MoveAbilityBypassAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, applyPostBattleInitAbAttrs, PostBattleInitAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./field/arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; @@ -2869,6 +2869,14 @@ export class FaintPhase extends PokemonPhase { this.scene.queueMessage(getPokemonMessage(pokemon, ' fainted!'), null, true); + const alivePlayField = this.scene.getField(true); + alivePlayField.forEach(p => applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p)); + if (pokemon.turnData?.attacksReceived?.length) { + const defeatSource = this.scene.getPokemonById(pokemon.turnData.attacksReceived[0].sourceId); + if (defeatSource?.isOnField()) + applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); + } + if (this.player) { const nonFaintedPartyMembers = this.scene.getParty().filter(p => !p.isFainted()); const nonFaintedPartyMemberCount = nonFaintedPartyMembers.length; @@ -3032,14 +3040,7 @@ export class VictoryPhase extends PokemonPhase { } } } - - const defeatedPokemon = this.getPokemon(); - if (defeatedPokemon.turnData?.attacksReceived?.length) { - const defeatSource = this.scene.getPokemonById(defeatedPokemon.turnData.attacksReceived[0].sourceId); - if (defeatSource?.isOnField()) - applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); - } - + if (!this.scene.getEnemyParty().find(p => this.scene.currentBattle.battleType ? !p?.isFainted(true) : p.isOnField())) { this.scene.pushPhase(new BattleEndPhase(this.scene)); if (this.scene.currentBattle.battleType === BattleType.TRAINER)