mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-21 14:59:26 +02:00
refactored and added postdamageattr
This commit is contained in:
parent
cce3818690
commit
47a34284ae
@ -1,10 +1,10 @@
|
|||||||
import Pokemon, { EnemyPokemon, HitResult, PlayerPokemon, PokemonMove } from "../field/pokemon";
|
import Pokemon, { EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove } from "../field/pokemon";
|
||||||
import { Type } from "./type";
|
import { Type } from "./type";
|
||||||
import { Constructor } from "#app/utils";
|
import { Constructor } from "#app/utils";
|
||||||
import * as Utils from "../utils";
|
import * as Utils from "../utils";
|
||||||
import { getPokemonNameWithAffix } from "../messages";
|
import { getPokemonNameWithAffix } from "../messages";
|
||||||
import { Weather, WeatherType } from "./weather";
|
import { Weather, WeatherType } from "./weather";
|
||||||
import { BattlerTag, DamagingTrapTag, GroundedTag } from "./battler-tags";
|
import { BattlerTag, GroundedTag } from "./battler-tags";
|
||||||
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
|
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
|
||||||
import { Gender } from "./gender";
|
import { Gender } from "./gender";
|
||||||
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move";
|
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move";
|
||||||
@ -34,9 +34,7 @@ import { SwitchPhase } from "#app/phases/switch-phase";
|
|||||||
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
|
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
|
||||||
import { BattleEndPhase } from "#app/phases/battle-end-phase";
|
import { BattleEndPhase } from "#app/phases/battle-end-phase";
|
||||||
import { NewBattlePhase } from "#app/phases/new-battle-phase";
|
import { NewBattlePhase } from "#app/phases/new-battle-phase";
|
||||||
import { PostSummonPhase } from "#app/phases/post-summon-phase";
|
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||||
import { WeatherEffectPhase } from "#app/phases/weather-effect-phase";
|
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
|
||||||
|
|
||||||
export class Ability implements Localizable {
|
export class Ability implements Localizable {
|
||||||
public id: Abilities;
|
public id: Abilities;
|
||||||
@ -3091,223 +3089,6 @@ function getSheerForceHitDisableAbCondition(): AbAttrCondition {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to determine if the Pokémon's health has been knocked below half.
|
|
||||||
*
|
|
||||||
* This function checks different phases like {@linkcode PostSummonPhase}, {@linkcode WeatherEffectPhase},
|
|
||||||
* {@linkcode TurnEndPhase}, and evaluates various conditions like Arena traps, status effects, passive damage sources,
|
|
||||||
* and healing items, to determine if the Pokémon's health has been knocked below half.
|
|
||||||
*
|
|
||||||
* Uses:
|
|
||||||
* - {@linkcode calculateTurnEndDamage} to calculate damage during the {@linkcode TurnEndPhase}.
|
|
||||||
* - {@linkcode calculateSleepDamage} to calculate additional damage while the Pokémon is sleeping.
|
|
||||||
* - {@linkcode calculateShellBellRecovery} to account for Shell Bell recovery.
|
|
||||||
* - {@linkcode checkSheerForceCondition} to verify if Sheer Force ability prevents effects of attacks.
|
|
||||||
*
|
|
||||||
* @returns {AbAttrCondition} A condition function that checks if the Pokémon's health has been knocked below half.
|
|
||||||
*/
|
|
||||||
function getHealthKnockedBelowHalf(): AbAttrCondition {
|
|
||||||
return (pokemon: Pokemon) => {
|
|
||||||
const maxPokemonHealth = pokemon.getMaxHp();
|
|
||||||
const pokemonHealth = pokemon.hp;
|
|
||||||
const currentPhase = pokemon.scene.getCurrentPhase();
|
|
||||||
|
|
||||||
// Helper function to calculate initial health based on damage taken
|
|
||||||
const getInitialHealth = (damageTaken: number = 0) => pokemonHealth + damageTaken;
|
|
||||||
|
|
||||||
// Helper function to check if health has dropped below half
|
|
||||||
const isHealthBelowHalf = (initialHealth: number, currentHealth: number) =>
|
|
||||||
initialHealth >= maxPokemonHealth / 2 && currentHealth < maxPokemonHealth / 2;
|
|
||||||
|
|
||||||
// PostSummonPhase
|
|
||||||
/**
|
|
||||||
* In the {@linkcode PostSummonPhase}, the function checks for Arena traps on the
|
|
||||||
* Pokemon's side and calculates damage taken during the summon.
|
|
||||||
* If the initial health (current HP + damage taken) was above half but now is below half,
|
|
||||||
* it returns true.
|
|
||||||
*/
|
|
||||||
if (currentPhase instanceof PostSummonPhase) {
|
|
||||||
const side = pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
|
||||||
if (pokemon.scene?.arena.findTagsOnSide(t => t instanceof ArenaTrapTag, side).length > 0) {
|
|
||||||
|
|
||||||
const damageTaken = pokemon.turnData?.damageTaken;
|
|
||||||
const initialHealth = pokemonHealth + damageTaken;
|
|
||||||
|
|
||||||
// Check if the initial health was above half
|
|
||||||
if (initialHealth <= maxPokemonHealth / 2) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// Check if current health dropped below half
|
|
||||||
if (pokemonHealth < maxPokemonHealth / 2) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// WeatherEffectPhase
|
|
||||||
/**
|
|
||||||
* In the {@linkcode WeatherEffectPhase}, this function automatically returns true.
|
|
||||||
* This is because certain weather effects (like Hail or Sandstorm) might reduce HP below half,
|
|
||||||
* and the specific weather effect calculations are handled {@linkcode PostWeatherForceSwitchOutAttr}.
|
|
||||||
*/
|
|
||||||
if (currentPhase instanceof WeatherEffectPhase) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TurnEndPhase
|
|
||||||
/**
|
|
||||||
* During the {@linkcode TurnEndPhase}, the function calculates damage from various sources:
|
|
||||||
* - Poison, Toxic, Burn status effects
|
|
||||||
* - Leech Seed, Curse, Salt Cure, and traps like Whirlpool
|
|
||||||
* It uses {@linkcode calculateTurnEndDamage} for this purpose and checks if the Pokémon's health
|
|
||||||
* drops below half after the turn-end effects.
|
|
||||||
*/
|
|
||||||
if (currentPhase instanceof TurnEndPhase) {
|
|
||||||
const damageTaken = calculateTurnEndDamage(pokemon);
|
|
||||||
const initialHealth = getInitialHealth(damageTaken);
|
|
||||||
return isHealthBelowHalf(initialHealth, pokemonHealth);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TurnData checks
|
|
||||||
/**
|
|
||||||
* The function checks if the last move received was affected by Sheer Force. If so,
|
|
||||||
* Sheer Force prevents the activation of the ability. The function calls
|
|
||||||
* {@linkcode checkSheerForceCondition} to verify this condition.
|
|
||||||
*/
|
|
||||||
if (pokemon.turnData) {
|
|
||||||
const sheerForceCondition = checkSheerForceCondition(pokemon);
|
|
||||||
if (sheerForceCondition !== null) {
|
|
||||||
return sheerForceCondition;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Additionally, the function calculates the health recovery from Shell Bell using
|
|
||||||
* {@linkcode calculateShellBellRecovery} and checks if the Pokemon's health dropped
|
|
||||||
* below half, irregardless of Shell Bell recovery
|
|
||||||
*/
|
|
||||||
const shellBellRecovery = calculateShellBellRecovery(pokemon);
|
|
||||||
const initialHealth = getInitialHealth(pokemon.turnData?.damageTaken ?? 0);
|
|
||||||
return isHealthBelowHalf(initialHealth, pokemonHealth - shellBellRecovery);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the damage taken by the Pokémon during the {@linkcode TurnEndPhase}.
|
|
||||||
*
|
|
||||||
* This function considers different status effects like Poison, Toxic, Burn, and also
|
|
||||||
* additional damage from Leech Seed, Curse, Salt Cure, and traps like Whirlpool.
|
|
||||||
*
|
|
||||||
* @param {Pokemon} pokemon - The Pokémon whose damage is being calculated.
|
|
||||||
* @returns {number} The total damage taken during the turn end phase.
|
|
||||||
*/
|
|
||||||
function calculateTurnEndDamage(pokemon: Pokemon): number {
|
|
||||||
let damageTaken = 0;
|
|
||||||
const maxHp = pokemon.getMaxHp();
|
|
||||||
|
|
||||||
if (pokemon.status?.isPostTurn()) {
|
|
||||||
switch (pokemon.status.effect) {
|
|
||||||
case StatusEffect.POISON:
|
|
||||||
damageTaken += Math.max(maxHp >> 3, 1); // Poison damage
|
|
||||||
break;
|
|
||||||
case StatusEffect.TOXIC:
|
|
||||||
damageTaken += Math.max(Math.floor((maxHp / 16) * pokemon.status.turnCount), 1); // Toxic damage
|
|
||||||
break;
|
|
||||||
case StatusEffect.BURN:
|
|
||||||
damageTaken += Math.max(maxHp >> 4, 1); // Burn damage
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional damage sources
|
|
||||||
if (pokemon.status?.effect === StatusEffect.SLEEP) {
|
|
||||||
damageTaken += calculateSleepDamage(pokemon);
|
|
||||||
}
|
|
||||||
if (pokemon.getTag(BattlerTagType.SEEDED)) {
|
|
||||||
damageTaken += Utils.toDmgValue(maxHp / 8); // Seeded damage
|
|
||||||
}
|
|
||||||
if (pokemon.getTag(BattlerTagType.CURSED)) {
|
|
||||||
damageTaken += Utils.toDmgValue(maxHp / 4); // Cursed damage
|
|
||||||
}
|
|
||||||
if (pokemon.getTag(BattlerTagType.SALT_CURED)) {
|
|
||||||
const isSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER);
|
|
||||||
damageTaken += Utils.toDmgValue(isSteelOrWater ? maxHp / 4 : maxHp / 8);
|
|
||||||
}
|
|
||||||
if (pokemon.findTags(tag => tag instanceof DamagingTrapTag).length > 0) {
|
|
||||||
damageTaken += Utils.toDmgValue(maxHp / 8); // Trap damage
|
|
||||||
}
|
|
||||||
|
|
||||||
return damageTaken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates damage that occurs due to sleeping, if any opponents have abilities
|
|
||||||
* that hurt sleeping Pokémon during {@linkcode TurnEndPhase}.
|
|
||||||
*
|
|
||||||
* @param {Pokemon} pokemon - The Pokémon whose damage is being calculated.
|
|
||||||
* @returns {number} The additional damage caused by sleep-related abilities (Bad Dreams).
|
|
||||||
*/
|
|
||||||
function calculateSleepDamage(pokemon: Pokemon): number {
|
|
||||||
let damageTaken = 0;
|
|
||||||
for (const opponent of pokemon.getOpponents()) {
|
|
||||||
if (opponent.hasAbilityWithAttr(PostTurnHurtIfSleepingAbAttr)) {
|
|
||||||
damageTaken += Utils.toDmgValue(pokemon.getMaxHp() / 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pokemon.getTag(BattlerTagType.NIGHTMARE)) {
|
|
||||||
damageTaken += Utils.toDmgValue(pokemon.getMaxHp() / 4);
|
|
||||||
}
|
|
||||||
return damageTaken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the amount of recovery from the Shell Bell item.
|
|
||||||
*
|
|
||||||
* If the Pokémon is holding a Shell Bell, this function computes the amount of health
|
|
||||||
* recovered based on the damage dealt in the current turn. The recovery is multiplied by the
|
|
||||||
* Shell Bell's modifier (if any). This covers the case where if the pokemon's health falls
|
|
||||||
* below half and recovers back above half from a Shell Bell,
|
|
||||||
* Wimp Out will activate even after the Shell Bell recovery
|
|
||||||
*
|
|
||||||
* @param {Pokemon} pokemon - The Pokémon whose Shell Bell recovery is being calculated.
|
|
||||||
* @returns {number} The amount of health recovered by Shell Bell.
|
|
||||||
*/
|
|
||||||
function calculateShellBellRecovery(pokemon: Pokemon): number {
|
|
||||||
const shellBellModifier = pokemon.getHeldItems().find(m => m instanceof HitHealModifier);
|
|
||||||
if (shellBellModifier) {
|
|
||||||
return Utils.toDmgValue(pokemon.turnData.damageDealt / 8) * shellBellModifier.stackCount;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether Sheer Force prevents the effects of the last attack received by the Pokémon.
|
|
||||||
*
|
|
||||||
* If the last attack received by the Pokémon is affected by Sheer Force (meaning that
|
|
||||||
* additional effects like status changes are suppressed), the function returns `false`
|
|
||||||
* to indicate that the additional effects won't apply. Otherwise, the function returns `null`.
|
|
||||||
*
|
|
||||||
* @param {Pokemon} pokemon - The Pokémon being evaluated.
|
|
||||||
* @returns {boolean | null} `false` if Sheer Force prevents effects, otherwise `null` if not applicable.
|
|
||||||
*/
|
|
||||||
function checkSheerForceCondition(pokemon: Pokemon): boolean | null {
|
|
||||||
const lastReceivedAttack = pokemon.turnData?.attacksReceived[0];
|
|
||||||
if (lastReceivedAttack) {
|
|
||||||
const lastAttacker = pokemon.getOpponents().find(p => p.id === lastReceivedAttack.sourceId);
|
|
||||||
if (lastAttacker) {
|
|
||||||
const isSheerForceAffected = allMoves[lastReceivedAttack.move].chance >= 0 && lastAttacker.hasAbility(Abilities.SHEER_FORCE);
|
|
||||||
return isSheerForceAffected ? false : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition {
|
function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition {
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
if (!pokemon.scene?.arena) {
|
if (!pokemon.scene?.arena) {
|
||||||
@ -4933,7 +4714,7 @@ async function applyAbAttrsInternal<TAttr extends AbAttr>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shared helper class for common functionality
|
//Helper class for Switch out logic
|
||||||
class ForceSwitchOutHelper {
|
class ForceSwitchOutHelper {
|
||||||
constructor(private switchType: SwitchType) {}
|
constructor(private switchType: SwitchType) {}
|
||||||
/**
|
/**
|
||||||
@ -4958,7 +4739,7 @@ class ForceSwitchOutHelper {
|
|||||||
|
|
||||||
if (switchOutTarget.hp > 0) {
|
if (switchOutTarget.hp > 0) {
|
||||||
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
||||||
pokemon.scene.unshiftPhase(new SwitchPhase(pokemon.scene, SwitchType.SWITCH, switchOutTarget.getFieldIndex(), true, true));
|
pokemon.scene.prependToPhase(new SwitchPhase(pokemon.scene, this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// If the battle is not a wild Pokémon encounter
|
// If the battle is not a wild Pokémon encounter
|
||||||
@ -4967,16 +4748,14 @@ class ForceSwitchOutHelper {
|
|||||||
* If yes, the Pokémon leaves the field and a new SwitchSummonPhase is initiated.
|
* If yes, the Pokémon leaves the field and a new SwitchSummonPhase is initiated.
|
||||||
*/
|
*/
|
||||||
} else if (pokemon.scene.currentBattle.battleType !== BattleType.WILD) {
|
} else if (pokemon.scene.currentBattle.battleType !== BattleType.WILD) {
|
||||||
|
|
||||||
if (switchOutTarget.scene.getEnemyParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) {
|
if (switchOutTarget.scene.getEnemyParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (switchOutTarget.hp > 0) {
|
if (switchOutTarget.hp > 0) {
|
||||||
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
||||||
pokemon.scene.unshiftPhase(new SwitchSummonPhase(pokemon.scene, this.switchType, switchOutTarget.getFieldIndex(),
|
pokemon.scene.prependToPhase(new SwitchSummonPhase(pokemon.scene, this.switchType, switchOutTarget.getFieldIndex(),
|
||||||
(pokemon.scene.currentBattle.trainer ? pokemon.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0),
|
(pokemon.scene.currentBattle.trainer ? pokemon.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0),
|
||||||
false, false));
|
false, false), MoveEndPhase);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// If it's a wild Pokémon encounter
|
// If it's a wild Pokémon encounter
|
||||||
@ -5057,313 +4836,105 @@ class ForceSwitchOutHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute that forces a switch-out after defending against an attack.
|
* Calculates the amount of recovery from the Shell Bell item.
|
||||||
* This triggers after the Pokémon is attacked and takes damage.
|
|
||||||
*/
|
|
||||||
export class PostDefendForceSwitchOutAttr extends PostDefendAbAttr {
|
|
||||||
private helper: ForceSwitchOutHelper;
|
|
||||||
|
|
||||||
constructor(private switchType: SwitchType = SwitchType.SWITCH) {
|
|
||||||
super();
|
|
||||||
this.helper = new ForceSwitchOutHelper(switchType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the switch-out logic after an attack hits the Pokemon.
|
|
||||||
* Checks if the Pokemon's health drops below half for a forced switch-out
|
|
||||||
* through {@linkcode getHealthKnockedBelowHalf}, which tracks the damage taken from the attack.
|
|
||||||
*
|
*
|
||||||
* If the attacker is attacking with a multi-hit move, the ability will trigger after the last hit
|
* If the Pokémon is holding a Shell Bell, this function computes the amount of health
|
||||||
* If hit with a phasing move, the phasing move's effect is applied and not the ability
|
* recovered based on the damage dealt in the current turn. The recovery is multiplied by the
|
||||||
|
* Shell Bell's modifier (if any).
|
||||||
*
|
*
|
||||||
* @param {Pokemon} pokemon The defending Pokémon with this ability.
|
* @param {Pokemon} pokemon - The Pokémon whose Shell Bell recovery is being calculated.
|
||||||
* @param {boolean} _passive N/A
|
* @returns {number} The amount of health recovered by Shell Bell.
|
||||||
* @param {boolean} simulated Whether the ability is being simulated.
|
|
||||||
* @param {Pokemon} attacker The attacking Pokémon.
|
|
||||||
* @param {Move} move The move that triggered the post-defend effect.
|
|
||||||
* @param {HitResult | null} hitResult N/A
|
|
||||||
* @param {any[]} args N/A
|
|
||||||
* @returns {boolean} True if the switch-out logic was successfully applied.
|
|
||||||
*/
|
*/
|
||||||
|
function calculateShellBellRecovery(pokemon: Pokemon): number {
|
||||||
|
const shellBellModifier = pokemon.getHeldItems().find(m => m instanceof HitHealModifier);
|
||||||
|
if (shellBellModifier) {
|
||||||
|
return Utils.toDmgValue(pokemon.turnData.damageDealt / 8) * shellBellModifier.stackCount;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
/**
|
||||||
for (const opponent of pokemon.getOpponents()) {
|
* Triggers after the Pokemon takes any damage
|
||||||
if (!this.helper.getSwitchOutCondition(pokemon, opponent)) {
|
* @extends AbAttr
|
||||||
|
*/
|
||||||
|
export class PostDamageAbAttr extends AbAttr {
|
||||||
|
applyPostDamageAbAttrs(pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (attacker.turnData.hitsLeft > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (pokemon.getTag(BattlerTagType.TRAPPED) !== undefined) {
|
|
||||||
pokemon.removeTag(BattlerTagType.TRAPPED);
|
|
||||||
}
|
|
||||||
if (move.id === Moves.DRAGON_TAIL || move.id === Moves.CIRCLE_THROW) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this.helper.switchOutLogic(pokemon);
|
|
||||||
}
|
|
||||||
|
|
||||||
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
|
||||||
return this.helper.getFailedText(target);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ability attribute for forcing a Pokémon to switch out after an attack.
|
* Ability attribute for forcing a Pokémon to switch out after its health drops below half.
|
||||||
* This triggers after the Pokémon attacks and takes damage themselves.
|
* This attribute checks various conditions related to the damage received, the moves used by the Pokémon
|
||||||
|
* and its opponents, and determines whether a forced switch-out should occur.
|
||||||
*
|
*
|
||||||
* @extends PostAttackAbAttr
|
* @extends PostDamageAbAttr
|
||||||
*/
|
*/
|
||||||
export class PostAttackForceSwitchOutAttr extends PostAttackAbAttr {
|
export class PostDamageForceSwitchAttr extends PostDamageAbAttr {
|
||||||
private helper: ForceSwitchOutHelper;
|
private helper: ForceSwitchOutHelper;
|
||||||
|
private hpRatio: number;
|
||||||
|
|
||||||
constructor(private switchType: SwitchType = SwitchType.SWITCH) {
|
constructor(hpRatio:number = 0.5) {
|
||||||
super();
|
super();
|
||||||
this.helper = new ForceSwitchOutHelper(switchType);
|
this.hpRatio = hpRatio;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the switch-out logic after an attack is made. Checks if the Pokemon's health drops
|
|
||||||
* below half for a forced switch-out through {@linkcode getHealthKnockedBelowHalf}, which
|
|
||||||
* tracks the damage taken following its attack.
|
|
||||||
* If the attacker cuts their own health with a move like Curse or Substitute, the ability is
|
|
||||||
* not triggered
|
|
||||||
*
|
|
||||||
* @param {Pokemon} pokemon The attacking Pokémon with this ability.
|
|
||||||
* @param {boolean} passive N/A
|
|
||||||
* @param {boolean} simulated Whether the ability is being simulated.
|
|
||||||
* @param {Pokemon} defender The defending Pokémon.
|
|
||||||
* @param {Move} move The move that triggered the post-attack effect.
|
|
||||||
* @param {HitResult | null} hitResult N/A
|
|
||||||
* @param {any[]} args N/A
|
|
||||||
* @returns {boolean} True if the switch-out logic was successfully applied.
|
|
||||||
*/
|
|
||||||
applyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
|
||||||
|
|
||||||
for (const opponent of pokemon.getOpponents()) {
|
|
||||||
if (!this.helper.getSwitchOutCondition(pokemon, opponent)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pokemon.getTag(BattlerTagType.TRAPPED) !== undefined) {
|
|
||||||
pokemon.removeTag(BattlerTagType.TRAPPED);
|
|
||||||
}
|
|
||||||
if ([ Moves.PAIN_SPLIT, Moves.SUBSTITUTE, Moves.BELLY_DRUM ].includes(move.id)) {
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (pokemon.getTypes(true).includes(Type.GHOST) && move.id === Moves.CURSE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.helper.switchOutLogic(pokemon);
|
|
||||||
}
|
|
||||||
|
|
||||||
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
|
||||||
return this.helper.getFailedText(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ability attribute for forcing a Pokémon to switch out after being summoned.
|
|
||||||
* This triggers after the Pokémon is summoned and takes damage from Arena Traps like
|
|
||||||
* Stealth Rocks.
|
|
||||||
*
|
|
||||||
* @extends PostSummonAbAttr
|
|
||||||
*/
|
|
||||||
export class PostSummonForceSwitchOutAttr extends PostSummonAbAttr {
|
|
||||||
private helper: ForceSwitchOutHelper;
|
|
||||||
|
|
||||||
constructor(private switchType: SwitchType = SwitchType.SWITCH) {
|
|
||||||
super();
|
|
||||||
this.helper = new ForceSwitchOutHelper(switchType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the switch-out logic after the Pokémon is summoned. Checks if the Pokemon's health drops
|
|
||||||
* below half for a forced switch-out through {@linkcode getHealthKnockedBelowHalf}, which
|
|
||||||
* tracks the damage taken upon entry. If it drops below half, the pokemon is switched out
|
|
||||||
*
|
|
||||||
* @param {Pokemon} pokemon The Pokémon summoned with this ability.
|
|
||||||
* @param {boolean} passive N/A
|
|
||||||
* @param {boolean} simulated Whether the ability is being simulated.
|
|
||||||
* @param {any[]} args N/A
|
|
||||||
* @returns {boolean} True if the switch-out logic was successfully applied.
|
|
||||||
*/
|
|
||||||
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
|
||||||
if (pokemon.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const opponent of pokemon.getOpponents()) {
|
|
||||||
if (!this.helper.getSwitchOutCondition(pokemon, opponent)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pokemon.getTag(BattlerTagType.TRAPPED) !== undefined) {
|
|
||||||
pokemon.removeTag(BattlerTagType.TRAPPED);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.helper.switchOutLogic(pokemon);
|
|
||||||
}
|
|
||||||
|
|
||||||
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
|
||||||
return this.helper.getFailedText(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ability attribute for forcing a Pokémon to switch out at the end of the turn,
|
|
||||||
* if its health drops below half. This attribute triggers from effects like
|
|
||||||
* Poison, Leech Seed, Curse etc.
|
|
||||||
*
|
|
||||||
* @extends PostTurnAbAttr
|
|
||||||
*/
|
|
||||||
export class PostTurnForceSwitchOutAttr extends PostTurnAbAttr {
|
|
||||||
private helper: ForceSwitchOutHelper;
|
|
||||||
|
|
||||||
constructor(private switchType: SwitchType = SwitchType.SWITCH) {
|
|
||||||
super();
|
|
||||||
this.helper = new ForceSwitchOutHelper(switchType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the switch-out logic at the end of the turn. Checks if the Pokemon's health drops
|
|
||||||
* below half for a forced switch-out through {@linkcode getHealthKnockedBelowHalf}, which
|
|
||||||
* simulates the damage that it would take. If it drops below half, the pokemon is switched out
|
|
||||||
*
|
|
||||||
* @param {Pokemon} pokemon The Pokémon attempting to switch out.
|
|
||||||
* @param {boolean} passive N/A
|
|
||||||
* @param {boolean} simulated Whether the ability is being simulated.
|
|
||||||
* @param {any[]} args N/A
|
|
||||||
* @returns {boolean | Promise<boolean>} True if the switch-out logic was successfully applied.
|
|
||||||
*/
|
|
||||||
applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
|
||||||
if (pokemon.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (pokemon.status?.isPostTurn() && pokemon.hasAbilityWithAttr(BlockStatusDamageAbAttr)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const opponent of pokemon.getOpponents()) {
|
|
||||||
if (!this.helper.getSwitchOutCondition(pokemon, opponent)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pokemon.getTag(BattlerTagType.TRAPPED) !== undefined) {
|
|
||||||
pokemon.removeTag(BattlerTagType.TRAPPED);
|
|
||||||
}
|
|
||||||
return this.helper.switchOutLogic(pokemon);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
|
||||||
return this.helper.getFailedText(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ability attribute for forcing a Pokémon to switch out during weather.
|
|
||||||
* This attribute is triggered during a weather lapse and applies switch-out logic based on
|
|
||||||
* the damage taken during weather conditions.
|
|
||||||
*
|
|
||||||
* @extends PostWeatherLapseAbAttr
|
|
||||||
*/
|
|
||||||
export class PostWeatherForceSwitchOutAttr extends PostWeatherLapseAbAttr {
|
|
||||||
private helper: ForceSwitchOutHelper;
|
|
||||||
private damageFactor: integer;
|
|
||||||
|
|
||||||
constructor(damageFactor: integer, ...weatherTypes: WeatherType[]) {
|
|
||||||
super(...weatherTypes);
|
|
||||||
this.damageFactor = damageFactor;
|
|
||||||
this.helper = new ForceSwitchOutHelper(SwitchType.SWITCH);
|
this.helper = new ForceSwitchOutHelper(SwitchType.SWITCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the switch-out logic during a weather that damages Pokemon such as Hail or Sandstorm.
|
* Applies the switch-out logic after the Pokémon takes damage.
|
||||||
* If the Pokemon would fall below half health from the simulated weather damage amount, the weather damage is
|
* Checks various conditions based on the moves used by the Pokémon, the opponents' moves, and
|
||||||
* applied and the Pokmeon is switched out.
|
* the Pokémon's health after damage to determine whether the switch-out should occur.
|
||||||
*
|
*
|
||||||
* @param {Pokemon} pokemon The Pokémon affected by the weather.
|
* @param {Pokemon} pokemon The Pokémon that took damage.
|
||||||
|
* @param {number} damage The amount of damage taken by the Pokémon.
|
||||||
* @param {boolean} passive N/A
|
* @param {boolean} passive N/A
|
||||||
* @param {boolean} simulated Whether the ability is being simulated.
|
* @param {boolean} simulated Whether the ability is being simulated.
|
||||||
* @param {Weather} weather The current weather condition.
|
|
||||||
* @param {any[]} args N/A
|
* @param {any[]} args N/A
|
||||||
* @returns {boolean} True if the switch-out logic was successfully applied.
|
* @returns {boolean | Promise<boolean>} True if the switch-out logic was successfully applied, false otherwise.
|
||||||
*/
|
*/
|
||||||
|
applyPostDamageAbAttrs(pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||||
applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, args: any[]): boolean {
|
const moveHistory = pokemon.getMoveHistory();
|
||||||
if (pokemon.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
// Will not activate when the Pokémon's HP is lowered by cutting its own HP
|
||||||
|
const fordbiddenAttackingMoves = [ Moves.BELLY_DRUM, Moves.SUBSTITUTE, Moves.CURSE, Moves.PAIN_SPLIT ];
|
||||||
|
if (moveHistory.length > 0) {
|
||||||
|
const lastMoveUsed = moveHistory[moveHistory.length - 1];
|
||||||
|
// Will not activate if the Pokémon's HP falls below half while it is in the air during Sky Drop.
|
||||||
|
if (fordbiddenAttackingMoves.includes(lastMoveUsed.move) || (lastMoveUsed.move === Moves.SKY_DROP && lastMoveUsed.result === MoveResult.OTHER)) {
|
||||||
|
return false;
|
||||||
|
// Will not activate if the Pokémon's HP falls below half due to hurting itself in confusion
|
||||||
|
} else if (lastMoveUsed.move === 0 && pokemon.getTag(BattlerTagType.CONFUSED)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Dragon Tail and Circle Throw switch out Pokémon before the Ability activates.
|
||||||
|
const fordbiddenDefendingMoves = [ Moves.DRAGON_TAIL, Moves.CIRCLE_THROW ];
|
||||||
|
for (const opponent of pokemon.getOpponents()) {
|
||||||
|
const enemyMoveHistory = opponent.getMoveHistory();
|
||||||
|
if (enemyMoveHistory.length > 0) {
|
||||||
|
const enemyLastMoveUsed = enemyMoveHistory[enemyMoveHistory.length - 1];
|
||||||
|
if (fordbiddenDefendingMoves.includes(enemyLastMoveUsed.move)) {
|
||||||
|
return false;
|
||||||
|
// Will not activate if the Pokémon's HP falls below half by a move affected by Sheer Force.
|
||||||
|
} else if (allMoves[enemyLastMoveUsed.move].chance >= 0 && opponent.hasAbility(Abilities.SHEER_FORCE)) {
|
||||||
|
return false;
|
||||||
|
// Activate only after the last hit of multistrike moves
|
||||||
|
} else if (opponent.turnData.hitsLeft > 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (pokemon.hp + damage >= pokemon.getMaxHp() * this.hpRatio) {
|
||||||
|
// Activates if it falls below half and recovers back above half from a Shell Bell
|
||||||
|
const shellBellHeal = calculateShellBellRecovery(pokemon);
|
||||||
|
if (pokemon.hp - shellBellHeal < pokemon.getMaxHp() / 2) {
|
||||||
for (const opponent of pokemon.getOpponents()) {
|
for (const opponent of pokemon.getOpponents()) {
|
||||||
if (!this.helper.getSwitchOutCondition(pokemon, opponent)) {
|
if (!this.helper.getSwitchOutCondition(pokemon, opponent)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pokemon.getTag(BattlerTagType.TRAPPED) !== undefined) {
|
// Trapping moves do not prevent activation
|
||||||
pokemon.removeTag(BattlerTagType.TRAPPED);
|
|
||||||
}
|
|
||||||
const damageAmount = Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.damageFactor));
|
|
||||||
|
|
||||||
if (pokemon.hp + damageAmount >= pokemon.getMaxHp() / 2) {
|
|
||||||
if (pokemon.hp < pokemon.getMaxHp() / 2) {
|
|
||||||
return this.helper.switchOutLogic(pokemon);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
|
||||||
return this.helper.getFailedText(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ability attribute for forcing a Pokémon to switch out after taking damage
|
|
||||||
* from KOing an enemy (Aftermath, Innards Out).
|
|
||||||
*
|
|
||||||
* @extends PostVictoryAbAttr
|
|
||||||
*/
|
|
||||||
export class PostVictoryForceSwitchOutAttr extends PostVictoryAbAttr {
|
|
||||||
private helper: ForceSwitchOutHelper;
|
|
||||||
|
|
||||||
constructor(private switchType: SwitchType = SwitchType.SWITCH) {
|
|
||||||
super();
|
|
||||||
this.helper = new ForceSwitchOutHelper(switchType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the switch-out logic after a KO. Checks if the conditions for a forced switch-out
|
|
||||||
* are met based on whether the Pokémon's health dropped below half following damage taken during the KO.
|
|
||||||
*
|
|
||||||
* @param {Pokemon} pokemon the {@linkcode Pokemon} with this ability
|
|
||||||
* @param {boolean} passive N/A
|
|
||||||
* @param {boolean} simulated Whether the ability is being simulated.
|
|
||||||
* @param {any[]} args N/A
|
|
||||||
* @returns {boolean} True if the switch-out logic was successfully applied.
|
|
||||||
*/
|
|
||||||
applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
|
||||||
const pokemonHealth = pokemon.hp;
|
|
||||||
const maxPokemonHealth = pokemon.getMaxHp();
|
|
||||||
const damageTaken = pokemon.turnData?.damageTaken;
|
|
||||||
const initialHealth = pokemonHealth + damageTaken;
|
|
||||||
// Check if the initial health was above half
|
|
||||||
if (initialHealth <= maxPokemonHealth / 2) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// Check if current health dropped below half
|
|
||||||
if (pokemonHealth < maxPokemonHealth / 2) {
|
|
||||||
for (const opponent of pokemon.getOpponents()) {
|
|
||||||
if (!this.helper.getSwitchOutCondition(pokemon, opponent)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pokemon.getTag(BattlerTagType.TRAPPED) !== undefined) {
|
if (pokemon.getTag(BattlerTagType.TRAPPED) !== undefined) {
|
||||||
pokemon.removeTag(BattlerTagType.TRAPPED);
|
pokemon.removeTag(BattlerTagType.TRAPPED);
|
||||||
}
|
}
|
||||||
@ -5371,9 +4942,10 @@ export class PostVictoryForceSwitchOutAttr extends PostVictoryAbAttr {
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
||||||
return this.helper.getFailedText(target);
|
return this.helper.getFailedText(target);
|
||||||
}
|
}
|
||||||
@ -5413,6 +4985,11 @@ export function applyPostSetStatusAbAttrs(attrType: Constructor<PostSetStatusAbA
|
|||||||
return applyAbAttrsInternal<PostSetStatusAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), args, false, simulated);
|
return applyAbAttrsInternal<PostSetStatusAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), args, false, simulated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyPostDamageAbAttrs(attrType: Constructor<PostDamageAbAttr>,
|
||||||
|
pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean = false, args: any[]): Promise<void> {
|
||||||
|
return applyAbAttrsInternal<PostDamageAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostDamageAbAttrs(pokemon, damage, passive, simulated, args), args);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies a field Stat multiplier attribute
|
* Applies a field Stat multiplier attribute
|
||||||
* @param attrType {@linkcode FieldMultiplyStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being
|
* @param attrType {@linkcode FieldMultiplyStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being
|
||||||
@ -6154,21 +5731,11 @@ export function initAbilities() {
|
|||||||
new Ability(Abilities.STAMINA, 7)
|
new Ability(Abilities.STAMINA, 7)
|
||||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1),
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1),
|
||||||
new Ability(Abilities.WIMP_OUT, 7)
|
new Ability(Abilities.WIMP_OUT, 7)
|
||||||
.attr(PostDefendForceSwitchOutAttr)
|
.attr(PostDamageForceSwitchAttr),
|
||||||
.attr(PostAttackForceSwitchOutAttr)
|
// .condition(getSheerForceHitDisableAbCondition()),
|
||||||
.attr(PostSummonForceSwitchOutAttr)
|
|
||||||
.attr(PostTurnForceSwitchOutAttr)
|
|
||||||
.attr(PostWeatherForceSwitchOutAttr, 1, WeatherType.HAIL, WeatherType.SANDSTORM)
|
|
||||||
.attr(PostVictoryForceSwitchOutAttr)
|
|
||||||
.condition(getHealthKnockedBelowHalf()),
|
|
||||||
new Ability(Abilities.EMERGENCY_EXIT, 7)
|
new Ability(Abilities.EMERGENCY_EXIT, 7)
|
||||||
.attr(PostDefendForceSwitchOutAttr)
|
.attr(PostDamageForceSwitchAttr),
|
||||||
.attr(PostAttackForceSwitchOutAttr)
|
// .condition(getSheerForceHitDisableAbCondition()),
|
||||||
.attr(PostSummonForceSwitchOutAttr)
|
|
||||||
.attr(PostTurnForceSwitchOutAttr)
|
|
||||||
.attr(PostWeatherForceSwitchOutAttr, 1, WeatherType.HAIL, WeatherType.SANDSTORM)
|
|
||||||
.attr(PostVictoryForceSwitchOutAttr)
|
|
||||||
.condition(getHealthKnockedBelowHalf()),
|
|
||||||
new Ability(Abilities.WATER_COMPACTION, 7)
|
new Ability(Abilities.WATER_COMPACTION, 7)
|
||||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
|
||||||
new Ability(Abilities.MERCILESS, 7)
|
new Ability(Abilities.MERCILESS, 7)
|
||||||
|
@ -8,7 +8,7 @@ import { Constructor, NumberHolder } from "#app/utils";
|
|||||||
import * as Utils from "../utils";
|
import * as Utils from "../utils";
|
||||||
import { WeatherType } from "./weather";
|
import { WeatherType } from "./weather";
|
||||||
import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag";
|
import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag";
|
||||||
import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, PostDefendForceSwitchOutAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability";
|
import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, PostDamageForceSwitchAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability";
|
||||||
import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier";
|
import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier";
|
||||||
import { BattlerIndex, BattleType } from "../battle";
|
import { BattlerIndex, BattleType } from "../battle";
|
||||||
import { TerrainType } from "./terrain";
|
import { TerrainType } from "./terrain";
|
||||||
@ -5659,7 +5659,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
*/
|
*/
|
||||||
const switchOutTarget = this.selfSwitch ? user : target;
|
const switchOutTarget = this.selfSwitch ? user : target;
|
||||||
if (switchOutTarget instanceof PlayerPokemon) {
|
if (switchOutTarget instanceof PlayerPokemon) {
|
||||||
if (target.getAbility().hasAttr(PostDefendForceSwitchOutAttr) &&
|
if (target.getAbility().hasAttr(PostDamageForceSwitchAttr) &&
|
||||||
(move.id === Moves.U_TURN || move.id === Moves.VOLT_SWITCH)) {
|
(move.id === Moves.U_TURN || move.id === Moves.VOLT_SWITCH)) {
|
||||||
if (shouldPreventSwitchOut(target)) {
|
if (shouldPreventSwitchOut(target)) {
|
||||||
return false;
|
return false;
|
||||||
@ -5694,7 +5694,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
* Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch
|
* Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch
|
||||||
* If it did, the user of U-turn or Volt Switch will not be switched out.
|
* If it did, the user of U-turn or Volt Switch will not be switched out.
|
||||||
*/
|
*/
|
||||||
if (target.getAbility().hasAttr(PostDefendForceSwitchOutAttr) &&
|
if (target.getAbility().hasAttr(PostDamageForceSwitchAttr) &&
|
||||||
(move.id === Moves.U_TURN || move.id === Moves.VOLT_SWITCH)) {
|
(move.id === Moves.U_TURN || move.id === Moves.VOLT_SWITCH)) {
|
||||||
if (shouldPreventSwitchOut(target)) {
|
if (shouldPreventSwitchOut(target)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -22,7 +22,7 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/
|
|||||||
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags";
|
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags";
|
||||||
import { WeatherType } from "#app/data/weather";
|
import { WeatherType } from "#app/data/weather";
|
||||||
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
||||||
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr } from "#app/data/ability";
|
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, applyPostDamageAbAttrs, PostDamageAbAttr } from "#app/data/ability";
|
||||||
import PokemonData from "#app/system/pokemon-data";
|
import PokemonData from "#app/system/pokemon-data";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
@ -2892,6 +2892,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
damage = this.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase);
|
damage = this.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase);
|
||||||
// Damage amount may have changed, but needed to be queued before calling damage function
|
// Damage amount may have changed, but needed to be queued before calling damage function
|
||||||
damagePhase.updateAmount(damage);
|
damagePhase.updateAmount(damage);
|
||||||
|
applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, []);
|
||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { applyAbAttrs, BlockNonDirectDamageAbAttr, BlockStatusDamageAbAttr, ReduceBurnDamageAbAttr } from "#app/data/ability";
|
import { applyAbAttrs, applyPostDamageAbAttrs, BlockNonDirectDamageAbAttr, BlockStatusDamageAbAttr, PostDamageAbAttr, ReduceBurnDamageAbAttr } from "#app/data/ability";
|
||||||
import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims";
|
import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims";
|
||||||
import { getStatusEffectActivationText } from "#app/data/status-effect";
|
import { getStatusEffectActivationText } from "#app/data/status-effect";
|
||||||
import { BattleSpec } from "#app/enums/battle-spec";
|
import { BattleSpec } from "#app/enums/battle-spec";
|
||||||
@ -41,6 +41,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
|||||||
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...
|
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...
|
||||||
this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true));
|
this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true));
|
||||||
pokemon.updateInfo();
|
pokemon.updateInfo();
|
||||||
|
applyPostDamageAbAttrs(PostDamageAbAttr, pokemon, damage.value, pokemon.hasPassive(), false, []);
|
||||||
}
|
}
|
||||||
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, false, () => this.end());
|
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, false, () => this.end());
|
||||||
} else {
|
} else {
|
||||||
|
@ -62,29 +62,6 @@ describe("Abilities - Wimp Out", () => {
|
|||||||
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
|
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
|
||||||
expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.TYRUNT);
|
expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.TYRUNT);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("triggers status on the wimp out user before a new pokemon is switched in", async () => {
|
|
||||||
// arrange
|
|
||||||
game.override
|
|
||||||
.enemyMoveset(Moves.SLUDGE_BOMB)
|
|
||||||
.startingLevel(85);
|
|
||||||
await game.startBattle([
|
|
||||||
Species.WIMPOD,
|
|
||||||
Species.TYRUNT
|
|
||||||
]);
|
|
||||||
vi.spyOn(game.scene.getEnemyPokemon()!, "randSeedInt").mockReturnValue(0);
|
|
||||||
|
|
||||||
// act
|
|
||||||
game.move.select(Moves.SLUDGE_BOMB);
|
|
||||||
game.doSelectPartyPokemon(1);
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
|
||||||
|
|
||||||
// assert
|
|
||||||
const playerPkm = game.scene.getPlayerPokemon()!;
|
|
||||||
expect(game.scene.getParty()[1].status?.effect).toEqual(StatusEffect.POISON);
|
|
||||||
expect(playerPkm.species.speciesId).toEqual(Species.TYRUNT);
|
|
||||||
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
|
|
||||||
});
|
|
||||||
it("It makes wild pokemon flee if triggered", async () => {
|
it("It makes wild pokemon flee if triggered", async () => {
|
||||||
// arrange
|
// arrange
|
||||||
game.override
|
game.override
|
||||||
@ -128,30 +105,6 @@ describe("Abilities - Wimp Out", () => {
|
|||||||
expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase");
|
expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase");
|
||||||
expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.WIMPOD);
|
expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.WIMPOD);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("triggers after last hit of multi hit move", async () => {
|
|
||||||
// arrange
|
|
||||||
game.override
|
|
||||||
.enemyMoveset(Moves.BULLET_SEED)
|
|
||||||
.enemyAbility(Abilities.SKILL_LINK)
|
|
||||||
.startingLevel(110)
|
|
||||||
.enemyLevel(80);
|
|
||||||
await game.startBattle([
|
|
||||||
Species.WIMPOD,
|
|
||||||
Species.TYRUNT
|
|
||||||
]);
|
|
||||||
|
|
||||||
// act
|
|
||||||
game.move.select(Moves.BULLET_SEED);
|
|
||||||
game.doSelectPartyPokemon(1);
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
|
||||||
|
|
||||||
|
|
||||||
// assert
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
|
||||||
expect(enemyPokemon.turnData.hitsLeft).toBe(0);
|
|
||||||
expect(enemyPokemon.turnData.hitCount).toBe(5);
|
|
||||||
});
|
|
||||||
it("Trapping moves do not prevent Wimp Out from activating.", async () => {
|
it("Trapping moves do not prevent Wimp Out from activating.", async () => {
|
||||||
// arrange
|
// arrange
|
||||||
game.override
|
game.override
|
||||||
@ -675,4 +628,48 @@ describe("Abilities - Wimp Out", () => {
|
|||||||
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
|
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
|
||||||
expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.TYRUNT);
|
expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.TYRUNT);
|
||||||
});
|
});
|
||||||
|
it("triggers status on the wimp out user before a new pokemon is switched in", async () => {
|
||||||
|
// arrange
|
||||||
|
game.override
|
||||||
|
.enemyMoveset(Moves.SLUDGE_BOMB)
|
||||||
|
.startingLevel(80);
|
||||||
|
await game.startBattle([
|
||||||
|
Species.WIMPOD,
|
||||||
|
Species.TYRUNT
|
||||||
|
]);
|
||||||
|
vi.spyOn(game.scene.getEnemyPokemon()!, "randSeedInt").mockReturnValue(0);
|
||||||
|
|
||||||
|
// act
|
||||||
|
game.move.select(Moves.SLUDGE_BOMB);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const playerPkm = game.scene.getPlayerPokemon()!;
|
||||||
|
expect(game.scene.getParty()[1].status?.effect).toEqual(StatusEffect.POISON);
|
||||||
|
expect(playerPkm.species.speciesId).toEqual(Species.TYRUNT);
|
||||||
|
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
|
||||||
|
});
|
||||||
|
it("triggers after last hit of multi hit move", async () => {
|
||||||
|
// arrange
|
||||||
|
game.override
|
||||||
|
.enemyMoveset(Moves.BULLET_SEED)
|
||||||
|
.enemyAbility(Abilities.SKILL_LINK)
|
||||||
|
.startingLevel(110)
|
||||||
|
.enemyLevel(80);
|
||||||
|
await game.startBattle([
|
||||||
|
Species.WIMPOD,
|
||||||
|
Species.TYRUNT
|
||||||
|
]);
|
||||||
|
|
||||||
|
// act
|
||||||
|
game.move.select(Moves.BULLET_SEED);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
expect(enemyPokemon.turnData.hitsLeft).toBe(0);
|
||||||
|
expect(enemyPokemon.turnData.hitCount).toBe(5);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user