From 1ae5847e4914fcb85bd164c2f2edba827c556482 Mon Sep 17 00:00:00 2001 From: NxKarim <43686802+NxKarim@users.noreply.github.com> Date: Sun, 19 May 2024 08:06:58 -0600 Subject: [PATCH] Implements Merciless (#440) * Implemented Merciless Attribute CritIfTargetIsPoisonedAbAttr Fix BlockCritAbAttr * Changed CritIfTargetPoisonedAbAttr to ConditionalCritAbAttr Now any condition related to user, target or the move used can be applied to guarantee a critical hit. --- src/data/ability.ts | 32 +++++++++++++++++++++++++++++++- src/field/pokemon.ts | 15 ++++++++------- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index a257525efd0..519e2411612 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1822,6 +1822,36 @@ export class MultCritAbAttr extends AbAttr { } } +/** + * Guarantees a critical hit according to the given condition, except if target prevents critical hits. ie. Merciless + * @extends AbAttr + * @see {@linkcode apply} + */ +export class ConditionalCritAbAttr extends AbAttr { + private condition: PokemonAttackCondition; + + constructor(condition: PokemonAttackCondition, checkUser?: Boolean) { + super(); + + this.condition = condition; + } + + /** + * @param pokemon {@linkcode Pokemon} user. + * @param args [0] {@linkcode Utils.BooleanHolder} If true critical hit is guaranteed. + * [1] {@linkcode Pokemon} Target. + * [2] {@linkcode Move} used by ability user. + */ + apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + const target = (args[1] as Pokemon); + const move = (args[2] as Move); + if(!this.condition(pokemon,target,move)) + return false; + + (args[0] as Utils.BooleanHolder).value = true; + return true; + } +} export class BlockNonDirectDamageAbAttr extends AbAttr { apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { @@ -3442,7 +3472,7 @@ export function initAbilities() { new Ability(Abilities.WATER_COMPACTION, 7) .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, BattleStat.DEF, 2), new Ability(Abilities.MERCILESS, 7) - .unimplemented(), + .attr(ConditionalCritAbAttr, (user, target, move) => target.status?.effect === StatusEffect.TOXIC || target.status?.effect === StatusEffect.POISON), new Ability(Abilities.SHIELDS_DOWN, 7) .attr(PostBattleInitFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0)) .attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0)) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e25fbe5fa71..a1347bcd256 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, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr } from '../data/ability'; +import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr } from '../data/ability'; import { Abilities } from "#app/data/enums/abilities"; import PokemonData from '../system/pokemon-data'; import Battle, { BattlerIndex } from '../battle'; @@ -1523,6 +1523,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const critOnly = new Utils.BooleanHolder(false); const critAlways = source.getTag(BattlerTagType.ALWAYS_CRIT); applyMoveAttrs(CritOnlyAttr, source, this, move, critOnly); + applyAbAttrs(ConditionalCritAbAttr, source, null, critOnly, this, move); if (critOnly.value || critAlways) isCritical = true; else { @@ -1538,12 +1539,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { critLevel.value += 2; const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))]; isCritical = !source.getTag(BattlerTagType.NO_CRIT) && (critChance === 1 || !this.scene.randBattleSeedInt(critChance)); - if (isCritical) { - const blockCrit = new Utils.BooleanHolder(false); - applyAbAttrs(BlockCritAbAttr, this, null, blockCrit); - if (blockCrit.value) - isCritical = false; - } + } + if (isCritical) { + const blockCrit = new Utils.BooleanHolder(false); + applyAbAttrs(BlockCritAbAttr, this, null, blockCrit); + if (blockCrit.value) + isCritical = false; } const sourceAtk = new Utils.IntegerHolder(source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this, null, isCritical)); const targetDef = new Utils.IntegerHolder(this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, isCritical));