From 71e820f149bf1aea673386e4ab6781ce38510047 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Mon, 11 Mar 2024 20:55:41 -0400 Subject: [PATCH] Add redirection logic to Lightning Rod and Storm Drain --- src/battle-scene.ts | 8 +++++--- src/data/ability.ts | 37 ++++++++++++++++++++++++++++++++++++- src/data/weather.ts | 2 +- src/field/pokemon.ts | 2 +- src/phases.ts | 21 +++++++++++++++------ 5 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a4c5bb7efb4..4219123ad6a 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -711,13 +711,15 @@ export default class BattleScene extends Phaser.Scene { return party.slice(0, Math.min(party.length, this.currentBattle?.double ? 2 : 1)); } - getField(): Pokemon[] { + getField(activeOnly: boolean = false): Pokemon[] { const ret = new Array(4).fill(null); const playerField = this.getPlayerField(); const enemyField = this.getEnemyField(); ret.splice(0, playerField.length, ...playerField); ret.splice(2, enemyField.length, ...enemyField); - return ret; + return activeOnly + ? ret.filter(p => p?.isActive()) + : ret; } getPokemonById(pokemonId: integer): Pokemon { @@ -935,7 +937,7 @@ export default class BattleScene extends Phaser.Scene { updateFieldScale(): Promise { return new Promise(resolve => { - const fieldScale = Math.floor(Math.pow(1 / this.getField().filter(p => p?.isActive()) + const fieldScale = Math.floor(Math.pow(1 / this.getField(true) .map(p => p.getSpriteScale()) .reduce((highestScale: number, scale: number) => highestScale = Math.max(scale, highestScale), 0), 0.7) * 40 ) / 40; diff --git a/src/data/ability.ts b/src/data/ability.ts index fee7eab9f72..fff833e68c1 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -8,7 +8,7 @@ import { Weather, WeatherType } from "./weather"; import { BattlerTag } from "./battler-tags"; import { BattlerTagType } from "./enums/battler-tag-type"; import { StatusEffect, getStatusEffectDescriptor } from "./status-effect"; -import Move, { AttackMove, MoveCategory, MoveFlags, RecoilAttr, StatusMoveTypeImmunityAttr } from "./move"; +import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, RecoilAttr, StatusMoveTypeImmunityAttr, allMoves } from "./move"; import { ArenaTagType } from "./enums/arena-tag-type"; import { Stat } from "./pokemon-stat"; import { PokemonHeldItemModifier } from "../modifier/modifier"; @@ -1062,6 +1062,39 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { } } +export class RedirectMoveAbAttr extends AbAttr { + apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (this.canRedirect(args[0] as Moves)) { + const target = args[1] as Utils.IntegerHolder; + const newTarget = pokemon.getBattlerIndex(); + if (target.value !== newTarget) { + target.value = newTarget; + return true; + } + } + + return false; + } + + canRedirect(moveId: Moves): boolean { + const move = allMoves[moveId]; + return !![ MoveTarget.NEAR_OTHER, MoveTarget.OTHER ].find(t => move.moveTarget === t); + } +} + +export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr { + public type: Type; + + constructor(type: Type) { + super(); + this.type = type; + } + + canRedirect(moveId: Moves): boolean { + return super.canRedirect(moveId) && allMoves[moveId].type === this.type; + } +} + export class ReduceStatusEffectDurationAbAttr extends AbAttr { private statusEffect: StatusEffect; @@ -1681,6 +1714,7 @@ export function initAbilities() { new Ability(Abilities.NATURAL_CURE, "Natural Cure", "All status conditions heal when the Pokémon switches out.", 3) .attr(PreSwitchOutResetStatusAbAttr), new Ability(Abilities.LIGHTNING_ROD, "Lightning Rod", "The Pokémon draws in all Electric-type moves. Instead of being hit by Electric-type moves, it boosts its Sp. Atk.", 3) + .attr(RedirectTypeMoveAbAttr, Type.ELECTRIC) .attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPATK, 1), new Ability(Abilities.SERENE_GRACE, "Serene Grace (N)", "Boosts the likelihood of additional effects occurring when attacking.", 3), new Ability(Abilities.SWIFT_SWIM, "Swift Swim", "Boosts the Pokémon's Speed stat in rain.", 3) @@ -1831,6 +1865,7 @@ export function initAbilities() { .attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.SLOW_START, 5), new Ability(Abilities.SCRAPPY, "Scrappy (N)", "The Pokémon can hit Ghost-type Pokémon with Normal- and Fighting-type moves.", 4), new Ability(Abilities.STORM_DRAIN, "Storm Drain", "Draws in all Water-type moves. Instead of being hit by Water-type moves, it boosts its Sp. Atk.", 4) + .attr(RedirectTypeMoveAbAttr, Type.WATER) .attr(TypeImmunityStatChangeAbAttr, Type.WATER, BattleStat.SPATK, 1), new Ability(Abilities.ICE_BODY, "Ice Body", "The Pokémon gradually regains HP in a hailstorm.", 4) .attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL), diff --git a/src/data/weather.ts b/src/data/weather.ts index 398bee6d1fc..6605ccd4259 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -101,7 +101,7 @@ export class Weather { } isEffectSuppressed(scene: BattleScene): boolean { - const field = scene.getField().filter(p => p); + const field = scene.getField(true); for (let pokemon of field) { const suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0c0a7feefd4..1188a6d382d 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2303,7 +2303,7 @@ export class EnemyPokemon extends Pokemon { getNextTargets(moveId: Moves): BattlerIndex[] { const moveTargets = getMoveTargets(this, moveId); - const targets = this.scene.getField().filter(p => p?.isActive(true) && moveTargets.targets.indexOf(p.getBattlerIndex()) > -1); + const targets = this.scene.getField(true).filter(p => moveTargets.targets.indexOf(p.getBattlerIndex()) > -1); if (moveTargets.multiple) return targets.map(p => p.getBattlerIndex()); diff --git a/src/phases.ts b/src/phases.ts index 064e866e393..e9fc8a4f5ea 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -30,7 +30,7 @@ import { Weather, WeatherType, getRandomWeatherType, getWeatherDamageMessage, ge 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, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability"; +import { Abilities, CheckTrappedAbAttr, 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 } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./field/arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; @@ -369,7 +369,7 @@ export abstract class FieldPhase extends BattlePhase { } executeForAll(func: PokemonFunc): void { - const field = this.scene.getField().filter(p => p?.summonData && p.isActive()); + const field = this.scene.getField(true).filter(p => p.summonData); field.forEach(pokemon => func(pokemon)); } } @@ -1867,6 +1867,15 @@ export class MovePhase extends BattlePhase { return this.end(); } + // Move redirection abilities (ie. Storm Drain) only support single target moves + const moveTarget = this.targets.length === 1 + ? new Utils.IntegerHolder(this.targets[0]) + : null; + if (moveTarget) { + this.scene.getField(true).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget)); + this.targets[0] = moveTarget.value; + } + if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) { if (this.pokemon.turnData.attacksReceived.length) { const attacker = this.pokemon.turnData.attacksReceived.length ? this.pokemon.scene.getPokemonById(this.pokemon.turnData.attacksReceived[0].sourceId) : null; @@ -1880,8 +1889,8 @@ export class MovePhase extends BattlePhase { } } - const targets = this.scene.getField().filter(p => { - if (p?.isActive(true) && this.targets.indexOf(p.getBattlerIndex()) > -1) { + const targets = this.scene.getField(true).filter(p => { + if (this.targets.indexOf(p.getBattlerIndex()) > -1) { const hiddenTag = p.getTag(HiddenTag); if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length) return false; @@ -2197,7 +2206,7 @@ export class MoveEffectPhase extends PokemonPhase { } getTargets(): Pokemon[] { - return this.scene.getField().filter(p => p?.isActive(true) && this.targets.indexOf(p.getBattlerIndex()) > -1); + return this.scene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1); } getTarget(): Pokemon { @@ -2690,7 +2699,7 @@ export class FaintPhase extends PokemonPhase { } pokemon.lapseTags(BattlerTagLapseType.FAINT); - this.scene.getField().filter(p => p !== pokemon && p?.isActive(true)).forEach(p => p.removeTagsBySourceId(pokemon.id)); + this.scene.getField(true).filter(p => p !== pokemon).forEach(p => p.removeTagsBySourceId(pokemon.id)); pokemon.faintCry(() => { const friendshipDecrease = new Utils.IntegerHolder(10);