[Enhancement] Add support for simulated calls to Abilities (#3529)

* Adds simulated tag support to all abilities

* Fix compiler errors in ability.ts

Most things are still on fire 😢

* Fix errors left over after merge

* Another pass through simulated ability call logic

* Fix leftover errors from merge resolution

* Another gh pages issue :pikamad:

* Simulated call fixes based on Kev's feedback

* RIP phases.ts

---------

Co-authored-by: Xavion3 <xavion333@gmail.com>
This commit is contained in:
innerthunder 2024-08-21 18:29:21 -07:00 committed by Jesse Chung
parent 0567b4db73
commit 754d377539
7 changed files with 82 additions and 116 deletions

View File

@ -372,8 +372,7 @@ export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr {
* Example: Levitate
*/
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
// this is a hacky way to fix the Levitate/Thousand Arrows interaction, but it works for now...
if (move.category !== MoveCategory.STATUS && !move.hasAttr(NeutralDamageAgainstFlyingTypeMultiplierAttr)) {
if (move.category !== MoveCategory.STATUS) {
return super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
}
return false;
@ -392,8 +391,7 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
if (!pokemon.isFullHp() && !simulated) {
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
Utils.toDmgValue(pokemon.getMaxHp() / 4), i18next.t("abilityTriggers:typeImmunityHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true));
cancelled.value = true; // Suppresses "No Effect" message
Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), i18next.t("abilityTriggers:typeImmunityHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true));
}
return true;
}
@ -417,7 +415,7 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr {
const ret = super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
if (ret) {
cancelled.value = true; // Suppresses "No Effect" message
cancelled.value = true;
if (!simulated) {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels));
}
@ -442,7 +440,7 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr {
const ret = super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
if (ret) {
cancelled.value = true; // Suppresses "No Effect" message
cancelled.value = true;
if (!simulated) {
pokemon.addTag(this.tagType, this.turnCount, undefined, pokemon.id);
}
@ -458,8 +456,8 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
}
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(pokemon.getMoveType(move), attacker) < 2) {
cancelled.value = true; // Suppresses "No Effect" message
if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.type, attacker) < 2) {
cancelled.value = true;
(args[0] as Utils.NumberHolder).value = 0;
return true;
}
@ -807,7 +805,7 @@ export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
if (simulated) {
return true;
}
const type = attacker.getMoveType(move);
const type = move.type;
const pokemonTypes = pokemon.getTypes(true);
if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) {
pokemon.summonData.types = [ type ];
@ -948,8 +946,8 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
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)) {
attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio));
attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
attacker.turnData.damageTaken += Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio));
return true;
}
@ -1264,7 +1262,6 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
super(true);
}
// TODO: Decouple this into two attributes (type change / power boost)
applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if (this.condition && this.condition(pokemon, defender, move)) {
if (args[0] && args[0] instanceof Utils.NumberHolder) {
@ -1305,10 +1302,17 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
) {
const moveType = pokemon.getMoveType(move);
if (pokemon.getTypes().some((t) => t !== moveType)) {
// Moves like Weather Ball ignore effects of abilities like Normalize and Refrigerate
if (move.findAttr(attr => attr instanceof VariableMoveTypeAttr)) {
applyMoveAttrs(VariableMoveTypeAttr, pokemon, null, moveCopy);
} else {
applyPreAttackAbAttrs(MoveTypeChangeAttr, pokemon, null, moveCopy);
}
if (pokemon.getTypes().some((t) => t !== moveCopy.type)) {
if (!simulated) {
this.moveType = moveType;
pokemon.summonData.types = [moveType];
this.moveType = moveCopy.type;
pokemon.summonData.types = [moveCopy.type];
pokemon.updateInfo();
}
@ -2086,7 +2090,7 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr {
if (target?.isActive(true)) {
if (!simulated) {
target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(),
Utils.toDmgValue(pokemon.getMaxHp() / this.healRatio), i18next.t("abilityTriggers:postSummonAllyHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(target), pokemonName: pokemon.name }), true, !this.showAnim));
Math.max(Math.floor(pokemon.getMaxHp() / this.healRatio), 1), i18next.t("abilityTriggers:postSummonAllyHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(target), pokemonName: pokemon.name }), true, !this.showAnim));
}
return true;
@ -2547,7 +2551,7 @@ export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr {
applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
if (!pokemon.isFullHp()) {
if (!simulated) {
const healAmount = Utils.toDmgValue(pokemon.getMaxHp() * 0.33);
const healAmount = Math.floor(pokemon.getMaxHp() * 0.33);
pokemon.heal(healAmount);
pokemon.updateInfo();
}
@ -2658,7 +2662,7 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
if (simulated) {
return defender.canAddTag(BattlerTagType.CONFUSED);
} else {
return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3, 2), move.id, defender.id);
return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3,2), move.id, defender.id);
}
}
return false;
@ -3125,53 +3129,6 @@ export class PostWeatherChangeAbAttr extends AbAttr {
}
}
/**
* Triggers weather-based form change when weather changes.
* Used by Forecast and Flower Gift.
* @extends PostWeatherChangeAbAttr
*/
export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr {
private ability: Abilities;
private formRevertingWeathers: WeatherType[];
constructor(ability: Abilities, formRevertingWeathers: WeatherType[]) {
super(false);
this.ability = ability;
this.formRevertingWeathers = formRevertingWeathers;
}
/**
* Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the
* weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges}
* @param {Pokemon} pokemon the Pokemon with this ability
* @param passive n/a
* @param weather n/a
* @param args n/a
* @returns whether the form change was triggered
*/
applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean {
const isCastformWithForecast = (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST);
const isCherrimWithFlowerGift = (pokemon.species.speciesId === Species.CHERRIM && this.ability === Abilities.FLOWER_GIFT);
if (isCastformWithForecast || isCherrimWithFlowerGift) {
if (simulated) {
return simulated;
}
const weatherType = pokemon.scene.arena.weather?.weatherType;
if (weatherType && this.formRevertingWeathers.includes(weatherType)) {
pokemon.scene.arena.triggerWeatherBasedFormChangesToNormal();
} else {
pokemon.scene.arena.triggerWeatherBasedFormChanges();
}
return true;
}
return false;
}
}
export class PostWeatherChangeAddBattlerTagAttr extends PostWeatherChangeAbAttr {
private tagType: BattlerTagType;
private turnCount: integer;
@ -3232,7 +3189,7 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr {
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
if (!simulated) {
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), i18next.t("abilityTriggers:postWeatherLapseHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true));
Math.max(Math.floor(pokemon.getMaxHp() / (16 / this.healFactor)), 1), i18next.t("abilityTriggers:postWeatherLapseHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true));
}
return true;
}
@ -3259,7 +3216,7 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr {
if (!simulated) {
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
scene.queueMessage(i18next.t("abilityTriggers:postWeatherLapseDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }));
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.damageFactor)), HitResult.OTHER);
pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / (16 / this.damageFactor)), HitResult.OTHER);
}
return true;
@ -3339,7 +3296,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr {
const scene = pokemon.scene;
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Utils.toDmgValue(pokemon.getMaxHp() / 8), i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), true));
Math.max(Math.floor(pokemon.getMaxHp() / 8), 1), i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), true));
}
return true;
}
@ -3464,18 +3421,6 @@ export class MoodyAbAttr extends PostTurnAbAttr {
constructor() {
super(true);
}
/**
* Randomly increases one BattleStat by 2 stages and decreases a different BattleStat by 1 stage
* @param {Pokemon} 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
*
* Any BattleStats at +6 or -6 are excluded from being increased or decreased, respectively
* If the pokemon already has all BattleStats raised to stage 6, it will only decrease one BattleStat by 1 stage
* If the pokemon already has all BattleStats lowered to stage -6, it will only increase one BattleStat by 2 stages
*/
applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
const selectableStats = [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD];
const increaseStatArray = selectableStats.filter(s => pokemon.summonData.battleStats[s] < 6);
@ -3487,7 +3432,7 @@ export class MoodyAbAttr extends PostTurnAbAttr {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [increaseStat], 2));
}
if (!simulated && decreaseStatArray.length > 0) {
const decreaseStat = decreaseStatArray[Utils.randInt(decreaseStatArray.length)];
const decreaseStat = selectableStats[Utils.randInt(selectableStats.length)];
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [decreaseStat], -1));
}
return true;
@ -3522,7 +3467,7 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr {
const scene = pokemon.scene;
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Utils.toDmgValue(pokemon.getMaxHp() / 16), i18next.t("abilityTriggers:postTurnHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true));
Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), i18next.t("abilityTriggers:postTurnHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true));
}
return true;
@ -3574,7 +3519,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr {
for (const opp of pokemon.getOpponents()) {
if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
if (!simulated) {
opp.damageAndUpdate(Utils.toDmgValue(opp.getMaxHp() / 8), HitResult.OTHER);
opp.damageAndUpdate(Math.floor(Math.max(1, opp.getMaxHp() / 8)), HitResult.OTHER);
pokemon.scene.queueMessage(i18next.t("abilityTriggers:badDreams", {pokemonName: getPokemonNameWithAffix(opp)}));
}
hadEffect = true;
@ -3776,7 +3721,7 @@ export class ReduceBurnDamageAbAttr extends AbAttr {
* @returns `true`
*/
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
(args[0] as Utils.NumberHolder).value = Utils.toDmgValue((args[0] as Utils.NumberHolder).value * this.multiplier);
(args[0] as Utils.NumberHolder).value = Math.max(Math.floor((args[0] as Utils.NumberHolder).value * this.multiplier), 1);
return true;
}
@ -3821,7 +3766,7 @@ export class HealFromBerryUseAbAttr extends AbAttr {
new PokemonHealPhase(
pokemon.scene,
pokemon.getBattlerIndex(),
Utils.toDmgValue(pokemon.getMaxHp() * this.healPercent),
Math.max(Math.floor(pokemon.getMaxHp() * this.healPercent), 1),
i18next.t("abilityTriggers:healFromBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }),
true
)
@ -4044,8 +3989,8 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr {
return false;
}
if (!simulated) {
attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio));
attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
attacker.turnData.damageTaken += Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio));
}
return true;
}
@ -4433,7 +4378,7 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
(args[0] as Utils.NumberHolder).value = this.multiplier;
pokemon.removeTag(this.tagType);
if (this.recoilDamageFunc) {
pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), HitResult.OTHER, false, false, true, true);
pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), HitResult.OTHER);
}
}
return true;
@ -4546,7 +4491,7 @@ async function applyAbAttrsInternal<TAttr extends AbAttr>(
applyFunc: AbAttrApplyFunc<TAttr>,
args: any[],
showAbilityInstant: boolean = false,
simulated: boolean = false,
quiet: boolean = false,
messages: string[] = [],
) {
for (const passive of [false, true]) {
@ -4572,20 +4517,23 @@ async function applyAbAttrsInternal<TAttr extends AbAttr>(
if (pokemon.summonData && !pokemon.summonData.abilitiesApplied.includes(ability.id)) {
pokemon.summonData.abilitiesApplied.push(ability.id);
}
if (pokemon.battleData && !simulated && !pokemon.battleData.abilitiesApplied.includes(ability.id)) {
if (pokemon.battleData && !quiet && !pokemon.battleData.abilitiesApplied.includes(ability.id)) {
pokemon.battleData.abilitiesApplied.push(ability.id);
}
if (attr.showAbility && !simulated) {
if (attr.showAbility && !quiet) {
if (showAbilityInstant) {
pokemon.scene.abilityBar.showAbility(pokemon, passive);
} else {
queueShowAbility(pokemon, passive);
}
}
const message = attr.getTriggerMessage(pokemon, ability.name, args);
if (message) {
if (!simulated) {
if (!quiet) {
const message = attr.getTriggerMessage(pokemon, ability.name, args);
if (message) {
pokemon.scene.queueMessage(message);
messages.push(message);
}
}
messages.push(message!);

View File

@ -70,7 +70,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4));
const hpHealed = new Utils.NumberHolder(Math.floor(pokemon.getMaxHp() / 4));
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true));

View File

@ -743,7 +743,7 @@ export default class Move implements Localizable {
const power = new Utils.NumberHolder(this.power);
const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1);
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, null, typeChangeMovePowerMultiplier);
applyPreAttackAbAttrs(MoveTypeChangeAttr, source, target, this, simulated, typeChangeMovePowerMultiplier);
const sourceTeraType = source.getTeraType();
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !source.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
@ -1955,7 +1955,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
}
if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
&& pokemon.checkIfCanSetStatus(this.effect, false, false, user) && pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
&& pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
return true;
}

