From 670ea01cb4441b236b5edb4d10df3c2e22a561b5 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Tue, 11 Jun 2024 06:37:10 -0700 Subject: [PATCH] [Ability] Implement Treasures of Ruin Abilities (#1637) * Implement Treasures of Ruin abilities * Reformat documentation --------- Co-authored-by: Temps Ray --- src/data/ability.ts | 83 +++++++++++++++++++++++++++++++++++++++----- src/field/pokemon.ts | 32 ++++++++++++++++- 2 files changed, 105 insertions(+), 10 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index ae607d253c1..d17bbc519da 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -12,7 +12,7 @@ import { Gender } from "./gender"; import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, StatusMoveTypeImmunityAttr, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr } from "./move"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagType } from "./enums/arena-tag-type"; -import { Stat } from "./pokemon-stat"; +import { Stat, getStatName } from "./pokemon-stat"; import { BerryModifier, PokemonHeldItemModifier } from "../modifier/modifier"; import { Moves } from "./enums/moves"; import { TerrainType } from "./terrain"; @@ -1032,6 +1032,52 @@ export class FieldPreventExplosiveMovesAbAttr extends AbAttr { } } +/** + * Multiplies a BattleStat if the checked Pokemon lacks this ability. + * If this ability cannot stack, a BooleanHolder can be used to prevent this from stacking. + * @see {@link applyFieldBattleStatMultiplierAbAttrs} + * @see {@link applyFieldBattleStat} + * @see {@link Utils.BooleanHolder} + */ +export class FieldMultiplyBattleStatAbAttr extends AbAttr { + private stat: Stat; + private multiplier: number; + private canStack: boolean; + + constructor(stat: Stat, multiplier: number, canStack: boolean = false) { + super(false); + + this.stat = stat; + this.multiplier = multiplier; + this.canStack = canStack; + } + + /** + * applyFieldBattleStat: Tries to multiply a Pokemon's BattleStat + * @param pokemon {@linkcode Pokemon} the Pokemon using this ability + * @param passive {@linkcode boolean} unused + * @param stat {@linkcode Stat} the type of the checked stat + * @param statValue {@linkcode Utils.NumberHolder} the value of the checked stat + * @param checkedPokemon {@linkcode Pokemon} the Pokemon this ability is targeting + * @param hasApplied {@linkcode Utils.BooleanHolder} whether or not another multiplier has been applied to this stat + * @param args {any[]} unused + * @returns true if this changed the checked stat, false otherwise. + */ + applyFieldBattleStat(pokemon: Pokemon, passive: boolean, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, args: any[]): boolean { + if (!this.canStack && hasApplied.value) { + return false; + } + + if (this.stat === stat && checkedPokemon.getAbilityAttrs(FieldMultiplyBattleStatAbAttr).every(attr => (attr as FieldMultiplyBattleStatAbAttr).stat !== stat)) { + statValue.value *= this.multiplier; + hasApplied.value = true; + return true; + } + return false; + } + +} + export class MoveTypeChangeAttr extends PreAttackAbAttr { private newType: Type; private powerMultiplier: number; @@ -3573,6 +3619,21 @@ export function applyBattleStatMultiplierAbAttrs(attrType: { new(...args: any[]) return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyBattleStat(pokemon, passive, battleStat, statValue, args), args); } +/** + * Applies a field Battle Stat multiplier attribute + * @param attrType {@linkcode FieldMultiplyBattleStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being + * @param pokemon {@linkcode Pokemon} the Pokemon applying this ability + * @param stat {@linkcode Stat} the type of the checked stat + * @param statValue {@linkcode Utils.NumberHolder} the value of the checked stat + * @param checkedPokemon {@linkcode Pokemon} the Pokemon with the checked stat + * @param hasApplied {@linkcode Utils.BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat + * @param args unused + */ +export function applyFieldBattleStatMultiplierAbAttrs(attrType: { new(...args: any[]): FieldMultiplyBattleStatAbAttr }, + pokemon: Pokemon, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyFieldBattleStat(pokemon, passive, stat, statValue, checkedPokemon, hasApplied, args), args); +} + export function applyPreAttackAbAttrs(attrType: { new(...args: any[]): PreAttackAbAttr }, pokemon: Pokemon, defender: Pokemon, move: Move, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args); @@ -4596,17 +4657,21 @@ export function initAbilities() { .ignorable() .partial(), new Ability(Abilities.VESSEL_OF_RUIN, 9) - .ignorable() - .unimplemented(), + .attr(FieldMultiplyBattleStatAbAttr, Stat.SPATK, 0.75) + .attr(PostSummonMessageAbAttr, (user) => getPokemonMessage(user, `'s Vessel of Ruin lowered the ${getStatName(Stat.SPATK)}\nof all surrounding Pokémon!`)) + .ignorable(), new Ability(Abilities.SWORD_OF_RUIN, 9) - .ignorable() - .unimplemented(), + .attr(FieldMultiplyBattleStatAbAttr, Stat.DEF, 0.75) + .attr(PostSummonMessageAbAttr, (user) => getPokemonMessage(user, `'s Sword of Ruin lowered the ${getStatName(Stat.DEF)}\nof all surrounding Pokémon!`)) + .ignorable(), new Ability(Abilities.TABLETS_OF_RUIN, 9) - .ignorable() - .unimplemented(), + .attr(FieldMultiplyBattleStatAbAttr, Stat.ATK, 0.75) + .attr(PostSummonMessageAbAttr, (user) => getPokemonMessage(user, `'s Tablets of Ruin lowered the ${getStatName(Stat.ATK)}\nof all surrounding Pokémon!`)) + .ignorable(), new Ability(Abilities.BEADS_OF_RUIN, 9) - .ignorable() - .unimplemented(), + .attr(FieldMultiplyBattleStatAbAttr, Stat.SPDEF, 0.75) + .attr(PostSummonMessageAbAttr, (user) => getPokemonMessage(user, `'s Beads of Ruin lowered the ${getStatName(Stat.SPDEF)}\nof all surrounding Pokémon!`)) + .ignorable(), new Ability(Abilities.ORICHALCUM_PULSE, 9) .attr(PostSummonWeatherChangeAbAttr, WeatherType.SUNNY) .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SUNNY) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8a4a0ab837a..7bf3c8ca52d 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, MoveTypeChangeAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr } from "../data/ability"; +import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr } from "../data/ability"; import { Abilities } from "#app/data/enums/abilities"; import PokemonData from "../system/pokemon-data"; import { BattlerIndex } from "../battle"; @@ -656,6 +656,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), battleStat as integer as TempBattleStat, statLevel); } const statValue = new Utils.NumberHolder(this.getStat(stat)); + const fieldApplied = new Utils.BooleanHolder(false); + for (const pokemon of this.scene.getField(true)) { + applyFieldBattleStatMultiplierAbAttrs(FieldMultiplyBattleStatAbAttr, pokemon, stat, statValue, this, fieldApplied); + if (fieldApplied.value) { + break; + } + } applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, battleStat, statValue); let ret = statValue.value * (Math.max(2, 2 + statLevel.value) / Math.max(2, 2 - statLevel.value)); switch (stat) { @@ -951,6 +958,29 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return allAbilities[starterPassiveAbilities[starterSpeciesId]]; } + /** + * Gets a list of all instances of a given ability attribute among abilities this pokemon has. + * Accounts for all the various effects which can affect whether an ability will be present or + * in effect, and both passive and non-passive. + * @param attrType {@linkcode AbAttr} The ability attribute to check for. + * @param canApply {@linkcode Boolean} If false, it doesn't check whether the ability is currently active + * @param ignoreOverride {@linkcode Boolean} If true, it ignores ability changing effects + * @returns {AbAttr[]} A list of all the ability attributes on this ability. + */ + getAbilityAttrs(attrType: { new(...args: any[]): AbAttr }, canApply: boolean = true, ignoreOverride?: boolean): AbAttr[] { + const abilityAttrs: AbAttr[] = []; + + if (!canApply || this.canApplyAbility()) { + abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs(attrType)); + } + + if (!canApply || this.canApplyAbility(true)) { + abilityAttrs.push(...this.getPassiveAbility().getAttrs(attrType)); + } + + return abilityAttrs; + } + /** * Checks if a pokemon has a passive either from: * - bought with starter candy