diff --git a/src/data/ability.ts b/src/data/ability.ts index 681fff651..bae2744ae 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -869,6 +869,13 @@ export class MoveTypeChangePowerMultiplierAbAttr extends VariableMoveTypeAbAttr } } +export class FieldPreventExplosiveMovesAbAttr extends AbAttr { + apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + cancelled.value = true; + return true; + } +} + export class MoveTypeChangeAttr extends PreAttackAbAttr { private newType: Type; private powerMultiplier: number; @@ -2057,6 +2064,11 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { + const cancelled = new Utils.BooleanHolder(false); + pokemon.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)) + if (cancelled) { + return false; + } attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); return true; } @@ -2468,8 +2480,8 @@ export function initAbilities() { .attr(BlockOneHitKOAbAttr) .ignorable(), new Ability(Abilities.DAMP, 3) - .ignorable() - .unimplemented(), + .attr(FieldPreventExplosiveMovesAbAttr) + .ignorable(), new Ability(Abilities.LIMBER, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.PARALYSIS) .ignorable(), diff --git a/src/data/move.ts b/src/data/move.ts index 4b8e2b6d9..b4604f7fc 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, MoveAbilityBypassAbAttr, ReverseDrainAbAttr } from "./ability"; +import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, NoTransformAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr } from "./ability"; import { Abilities } from "./enums/abilities"; import { allAbilities } from './ability'; import { PokemonHeldItemModifier } from "../modifier/modifier"; @@ -725,6 +725,40 @@ export class SacrificialAttr extends MoveEffectAttr { } } +export class HalfSacrificialAttr 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; + + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); + if (!cancelled){ + user.damageAndUpdate(Math.ceil(user.getMaxHp()/2), HitResult.OTHER, false, true, true); + } + return true; + } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + if (user.isBoss()) + return -10; + return Math.ceil(((1 - user.getHpRatio()/2) * 10 - 10) * (target.getAttackTypeEffectiveness(move.type) - 0.5)); + } +} + +export class ExplosiveAttr extends MoveEffectAttr { + getCondition(): MoveCondition | MoveConditionFunc { + return (user, target, move) =>{ + const cancelled = new Utils.BooleanHolder(false); + user.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)); + return !cancelled.value; + } + } +} + export enum MultiHitType { _2, _2_TO_5, @@ -4000,6 +4034,7 @@ export function initMoves() { .attr(CopyMoveAttr) .ignoresVirtual(), new AttackMove(Moves.SELF_DESTRUCT, Type.NORMAL, MoveCategory.PHYSICAL, 200, 100, 5, -1, 0, 1) + .attr(ExplosiveAttr) .attr(SacrificialAttr) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), @@ -4090,6 +4125,7 @@ export function initMoves() { new AttackMove(Moves.CRABHAMMER, Type.WATER, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 1) .attr(HighCritAttr), new AttackMove(Moves.EXPLOSION, Type.NORMAL, MoveCategory.PHYSICAL, 250, 100, 5, -1, 0, 1) + .attr(ExplosiveAttr) .attr(SacrificialAttr) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), @@ -5622,7 +5658,8 @@ export function initMoves() { .partial(), /* End Unused */ new AttackMove(Moves.MIND_BLOWN, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 7) - .attr(RecoilAttr, true, 0.5) + .attr(ExplosiveAttr) + .attr(HalfSacrificialAttr) .target(MoveTarget.ALL_NEAR_OTHERS), new AttackMove(Moves.PLASMA_FISTS, Type.ELECTRIC, MoveCategory.PHYSICAL, 100, 100, 15, -1, 0, 7) .punchingMove() @@ -5837,7 +5874,7 @@ export function initMoves() { new AttackMove(Moves.ETERNABEAM, Type.DRAGON, MoveCategory.SPECIAL, 160, 90, 5, -1, 0, 8) .attr(RechargeAttr), new AttackMove(Moves.STEEL_BEAM, Type.STEEL, MoveCategory.SPECIAL, 140, 95, 5, -1, 0, 8) - .attr(RecoilAttr, true, 0.5), + .attr(HalfSacrificialAttr), new AttackMove(Moves.EXPANDING_FORCE, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 8) .partial(), new AttackMove(Moves.STEEL_ROLLER, Type.STEEL, MoveCategory.PHYSICAL, 130, 100, 5, -1, 0, 8) @@ -5858,8 +5895,10 @@ export function initMoves() { .attr(StatusEffectAttr, StatusEffect.POISON) .partial(), new AttackMove(Moves.MISTY_EXPLOSION, Type.FAIRY, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8) + .attr(ExplosiveAttr) + .attr(SacrificialAttr) .target(MoveTarget.ALL_NEAR_OTHERS) - .partial(), + .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.MISTY && user.isGrounded() ? 1.5 : 1), new AttackMove(Moves.GRASSY_GLIDE, Type.GRASS, MoveCategory.PHYSICAL, 55, 100, 20, -1, 0, 8) .partial(), new AttackMove(Moves.RISING_VOLTAGE, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 8) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 3bbc2a74d..fae667a71 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4,7 +4,7 @@ import { Variant, VariantSet, variantColorCache } from '#app/data/variant'; import { variantData } from '#app/data/variant'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from '../ui/battle-info'; import { Moves } from "../data/enums/moves"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr } from "../data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, ExplosiveAttr } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, starterPassiveAbilities } from '../data/pokemon-species'; import * as Utils from '../utils'; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from '../data/type'; @@ -1229,6 +1229,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (types.find(t => move.isTypeImmune(t))) typeMultiplier.value = 0; + applyMoveAttrs(ExplosiveAttr, source, null, battlerMove.getMove()); switch (moveCategory) { case MoveCategory.PHYSICAL: case MoveCategory.SPECIAL: