diff --git a/public/images/arenas/tall_grass_a.png b/public/images/arenas/tall_grass_a.png new file mode 100644 index 00000000000..5fa1421903d Binary files /dev/null and b/public/images/arenas/tall_grass_a.png differ diff --git a/public/images/arenas/tall_grass_b.png b/public/images/arenas/tall_grass_b.png new file mode 100644 index 00000000000..39d23a2247f Binary files /dev/null and b/public/images/arenas/tall_grass_b.png differ diff --git a/public/images/arenas/tall_grass_bg.png b/public/images/arenas/tall_grass_bg.png new file mode 100644 index 00000000000..03354486a35 Binary files /dev/null and b/public/images/arenas/tall_grass_bg.png differ diff --git a/src/arena.ts b/src/arena.ts index bd7908d75ff..cfe224e00a3 100644 --- a/src/arena.ts +++ b/src/arena.ts @@ -99,8 +99,6 @@ export class Arena { getBiomeKey(): string { switch (this.biomeType) { - case Biome.TALL_GRASS: - return 'grass'; case Biome.CITY: return 'dojo'; case Biome.LAKE: diff --git a/src/battle-phases.ts b/src/battle-phases.ts index ea1e8145b7e..e85cd412302 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -25,7 +25,7 @@ import { Gender } from "./data/gender"; import { Weather, WeatherType, getRandomWeatherType, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTrapTag, TrickRoomTag } from "./data/arena-tag"; -import { PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAttr, SuppressWeatherEffectAbAttr, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability"; +import { ArenaTrapAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAttr, SuppressWeatherEffectAbAttr, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; export class CheckLoadPhase extends BattlePhase { @@ -596,11 +596,11 @@ export class CommandPhase extends FieldPhase { if (moveQueue.length) { const queuedMove = moveQueue[0]; if (!queuedMove.move) - this.handleCommand(Command.FIGHT, -1); + this.handleCommand(Command.FIGHT, -1, false); else { const moveIndex = playerPokemon.moveset.findIndex(m => m.moveId === queuedMove.move); - if (playerPokemon.moveset[moveIndex].isUsable(queuedMove.ignorePP)) - this.handleCommand(Command.FIGHT, moveIndex); + if (moveIndex > -1 && playerPokemon.moveset[moveIndex].isUsable(queuedMove.ignorePP)) + this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP); } } else this.scene.ui.setMode(Mode.COMMAND); @@ -614,9 +614,9 @@ export class CommandPhase extends FieldPhase { let isDelayed = (command: Command, playerMove: PokemonMove, enemyMove: PokemonMove) => { switch (command) { case Command.FIGHT: - if (playerMove && enemyMove) { - const playerMovePriority = playerMove.getMove().priority; - const enemyMovePriority = enemyMove.getMove().priority; + if (playerMove || enemyMove) { + const playerMovePriority = playerMove?.getMove()?.priority || 0; + const enemyMovePriority = enemyMove?.getMove()?.priority || 0; if (playerMovePriority !== enemyMovePriority) return playerMovePriority < enemyMovePriority; } @@ -641,9 +641,9 @@ export class CommandPhase extends FieldPhase { break; } - if (playerPokemon.trySelectMove(cursor)) { + if (playerPokemon.trySelectMove(cursor, args[0] as boolean)) { playerMove = playerPokemon.moveset[cursor]; - const playerPhase = new PlayerMovePhase(this.scene, playerPokemon, playerMove); + const playerPhase = new PlayerMovePhase(this.scene, playerPokemon, playerMove, false, args[0] as boolean); this.scene.pushPhase(playerPhase); success = true; } else if (cursor < playerPokemon.moveset.length) { @@ -673,12 +673,13 @@ export class CommandPhase extends FieldPhase { break; case Command.POKEMON: const trapTag = playerPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; + const arenaTrapped = !!enemyPokemon.getAbility().getAttrs(ArenaTrapAbAttr).length; const batonPass = args[0] as boolean; - if (batonPass || !trapTag) { + if (batonPass || (!trapTag && !arenaTrapped)) { this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, cursor, true, args[0] as boolean)); success = true; } else - this.scene.ui.showText(`${this.scene.getPokemonById(trapTag.sourceId).name}'s ${trapTag.getMoveName()}\nprevents switching!`, null, () => { + this.scene.ui.showText(`${this.scene.getPokemonById(trapTag.sourceId).name}'s ${trapTag?.getMoveName() || enemyPokemon.getAbility().name}\nprevents switching!`, null, () => { this.scene.ui.showText(null, 0); }, null, true); break; @@ -692,19 +693,23 @@ export class CommandPhase extends FieldPhase { if (this.scene.arena.weather) this.scene.unshiftPhase(new WeatherEffectPhase(this.scene, this.scene.arena.weather)); - const enemyMove = enemyPokemon.getNextMove(); - const enemyPhase = new EnemyMovePhase(this.scene, enemyPokemon, enemyMove); - if (isDelayed(command, playerMove, enemyMove)) - this.scene.unshiftPhase(enemyPhase); - else - this.scene.pushPhase(enemyPhase); + const enemyNextMove = enemyPokemon.getNextMove(); + let enemyMove: PokemonMove; + if (enemyNextMove.move) { + enemyMove = enemyPokemon.moveset.find(m => m.moveId === enemyNextMove.move) || new PokemonMove(enemyNextMove.move, 0, 0); + const enemyPhase = new EnemyMovePhase(this.scene, enemyPokemon, enemyMove, false, enemyNextMove.ignorePP); + if (isDelayed(command, playerMove, enemyMove)) + this.scene.unshiftPhase(enemyPhase); + else + this.scene.pushPhase(enemyPhase); + } const statusEffectPhases: PostTurnStatusEffectPhase[] = []; if (playerPokemon.status && playerPokemon.status.isPostTurn()) statusEffectPhases.push(new PostTurnStatusEffectPhase(this.scene, true)); if (enemyPokemon.status && enemyPokemon.status.isPostTurn()) { const enemyStatusEffectPhase = new PostTurnStatusEffectPhase(this.scene, false); - if (isDelayed(command, playerMove, enemyMove)) + if (this.isPlayerDelayed()) statusEffectPhases.unshift(enemyStatusEffectPhase); else statusEffectPhases.push(enemyStatusEffectPhase); @@ -837,21 +842,23 @@ export abstract class MovePhase extends BattlePhase { protected pokemon: Pokemon; protected move: PokemonMove; protected followUp: boolean; + protected ignorePp: boolean; protected cancelled: boolean; - constructor(scene: BattleScene, pokemon: Pokemon, move: PokemonMove, followUp?: boolean) { + constructor(scene: BattleScene, pokemon: Pokemon, move: PokemonMove, followUp?: boolean, ignorePp?: boolean) { super(scene); this.pokemon = pokemon; this.move = move; this.followUp = !!followUp; + this.ignorePp = !!ignorePp; this.cancelled = false; } abstract getEffectPhase(): MoveEffectPhase; canMove(): boolean { - return !!this.pokemon.hp && this.move.isUsable(); + return !!this.pokemon.hp && this.move.isUsable(this.ignorePp); } cancel(): void { @@ -955,8 +962,8 @@ export abstract class MovePhase extends BattlePhase { } export class PlayerMovePhase extends MovePhase { - constructor(scene: BattleScene, pokemon: PlayerPokemon, move: PokemonMove, followUp?: boolean) { - super(scene, pokemon, move, followUp); + constructor(scene: BattleScene, pokemon: PlayerPokemon, move: PokemonMove, followUp?: boolean, ignorePp?: boolean) { + super(scene, pokemon, move, followUp, ignorePp); } getEffectPhase(): MoveEffectPhase { @@ -965,8 +972,8 @@ export class PlayerMovePhase extends MovePhase { } export class EnemyMovePhase extends MovePhase { - constructor(scene: BattleScene, pokemon: EnemyPokemon, move: PokemonMove, followUp?: boolean) { - super(scene, pokemon, move, followUp); + constructor(scene: BattleScene, pokemon: EnemyPokemon, move: PokemonMove, followUp?: boolean, ignorePp?: boolean) { + super(scene, pokemon, move, followUp, ignorePp); } getEffectPhase(): MoveEffectPhase { diff --git a/src/data/ability.ts b/src/data/ability.ts index bf79a643853..f91fb4d1380 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -247,6 +247,8 @@ export class ProtectStatAttr extends PreStatChangeAbAttr { } } +export class ArenaTrapAbAttr extends AbAttr { } + export class PreWeatherEffectAbAttr extends AbAttr { applyPreWeatherEffect(pokemon: Pokemon, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean { return false; @@ -636,7 +638,8 @@ export function initAbilities() { abilities.push( new Ability(Abilities.AIR_LOCK, "Air Lock", "Eliminates the effects of all weather.", 3) .attr(SuppressWeatherEffectAbAttr, true), - new Ability(Abilities.ARENA_TRAP, "Arena Trap (N)", "Prevents the foe from fleeing.", 3), + new Ability(Abilities.ARENA_TRAP, "Arena Trap", "Prevents the foe from fleeing.", 3) + .attr(ArenaTrapAbAttr), new Ability(Abilities.BATTLE_ARMOR, "Battle Armor (N)", "The POKéMON is protected against critical hits.", 3), new Ability(Abilities.BLAZE, "Blaze", "Powers up FIRE-type moves in a pinch.", 3) .attr(LowHpMoveTypePowerBoostAbAttr, Type.FIRE), diff --git a/src/pokemon.ts b/src/pokemon.ts index 48f0c393982..dae33b8673a 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -431,11 +431,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - trySelectMove(moveIndex: integer): boolean { + trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean { const move = this.moveset.length > moveIndex ? this.moveset[moveIndex] : null; - return move?.isUsable(); + return move?.isUsable(ignorePp); } showInfo() { @@ -991,13 +991,13 @@ export class EnemyPokemon extends Pokemon { } } - getNextMove(): PokemonMove { + getNextMove(): QueuedMove { const queuedMove = this.getMoveQueue().length ? this.moveset.find(m => m.moveId === this.getMoveQueue()[0].move) : null; if (queuedMove) { if (queuedMove.isUsable(this.getMoveQueue()[0].ignorePP)) - return queuedMove; + return { move: queuedMove.moveId, ignorePP: this.getMoveQueue()[0].ignorePP }; else { this.getMoveQueue().shift(); return this.getNextMove(); @@ -1007,10 +1007,10 @@ export class EnemyPokemon extends Pokemon { const movePool = this.moveset.filter(m => m.isUsable()); if (movePool.length) { if (movePool.length === 1) - return movePool[0]; + return { move: movePool[0].moveId }; switch (this.aiType) { case AiType.RANDOM: - return movePool[Utils.randInt(movePool.length)]; + return { move: movePool[Utils.randInt(movePool.length)].moveId }; case AiType.SMART_RANDOM: case AiType.SMART: const target = this.scene.getPlayerPokemon(); @@ -1072,11 +1072,11 @@ export class EnemyPokemon extends Pokemon { r++; } console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName())); - return sortedMovePool[r]; + return { move: sortedMovePool[r].moveId }; } } - return new PokemonMove(Moves.STRUGGLE, 0, 0); + return { move: Moves.STRUGGLE }; } isPlayer() { diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 94d7e1c8257..2757d50feb8 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -49,7 +49,7 @@ export default class FightUiHandler extends UiHandler { if (button === Button.CANCEL || button === Button.ACTION) { if (button === Button.ACTION) { - if ((this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, this.cursor)) + if ((this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, this.cursor, false)) success = true; else ui.playError();