View File

@ -1261,19 +1261,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* Currently only used by {@linkcode Pokemon.apply} to determine whether a "No effect" message should be shown.
* @returns The type damage multiplier, indicating the effectiveness of the move
*/
getMoveEffectiveness(source: Pokemon, move: Move, ignoreAbility: boolean = false, simulated: boolean = true, cancelled?: Utils.BooleanHolder): TypeDamageMultiplier {
if (move.hasAttr(TypelessAttr)) {
return 1;
}
const moveType = source.getMoveType(move);
const typeMultiplier = new Utils.NumberHolder((move.category !== MoveCategory.STATUS || move.hasAttr(RespectAttackTypeImmunityAttr))
? this.getAttackTypeEffectiveness(moveType, source, false, simulated)
: 1);
getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove, ignoreAbility: boolean = false): TypeDamageMultiplier {
const move = pokemonMove.getMove();
const typeless = move.hasAttr(TypelessAttr);
const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move, source));
const cancelled = new Utils.BooleanHolder(false);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
if (this.getTypes().find(t => move.isTypeImmune(source, this, t))) {
typeMultiplier.value = 0;
if (!typeless && !ignoreAbility) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, true, typeMultiplier);
}
if (!cancelled.value && !ignoreAbility) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, true, typeMultiplier);
}
const cancelledHolder = cancelled ?? new Utils.BooleanHolder(false);
@ -1334,7 +1332,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (source) {
const ignoreImmunity = new Utils.BooleanHolder(false);
if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) {
applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, simulated, moveType, defType);
applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, false, moveType, defType);
}
if (ignoreImmunity.value) {
return 1;
@ -2030,7 +2028,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const isPhysical = moveCategory === MoveCategory.PHYSICAL;
const sourceTeraType = source.getTeraType();
const power = move.calculateBattlePower(source, this);
if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, false, typeMultiplier);
applyMoveAttrs(NeutralDamageAgainstFlyingTypeMultiplierAttr, source, this, move, typeMultiplier);
}
if (!cancelled.value) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, false, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, false, typeMultiplier));
}
if (cancelled.value) {
// Cancelled moves fail silently
@ -2308,7 +2313,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
break;
case MoveCategory.STATUS:
if (!cancelled.value && typeMultiplier === 0) {
if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, false, typeMultiplier);
}
if (!cancelled.value) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, false, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, false, typeMultiplier));
}
if (!typeMultiplier.value) {
this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: getPokemonNameWithAffix(this) }));
}
result = (typeMultiplier === 0) ? HitResult.NO_EFFECT : HitResult.STATUS;

View File

@ -182,7 +182,10 @@ export class CommandPhase extends FieldPhase {
} else {
const batonPass = isSwitch && args[0] as boolean;
const trappedAbMessages: string[] = [];
if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) {
if (!batonPass) {
enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, trappedAbMessages, true));
}
if (batonPass || (!trapTag && !trapped.value)) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch
? { command: Command.POKEMON, cursor: cursor, args: args }
: { command: Command.RUN };

View File

@ -42,7 +42,10 @@ export class EnemyCommandPhase extends FieldPhase {
if (trainer && !enemyPokemon.getMoveQueue().length) {
const opponents = enemyPokemon.getOpponents();
if (!enemyPokemon.isTrapped()) {
const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag;
const trapped = new Utils.BooleanHolder(false);
opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, [], true));
if (!trapTag && !trapped.value) {
const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true);
if (partyMemberScores.length) {

View File

@ -107,8 +107,8 @@ export class TurnStartPhase extends FieldPhase {
applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority);
applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority);
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, false, aMove, aPriority);
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, false, bMove, bPriority);
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, false, aMove, aPriority); //TODO: is the bang correct here?
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, false, bMove, bPriority); //TODO: is the bang correct here?
// The game now checks for differences in priority levels.
// If the moves share the same original priority bracket, it can check for differences in battlerBypassSpeed and return the result.