Rewrite tags for contact protected and check moveFlags.doesFlagEffectApply

This commit is contained in:
Sirz Benjie 2025-04-08 11:59:47 -05:00
parent 0f0030d671
commit e108ffb62f
No known key found for this signature in database
GPG Key ID: 4A524B4D196C759E
3 changed files with 123 additions and 113 deletions

View File

@ -6735,7 +6735,7 @@ export function initAbilities() {
new Ability(Abilities.BAD_DREAMS, 4)
.attr(PostTurnHurtIfSleepingAbAttr),
new Ability(Abilities.PICKPOCKET, 5)
.attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT))
.attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}))
.condition(getSheerForceHitDisableAbCondition()),
new Ability(Abilities.SHEER_FORCE, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461 / 4096)
@ -7079,7 +7079,7 @@ export function initAbilities() {
new Ability(Abilities.BATTERY, 7)
.attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL ], 1.3),
new Ability(Abilities.FLUFFY, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 0.5)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}), 0.5)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.FIRE, 2)
.ignorable(),
new Ability(Abilities.DAZZLING, 7)
@ -7088,7 +7088,7 @@ export function initAbilities() {
new Ability(Abilities.SOUL_HEART, 7)
.attr(PostKnockOutStatStageChangeAbAttr, Stat.SPATK, 1),
new Ability(Abilities.TANGLING_HAIR, 7)
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), Stat.SPD, -1, false),
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}), Stat.SPD, -1, false),
new Ability(Abilities.RECEIVER, 7)
.attr(CopyFaintedAllyAbilityAbAttr)
.uncopiable(),

View File

