diff --git a/src/data/ability.ts b/src/data/ability.ts index fff31ba7d..5a4677d6d 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -306,17 +306,24 @@ export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultip } } -export class PreDefendReceivedMoveNullifierAbAttr extends PreDefendAbAttr { - protected condition: PokemonDefendCondition; - - constructor(condition: PokemonDefendCondition) { - super(); - - this.condition = condition; - } - +/* +Attribute used for the Disguise ability +Before getting hit and the ability has not been triggered for the battle nullifies damage from moves. +*/ +export class PreDefendDisguiseNullifyDamageAbAttr extends PreDefendAbAttr { + /** + * Sets damage received to 1 and hit result as "Effective". Set ability as triggered. + * @param {Pokemon} pokemon Pokemon has the ability + * @param {Pokemon} passive N/A + * @param {Pokemon} attacker N/A. + * @param {PokemonMove} move N/A. + * @param {any[]} args 0: Damage dealt to user. 1: Hit result type. + * @returns {boolean} true if the function succeeds + */ applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.condition(pokemon, attacker, move.getMove()) && (args[1] as Utils.NumberHolder).value != HitResult.NO_EFFECT && (args[1] as Utils.NumberHolder).value != HitResult.FAIL) { + if (pokemon.battleData && !pokemon.battleData.abilitiesApplied.includes(Abilities.DISGUISE) && (args[1] as Utils.NumberHolder).value != HitResult.NO_EFFECT && (args[1] as Utils.NumberHolder).value != HitResult.FAIL) { + //Damage dealt is set to 1 as 0 can cause some issues. This 1 damage is subtracted later. + console.log(pokemon.battleData); (args[0] as Utils.NumberHolder).value = 1; (args[1] as Utils.NumberHolder).value = HitResult.EFFECTIVE; return true; @@ -330,6 +337,39 @@ export class PreDefendReceivedMoveNullifierAbAttr extends PreDefendAbAttr { } } +/* +Attribute used for the Disguise ability +When the user is hit by confusion, Disguise triggers. +*/ +export class DisguiseConfusionInteractionAbAttr extends AbAttr { + /** + * Cancels confusion damage and triggers disguise. + * @param {Pokemon} pokemon Pokemon has the ability + * @param {Pokemon} passive N/A + * @param {Utils.BooleanHolder} damageCancelled If true confusion damage is cancelled. + * @param {any[]} args N/A + * @returns {boolean} true if the function succeeds + */ + apply(pokemon: Pokemon, passive: boolean, damageCancelled: Utils.BooleanHolder, args: any[]): boolean { + //This checks if ability has been triggered during battle, and if the current form is correct. + if((pokemon.battleData && pokemon.battleData.abilitiesApplied.includes(Abilities.DISGUISE)) || pokemon.formIndex === 1) + return false; + + damageCancelled.value = true; + const hpLost = Math.round(pokemon.getMaxHp() / 8) + pokemon.damageAndUpdate(hpLost, HitResult.OTHER); + pokemon.turnData.damageTaken += hpLost; + pokemon.battleData.abilitiesApplied.push(Abilities.DISGUISE); + pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + pokemon.scene.queueMessage(`Its disguise served it as a decoy!`); + return true; + } + + getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + return `${pokemon.name}\'s disguise was busted!`; + } +} + export class TypeImmunityAbAttr extends PreDefendAbAttr { private immuneType: Type; private condition: AbAttrCondition; @@ -455,25 +495,32 @@ export class PostDefendAbAttr extends AbAttr { } } -export class PostDefendDisguiseAbAttr extends PostDefendAbAttr { - protected condition: PokemonDefendCondition; - - constructor(condition: PokemonDefendCondition) { - super(true); - this.condition = condition; - } - +/* +Attribute used for the Disguise ability +After getting hit and the ability has not been triggered for the battle inflicts recoil damage on the user. +*/ +export class PostDefendDisguiseRecoilAbAttr extends PostDefendAbAttr { + /** + * Inflicts 1/8 user's of max HP as recoil when triggered, and sets ability as triggered for the battle. + * @param {Pokemon} pokemon Pokemon has the ability + * @param {Pokemon} passive N/A + * @param {Pokemon} attacker Pokémon that targeted the ability's user. + * @param {HitResult} hitResult The result type of the hit, should be "EFFECTIVE". + * @param {any[]} args N/A + * @returns {boolean} true if the function succeeds + */ applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - - if (this.condition(pokemon, attacker, move.getMove()) && (hitResult == HitResult.EFFECTIVE)) { + //If ability has not been triggered during the battle and the hit result is effective... + if (pokemon.battleData && !pokemon.battleData.abilitiesApplied.includes(Abilities.DISGUISE) && (hitResult == HitResult.EFFECTIVE)) { + //damageDealt is taken into account as it is and not just 1 to prevent issues. const damageDealt = attacker.turnData.damageDealt; - let recoilDamage = Math.round(pokemon.getMaxHp() / 8 - damageDealt); - if (!recoilDamage) + let hpLost = Math.round(pokemon.getMaxHp() / 8 - damageDealt); + if (!hpLost ) return false; - - pokemon.damageAndUpdate(recoilDamage, HitResult.OTHER); - pokemon.battleData.abilityTriggered = true; - pokemon.turnData.damageTaken += recoilDamage; + + pokemon.damageAndUpdate(hpLost , HitResult.OTHER); + pokemon.turnData.damageTaken += hpLost; + pokemon.battleData.abilitiesApplied.push(Abilities.DISGUISE); pokemon.scene.queueMessage(getPokemonMessage(pokemon, '\'s disguise was busted!')); return true; } @@ -1495,7 +1542,7 @@ export class PostSummonFormChangeAbAttr extends PostSummonAbAttr { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) return pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); - + return false; } } @@ -1724,25 +1771,6 @@ export class BlockNonDirectDamageAbAttr extends AbAttr { } } -export class DisguiseConfusionDamageInteractionAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - - if(pokemon.battleData.abilityTriggered == true) - return false; - - cancelled.value = true; - pokemon.damageAndUpdate(Math.round(pokemon.getMaxHp() / 8), HitResult.OTHER); - pokemon.battleData.abilityTriggered = true; - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); - pokemon.scene.queueMessage(`Its disguise served it as a decoy!`); - return true; - } - - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { - return `${pokemon.name}\'s disguise was busted!`; - } -} - export class BlockOneHitKOAbAttr extends AbAttr { apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { cancelled.value = true; @@ -2521,6 +2549,16 @@ export class NoFusionAbilityAbAttr extends AbAttr { } } +/** + * Attribute for to not be pushed to the Pokémon battleData.abilityApplied + * This is for abilities that have secondary attributes (like Form Change) that could malfunction. + * The ability has to be pushed to battleData.abilityApplied in another attribute. + */ +export class SpecialOncePerBattleAbilityAbAttr extends AbAttr { + constructor() { + super(false); + } +} export class IgnoreTypeImmunityAbAttr extends AbAttr { defenderType: Type; allowedMoveTypes: Type[]; @@ -2571,7 +2609,7 @@ function applyAbAttrsInternal(attrType: { new(...args: any return applyNextAbAttr(); pokemon.scene.setPhaseQueueSplice(); const onApplySuccess = () => { - if (pokemon.battleData && !pokemon.battleData.abilitiesApplied.includes(ability.id)) { + if (pokemon.battleData && !pokemon.battleData.abilitiesApplied.includes(ability.id) && !ability.hasAttr(SpecialOncePerBattleAbilityAbAttr)) { pokemon.battleData.abilitiesApplied.push(ability.id); } if (attr.showAbility && !quiet) { @@ -3329,18 +3367,19 @@ export function initAbilities() { .attr(UnsuppressableAbilityAbAttr) .attr(NoFusionAbilityAbAttr), new Ability(Abilities.DISGUISE, 7) - .attr(DisguiseConfusionDamageInteractionAbAttr) - .attr(PreDefendReceivedMoveNullifierAbAttr, (target, user, move) => target.battleData.abilityTriggered == false) - .attr(PostDefendDisguiseAbAttr, (target, user, move) => target.battleData.abilityTriggered == false) - .attr(PreDefendFormChangeAbAttr, p => p.battleData.abilityTriggered == false ? 0 : 1) - .attr(PostSummonFormChangeAbAttr, p => p.battleData.abilityTriggered == false ? 0 : 1) - .attr(PostBattleInitFormChangeAbAttr, p => p.battleData.abilityTriggered == false ? 0 : 1) - .attr(PostDefendFormChangeAbAttr, p => p.battleData.abilityTriggered == false ? 0 : 1) + .attr(DisguiseConfusionInteractionAbAttr) + .attr(PreDefendDisguiseNullifyDamageAbAttr) + .attr(PostDefendDisguiseRecoilAbAttr) + .attr(PreDefendFormChangeAbAttr, p => p.battleData && !p.battleData.abilitiesApplied.includes(Abilities.DISGUISE) ? 0 : 1) + .attr(PostSummonFormChangeAbAttr, p => p.battleData && !p.battleData.abilitiesApplied.includes(Abilities.DISGUISE) ? 0 : 1) + .attr(PostBattleInitFormChangeAbAttr, p => p.battleData && !p.battleData.abilitiesApplied.includes(Abilities.DISGUISE) ? 0 : 1) + .attr(PostDefendFormChangeAbAttr, p => p.battleData && !p.battleData.abilitiesApplied.includes(Abilities.DISGUISE) ? 0 : 1) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) .attr(NoFusionAbilityAbAttr) + .attr(SpecialOncePerBattleAbilityAbAttr) .ignorable() .partial(), new Ability(Abilities.BATTLE_BOND, 7) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 3c1b2c936..be48051c9 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -8,7 +8,7 @@ import * as Utils from "../utils"; import { Moves } from "./enums/moves"; import { ChargeAttr, MoveFlags, allMoves } from "./move"; import { Type } from "./type"; -import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, DisguiseConfusionDamageInteractionAbAttr } from "./ability"; +import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, DisguiseConfusionInteractionAbAttr } from "./ability"; import { Abilities } from "./enums/abilities"; import { BattlerTagType } from "./enums/battler-tag-type"; import { TerrainType } from "./terrain"; @@ -227,10 +227,10 @@ export class ConfusedTag extends BattlerTag { const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100)); pokemon.scene.queueMessage('It hurt itself in its\nconfusion!'); - const cancelled = new Utils.BooleanHolder(false); - applyAbAttrs(DisguiseConfusionDamageInteractionAbAttr, pokemon, cancelled); - - if (!cancelled.value) { + const damageCancelled = new Utils.BooleanHolder(false); + applyAbAttrs(DisguiseConfusionInteractionAbAttr, pokemon, damageCancelled); + //Confusion damage will not be dealt if Pokémon has Disguise and has not triggered. + if (!damageCancelled.value) { pokemon.damageAndUpdate(damage); pokemon.battleData.hitCount++; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 2dc0f638d..83c7ca8af 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -27,7 +27,7 @@ import { TempBattleStat } from '../data/temp-battle-stat'; import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from '../data/arena-tag'; import { ArenaTagType } from "../data/enums/arena-tag-type"; import { Biome } from "../data/enums/biome"; -import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, PreDefendReceivedMoveNullifierAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr } from '../data/ability'; +import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, PreDefendDisguiseNullifyDamageAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr } from '../data/ability'; import { Abilities } from "#app/data/enums/abilities"; import PokemonData from '../system/pokemon-data'; import Battle, { BattlerIndex } from '../battle'; @@ -1444,8 +1444,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { damage.value = 0; } + //All incoming damage from moves will be set to 1 if the target is a Disguise user. let preResult = new Utils.NumberHolder(result); - applyPreDefendAbAttrs(PreDefendReceivedMoveNullifierAbAttr, this, source, battlerMove, cancelled, damage , preResult); + applyPreDefendAbAttrs(PreDefendDisguiseNullifyDamageAbAttr, this, source, battlerMove, cancelled, damage , preResult); result = (preResult as Utils.NumberHolder).value; console.log('damage', damage.value, move.name, power.value, sourceAtk, targetDef); diff --git a/src/phases.ts b/src/phases.ts index 59022f5fe..51416fb3f 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1616,7 +1616,7 @@ export class TurnInitPhase extends FieldPhase { this.scene.currentBattle.addParticipant(pokemon as PlayerPokemon); pokemon.resetTurnData(); - + applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); this.scene.pushPhase(pokemon.isPlayer() ? new CommandPhase(this.scene, i) : new EnemyCommandPhase(this.scene, i - BattlerIndex.ENEMY)); } });