diff --git a/src/battle-scene.ts b/src/battle-scene.ts index e5bb31e38..5e7658e7e 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -19,7 +19,7 @@ import { } from "./data/move"; import { initMoves } from './data/move'; import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave } from './modifier/modifier-type'; import AbilityBar from './ui/ability-bar'; -import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, applyAbAttrs, initAbilities } from './data/ability'; +import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, applyAbAttrs, initAbilities } from './data/ability'; import Battle, { BattleType, FixedBattleConfig, fixedBattles } from './battle'; import { GameMode, GameModes, gameModes } from './game-mode'; import FieldSpritePipeline from './pipelines/field-sprite'; @@ -1595,8 +1595,9 @@ export default class BattleScene extends Phaser.Scene { } pushMovePhase(movePhase: MovePhase, priorityOverride?: integer): void { - const priority = priorityOverride !== undefined ? priorityOverride : movePhase.move.getMove().priority; - const lowerPriorityPhase = this.phaseQueue.find(p => p instanceof MovePhase && p.move.getMove().priority < priority); + const movePriority = new Utils.IntegerHolder(priorityOverride !== undefined ? priorityOverride : movePhase.move.getMove().priority); + applyAbAttrs(IncrementMovePriorityAbAttr, movePhase.pokemon, null, movePhase.move.getMove(), movePriority); + const lowerPriorityPhase = this.phaseQueue.find(p => p instanceof MovePhase && p.move.getMove().priority < movePriority.value); if (lowerPriorityPhase) this.phaseQueue.splice(this.phaseQueue.indexOf(lowerPriorityPhase), 0, movePhase); else diff --git a/src/data/ability.ts b/src/data/ability.ts index 93d7b7e9f..60657245d 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -914,6 +914,24 @@ export class BlockOneHitKOAbAttr extends AbAttr { } } +export class IncrementMovePriorityAbAttr extends AbAttr { + private moveIncrementFunc: (move: Move) => boolean; + + constructor(moveIncrementFunc: (move: Move) => boolean) { + super(true); + + this.moveIncrementFunc = moveIncrementFunc; + } + + apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (!this.moveIncrementFunc(args[0] as Move)) + return false; + + (args[0] as Utils.IntegerHolder).value++; + return true; + } +} + export class IgnoreContactAbAttr extends AbAttr { } export class PreWeatherEffectAbAttr extends AbAttr { @@ -2192,7 +2210,8 @@ export function initAbilities() { new Ability(Abilities.SAP_SIPPER, "Sap Sipper", "Boosts the Attack stat if hit by a Grass-type move instead of taking damage.", 5) .attr(TypeImmunityStatChangeAbAttr, Type.GRASS, BattleStat.ATK, 1) .ignorable(), - new Ability(Abilities.PRANKSTER, "Prankster (N)", "Gives priority to a status move.", 5), + new Ability(Abilities.PRANKSTER, "Prankster", "Gives priority to a status move.", 5) + .attr(IncrementMovePriorityAbAttr, (move: Move) => move.category === MoveCategory.STATUS), new Ability(Abilities.SAND_FORCE, "Sand Force", "Boosts the power of Rock-, Ground-, and Steel-type moves in a sandstorm.", 5) .attr(MoveTypePowerBoostAbAttr, Type.ROCK, 1.3) .attr(MoveTypePowerBoostAbAttr, Type.GROUND, 1.3) diff --git a/src/data/api.ts b/src/data/api.ts index 24866e008..aee0ece95 100644 --- a/src/data/api.ts +++ b/src/data/api.ts @@ -540,8 +540,7 @@ export async function printMoves() { } else if (matchingLine) flavorText = allMoves[move.id].effect; const moveTarget = targetMap[move.target.name]; - const moveTm = move.id < allMoves.length ? allMoves[move.id].tm : -1; - moveStr += `\n new ${move.damage_class.name !== 'status' ? 'Attack' : (moveTarget === MoveTarget.USER ? 'Self' : '') + 'Status'}Move(Moves.${moveEnumName}, "${moveName}", Type.${move.type.name.toUpperCase()}${move.damage_class.name !== 'status' ? `, MoveCategory.${move.damage_class.name.toUpperCase()}` : ''}${move.damage_class.name !== 'status' ? `, ${move.power || -1}` : ''}, ${move.accuracy || -1}, ${move.pp}, ${moveTm}, "${flavorText?.replace(/\n/g, '\\n').replace(/ /g, ' ').replace(/’/g, '\'') || ''}", ${move.effect_chance || -1}, ${move.priority}, ${generationMap[move.generation.name]})`; + moveStr += `\n new ${move.damage_class.name !== 'status' ? 'Attack' : (moveTarget === MoveTarget.USER ? 'Self' : '') + 'Status'}Move(Moves.${moveEnumName}, "${moveName}", Type.${move.type.name.toUpperCase()}${move.damage_class.name !== 'status' ? `, MoveCategory.${move.damage_class.name.toUpperCase()}` : ''}${move.damage_class.name !== 'status' ? `, ${move.power || -1}` : ''}, ${move.accuracy || -1}, ${move.pp}, "${flavorText?.replace(/\n/g, '\\n').replace(/ /g, ' ').replace(/’/g, '\'') || ''}", ${move.effect_chance || -1}, ${move.priority}, ${generationMap[move.generation.name]})`; const expectedTarget = move.damage_class.name !== 'status' || moveTarget !== MoveTarget.USER ? MoveTarget.NEAR_OTHER : MoveTarget.USER; if (matchingLine && matchingLine.length > 1) { const newLineIndex = matchingLine.indexOf('\n'); diff --git a/src/data/terrain.ts b/src/data/terrain.ts index 8e66a0d8f..c5af86240 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -1,5 +1,8 @@ +import Pokemon from "../field/pokemon"; import Move from "./move"; import { Type } from "./type"; +import * as Utils from "../utils"; +import { IncrementMovePriorityAbAttr, applyAbAttrs } from "./ability"; export enum TerrainType { NONE, @@ -44,10 +47,12 @@ export class Terrain { return 1; } - isMoveTerrainCancelled(move: Move): boolean { + isMoveTerrainCancelled(user: Pokemon, move: Move): boolean { switch (this.terrainType) { case TerrainType.PSYCHIC: - return move.priority > 0; + const priority = new Utils.IntegerHolder(move.priority); + applyAbAttrs(IncrementMovePriorityAbAttr, user, null, move, priority); + return priority.value > 0; } return false; diff --git a/src/field/arena.ts b/src/field/arena.ts index 891f3f1ad..7f23a9a34 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -17,7 +17,7 @@ import { Moves } from "../data/enums/moves"; import { TimeOfDay } from "../data/enums/time-of-day"; import { Terrain, TerrainType } from "../data/terrain"; import { PostTerrainChangeAbAttr, PostWeatherChangeAbAttr, applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs } from "../data/ability"; -import { PartyMemberStrength } from "../data/enums/party-member-strength"; +import Pokemon from "./pokemon"; const WEATHER_OVERRIDE = WeatherType.NONE; @@ -313,8 +313,8 @@ export class Arena { return this.weather && !this.weather.isEffectSuppressed(this.scene) && this.weather.isMoveWeatherCancelled(move); } - isMoveTerrainCancelled(move: Move) { - return this.terrain && this.terrain.isMoveTerrainCancelled(move); + isMoveTerrainCancelled(user: Pokemon, move: Move) { + return this.terrain && this.terrain.isMoveTerrainCancelled(user, move); } getAttackTypeMultiplier(attackType: Type, grounded: boolean): number { diff --git a/src/phases.ts b/src/phases.ts index d6b802a0a..691a7eed4 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -30,7 +30,7 @@ import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, get import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTagType } from "./data/enums/arena-tag-type"; -import { Abilities, CheckTrappedAbAttr, MoveAbilityBypassAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs } from "./data/ability"; +import { Abilities, CheckTrappedAbAttr, MoveAbilityBypassAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./field/arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; @@ -1768,11 +1768,17 @@ export class TurnStartPhase extends FieldPhase { else if (bCommand.command === Command.FIGHT) return -1; } else if (aCommand.command === Command.FIGHT) { - const aPriority = allMoves[aCommand.move.move].priority; - const bPriority = allMoves[bCommand.move.move].priority; + const aMove = allMoves[aCommand.move.move]; + const bMove = allMoves[bCommand.move.move]; + + const aPriority = new Utils.IntegerHolder(aMove.priority); + const bPriority = new Utils.IntegerHolder(bMove.priority); + + applyAbAttrs(IncrementMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a), null, aMove, aPriority); + applyAbAttrs(IncrementMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b), null, bMove, bPriority); - if (aPriority !== bPriority) - return aPriority < bPriority ? 1 : -1; + if (aPriority.value !== bPriority.value) + return aPriority.value < bPriority.value ? 1 : -1; } const aIndex = order.indexOf(a); @@ -2070,7 +2076,7 @@ export class MovePhase extends BattlePhase { let failedText = null; if (success && this.scene.arena.isMoveWeatherCancelled(this.move.getMove())) success = false; - else if (success && this.scene.arena.isMoveTerrainCancelled(this.move.getMove())) { + else if (success && this.scene.arena.isMoveTerrainCancelled(this.pokemon, this.move.getMove())) { success = false; failedText = getTerrainBlockMessage(targets[0], this.scene.arena.terrain.terrainType); }