@ -52,6 +52,7 @@ export enum BattlerTagLapseType {
MOVE_EFFECT,
TURN_END,
HIT,
/** Tag lapses AFTER_HIT, applying its effects even if the user faints */
AFTER_HIT,
CUSTOM,
}
@ -498,7 +499,13 @@ export class BeakBlastChargingTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.AFTER_HIT) {
const phaseData = getMoveEffectPhaseData(pokemon);
if (phaseData?.move.hasFlag(MoveFlags.MAKES_CONTACT)) {
if (
phaseData?.move.doesFlagEffectApply({
flag: MoveFlags.MAKES_CONTACT,
user: phaseData.attacker,
target: pokemon,
})
) {
phaseData.attacker.trySetStatus(StatusEffect.BURN, true, pokemon);
}
return true;
@ -1614,16 +1621,50 @@ export class ProtectedTag extends BattlerTag {
/** Base class for `BattlerTag`s that block damaging moves but not status moves */
export class DamageProtectedTag extends ProtectedTag {}
/** Class for `BattlerTag`s that apply some effect when hit by a contact move */
export class ContactProtectedTag extends ProtectedTag {
/**
* Function to call when a contact move hits the pokemon with this tag.
* @param _attacker - The pokemon using the contact move
* @param _user - The pokemon that is being attacked and has the tag
* @param _move - The move used by the attacker
*/
onContact(_attacker: Pokemon, _user: Pokemon) {}
/**
* Lapse the tag and apply `onContact` if the move makes contact and
* `lapseType` is custom, respecting the move's flags and the pokemon's
* abilities, and whether the lapseType is custom.
*
* @param pokemon - The pokemon with the tag
* @param lapseType - The type of lapse to apply. If this is not {@linkcode BattlerTagLapseType.CUSTOM CUSTOM}, no effect will be applied.
* @returns Whether the tag continues to exist after the lapse.
*/
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
const moveData = getMoveEffectPhaseData(pokemon);
if (
lapseType === BattlerTagLapseType.CUSTOM &&
moveData &&
moveData.move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: moveData.attacker, target: pokemon })
) {
this.onContact(moveData.attacker, pokemon);
}
return ret;
}
}
/**
* `BattlerTag` class for moves that block damaging moves damage the enemy if the enemy's move makes contact
* Used by {@linkcode Moves.SPIKY_SHIELD}
*/
export class ContactDamageProtectedTag extends ProtectedTag {
export class ContactDamageProtectedTag extends ContactProtectedTag {
private damageRatio: number;
constructor(sourceMove: Moves, damageRatio: number) {
super(sourceMove, BattlerTagType.SPIKY_SHIELD);
this.damageRatio = damageRatio;
}
@ -1636,22 +1677,43 @@ export class ContactDamageProtectedTag extends ProtectedTag {
this.damageRatio = source.damageRatio;
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = globalScene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
result: HitResult.INDIRECT,
});
}
}
/**
* Damage the attacker by `this.damageRatio` of the target's max HP
* @param attacker - The pokemon using the contact move
* @param user - The pokemon that is being attacked and has the tag
*/
override onContact(attacker: Pokemon, user: Pokemon): void {
const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled);
if (!cancelled.value) {
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
result: HitResult.INDIRECT,
});
}
}
}
return ret;
export class ContactSetStatusProtectedTag extends ContactProtectedTag {
/**
* @param sourceMove The move that caused the tag to be applied
* @param tagType The type of the tag
* @param statusEffect The status effect to apply to the attacker
*/
constructor(
sourceMove: Moves,
tagType: BattlerTagType,
private statusEffect: StatusEffect,
) {
super(sourceMove, tagType);
}
/**
* Set the status effect on the attacker
* @param attacker - The pokemon using the contact move
* @param user - The pokemon that is being attacked and has the tag
*/
override onContact(attacker: Pokemon, user: Pokemon): void {
attacker.trySetStatus(this.statusEffect, true, user);
}
}
@ -1659,7 +1721,7 @@ export class ContactDamageProtectedTag extends ProtectedTag {
* `BattlerTag` class for moves that block damaging moves and lower enemy stats if the enemy's move makes contact
* Used by {@linkcode Moves.KINGS_SHIELD}, {@linkcode Moves.OBSTRUCT}, {@linkcode Moves.SILK_TRAP}
*/
export class ContactStatStageChangeProtectedTag extends DamageProtectedTag {
export class ContactStatStageChangeProtectedTag extends ContactProtectedTag {
private stat: BattleStat;
private levels: number;
@ -1674,68 +1736,19 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag {
* When given a battler tag or json representing one, load the data for it.
* @param {BattlerTag | any} source A battler tag
*/
loadTag(source: BattlerTag | any): void {
override loadTag(source: BattlerTag | any): void {
super.loadTag(source);
this.stat = source.stat;
this.levels = source.levels;
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = globalScene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [this.stat], this.levels));
}
}
return ret;
}
}
export class ContactPoisonProtectedTag extends ProtectedTag {
constructor(sourceMove: Moves) {
super(sourceMove, BattlerTagType.BANEFUL_BUNKER);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = globalScene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
attacker.trySetStatus(StatusEffect.POISON, true, pokemon);
}
}
return ret;
}
}
/**
* `BattlerTag` class for moves that block damaging moves and burn the enemy if the enemy's move makes contact
* Used by {@linkcode Moves.BURNING_BULWARK}
*/
export class ContactBurnProtectedTag extends DamageProtectedTag {
constructor(sourceMove: Moves) {
super(sourceMove, BattlerTagType.BURNING_BULWARK);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = globalScene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
attacker.trySetStatus(StatusEffect.BURN, true);
}
}
return ret;
/**
* Initiate the stat stage change on the attacker
* @param attacker - The pokemon using the contact move
* @param user - The pokemon that is being attacked and has the tag
*/
override onContact(attacker: Pokemon, _user: Pokemon): void {
globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [this.stat], this.levels));
}
}
@ -3518,9 +3531,9 @@ export function getBattlerTag(
case BattlerTagType.SILK_TRAP:
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1);
case BattlerTagType.BANEFUL_BUNKER:
return new ContactPoisonProtectedTag(sourceMove);
return new ContactSetStatusProtectedTag(sourceMove, tagType, StatusEffect.POISON);
case BattlerTagType.BURNING_BULWARK:
return new ContactBurnProtectedTag(sourceMove);
return new ContactSetStatusProtectedTag(sourceMove, tagType, StatusEffect.BURN);
case BattlerTagType.ENDURING:
return new EnduringTag(tagType, BattlerTagLapseType.TURN_END, sourceMove);
case BattlerTagType.ENDURE_TOKEN:

View File

@ -297,16 +297,16 @@ export class MoveEffectPhase extends PokemonPhase {
);
}
/** Is the target protected by Protect, etc. or a relevant conditional protection effect? */
const isProtected =
![MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.move.getMove().moveTarget) &&
(bypassIgnoreProtect.value ||
!this.move.getMove().doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target })) &&
(hasConditionalProtectApplied.value ||
(!target.findTags(t => t instanceof DamageProtectedTag).length &&
target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))) ||
(this.move.getMove().category !== MoveCategory.STATUS &&
target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType))));
/** Is the target protected by Protect, etc. or a relevant conditional protection effect? */
const isProtected =
![MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.move.getMove().moveTarget) &&
(bypassIgnoreProtect.value ||
!this.move.getMove().doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target })) &&
(hasConditionalProtectApplied.value ||
(!target.findTags(t => t instanceof DamageProtectedTag).length &&
target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))) ||
(this.move.getMove().category !== MoveCategory.STATUS &&
target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType))));
/** Is the target hidden by the effects of its Commander ability? */
const isCommanding =
@ -316,11 +316,11 @@ export class MoveEffectPhase extends PokemonPhase {
/** Is the target reflecting status moves from the magic coat move? */
const isReflecting = !!target.getTag(BattlerTagType.MAGIC_COAT);
/** Is the target's magic bounce ability not ignored and able to reflect this move? */
const canMagicBounce =
!isReflecting &&
!move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) &&
target.hasAbilityWithAttr(ReflectStatusMoveAbAttr);
/** Is the target's magic bounce ability not ignored and able to reflect this move? */
const canMagicBounce =
!isReflecting &&
!move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) &&
target.hasAbilityWithAttr(ReflectStatusMoveAbAttr);
const semiInvulnerableTag = target.getTag(SemiInvulnerableTag);
@ -333,21 +333,19 @@ export class MoveEffectPhase extends PokemonPhase {
(isReflecting || canMagicBounce) &&
!semiInvulnerableTag;
// If the move will bounce, then queue the bounce and move on to the next target
if (!target.switchOutStatus && willBounce) {
const newTargets = move.isMultiTarget()
? getMoveTargets(target, move.id).targets
: [user.getBattlerIndex()];
if (!isReflecting) {
// TODO: Ability displays should be handled by the ability
queuedPhases.push(
new ShowAbilityPhase(
target.getBattlerIndex(),
target.getPassiveAbility().hasAttr(ReflectStatusMoveAbAttr),
),
);
queuedPhases.push(new HideAbilityPhase());
}
// If the move will bounce, then queue the bounce and move on to the next target
if (!target.switchOutStatus && willBounce) {
const newTargets = move.isMultiTarget() ? getMoveTargets(target, move.id).targets : [user.getBattlerIndex()];
if (!isReflecting) {
// TODO: Ability displays should be handled by the ability
queuedPhases.push(
new ShowAbilityPhase(
target.getBattlerIndex(),
target.getPassiveAbility().hasAttr(ReflectStatusMoveAbAttr),
),
);
queuedPhases.push(new HideAbilityPhase());
}
queuedPhases.push(new MovePhase(target, newTargets, new PokemonMove(move.id, 0, 0, true), true, true, true));
continue;
@ -629,7 +627,7 @@ export class MoveEffectPhase extends PokemonPhase {
* @param hitResult - The {@linkcode HitResult} of the attempted move
* @returns a `Promise` intended to be passed into a `then()` call.
*/
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult) {
if (!target.isFainted() || target.canApplyAbility()) {
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult);
@ -637,10 +635,9 @@ export class MoveEffectPhase extends PokemonPhase {
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target);
}
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
}
}
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
}
/**