From 7219556e77942a37db01df347df99c34d357018c Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Thu, 9 May 2024 21:21:57 -0400 Subject: [PATCH] Hydro Steam (#231) * Hydro Steam, Spirit Shackle, U-turn fix Match formatting to project style Co-authored-by: Samuel H * Fix Hydro Steam power up in Sun, even harsh sun if type changes * Fix issue with Hydro Steam outside sun * Add comments to IgnoreWeatherTypeDebuffAttr for documentation * Move U-turn fix to another PR --------- Co-authored-by: Samuel H --- src/data/move.ts | 30 +++++++++++++++++++++++++++++- src/field/pokemon.ts | 9 +++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index b85a3cea29f..6e4e3f60fcb 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -888,6 +888,33 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr { } } +/** + * Attribute used for moves which ignore type-based debuffs from weather, namely Hydro Steam. + * Called during damage calculation after getting said debuff from getAttackTypeMultiplier in the Pokemon class. + */ +export class IgnoreWeatherTypeDebuffAttr extends MoveAttr { + public weather: WeatherType; + constructor(weather: WeatherType){ + super(); + this.weather = weather; + } + /** + * Changes the type-based weather modifier if this move's power would be reduced by it + * @param user Pokemon that used the move + * @param target N/A + * @param move Move with this attribute + * @param args Utils.NumberHolder for arenaAttackTypeMultiplier + * @returns true if the function succeeds + */ + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + const weatherModifier=args[0] as Utils.NumberHolder; + //If the type-based attack power modifier due to weather (e.g. Water moves in Sun) is below 1, set it to 1 + if (user.scene.arena.weather?.weatherType === this.weather) + weatherModifier.value = Math.max(weatherModifier.value, 1); + return true; + } +} + export abstract class WeatherHealAttr extends HealAttr { constructor() { super(0.5); @@ -6666,7 +6693,8 @@ export function initMoves() { .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.ELECTRIC && user.isGrounded() ? 1.5 : 1) .slicingMove(), new AttackMove(Moves.HYDRO_STEAM, Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 9) - .partial(), + .attr(IgnoreWeatherTypeDebuffAttr, WeatherType.SUNNY) + .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, Type.DARK, MoveCategory.SPECIAL, -1, 90, 10, -1, 0, 9) .attr(TargetHalfHpDamageAttr), new AttackMove(Moves.COLLISION_COURSE, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 52378a284b4..8afff1b2724 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, CounterDamageAttr } 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, CounterDamageAttr, IgnoreWeatherTypeDebuffAttr } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from '../data/pokemon-species'; import * as Utils from '../utils'; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from '../data/type'; @@ -1338,7 +1338,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { source.removeTag(typeBoost.tagType); } } - const arenaAttackTypeMultiplier = this.scene.arena.getAttackTypeMultiplier(type, source.isGrounded()); + const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(type, source.isGrounded())); + applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); 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); @@ -1383,7 +1384,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!isCritical) { this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, move.category, this.scene.currentBattle.double, screenMultiplier); } - const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier) === 0; + const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0; const sourceTypes = source.getTypes(); const matchesSourceType = sourceTypes[0] === type || (sourceTypes.length > 1 && sourceTypes[1] === type); let stabMultiplier = new Utils.NumberHolder(1); @@ -1401,7 +1402,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(VariableDefAttr, source, this, move, targetDef); if (!isTypeImmune) { - damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier * screenMultiplier.value * ((this.scene.randBattleSeedInt(15) + 85) / 100) * criticalMultiplier.value); + damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * ((this.scene.randBattleSeedInt(15) + 85) / 100) * criticalMultiplier.value); if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) { const burnDamageReductionCancelled = new Utils.BooleanHolder(false); applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled);