From 5cbc3ae24958522a0fb1f12b194e13f4f3f59b45 Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Tue, 16 Apr 2024 13:54:19 -0400 Subject: [PATCH] Implemented explosives, fixed immunity abilities --- src/data/ability.ts | 60 +++++++++++++++++++++++++++++++++++++++++--- src/data/move.ts | 48 +++++++++++++++++++++++++++++++---- src/field/pokemon.ts | 3 ++- 3 files changed, 101 insertions(+), 10 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 768dcd04b61..9150b264daa 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -9,7 +9,7 @@ import { BattlerTag } from "./battler-tags"; import { BattlerTagType } from "./enums/battler-tag-type"; import { StatusEffect, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect"; import { Gender } from "./gender"; -import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, RecoilAttr, StatusMoveTypeImmunityAttr, allMoves } from "./move"; +import Move, { AttackMove, Explosive, MoveCategory, MoveFlags, MoveTarget, RecoilAttr, StatusMoveTypeImmunityAttr, allMoves } from "./move"; import { ArenaTagType } from "./enums/arena-tag-type"; import { Stat } from "./pokemon-stat"; import { PokemonHeldItemModifier } from "../modifier/modifier"; @@ -400,6 +400,23 @@ export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { } } +export class ExplosiveMoveImmunityAbAttr extends PreDefendAbAttr { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { + + if(move.getMove().getAttrs(Explosive).length) { + cancelled.value = true; + return true; + } + + return false; + } + + apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + cancelled.value = true; + return true; + } +} + export class PostStatChangeAbAttr extends AbAttr { applyPostStatChange(pokemon: Pokemon, statsChanged: BattleStat[], levelChanged: integer, selfTarget: boolean, args: any[]): boolean | Promise { return false; @@ -818,6 +835,31 @@ export class BattleStatMultiplierAbAttr extends AbAttr { } } +export class ContinuousResetStatusAbAttr extends AbAttr { + private immuneEffects: StatusEffect[]; + private curedStatus: StatusEffect; + + constructor(...immuneEffects: StatusEffect[]) { + super(); + + this.immuneEffects = immuneEffects; + } + + apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + if (this.immuneEffects.includes(pokemon.status?.effect)) { + this.curedStatus=pokemon.status.effect; + pokemon.resetStatus(); + return true; + } + return false; + } + + getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + return getPokemonMessage(pokemon, getStatusEffectHealText(this.curedStatus)); + } + +} + export class PostAttackAbAttr extends AbAttr { applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean | Promise { return false; @@ -2163,10 +2205,12 @@ export function initAbilities() { .attr(PreDefendFullHpEndureAbAttr) .attr(BlockOneHitKOAbAttr) .ignorable(), - new Ability(Abilities.DAMP, "Damp (N)", "Prevents the use of explosive moves, such as Self-Destruct, by dampening its surroundings.", 3) + new Ability(Abilities.DAMP, "Damp", "Prevents the use of explosive moves, such as Self-Destruct, by dampening its surroundings.", 3) + .attr(ExplosiveMoveImmunityAbAttr) .ignorable(), new Ability(Abilities.LIMBER, "Limber", "Its limber body protects the Pokémon from paralysis.", 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.PARALYSIS) + .conditionalAttr(pokemon => pokemon.status?.effect==StatusEffect.PARALYSIS, ContinuousResetStatusAbAttr, StatusEffect.PARALYSIS) .ignorable(), new Ability(Abilities.SAND_VEIL, "Sand Veil", "Boosts the Pokémon's evasiveness in a sandstorm.", 3) .attr(BattleStatMultiplierAbAttr, BattleStat.EVA, 1.2) @@ -2191,11 +2235,13 @@ export function initAbilities() { new Ability(Abilities.INSOMNIA, "Insomnia", "The Pokémon is suffering from insomnia and cannot fall asleep.", 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) + .conditionalAttr(pokemon => pokemon.status?.effect==StatusEffect.SLEEP, ContinuousResetStatusAbAttr, StatusEffect.SLEEP) .ignorable(), new Ability(Abilities.COLOR_CHANGE, "Color Change", "The Pokémon's type becomes the type of the move used on it.", 3) .attr(PostDefendTypeChangeAbAttr), new Ability(Abilities.IMMUNITY, "Immunity", "The immune system of the Pokémon prevents it from getting poisoned.", 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) + .conditionalAttr(pokemon => pokemon.status?.effect==StatusEffect.POISON||pokemon.status?.effect==StatusEffect.TOXIC, ContinuousResetStatusAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .ignorable(), new Ability(Abilities.FLASH_FIRE, "Flash Fire", "Powers up the Pokémon's Fire-type moves if it's hit by one.", 3) .attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1, (pokemon: Pokemon) => !pokemon.status || pokemon.status.effect !== StatusEffect.FREEZE) @@ -2258,9 +2304,11 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.MAGMA_ARMOR, "Magma Armor", "The Pokémon is covered with hot magma, which prevents the Pokémon from becoming frozen.", 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE) + .conditionalAttr(pokemon => pokemon.status?.effect==StatusEffect.FREEZE, ContinuousResetStatusAbAttr, StatusEffect.FREEZE) .ignorable(), new Ability(Abilities.WATER_VEIL, "Water Veil", "The Pokémon is covered with a water veil, which prevents the Pokémon from getting a burn.", 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) + .conditionalAttr(pokemon => pokemon.status?.effect==StatusEffect.BURN, ContinuousResetStatusAbAttr, StatusEffect.BURN) .ignorable(), new Ability(Abilities.MAGNET_PULL, "Magnet Pull (N)", "Prevents Steel-type Pokémon from escaping using its magnetic force.", 3) /*.attr(ArenaTrapAbAttr) @@ -2335,6 +2383,7 @@ export function initAbilities() { new Ability(Abilities.VITAL_SPIRIT, "Vital Spirit", "The Pokémon is full of vitality, and that prevents it from falling asleep.", 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) + .conditionalAttr(pokemon => pokemon.status?.effect==StatusEffect.SLEEP, ContinuousResetStatusAbAttr, StatusEffect.SLEEP) .ignorable(), new Ability(Abilities.WHITE_SMOKE, "White Smoke", "The Pokémon is protected by its white smoke, which prevents other Pokémon from lowering its stats.", 3) .attr(ProtectStatAbAttr) @@ -2396,7 +2445,7 @@ export function initAbilities() { .attr(BattleStatMultiplierAbAttr, BattleStat.SPATK, 1.5) .condition(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)), new Ability(Abilities.QUICK_FEET, "Quick Feet", "Boosts the Speed stat if the Pokémon has a status condition.", 4) - .conditionalAttr(pokemon => pokemon.status.effect === StatusEffect.PARALYSIS, BattleStatMultiplierAbAttr, BattleStat.SPD, 2) + .conditionalAttr(pokemon => pokemon.status?.effect === StatusEffect.PARALYSIS, BattleStatMultiplierAbAttr, BattleStat.SPD, 2) .conditionalAttr(pokemon => !!pokemon.status, BattleStatMultiplierAbAttr, BattleStat.SPD, 1.5), new Ability(Abilities.NORMALIZE, "Normalize", "All the Pokémon's moves become Normal type. The power of those moves is boosted a little.", 4) .attr(MoveTypeChangeAttr, Type.NORMAL, 1.2, (user, target, move) => move.id !== Moves.HIDDEN_POWER && move.id !== Moves.WEATHER_BALL && @@ -2642,6 +2691,7 @@ export function initAbilities() { .attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(MoveTypePowerBoostAbAttr, Type.WATER, 1) .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) + .conditionalAttr(pokemon => pokemon.status?.effect==StatusEffect.BURN, ContinuousResetStatusAbAttr, StatusEffect.BURN) .ignorable(), new Ability(Abilities.STEELWORKER, "Steelworker", "Powers up Steel-type moves.", 7) .attr(MoveTypePowerBoostAbAttr, Type.STEEL), @@ -2792,8 +2842,9 @@ export function initAbilities() { .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr), - new Ability(Abilities.PASTEL_VEIL, "Pastel Veil", "Protects the Pokémon and its ally Pokémon from being poisoned.", 8) + new Ability(Abilities.PASTEL_VEIL, "Pastel Veil (P)", "Protects the Pokémon and its ally Pokémon from being poisoned.", 8) .attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) + .conditionalAttr(pokemon => pokemon.status?.effect==StatusEffect.POISON||pokemon.status?.effect==StatusEffect.TOXIC, ContinuousResetStatusAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .ignorable(), new Ability(Abilities.HUNGER_SWITCH, "Hunger Switch", "The Pokémon changes its form, alternating between its Full Belly Mode and Hangry Mode after the end of each turn.", 8) .attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 0 : 1) @@ -2832,6 +2883,7 @@ export function initAbilities() { new Ability(Abilities.THERMAL_EXCHANGE, "Thermal Exchange", "Boosts the Attack stat when the Pokémon is hit by a Fire-type move. The Pokémon also cannot be burned.", 9) .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.FIRE, BattleStat.ATK, 1) .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) + .conditionalAttr(pokemon => pokemon.status?.effect==StatusEffect.BURN, ContinuousResetStatusAbAttr, StatusEffect.BURN) .ignorable(), new Ability(Abilities.ANGER_SHELL, "Anger Shell (N)", "When an attack causes its HP to drop to half or less, the Pokémon gets angry. This lowers its Defense and Sp. Def stats but boosts its Attack, Sp. Atk, and Speed stats.", 9), new Ability(Abilities.PURIFYING_SALT, "Purifying Salt", "The Pokémon's pure salt protects it from status conditions and halves the damage taken from Ghost-type moves.", 9) diff --git a/src/data/move.ts b/src/data/move.ts index 73a5ca42302..3cb370d0002 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -12,7 +12,7 @@ import * as Utils from "../utils"; import { WeatherType } from "./weather"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagType } from "./enums/arena-tag-type"; -import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, NoTransformAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr } from "./ability"; +import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, NoTransformAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, ExplosiveMoveImmunityAbAttr } from "./ability"; import { Abilities } from "./enums/abilities"; import { allAbilities } from './ability'; import { PokemonHeldItemModifier } from "../modifier/modifier"; @@ -688,6 +688,27 @@ export class SacrificialAttr extends MoveEffectAttr { } } +export class HalfHpAttackAttr extends MoveEffectAttr { + constructor() { + super(true, MoveEffectTrigger.PRE_APPLY); + } + + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (!super.apply(user, target, move, args)) + return false; + + user.damageAndUpdate(Math.ceil(user.hp/2), HitResult.OTHER, false, true, true); + + return true; + } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + if (user.isBoss()) + return -20; + return Math.ceil(((1 - user.getHpRatio()) * 10 - 10) * (target.getAttackTypeEffectiveness(move.type) - 0.5)); + } +} + export enum MultiHitType { _2, _2_TO_5, @@ -1144,6 +1165,16 @@ export class ClearTerrainAttr extends MoveEffectAttr { } } +export class Explosive extends MoveAttr { + getCondition(): MoveConditionFunc { + return (user, target, move) => { + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(ExplosiveMoveImmunityAbAttr, target, cancelled); + return cancelled.value; + } + } +} + export class OneHitKOAttr extends MoveAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (target.isBossImmune()) @@ -3658,6 +3689,7 @@ export function initMoves() { .attr(CopyMoveAttr) .ignoresVirtual(), new AttackMove(Moves.SELF_DESTRUCT, "Self-Destruct", Type.NORMAL, MoveCategory.PHYSICAL, 200, 100, 5, "The user attacks everything around it by causing an explosion. The user faints upon using this move.", -1, 0, 1) + .attr(Explosive) .attr(SacrificialAttr) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), @@ -3748,6 +3780,7 @@ export function initMoves() { new AttackMove(Moves.CRABHAMMER, "Crabhammer", Type.WATER, MoveCategory.PHYSICAL, 100, 90, 10, "The target is hammered with a large pincer. Critical hits land more easily.", -1, 0, 1) .attr(HighCritAttr), new AttackMove(Moves.EXPLOSION, "Explosion", Type.NORMAL, MoveCategory.PHYSICAL, 250, 100, 5, "The user attacks everything around it by causing a tremendous explosion. The user faints upon using this move.", -1, 0, 1) + .attr(Explosive) .attr(SacrificialAttr) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), @@ -4958,6 +4991,7 @@ export function initMoves() { new SelfStatusMove(Moves.BANEFUL_BUNKER, "Baneful Bunker", Type.POISON, -1, 10, "In addition to protecting the user from attacks, this move also poisons any attacker that makes direct contact.", -1, 4, 7) .attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER), new AttackMove(Moves.SPIRIT_SHACKLE, "Spirit Shackle (P)", Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, "The user attacks while simultaneously stitching the target's shadow to the ground to prevent the target from escaping.", -1, 0, 7) + .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1) .makesContact(false), new AttackMove(Moves.DARKEST_LARIAT, "Darkest Lariat", Type.DARK, MoveCategory.PHYSICAL, 85, 100, 10, "The user swings both arms and hits the target. The target's stat changes don't affect this attack's damage.", -1, 0, 7) .attr(IgnoreOpponentStatChangesAttr), @@ -5080,7 +5114,8 @@ export function initMoves() { new AttackMove(Moves.TEN_MILLION_VOLT_THUNDERBOLT, "10,000,000 Volt Thunderbolt (P)", Type.ELECTRIC, MoveCategory.SPECIAL, 195, -1, 1, "The user, Pikachu wearing a cap, powers up a jolt of electricity using its Z-Power and unleashes it. Critical hits land more easily.", -1, 0, 7), /* End Unused */ new AttackMove(Moves.MIND_BLOWN, "Mind Blown", Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, "The user attacks everything around it by causing its own head to explode. This also damages the user.", -1, 0, 7) - .attr(RecoilAttr, true, 0.5) + .attr(Explosive) + .attr(HalfHpAttackAttr) .target(MoveTarget.ALL_NEAR_OTHERS), new AttackMove(Moves.PLASMA_FISTS, "Plasma Fists (P)", Type.ELECTRIC, MoveCategory.PHYSICAL, 100, 100, 15, "The user attacks with electrically charged fists. This move changes Normal-type moves to Electric-type moves.", -1, 0, 7) .punchingMove(), @@ -5257,7 +5292,7 @@ export function initMoves() { new AttackMove(Moves.ETERNABEAM, "Eternabeam", Type.DRAGON, MoveCategory.SPECIAL, 160, 90, 5, "This is Eternatus's most powerful attack in its original form. The user can't move on the next turn.", -1, 0, 8) .attr(RechargeAttr), new AttackMove(Moves.STEEL_BEAM, "Steel Beam", Type.STEEL, MoveCategory.SPECIAL, 140, 95, 5, "The user fires a beam of steel that it collected from its entire body. This also damages the user.", -1, 0, 8) - .attr(RecoilAttr, true, 0.5), + .attr(HalfHpAttackAttr), new AttackMove(Moves.EXPANDING_FORCE, "Expanding Force (P)", Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, "The user attacks the target with its psychic power. This move's power goes up and damages all opposing Pokémon on Psychic Terrain.", -1, 0, 8), new AttackMove(Moves.STEEL_ROLLER, "Steel Roller", Type.STEEL, MoveCategory.PHYSICAL, 130, 100, 5, "The user attacks while destroying the terrain. This move fails when the ground hasn't turned into a terrain.", -1, 0, 8) .attr(ClearTerrainAttr) @@ -5274,7 +5309,9 @@ export function initMoves() { new AttackMove(Moves.SHELL_SIDE_ARM, "Shell Side Arm (P)", Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, "This move inflicts physical or special damage, whichever will be more effective. This may also poison the target.", 20, 0, 8) .attr(ShellSideArmCategoryAttr) .attr(StatusEffectAttr, StatusEffect.POISON), - new AttackMove(Moves.MISTY_EXPLOSION, "Misty Explosion (P)", Type.FAIRY, MoveCategory.SPECIAL, 100, 100, 5, "The user attacks everything around it and faints upon using this move. This move's power is increased on Misty Terrain.", -1, 0, 8) + new AttackMove(Moves.MISTY_EXPLOSION, "Misty Explosion", Type.FAIRY, MoveCategory.SPECIAL, 100, 100, 5, "The user attacks everything around it and faints upon using this move. This move's power is increased on Misty Terrain.", -1, 0, 8) + .attr(Explosive) + .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.MISTY && target.isGrounded() ? 1.5 : 1) .target(MoveTarget.ALL_NEAR_OTHERS), new AttackMove(Moves.GRASSY_GLIDE, "Grassy Glide (P)", Type.GRASS, MoveCategory.PHYSICAL, 55, 100, 20, "Gliding on the ground, the user attacks the target. This move always goes first on Grassy Terrain.", -1, 0, 8), new AttackMove(Moves.RISING_VOLTAGE, "Rising Voltage", Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, "The user attacks with electric voltage rising from the ground. This move's power doubles when the target is on Electric Terrain.", -1, 0, 8) @@ -5542,7 +5579,8 @@ export function initMoves() { new AttackMove(Moves.PSYBLADE, "Psyblade", Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 100, 15, "The user rends the target with an ethereal blade. This move's power is boosted by 50 percent if the user is on Electric Terrain.", -1, 0, 9) .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.ELECTRIC && user.isGrounded() ? 1.5 : 1) .slicingMove(), - new AttackMove(Moves.HYDRO_STEAM, "Hydro Steam (P)", Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, "The user blasts the target with boiling-hot water. This move's power is not lowered in harsh sunlight but rather boosted by 50 percent.", -1, 0, 9), + new AttackMove(Moves.HYDRO_STEAM, "Hydro Steam", Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, "The user blasts the target with boiling-hot water. This move's power is not lowered in harsh sunlight but rather boosted by 50 percent.", -1, 0, 9) + .attr(MovePowerMultiplierAttr, (user, target, move) => [WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(user.scene.arena.weather?.weatherType) && !user.scene.arena.weather?.isEffectSuppressed(user.scene) ? 1.5 : 1), new AttackMove(Moves.RUINATION, "Ruination", Type.DARK, MoveCategory.SPECIAL, 1, 90, 10, "The user summons a ruinous disaster. This cuts the target's HP in half.", -1, 0, 9) .attr(TargetHalfHpDamageAttr), new AttackMove(Moves.COLLISION_COURSE, "Collision Course", Type.FIGHTING, MoveCategory.PHYSICAL, 100, 100, 5, "The user transforms and crashes to the ground, causing a massive prehistoric explosion. This move's power is boosted more than usual if it's a supereffective hit.", -1, 0, 9) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6c9df8ad0cf..eaab885ec82 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1183,7 +1183,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { else { if (source.findTag(t => t instanceof TypeBoostTag && (t as TypeBoostTag).boostedType === type)) power.value *= 1.5; - const arenaAttackTypeMultiplier = this.scene.arena.getAttackTypeMultiplier(type, source.isGrounded()); + const arenaAttackTypeMultiplier = (move.id == Moves.HYDRO_STEAM && this.scene.arena.weather.weatherType==WeatherType.SUNNY)? + 1 : this.scene.arena.getAttackTypeMultiplier(type, source.isGrounded()); if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && this.isGrounded() && type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) power.value /= 2; applyMoveAttrs(VariablePowerAttr, source, this, move, power);