diff --git a/src/battle-phases.ts b/src/battle-phases.ts index b1c229d2b..64b33b6e6 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -449,9 +449,17 @@ export class CommandPhase extends BattlePhase { playerPokemon.resetTurnData(); this.scene.getEnemyPokemon().resetTurnData(); - if (playerPokemon.summonData.moveQueue.length) - this.handleCommand(Command.FIGHT, playerPokemon.moveset.findIndex(m => m.moveId === playerPokemon.summonData.moveQueue[0].move)); - else + while (playerPokemon.summonData.moveQueue.length && playerPokemon.summonData.moveQueue[0] + && !playerPokemon.moveset[playerPokemon.moveset.findIndex(m => m.moveId === playerPokemon.summonData.moveQueue[0].move)] + .isUsable(playerPokemon.summonData.moveQueue[0].ignorePP)) + playerPokemon.summonData.moveQueue.shift(); + + if (playerPokemon.summonData.moveQueue.length) { + const queuedMove = playerPokemon.summonData.moveQueue[0]; + const moveIndex = playerPokemon.moveset.findIndex(m => m.moveId === queuedMove.move); + if (playerPokemon.moveset[moveIndex].isUsable(queuedMove.ignorePP)) + this.handleCommand(Command.FIGHT, moveIndex); + } else this.scene.ui.setMode(Mode.COMMAND); } @@ -492,7 +500,12 @@ export class CommandPhase extends BattlePhase { const playerPhase = new PlayerMovePhase(this.scene, playerPokemon, playerMove); this.scene.pushPhase(playerPhase); success = true; + } else if (cursor < playerPokemon.moveset.length) { + const move = playerPokemon.moveset[cursor]; + if (move.isDisabled()) + this.scene.ui.showText(`${move.getName()} is disabled!`); } + break; case Command.BALL: if (cursor < 4) { @@ -557,16 +570,32 @@ export class TurnEndPhase extends BattlePhase { const playerPokemon = this.scene.getPlayerPokemon(); const enemyPokemon = this.scene.getEnemyPokemon(); + + const handlePokemon = (pokemon: Pokemon) => { + if (!pokemon) + return; - if (playerPokemon) { - playerPokemon.lapseTags(BattleTagLapseType.TURN_END); - playerPokemon.battleSummonData.turnCount++; - } + pokemon.lapseTags(BattleTagLapseType.TURN_END); + + const disabledMoves = pokemon.moveset.filter(m => m.isDisabled()); + for (let dm of disabledMoves) { + if (!--dm.disableTurns) + this.scene.pushPhase(new MessagePhase(this.scene, `${dm.getName()} is disabled\nno more!`)); + } - if (enemyPokemon) { - enemyPokemon.lapseTags(BattleTagLapseType.TURN_END); - enemyPokemon.battleSummonData.turnCount++; - } + pokemon.battleSummonData.turnCount++; + }; + + const playerSpeed = playerPokemon?.getBattleStat(Stat.SPD) || 0; + const enemySpeed = enemyPokemon?.getBattleStat(Stat.SPD) || 0; + + const isDelayed = playerSpeed < enemySpeed || (playerSpeed === enemySpeed && Utils.randInt(2) === 1); + + if (!isDelayed) + handlePokemon(playerPokemon); + handlePokemon(enemyPokemon); + if (isDelayed) + handlePokemon(playerPokemon); if (this.scene.arena.weather && !this.scene.arena.weather.lapse()) this.scene.arena.trySetWeather(WeatherType.NONE, false); @@ -655,7 +684,7 @@ export abstract class MovePhase extends BattlePhase { abstract getEffectPhase(): MoveEffectPhase; canMove(): boolean { - return !!this.pokemon.hp; + return !!this.pokemon.hp && this.move.isUsable(); } cancel(): void { @@ -665,6 +694,8 @@ export abstract class MovePhase extends BattlePhase { start() { super.start(); + console.log(Moves[this.move.moveId]); + const target = this.pokemon.isPlayer() ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon(); if (!this.followUp && this.canMove()) @@ -685,7 +716,7 @@ export abstract class MovePhase extends BattlePhase { if (!failed.value && this.scene.arena.isMoveWeatherCancelled(this.move.getMove())) failed.value = true; if (failed.value) { - this.pokemon.summonData.moveHistory.push({ move: this.move.moveId, result: MoveResult.FAILED }); + this.pokemon.summonData.moveHistory.push({ move: this.move.moveId, result: MoveResult.FAILED, virtual: this.move.virtual }); this.scene.unshiftPhase(new MessagePhase(this.scene, 'But it failed!')); } else this.scene.unshiftPhase(this.getEffectPhase()); @@ -694,6 +725,8 @@ export abstract class MovePhase extends BattlePhase { }; if (!this.canMove()) { + if (this.move.isDisabled()) + this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.move.getName()} is disabled!`)); this.end(); return; } @@ -805,7 +838,7 @@ abstract class MoveEffectPhase extends PokemonPhase { if (!this.hitCheck()) { this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(user, '\'s\nattack missed!'))); - user.summonData.moveHistory.push({ move: this.move.moveId, result: MoveResult.MISSED }); + user.summonData.moveHistory.push({ move: this.move.moveId, result: MoveResult.MISSED, virtual: this.move.virtual }); applyMoveAttrs(MissEffectAttr, user, target, this.move.getMove()); this.end(); return; @@ -816,7 +849,7 @@ abstract class MoveEffectPhase extends PokemonPhase { new MoveAnim(this.move.getMove().id as Moves, user, target).play(this.scene, () => { const result = !isProtected ? target.apply(user, this.move) : MoveResult.NO_EFFECT; ++user.turnData.hitCount; - user.summonData.moveHistory.push({ move: this.move.moveId, result: result }); + user.summonData.moveHistory.push({ move: this.move.moveId, result: result, virtual: this.move.virtual }); applyMoveAttrs(MoveEffectAttr, user, target, this.move.getMove()); // Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present if (!isProtected && target.hp && !this.move.getMove().getAttrs(ChargeAttr).filter(ca => (ca as ChargeAttr).chargeEffect).length) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 3f2e2f8ee..a465ea3ca 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -10,13 +10,14 @@ import { PokeballType } from './pokeball'; import { Species } from './species'; import { initAutoPlay } from './auto-play'; import { Battle } from './battle'; -import { initCommonAnims, loadCommonAnimAssets, populateAnims } from './battle-anims'; +import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from './battle-anims'; import { BattlePhase } from './battle-phase'; import { initGameSpeed } from './game-speed'; import { Arena } from './arena'; import { GameData } from './game-data'; import StarterSelectUiHandler from './ui/starter-select-ui-handler'; import { TextStyle, addTextObject } from './text'; +import { Moves } from './move'; const enableAuto = true; export const startingLevel = 5; @@ -363,7 +364,11 @@ export default class BattleScene extends Phaser.Scene { ui.setup(); - Promise.all([ Promise.all(loadPokemonAssets), initCommonAnims().then(() => loadCommonAnimAssets(this, true)) ]).then(() => { + Promise.all([ + Promise.all(loadPokemonAssets), + initCommonAnims().then(() => loadCommonAnimAssets(this, true)), + initMoveAnim(Moves.STRUGGLE).then(() => loadMoveAnimAssets(this, [ Moves.STRUGGLE ], true)) + ]).then(() => { if (enableAuto) initAutoPlay.apply(this); diff --git a/src/move.ts b/src/move.ts index c1af70d46..618a5ab2c 100644 --- a/src/move.ts +++ b/src/move.ts @@ -1184,6 +1184,60 @@ export class MissEffectAttr extends MoveAttr { } } +export class DisableMoveAttr extends MoveEffectAttr { + constructor() { + super(false); + } + + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (!super.apply(user, target, move, args)) + return false; + + const moveQueue = target.getLastXMoves(); + let turnMove: TurnMove; + while (moveQueue.length) { + turnMove = moveQueue.shift(); + if (turnMove.virtual) + continue; + + const moveIndex = target.moveset.findIndex(m => m.moveId === turnMove.move); + if (moveIndex === -1) + return false; + + const disabledMove = target.moveset[moveIndex]; + disabledMove.disableTurns = 4; + + console.log(disabledMove); + + user.scene.unshiftPhase(new MessagePhase(user.scene, getPokemonMessage(target, `'s ${disabledMove.getName()}\nwas disabled!`))) + + return true; + } + + return false; + } +} + +export class DisableMoveConditionalMoveAttr extends ConditionalMoveAttr { + constructor() { + super((user: Pokemon, target: Pokemon, move: Move) => { + const moveQueue = target.getLastXMoves(); + let turnMove: TurnMove; + while (moveQueue.length) { + turnMove = moveQueue.shift(); + if (turnMove.virtual) + continue; + + const move = target.moveset.find(m => m.moveId === turnMove.move); + if (!move) + continue; + + return !move.isDisabled(); + } + }); + } +} + export class FrenzyAttr extends MoveEffectAttr { constructor() { super(true); @@ -1302,16 +1356,12 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { } export class RandomMoveAttr extends OverrideMoveEffectAttr { - constructor() { - super(); - } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { return new Promise(resolve => { const moveIds = Utils.getEnumValues(Moves).filter(m => m !== move.id); const moveId = moveIds[Utils.randInt(moveIds.length)]; user.summonData.moveQueue.push({ move: moveId, ignorePP: true }); - user.scene.unshiftPhase(user.isPlayer() ? new PlayerMovePhase(user.scene, user as PlayerPokemon, new PokemonMove(moveId), true) : new EnemyMovePhase(user.scene, user as EnemyPokemon, new PokemonMove(moveId), true)); + user.scene.unshiftPhase(user.isPlayer() ? new PlayerMovePhase(user.scene, user as PlayerPokemon, new PokemonMove(moveId, 0, 0, true), true) : new EnemyMovePhase(user.scene, user as EnemyPokemon, new PokemonMove(moveId, 0, 0, true), true)); initMoveAnim(moveId).then(() => { loadMoveAnimAssets(user.scene, [ moveId ], true) .then(() => resolve(true)); @@ -1374,7 +1424,7 @@ export const allMoves = [ new StatusMove(Moves.SING, "Sing", Type.NORMAL, 55, 15, -1, "Puts opponent to sleep.", -1, 0, 1, new StatusEffectAttr(StatusEffect.SLEEP)), new StatusMove(Moves.SUPERSONIC, "Supersonic", Type.NORMAL, 55, 20, -1, "Confuses opponent.", -1, 0, 1, new ConfuseAttr()), new AttackMove(Moves.SONIC_BOOM, "Sonic Boom", Type.NORMAL, MoveCategory.SPECIAL, -1, 90, 20, -1, "Always inflicts 20 HP.", -1, 0, 1, new FixedDamageAttr(20)), - new StatusMove(Moves.DISABLE, "Disable", Type.NORMAL, 100, 20, -1, "Opponent can't use its last attack for a few turns.", -1, 0, 1), + new StatusMove(Moves.DISABLE, "Disable", Type.NORMAL, 100, 20, -1, "Opponent can't use its last attack for a few turns.", -1, 0, 1, new DisableMoveConditionalMoveAttr(), new DisableMoveAttr()), new AttackMove(Moves.ACID, "Acid", Type.POISON, MoveCategory.SPECIAL, 40, 100, 30, -1, "May lower opponent's Special Defense.", 10, 0, 1, new StatChangeAttr(BattleStat.SPDEF, -1)), new AttackMove(Moves.EMBER, "Ember", Type.FIRE, MoveCategory.SPECIAL, 40, 100, 25, -1, "May burn opponent.", 10, 0, 1, new StatusEffectAttr(StatusEffect.BURN)), new AttackMove(Moves.FLAMETHROWER, "Flamethrower", Type.FIRE, MoveCategory.SPECIAL, 90, 100, 15, 125, "May burn opponent.", 10, 0, 1, new StatusEffectAttr(StatusEffect.BURN)), diff --git a/src/pokemon.ts b/src/pokemon.ts index be7170686..9d19b02f1 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -874,8 +874,14 @@ export class EnemyPokemon extends Pokemon { const queuedMove = this.summonData.moveQueue.length ? this.moveset.find(m => m.moveId === this.summonData.moveQueue[0].move) : null; - if (queuedMove && (this.summonData.moveQueue[0].ignorePP || queuedMove.isUsable())) - return queuedMove; + if (queuedMove) { + if (queuedMove.isUsable(this.summonData.moveQueue[0].ignorePP)) + return queuedMove; + else { + this.summonData.moveQueue.shift(); + return this.getNextMove(); + } + } const movePool = this.moveset.filter(m => m.isUsable()); if (movePool.length) { @@ -949,6 +955,7 @@ export class EnemyPokemon extends Pokemon { return sortedMovePool[r]; } } + return new PokemonMove(Moves.STRUGGLE, 0, 0); } @@ -973,6 +980,7 @@ export class EnemyPokemon extends Pokemon { export interface TurnMove { move: Moves; result: MoveResult; + virtual?: boolean; } export interface QueuedMove { @@ -1022,19 +1030,25 @@ export class PokemonMove { public moveId: Moves; public ppUsed: integer; public ppUp: integer; + public virtual: boolean; public disableTurns: integer; - constructor(moveId: Moves, ppUsed?: integer, ppUp?: integer) { + constructor(moveId: Moves, ppUsed?: integer, ppUp?: integer, virtual?: boolean) { this.moveId = moveId; this.ppUsed = ppUsed || 0; this.ppUp = ppUp || 0; + this.virtual = !!virtual; this.disableTurns = 0; } - isUsable(): boolean { - if (this.disableTurns > 0) + isUsable(ignorePp?: boolean): boolean { + if (this.isDisabled()) return false; - return this.ppUsed < this.getMove().pp + this.ppUp; + return ignorePp || this.ppUsed < this.getMove().pp + this.ppUp || this.getMove().pp === -1; + } + + isDisabled(): boolean { + return !!this.disableTurns; } getMove(): Move {