From 05432181b628a5be4e862df29487133b82e846b3 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Tue, 16 May 2023 14:47:14 -0400 Subject: [PATCH] Updates to double battles --- src/arena.ts | 2 +- src/battle-phases.ts | 211 +++++++++++++----------- src/battle-scene.ts | 4 +- src/battle.ts | 6 +- src/data/ability.ts | 33 ++-- src/data/arena-tag.ts | 6 +- src/data/battle-anims.ts | 4 +- src/data/battler-tag.ts | 32 ++-- src/data/berry.ts | 7 +- src/data/move.ts | 248 ++++++++++++++++++++++++----- src/modifier/modifier-type.ts | 8 +- src/modifier/modifier.ts | 4 +- src/pokemon.ts | 110 +++++++------ src/system/auto-play.ts | 2 +- src/ui/battle-info.ts | 3 + src/ui/party-ui-handler.ts | 2 +- src/ui/target-select-ui-handler.ts | 12 +- 17 files changed, 456 insertions(+), 238 deletions(-) diff --git a/src/arena.ts b/src/arena.ts index fd5fa24945a..5c011c4e368 100644 --- a/src/arena.ts +++ b/src/arena.ts @@ -161,7 +161,7 @@ export class Arena { this.weather = weather ? new Weather(weather, viaMove ? 5 : 0) : null; if (this.weather) { - this.scene.unshiftPhase(new CommonAnimPhase(this.scene, true, 0, 0, CommonAnim.SUNNY + (weather - 1))); + this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1))); this.scene.queueMessage(getWeatherStartMessage(weather)); } else this.scene.queueMessage(getWeatherClearMessage(oldWeatherType)); diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 42b6a139a69..f112df3fdaf 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -1,7 +1,7 @@ import BattleScene, { maxExpLevel, startingLevel, startingWave } from "./battle-scene"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult } from "./pokemon"; import * as Utils from './utils'; -import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveCategory, MoveEffectAttr, MoveFlags, MoveHitEffectAttr, Moves, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets } from "./data/move"; +import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveCategory, MoveEffectAttr, MoveFlags, MoveHitEffectAttr, Moves, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets, MoveTargetSet } from "./data/move"; import { Mode } from './ui/ui'; import { Command } from "./ui/command-ui-handler"; import { Stat } from "./data/pokemon-stat"; @@ -28,7 +28,7 @@ import { ArenaTagType, ArenaTrapTag, TrickRoomTag } from "./data/arena-tag"; import { CheckTrappedAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, SuppressWeatherEffectAbAttr, applyCheckTrappedAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./arena"; -import { BattleTarget, TurnCommand } from "./battle"; +import { BattlerIndex, TurnCommand } from "./battle"; export class CheckLoadPhase extends BattlePhase { private loaded: boolean; @@ -119,9 +119,9 @@ export class SelectStarterPhase extends BattlePhase { type PokemonFunc = (pokemon: Pokemon) => void; export abstract class FieldPhase extends BattlePhase { - getOrder(): BattleTarget[] { - const playerField = this.scene.getPlayerField() as Pokemon[]; - const enemyField = this.scene.getEnemyField() as Pokemon[]; + getOrder(): BattlerIndex[] { + const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[]; + const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[]; let orderedTargets: Pokemon[] = playerField.concat(enemyField).sort((a: Pokemon, b: Pokemon) => { const aSpeed = a?.getBattleStat(Stat.SPD) || 0; @@ -136,38 +136,47 @@ export abstract class FieldPhase extends BattlePhase { if (speedReversed.value) orderedTargets = orderedTargets.reverse(); - return orderedTargets.map(t => t.getFieldIndex() + (!t.isPlayer() ? BattleTarget.ENEMY : 0)); + return orderedTargets.map(t => t.getFieldIndex() + (!t.isPlayer() ? BattlerIndex.ENEMY : 0)); } executeForAll(func: PokemonFunc): void { - const field = this.scene.getField().filter(p => p?.hp); + const field = this.scene.getField().filter(p => p?.isActive()); field.forEach(pokemon => func(pokemon)); } } export abstract class PokemonPhase extends FieldPhase { + protected battlerIndex: BattlerIndex; protected player: boolean; protected fieldIndex: integer; - constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { super(scene); - this.player = player; - this.fieldIndex = fieldIndex; + if (battlerIndex === undefined) + battlerIndex = scene.getField().find(p => p?.isActive()).getBattlerIndex(); + + this.battlerIndex = battlerIndex; + this.player = battlerIndex < 2; + this.fieldIndex = battlerIndex % 2; } getPokemon() { - return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex || 0]; + return this.scene.getField()[this.battlerIndex]; } } -export abstract class PartyMemberPokemonPhase extends PokemonPhase { +export abstract class PartyMemberPokemonPhase extends FieldPhase { protected partyMemberIndex: integer; + protected fieldIndex: integer; constructor(scene: BattleScene, partyMemberIndex: integer) { - super(scene, true, partyMemberIndex); + super(scene); this.partyMemberIndex = partyMemberIndex; + this.fieldIndex = partyMemberIndex < this.scene.currentBattle.getBattlerCount() + ? partyMemberIndex + : -1; } getPokemon() { @@ -263,7 +272,7 @@ export class EncounterPhase extends BattlePhase { enemyField.forEach((enemyPokemon, e) => { if (enemyPokemon.shiny) - this.scene.unshiftPhase(new ShinySparklePhase(this.scene, false, e)); + this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e)); }); enemyField.forEach(enemyPokemon => this.scene.arena.applyTags(ArenaTrapTag, enemyPokemon)); @@ -308,7 +317,7 @@ export class NextEncounterPhase extends EncounterPhase { end() { this.scene.getEnemyField().forEach((enemyPokemon, e) => { if (enemyPokemon.shiny) - this.scene.unshiftPhase(new ShinySparklePhase(this.scene, false, e)); + this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e)); }); this.scene.unshiftPhase(new CheckSwitchPhase(this.scene, 0)); @@ -528,7 +537,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { playerField.forEach((pokemon, p) => { if (pokemon.shiny) - this.scene.unshiftPhase(new ShinySparklePhase(this.scene, true, p)); + this.scene.unshiftPhase(new ShinySparklePhase(this.scene, p)); }); playerField.forEach(pokemon => pokemon.resetTurnData()); @@ -636,7 +645,7 @@ export class CheckSwitchPhase extends BattlePhase { return; } - if (!this.scene.getParty().slice(1).filter(p => p.hp).length) { + if (!this.scene.getParty().slice(1).filter(p => p.isActive()).length) { super.end(); return; } @@ -679,19 +688,23 @@ export class TurnInitPhase extends FieldPhase { super.start(); this.scene.getPlayerField().forEach(playerPokemon => { - this.scene.currentBattle.addParticipant(playerPokemon); + if (playerPokemon.isActive()) + this.scene.currentBattle.addParticipant(playerPokemon); playerPokemon.resetTurnData(); }); - this.scene.getEnemyField().forEach(enemyPokemon => enemyPokemon.resetTurnData()); + this.scene.getEnemyField().forEach(enemyPokemon => { + if (enemyPokemon.isActive()) + enemyPokemon.resetTurnData() + }); const order = this.getOrder(); for (let o of order) { - if (o < BattleTarget.ENEMY) + if (o < BattlerIndex.ENEMY) this.scene.pushPhase(new CommandPhase(this.scene, o)); else - this.scene.pushPhase(new EnemyCommandPhase(this.scene, o - BattleTarget.ENEMY)); + this.scene.pushPhase(new EnemyCommandPhase(this.scene, o - BattlerIndex.ENEMY)); } this.scene.pushPhase(new TurnStartPhase(this.scene)); @@ -727,8 +740,10 @@ export class CommandPhase extends FieldPhase { this.handleCommand(Command.FIGHT, -1, false); else { const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move); - if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex].isUsable(queuedMove.ignorePP)) - this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP); + if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex].isUsable(queuedMove.ignorePP)) { + this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, { targets: queuedMove.targets, multiple: queuedMove.targets.length > 1 }); + } else + this.scene.ui.setMode(Mode.COMMAND); } } else this.scene.ui.setMode(Mode.COMMAND); @@ -743,10 +758,11 @@ export class CommandPhase extends FieldPhase { case Command.FIGHT: if (cursor === -1 || playerPokemon.trySelectMove(cursor, args[0] as boolean)) { const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, - move: cursor > -1 ? { move: playerPokemon.moveset[cursor].moveId } : null, args: args }; // TODO: Struggle logic - const moveTargets = getMoveTargets(playerPokemon, playerPokemon.moveset[cursor].moveId); + move: cursor > -1 ? { move: playerPokemon.moveset[cursor].moveId, targets: [] } : null, args: args }; // TODO: Struggle logic + const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, cursor > -1 ? playerPokemon.moveset[cursor].moveId : Moves.NONE) : args[2]; + console.log(moveTargets, playerPokemon.name); if (moveTargets.targets.length <= 1 || moveTargets.multiple) - turnCommand.targets = moveTargets.targets; + turnCommand.move.targets = moveTargets.targets; else this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; @@ -827,10 +843,9 @@ export class EnemyCommandPhase extends FieldPhase { const enemyPokemon = this.scene.getEnemyField()[this.fieldIndex]; const nextMove = enemyPokemon.getNextMove(); - const moveTargets = getMoveTargets(enemyPokemon, nextMove.move); - this.scene.currentBattle.turnCommands[this.fieldIndex + BattleTarget.ENEMY] = - { command: Command.FIGHT, move: nextMove, targets: !moveTargets.multiple ? [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ] : moveTargets.targets }; + this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = + { command: Command.FIGHT, move: nextMove }; this.end(); } @@ -838,13 +853,13 @@ export class EnemyCommandPhase extends FieldPhase { export class SelectTargetPhase extends PokemonPhase { constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, true, fieldIndex); + super(scene, fieldIndex); } start() { super.start(); - const move = this.scene.currentBattle.turnCommands[this.fieldIndex].move?.move || Moves.NONE; + const move = this.scene.currentBattle.turnCommands[this.fieldIndex].move?.move; this.scene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (cursor: integer) => { this.scene.ui.setMode(Mode.MESSAGE); if (cursor === -1) { @@ -909,13 +924,13 @@ export class TurnStartPhase extends FieldPhase { const move = pokemon.getMoveset().find(m => m.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); if (pokemon.isPlayer()) { if (turnCommand.cursor === -1) - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets, move)); + this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move)); else { - const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets, move, false, queuedMove.ignorePP); + const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move, false, queuedMove.ignorePP); this.scene.pushPhase(playerPhase); } } else - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets, move, false, queuedMove.ignorePP)); + this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move, false, queuedMove.ignorePP)); break; case Command.BALL: this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets[0] % 2, turnCommand.cursor)); @@ -933,7 +948,7 @@ export class TurnStartPhase extends FieldPhase { for (let o of order) { if (field[o].status && field[o].status.isPostTurn()) - this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, !Math.floor(o / 2), o % 2)); + this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o)); } this.scene.pushPhase(new TurnEndPhase(this.scene)); @@ -953,9 +968,6 @@ export class TurnEndPhase extends FieldPhase { this.scene.currentBattle.incrementTurn(); const handlePokemon = (pokemon: Pokemon) => { - if (!pokemon || !pokemon.hp) - return; - pokemon.lapseTags(BattlerTagLapseType.TURN_END); const disabledMoves = pokemon.getMoveset().filter(m => m.isDisabled()); @@ -966,7 +978,7 @@ export class TurnEndPhase extends FieldPhase { const hasUsableBerry = !!this.scene.findModifier(m => m instanceof BerryModifier && m.shouldApply([ pokemon ]), pokemon.isPlayer()); if (hasUsableBerry) - this.scene.pushPhase(new BerryPhase(this.scene, pokemon.isPlayer(), pokemon.getFieldIndex())); + this.scene.pushPhase(new BerryPhase(this.scene, pokemon.getBattlerIndex())); this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); @@ -1017,8 +1029,11 @@ export class CommonAnimPhase extends PokemonPhase { private anim: CommonAnim; private targetIndex: integer; - constructor(scene: BattleScene, player: boolean, fieldIndex: integer, targetIndex: integer, anim: CommonAnim) { - super(scene, player, fieldIndex); + constructor(scene: BattleScene, battlerIndex: BattlerIndex, targetIndex: BattlerIndex, anim: CommonAnim) { + super(scene, battlerIndex); + + if (targetIndex === undefined) + targetIndex = this.battlerIndex; this.anim = anim; this.targetIndex = targetIndex; @@ -1033,13 +1048,13 @@ export class CommonAnimPhase extends PokemonPhase { export class MovePhase extends BattlePhase { protected pokemon: Pokemon; - protected targets: BattleTarget[]; + protected targets: BattlerIndex[]; protected move: PokemonMove; protected followUp: boolean; protected ignorePp: boolean; protected cancelled: boolean; - constructor(scene: BattleScene, pokemon: Pokemon, targets: BattleTarget[], move: PokemonMove, followUp?: boolean, ignorePp?: boolean) { + constructor(scene: BattleScene, pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp?: boolean, ignorePp?: boolean) { super(scene); this.pokemon = pokemon; @@ -1063,8 +1078,6 @@ export class MovePhase extends BattlePhase { console.log(Moves[this.move.moveId]); - console.log(this.scene.currentBattle.turnCommands); - if (!this.canMove()) { if (this.move.isDisabled()) this.scene.queueMessage(`${this.move.getName()} is disabled!`); @@ -1074,7 +1087,15 @@ export class MovePhase extends BattlePhase { console.log(this.targets); - const targets = this.scene.getField().filter(p => p && p.hp && this.targets.indexOf(p.getBattleTarget()) > -1); + const targets = this.scene.getField().filter(p => { + if (p?.isActive() && this.targets.indexOf(p.getBattlerIndex()) > -1) { + const hiddenTag = p.getTag(HiddenTag); + if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length) + return false; + return true; + } + return false; + }); if (!this.followUp && this.canMove()) this.pokemon.lapseTags(BattlerTagLapseType.MOVE); @@ -1137,7 +1158,7 @@ export class MovePhase extends BattlePhase { } if (activated) { this.scene.queueMessage(getPokemonMessage(this.pokemon, getStatusEffectActivationText(this.pokemon.status.effect))); - this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.isPlayer(), this.pokemon.getFieldIndex(), this.pokemon.getBattleTarget(), CommonAnim.POISON + (this.pokemon.status.effect - 1))); + this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.POISON + (this.pokemon.status.effect - 1))); doMove(); } else { if (healed) { @@ -1152,12 +1173,12 @@ export class MovePhase extends BattlePhase { } getEffectPhase(): MoveEffectPhase { - return new MoveEffectPhase(this.scene, this.pokemon.isPlayer(), this.pokemon.getFieldIndex(), this.targets, this.move); + return new MoveEffectPhase(this.scene, this.pokemon.getBattlerIndex(), this.targets, this.move); } end() { if (!this.followUp && this.canMove()) - this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.isPlayer(), this.pokemon.getFieldIndex())); + this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex())); super.end(); } @@ -1165,10 +1186,10 @@ export class MovePhase extends BattlePhase { class MoveEffectPhase extends PokemonPhase { protected move: PokemonMove; - protected targets: BattleTarget[]; + protected targets: BattlerIndex[]; - constructor(scene: BattleScene, player: boolean, fieldIndex: integer, targets: BattleTarget[], move: PokemonMove) { - super(scene, player, fieldIndex); + constructor(scene: BattleScene, battlerIndex: BattlerIndex, targets: BattlerIndex[], move: PokemonMove) { + super(scene, battlerIndex); this.move = move; this.targets = targets; @@ -1203,7 +1224,7 @@ class MoveEffectPhase extends PokemonPhase { const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual }; user.pushMoveHistory(moveHistoryEntry); - const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattleTarget(), this.hitCheck(p) ])); + const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); if (targets.length === 1 && !targetHitChecks[this.targets[0]]) { this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!')); moveHistoryEntry.result = MoveResult.MISS; @@ -1213,9 +1234,9 @@ class MoveEffectPhase extends PokemonPhase { } // Move animation only needs one target - new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattleTarget()).play(this.scene, () => { + new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => { for (let target of targets) { - if (!targetHitChecks[target.getBattleTarget()]) { + if (!targetHitChecks[target.getBattlerIndex()]) { this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!')); if (moveHistoryEntry.result === MoveResult.PENDING) moveHistoryEntry.result = MoveResult.MISS; @@ -1242,7 +1263,7 @@ class MoveEffectPhase extends PokemonPhase { } if (!isProtected && !chargeEffect) { applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveHitEffectAttr && (!!target.hp || (attr as MoveHitEffectAttr).selfTarget), user, target, this.move.getMove()); - if (target.hp) + if (!target.isFainted()) applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult); if (this.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex()); @@ -1256,7 +1277,7 @@ class MoveEffectPhase extends PokemonPhase { end() { const user = this.getUserPokemon(); - if (--user.turnData.hitsLeft >= 1 && this.getTarget()?.hp) + if (--user.turnData.hitsLeft >= 1 && !this.getTarget().isFainted()) this.scene.unshiftPhase(this.getNewHitPhase()); else { if (user.turnData.hitCount > 1) @@ -1272,10 +1293,8 @@ class MoveEffectPhase extends PokemonPhase { return true; const hiddenTag = target.getTag(HiddenTag); - if (hiddenTag) { - if (!this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length) - return false; - } + if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length) + return false; if (this.getUserPokemon().getTag(BattlerTagType.IGNORE_ACCURACY)) return true; @@ -1312,7 +1331,7 @@ class MoveEffectPhase extends PokemonPhase { } getTargets(): Pokemon[] { - return this.scene.getField().filter(p => p?.hp && this.targets.indexOf(p.getBattleTarget()) > -1); + return this.scene.getField().filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1); } getTarget(): Pokemon { @@ -1320,13 +1339,13 @@ class MoveEffectPhase extends PokemonPhase { } getNewHitPhase() { - return new MoveEffectPhase(this.scene, this.player, this.fieldIndex, this.targets, this.move); + return new MoveEffectPhase(this.scene, this.battlerIndex, this.targets, this.move); } } export class MoveEndPhase extends PokemonPhase { - constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { - super(scene, player, fieldIndex); + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); } start() { @@ -1375,8 +1394,8 @@ export class MoveAnimTestPhase extends BattlePhase { } export class ShowAbilityPhase extends PokemonPhase { - constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { - super(scene, player, fieldIndex); + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); } start() { @@ -1391,10 +1410,8 @@ export class StatChangePhase extends PokemonPhase { private selfTarget: boolean; private levels: integer; - constructor(scene: BattleScene, player: boolean, fieldIndex: integer, selfTarget: boolean, stats: BattleStat[], levels: integer) { - super(scene, player, fieldIndex); - - console.log(this.player, this.fieldIndex); + constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: integer) { + super(scene, battlerIndex); const allStats = Utils.getEnumValues(BattleStat); this.selfTarget = selfTarget; @@ -1482,7 +1499,7 @@ export class WeatherEffectPhase extends CommonAnimPhase { private weather: Weather; constructor(scene: BattleScene, weather: Weather) { - super(scene, true, 0, 0, CommonAnim.SUNNY + (weather.weatherType - 1)); + super(scene, undefined, undefined, CommonAnim.SUNNY + (weather.weatherType - 1)); this.weather = weather; } @@ -1503,7 +1520,7 @@ export class WeatherEffectPhase extends CommonAnimPhase { return; this.scene.queueMessage(getWeatherDamageMessage(this.weather.weatherType, pokemon)); - this.scene.unshiftPhase(new DamagePhase(this.scene, pokemon.isPlayer(), pokemon.getFieldIndex())); + this.scene.unshiftPhase(new DamagePhase(this.scene, pokemon.getBattlerIndex())); pokemon.damage(Math.ceil(pokemon.getMaxHp() / 16)); }; @@ -1528,8 +1545,8 @@ export class ObtainStatusEffectPhase extends PokemonPhase { private cureTurn: integer; private sourceText: string; - constructor(scene: BattleScene, player: boolean, fieldIndex: integer, statusEffect: StatusEffect, cureTurn?: integer, sourceText?: string) { - super(scene, player, fieldIndex); + constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect: StatusEffect, cureTurn?: integer, sourceText?: string) { + super(scene, battlerIndex); this.statusEffect = statusEffect; this.cureTurn = cureTurn; @@ -1546,7 +1563,7 @@ export class ObtainStatusEffectPhase extends PokemonPhase { new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene, () => { this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectObtainText(this.statusEffect, this.sourceText))); if (pokemon.status.isPostTurn()) - this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.player, this.fieldIndex)); + this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex)); this.end(); }); return; @@ -1558,13 +1575,13 @@ export class ObtainStatusEffectPhase extends PokemonPhase { } export class PostTurnStatusEffectPhase extends PokemonPhase { - constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { - super(scene, player, fieldIndex); + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); } start() { const pokemon = this.getPokemon(); - if (pokemon?.hp && pokemon.status && pokemon.status.isPostTurn()) { + if (pokemon?.isActive() && pokemon.status && pokemon.status.isPostTurn()) { pokemon.status.incrementTurn(); new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => { this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect))); @@ -1616,8 +1633,8 @@ export class MessagePhase extends BattlePhase { export class DamagePhase extends PokemonPhase { private damageResult: DamageResult; - constructor(scene: BattleScene, player: boolean, fieldIndex: integer, damageResult?: DamageResult) { - super(scene, player, fieldIndex); + constructor(scene: BattleScene, battlerIndex: BattlerIndex, damageResult?: DamageResult) { + super(scene, battlerIndex); this.damageResult = damageResult || HitResult.EFFECTIVE; } @@ -1654,8 +1671,8 @@ export class DamagePhase extends PokemonPhase { } export class FaintPhase extends PokemonPhase { - constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { - super(scene, player, fieldIndex); + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); } start() { @@ -1664,7 +1681,11 @@ export class FaintPhase extends PokemonPhase { this.scene.queueMessage(getPokemonMessage(this.getPokemon(), ' fainted!'), null, true); if (this.player) { - this.scene.unshiftPhase(this.scene.getParty().filter(p => p.hp).length ? new SwitchPhase(this.scene, this.fieldIndex, true, false) : new GameOverPhase(this.scene)); + const nonFaintedPartyMemberCount = this.scene.getParty().filter(p => !p.isFainted()).length; + if (!nonFaintedPartyMemberCount) + this.scene.unshiftPhase(new GameOverPhase(this.scene)); + else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount()) + this.scene.unshiftPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); } else this.scene.unshiftPhase(new VictoryPhase(this.scene, this.fieldIndex)); @@ -1697,7 +1718,7 @@ export class FaintPhase extends PokemonPhase { export class VictoryPhase extends PokemonPhase { constructor(scene: BattleScene, targetIndex: integer) { - super(scene, true, targetIndex); + super(scene, targetIndex); } start() { @@ -1764,7 +1785,7 @@ export class VictoryPhase extends PokemonPhase { } } - if (!this.scene.currentBattle.enemyField.filter(p => p?.hp).length) { + if (!this.scene.currentBattle.enemyField.filter(p => p.isActive()).length) { this.scene.pushPhase(new BattleEndPhase(this.scene)); if (this.scene.currentBattle.waveIndex < this.scene.finalWave) { this.scene.pushPhase(new SelectModifierPhase(this.scene)); @@ -2014,8 +2035,8 @@ export class LearnMovePhase extends PartyMemberPokemonPhase { } export class BerryPhase extends CommonAnimPhase { - constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { - super(scene, player, fieldIndex, 0, CommonAnim.USE_ITEM); + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex, undefined, CommonAnim.USE_ITEM); } start() { @@ -2043,8 +2064,8 @@ export class PokemonHealPhase extends CommonAnimPhase { private showFullHpMessage: boolean; private skipAnim: boolean; - constructor(scene: BattleScene, player: boolean, fieldIndex: integer, hpHealed: integer, message: string, showFullHpMessage: boolean, skipAnim?: boolean) { - super(scene, player, fieldIndex, 0, CommonAnim.HEALTH_UP); + constructor(scene: BattleScene, battlerIndex: BattlerIndex, hpHealed: integer, message: string, showFullHpMessage: boolean, skipAnim?: boolean) { + super(scene, battlerIndex, undefined, CommonAnim.HEALTH_UP); this.hpHealed = hpHealed; this.message = message; @@ -2062,7 +2083,7 @@ export class PokemonHealPhase extends CommonAnimPhase { end() { const pokemon = this.getPokemon(); - if (!this.getPokemon().hp) { + if (!this.getPokemon().isActive()) { super.end(); return; } @@ -2091,7 +2112,7 @@ export class AttemptCapturePhase extends PokemonPhase { private originalY: number; constructor(scene: BattleScene, targetIndex: integer, pokeballType: PokeballType) { - super(scene, false, targetIndex); + super(scene, BattlerIndex.ENEMY + targetIndex); this.pokeballType = pokeballType; } @@ -2282,7 +2303,7 @@ export class AttemptCapturePhase extends PokemonPhase { export class AttemptRunPhase extends PokemonPhase { constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, true, fieldIndex); + super(scene, fieldIndex); } start() { @@ -2387,8 +2408,8 @@ export class SelectModifierPhase extends BattlePhase { } export class ShinySparklePhase extends PokemonPhase { - constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { - super(scene, player, fieldIndex); + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); } start() { diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 32e38180f6a..771272167e4 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -449,7 +449,7 @@ export default class BattleScene extends Phaser.Scene { } getPlayerPokemon(): PlayerPokemon { - return this.getPlayerField().find(() => true); + return this.getPlayerField().find(p => p.isActive()); } getPlayerField(): PlayerPokemon[] { @@ -458,7 +458,7 @@ export default class BattleScene extends Phaser.Scene { } getEnemyPokemon(): EnemyPokemon { - return this.currentBattle?.enemyField.find(() => true); + return this.getEnemyField().find(p => p.isActive()); } getEnemyField(): EnemyPokemon[] { diff --git a/src/battle.ts b/src/battle.ts index 0fae8c419db..ccf99d5c536 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -2,7 +2,7 @@ import { EnemyPokemon, PlayerPokemon, QueuedMove } from "./pokemon"; import { Command } from "./ui/command-ui-handler"; import * as Utils from "./utils"; -export enum BattleTarget { +export enum BattlerIndex { PLAYER, PLAYER_2, ENEMY, @@ -13,7 +13,7 @@ export interface TurnCommand { command: Command; cursor?: integer; move?: QueuedMove; - targets?: BattleTarget[]; + targets?: BattlerIndex[]; args?: any[]; }; @@ -61,7 +61,7 @@ export default class Battle { incrementTurn(): void { this.turn++; - this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattleTarget).map(bt => [ bt, null ])); + this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ])); } addParticipant(playerPokemon: PlayerPokemon): void { diff --git a/src/data/ability.ts b/src/data/ability.ts index 1027c0121ac..aefdd99c96b 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -157,7 +157,7 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { if (ret && pokemon.getHpRatio() < 1) { const scene = pokemon.scene; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), pokemon.getFieldIndex(), Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true)); + scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true)); return true; } @@ -181,7 +181,7 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr { if (ret) { cancelled.value = true; - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), true, [ this.stat ], this.levels)); + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels)); } return ret; @@ -406,13 +406,21 @@ export class PostSummonStatChangeAbAttr extends PostSummonAbAttr { } applyPostSummon(pokemon: Pokemon, args: any[]): boolean { - const statChangePhase = new StatChangePhase(pokemon.scene, pokemon.isPlayer() === this.selfTarget, pokemon.getFieldIndex(), this.selfTarget, this.stats, this.levels); + const statChangePhases: StatChangePhase[] = []; - if (!this.selfTarget && !pokemon.getOpponent(0)?.summonData) - pokemon.scene.pushPhase(statChangePhase); // TODO: This causes the ability bar to be shown at the wrong time - else - pokemon.scene.unshiftPhase(statChangePhase); + if (this.selfTarget) + statChangePhases.push(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels)); + else { + for (let opponent of pokemon.getOpponents()) + statChangePhases.push(new StatChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.levels)); + } + for (let statChangePhase of statChangePhases) { + if (!this.selfTarget && !statChangePhase.getPokemon().summonData) + pokemon.scene.pushPhase(statChangePhase); // TODO: This causes the ability bar to be shown at the wrong time + else + pokemon.scene.unshiftPhase(statChangePhase); + } return true; } @@ -576,7 +584,7 @@ export class PostTurnAbAttr extends AbAttr { export class PostTurnSpeedBoostAbAttr extends PostTurnAbAttr { applyPostTurn(pokemon: Pokemon, args: any[]): boolean { - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), true, [ BattleStat.SPD ], 1)); + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.SPD ], 1)); return true; } } @@ -594,7 +602,7 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr { applyPostTurn(pokemon: Pokemon, args: any[]): boolean { if (pokemon.getHpRatio() < 1) { const scene = pokemon.scene; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), pokemon.getFieldIndex(), + scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true)); return true; } @@ -633,7 +641,8 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr { applyPostWeatherLapse(pokemon: Pokemon, weather: Weather, args: any[]): boolean { if (pokemon.getHpRatio() < 1) { const scene = pokemon.scene; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), pokemon.getFieldIndex(), Math.max(Math.floor(pokemon.getMaxHp() / (16 / this.healFactor)), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true)); + scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), + Math.max(Math.floor(pokemon.getMaxHp() / (16 / this.healFactor)), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true)); return true; } @@ -654,7 +663,7 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { if (pokemon.getHpRatio() < 1) { const scene = pokemon.scene; scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby its ${pokemon.getAbility()}!`)); - scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), HitResult.OTHER)); + scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER)); pokemon.damage(Math.ceil(pokemon.getMaxHp() * (16 / this.damageFactor))); return true; } @@ -989,7 +998,7 @@ function canApplyAttr(pokemon: Pokemon, attr: AbAttr): boolean { } function queueShowAbility(pokemon: Pokemon): void { - pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex())); + pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex())); pokemon.scene.clearPhaseQueueSplice(); } diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 44e600b35d5..bf6383f75e4 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -151,7 +151,7 @@ class SpikesTag extends ArenaTrapTag { const damageHpRatio = 1 / (10 - 2 * this.layers); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is hurt\nby the spikes!')); - pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), HitResult.OTHER)); + pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER)); pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio)); return true; } @@ -176,7 +176,7 @@ class ToxicSpikesTag extends ArenaTrapTag { if (!pokemon.status && (!pokemon.isOfType(Type.FLYING) || pokemon.getTag(BattlerTagType.IGNORE_FLYING) || pokemon.scene.arena.getTag(ArenaTagType.GRAVITY))) { const toxic = this.layers > 1; - pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), + pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.getBattlerIndex(), !toxic ? StatusEffect.POISON : StatusEffect.TOXIC, null, `the ${this.getMoveName()}`)); return true; } @@ -225,7 +225,7 @@ class StealthRockTag extends ArenaTrapTag { if (damageHpRatio) { pokemon.scene.queueMessage(`Pointed stones dug into\n${pokemon.name}!`); - pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), HitResult.OTHER)); + pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER)); pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio)); } diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index d93cb8b0c76..516db0492bd 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -3,7 +3,7 @@ import BattleScene from "../battle-scene"; import { ChargeAttr, Moves, allMoves } from "./move"; import Pokemon from "../pokemon"; import * as Utils from "../utils"; -import { BattleTarget } from "../battle"; +import { BattlerIndex } from "../battle"; //import fs from 'vite-plugin-fs/browser'; export enum AnimFrameTarget { @@ -832,7 +832,7 @@ export class CommonBattleAnim extends BattleAnim { export class MoveAnim extends BattleAnim { public move: Moves; - constructor(move: Moves, user: Pokemon, target: BattleTarget) { + constructor(move: Moves, user: Pokemon, target: BattlerIndex) { super(user, user.scene.getField()[target]); this.move = move; diff --git a/src/data/battler-tag.ts b/src/data/battler-tag.ts index d593bd571c0..27572f710d8 100644 --- a/src/data/battler-tag.ts +++ b/src/data/battler-tag.ts @@ -103,7 +103,7 @@ export class RechargingTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.getMoveQueue().push({ move: Moves.NONE }) + pokemon.getMoveQueue().push({ move: Moves.NONE, targets: [] }) } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -177,7 +177,7 @@ export class ConfusedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, CommonAnim.CONFUSION)); + pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION)); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' became\nconfused!')); } @@ -198,14 +198,14 @@ export class ConfusedTag extends BattlerTag { if (ret) { pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nconfused!')); - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, CommonAnim.CONFUSION)); + pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION)); if (Utils.randInt(2)) { const atk = pokemon.getBattleStat(Stat.ATK); const def = pokemon.getBattleStat(Stat.DEF); const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * ((Utils.randInt(15) + 85) / 100)); pokemon.scene.queueMessage('It hurt itself in its\nconfusion!'); - pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex())); + pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex())); pokemon.damage(damage); (pokemon.scene.getCurrentPhase() as MovePhase).cancel(); } @@ -245,7 +245,7 @@ export class InfatuatedTag extends BattlerTag { if (ret) { pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is in love\nwith ${pokemon.scene.getPokemonById(this.sourceId).name}!`)); - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, CommonAnim.ATTRACT)); + pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT)); if (Utils.randInt(2)) { pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nimmobilized by love!')); @@ -283,12 +283,12 @@ export class SeedTag extends BattlerTag { if (ret) { const source = pokemon.scene.getPokemonById(this.sourceId); - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.isPlayer(), source.getFieldIndex(), pokemon.getFieldIndex(), CommonAnim.LEECH_SEED)); + pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED)); const damage = Math.max(Math.floor(pokemon.getMaxHp() / 8), 1); - pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex())); + pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex())); pokemon.damage(damage); - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, !source.isPlayer(), source.getFieldIndex(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by LEECH SEED!'), false, true)); + pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by LEECH SEED!'), false, true)); } return ret; @@ -321,10 +321,10 @@ export class NightmareTag extends BattlerTag { if (ret) { pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is locked\nin a NIGHTMARE!')); - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, CommonAnim.CURSE)); // TODO: Update animation type + pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type const damage = Math.ceil(pokemon.getMaxHp() / 4); - pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex())); + pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex())); pokemon.damage(damage); } @@ -345,7 +345,7 @@ export class IngrainTag extends TrappedTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), Math.floor(pokemon.getMaxHp() / 16), + pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), Math.floor(pokemon.getMaxHp() / 16), getPokemonMessage(pokemon, ` absorbed\nnutrients with its roots!`), true)); return ret; @@ -375,7 +375,7 @@ export class AquaRingTag extends BattlerTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), + pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), Math.floor(pokemon.getMaxHp() / 16), `${this.getMoveName()} restored\n${pokemon.name}\'s HP!`, true)); return ret; @@ -395,7 +395,7 @@ export class DrowsyTag extends BattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (!super.lapse(pokemon, lapseType)) { - pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), StatusEffect.SLEEP)); + pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.getBattlerIndex(), StatusEffect.SLEEP)); return false; } @@ -425,10 +425,10 @@ export abstract class DamagingTrapTag extends TrappedTag { if (ret) { pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby ${this.getMoveName()}!`)); - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, this.commonAnim)); + pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim)); const damage = Math.ceil(pokemon.getMaxHp() / 16); - pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex())); + pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex())); pokemon.damage(damage); } @@ -543,7 +543,7 @@ export class TruantTag extends BattlerTag { if (lastMove && lastMove.move !== Moves.NONE) { (pokemon.scene.getCurrentPhase() as MovePhase).cancel(); - pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex())); + pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex())); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nloafing around!')); } diff --git a/src/data/berry.ts b/src/data/berry.ts index 060f4ad6e86..4cb9fcdf945 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -78,7 +78,8 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { case BerryType.SITRUS: case BerryType.ENIGMA: return (pokemon: Pokemon) => { - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true)); + pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), + Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true)); }; case BerryType.LUM: return (pokemon: Pokemon) => { @@ -96,13 +97,13 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { case BerryType.APICOT: return (pokemon: Pokemon) => { const battleStat = (berryType - BerryType.LIECHI) as BattleStat; - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), true, [ battleStat ], 1)); + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], 1)); }; case BerryType.LANSAT: return (pokemon: Pokemon) => { pokemon.addTag(BattlerTagType.CRIT_BOOST); }; case BerryType.STARF: - return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), true, [ BattleStat.RAND ], 2)); + return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.RAND ], 2)); } } \ No newline at end of file diff --git a/src/data/move.ts b/src/data/move.ts index dad72ff31df..12957ddb91a 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -11,7 +11,8 @@ import { WeatherType } from "./weather"; import { ArenaTagType, ArenaTrapTag } from "./arena-tag"; import { BlockRecoilDamageAttr, applyAbAttrs } from "./ability"; import { PokemonHeldItemModifier } from "../modifier/modifier"; -import { BattleTarget } from "../battle"; +import { BattlerIndex } from "../battle"; +import { Stat } from "./pokemon-stat"; export enum MoveCategory { PHYSICAL, @@ -160,12 +161,67 @@ export default class Move { return true; } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + let score = 0; + + for (let attr of this.attrs) + score += attr.getUserBenefitScore(user, target, move); + + return score; + } + + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + let score = 0; + + for (let attr of this.attrs) + score += attr.getTargetBenefitScore(user, target, move); + + return score; + } } export class AttackMove extends Move { constructor(id: Moves, name: string, type: Type, category: MoveCategory, power: integer, accuracy: integer, pp: integer, tm: integer, effect: string, chance: integer, priority: integer, generation: integer) { super(id, name, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, tm, effect, chance, priority, generation); } + + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + let ret = super.getTargetBenefitScore(user, target, move); + + let attackScore = 0; + + const effectiveness = target.getAttackMoveEffectiveness(this.type); + attackScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2; + if (attackScore) { + if (this.category === MoveCategory.PHYSICAL) { + if (user.getBattleStat(Stat.ATK) > user.getBattleStat(Stat.SPATK)) { + const statRatio = user.getBattleStat(Stat.SPATK) / user.getBattleStat(Stat.ATK); + if (statRatio <= 0.75) + attackScore *= 2; + else if (statRatio <= 0.875) + attackScore *= 1.5; + } + } else { + if (user.getBattleStat(Stat.SPATK) > user.getBattleStat(Stat.ATK)) { + const statRatio = user.getBattleStat(Stat.ATK) / user.getBattleStat(Stat.SPATK); + if (statRatio <= 0.75) + attackScore *= 2; + else if (statRatio <= 0.875) + attackScore *= 1.5; + } + } + + const power = new Utils.NumberHolder(this.power); + applyMoveAttrs(VariablePowerAttr, user, target, move, power); + + attackScore += Math.floor(power.value / 5); + } + + ret -= attackScore; + + return ret; + } } export class StatusMove extends Move { @@ -756,6 +812,14 @@ export abstract class MoveAttr { getCondition(): MoveCondition { return null; } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + return 0; + } + + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + return 0; + } } export class MoveEffectAttr extends MoveAttr { @@ -793,6 +857,10 @@ export class HighCritAttr extends MoveAttr { return true; } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + return 3; + } } export class CritOnlyAttr extends MoveAttr { @@ -801,6 +869,10 @@ export class CritOnlyAttr extends MoveAttr { return true; } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + return 5; + } } export class FixedDamageAttr extends MoveAttr { @@ -890,12 +962,16 @@ export class RecoilAttr extends MoveEffectAttr { return false; const recoilDamage = Math.max(Math.floor((!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) / 4), 1); - user.scene.unshiftPhase(new DamagePhase(user.scene, user.isPlayer(), user.getFieldIndex(), HitResult.OTHER)); + user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER)); user.scene.queueMessage(getPokemonMessage(user, ' is hit\nwith recoil!')); user.damage(recoilDamage); return true; } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + return Math.floor((move.power / 5) / -4); + } } export class SacrificialAttr extends MoveEffectAttr { @@ -907,11 +983,15 @@ export class SacrificialAttr extends MoveEffectAttr { if (!super.apply(user, target, move, args)) return false; - user.scene.unshiftPhase(new DamagePhase(user.scene, user.isPlayer(), user.getFieldIndex(), HitResult.OTHER)); + user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER)); user.damage(user.getMaxHp()); return true; } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + return Math.ceil((1 - user.getHpRatio()) * 10) - 10; + } } export enum MultiHitType { @@ -937,9 +1017,13 @@ export class HealAttr extends MoveEffectAttr { } addHealPhase(user: Pokemon, healRatio: number) { - user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), user.getFieldIndex(), + user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), Math.max(Math.floor(user.getMaxHp() * healRatio), 1), getPokemonMessage(user, ' regained\nhealth!'), true, !this.showAnim)); } + + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + return (1 - (this.selfTarget ? user : target).getHpRatio()) * 20; + } } export class WeatherHealAttr extends HealAttr { @@ -979,10 +1063,14 @@ export class HitHealAttr extends MoveHitEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), user.getFieldIndex(), + user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1), getPokemonMessage(target, ` had its\nenergy drained!`), false, true)); return true; } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + return Math.floor(Math.max((1 - user.getHpRatio()) - 0.33, 0) * ((move.power / 5) / 4)); + } } export class MultiHitAttr extends MoveAttr { @@ -1019,6 +1107,10 @@ export class MultiHitAttr extends MoveAttr { (args[0] as Utils.IntegerHolder).value = hitTimes; return true; } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + return 5; + } } export class StatusEffectAttr extends MoveHitEffectAttr { @@ -1037,12 +1129,16 @@ export class StatusEffectAttr extends MoveHitEffectAttr { if (statusCheck) { const pokemon = this.selfTarget ? user : target; if (!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0)) { - user.scene.unshiftPhase(new ObtainStatusEffectPhase(user.scene, pokemon.isPlayer(), this.effect, this.cureTurn)); + user.scene.unshiftPhase(new ObtainStatusEffectPhase(user.scene, pokemon.getBattlerIndex(), this.effect, this.cureTurn)); return true; } } return false; } + + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + return !(this.selfTarget ? user : target).status ? Math.floor(move.chance * -0.1) : 0; + } } export class StealHeldItemAttr extends MoveHitEffectAttr { @@ -1051,8 +1147,7 @@ export class StealHeldItemAttr extends MoveHitEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const heldItems = user.scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; + const heldItems = this.getTargetHeldItems(target); if (heldItems.length) { const stolenItem = heldItems[Utils.randInt(heldItems.length)]; user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false); @@ -1063,6 +1158,21 @@ export class StealHeldItemAttr extends MoveHitEffectAttr { return false; } + + getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { + return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + && (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; + } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + const heldItems = this.getTargetHeldItems(target); + return heldItems.length ? 5 : 0; + } + + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + const heldItems = this.getTargetHeldItems(target); + return heldItems.length ? -5 : 0; + } } export class HealStatusEffectAttr extends MoveEffectAttr { @@ -1089,6 +1199,10 @@ export class HealStatusEffectAttr extends MoveEffectAttr { return false; } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + return user.status ? 10 : 0; + } } export class BypassSleepAttr extends MoveAttr { @@ -1173,8 +1287,8 @@ export class ChargeAttr extends OverrideMoveEffectAttr { user.addTag(this.tagType, 1, move.id, user.id); if (this.chargeEffect) applyMoveAttrs(MoveEffectAttr, user, target, move); - user.pushMoveHistory({ move: move.id, targets: [ target.getBattleTarget() ], result: MoveResult.OTHER }); - user.getMoveQueue().push({ move: move.id, ignorePP: true }); + user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); + user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }); resolve(true); }); } else @@ -1217,7 +1331,7 @@ export class StatChangeAttr extends MoveEffectAttr { if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) { const levels = this.getLevels(user); - user.scene.unshiftPhase(new StatChangePhase(user.scene, user.isPlayer() === this.selfTarget, user.getFieldIndex(), this.selfTarget, this.stats, levels)); + user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels)); return true; } @@ -1227,6 +1341,12 @@ export class StatChangeAttr extends MoveEffectAttr { getLevels(_user: Pokemon): integer { return this.levels; } + + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + // TODO: Add awareness of level limits + const levels = this.getLevels(user); + return (levels * 4) + (levels > 0 ? -2 : 2); + } } export class GrowthStatChangeAttr extends StatChangeAttr { @@ -1533,7 +1653,7 @@ export class FrenzyAttr extends MoveEffectAttr { } canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) { - return !!(this.selfTarget ? user.hp : target.hp); + return !(this.selfTarget ? user : target).isFainted(); } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { @@ -1543,7 +1663,7 @@ export class FrenzyAttr extends MoveEffectAttr { if (!user.getMoveQueue().length) { if (!user.getTag(BattlerTagType.FRENZY)) { const turnCount = Utils.randInt(2) + 1; - new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, ignorePP: true })); + new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true })); user.addTag(BattlerTagType.FRENZY, 1, move.id, user.id); } else { applyMoveAttrs(AddBattlerTagAttr, user, target, move, args); @@ -1599,6 +1719,48 @@ export class AddBattlerTagAttr extends MoveEffectAttr { ? (user: Pokemon, target: Pokemon, move: Move) => !(this.selfTarget ? user : target).getTag(this.tagType) : null; } + + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + switch (this.tagType) { + case BattlerTagType.FLINCHED: + return -5; + case BattlerTagType.CONFUSED: + return -5; + case BattlerTagType.INFATUATED: + return -5; + case BattlerTagType.SEEDED: + return -3; + case BattlerTagType.NIGHTMARE: + return -5; + case BattlerTagType.FRENZY: + return -2; + case BattlerTagType.INGRAIN: + return 3; + case BattlerTagType.AQUA_RING: + return 3; + case BattlerTagType.DROWSY: + return -5; + case BattlerTagType.TRAPPED: + case BattlerTagType.BIND: + case BattlerTagType.WRAP: + case BattlerTagType.FIRE_SPIN: + case BattlerTagType.WHIRLPOOL: + case BattlerTagType.CLAMP: + case BattlerTagType.SAND_TOMB: + case BattlerTagType.MAGMA_STORM: + return -3; + case BattlerTagType.PROTECTED: + return 10; + case BattlerTagType.FLYING: + return 5; + case BattlerTagType.CRIT_BOOST: + return 5; + case BattlerTagType.NO_CRIT: + return -5; + case BattlerTagType.IGNORE_ACCURACY: + return 3; + } + } } export class LapseBattlerTagAttr extends MoveEffectAttr { @@ -1683,6 +1845,10 @@ export class HitsTagAttr extends MoveAttr { this.tagType = tagType; this.doubleDamage = !!doubleDamage; } + + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + return target.getTag(this.tagType) ? this.doubleDamage ? 10 : 5 : 0; + } } export class AddArenaTagAttr extends MoveEffectAttr { @@ -1771,11 +1937,15 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { if (moves.length) { const move = moves[Utils.randInt(moves.length)]; const moveIndex = moveset.findIndex(m => m.moveId === move.moveId); - user.getMoveQueue().push({ move: move.moveId, ignorePP: this.enemyMoveset }); const moveTargets = getMoveTargets(user, move.moveId); if (!moveTargets.targets.length) return false; - const targets = moveTargets.multiple ? moveTargets.targets : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ]; + const targets = moveTargets.multiple || moveTargets.targets.length === 1 + ? moveTargets.targets + : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 + ? [ target.getBattlerIndex() ] + : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ]; + user.getMoveQueue().push({ move: move.moveId, targets: targets, ignorePP: this.enemyMoveset }); user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, moveset[moveIndex], true)); return true; } @@ -1789,13 +1959,18 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr { return new Promise(resolve => { const moveIds = Utils.getEnumValues(Moves).filter(m => !allMoves[m].hasFlag(MoveFlags.IGNORE_VIRTUAL)); const moveId = moveIds[Utils.randInt(moveIds.length)]; - user.getMoveQueue().push({ move: moveId, ignorePP: true }); + const moveTargets = getMoveTargets(user, moveId); if (!moveTargets.targets.length) { resolve(false); return; } - const targets = moveTargets.multiple ? moveTargets.targets : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ]; + const targets = moveTargets.multiple || moveTargets.targets.length === 1 + ? moveTargets.targets + : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 + ? [ target.getBattlerIndex() ] + : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ]; + user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true }); user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true)); initMoveAnim(moveId).then(() => { loadMoveAnimAssets(user.scene, [ moveId ], true) @@ -1831,13 +2006,16 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr { const copiedMove = targetMoves[0]; - user.getMoveQueue().push({ move: copiedMove.move, ignorePP: true }); - const moveTargets = getMoveTargets(user, copiedMove.move); if (!moveTargets.targets.length) return false; - const targets = moveTargets.multiple ? moveTargets.targets : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ]; + const targets = moveTargets.multiple || moveTargets.targets.length === 1 + ? moveTargets.targets + : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 + ? [ target.getBattlerIndex() ] + : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ]; + user.getMoveQueue().push({ move: copiedMove.move, targets: targets, ignorePP: true }); user.scene.unshiftPhase(new MovePhase(user.scene, user as PlayerPokemon, targets, new PokemonMove(copiedMove.move, 0, 0, true), true)); @@ -1947,62 +2125,58 @@ export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon } export type MoveTargetSet = { - targets: BattleTarget[]; + targets: BattlerIndex[]; multiple: boolean; } export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet { - const moveTarget = move ? allMoves[move].moveTarget : MoveTarget.NEAR_ENEMY; + const moveTarget = move ? allMoves[move].moveTarget : move === undefined ? undefined : MoveTarget.NEAR_ENEMY; const opponents = user.getOpponents(); - let set: BattleTarget[]; + let set: BattlerIndex[] = []; let multiple = false; switch (moveTarget) { case MoveTarget.USER: - set = [ user.getBattleTarget() ]; - break; - case MoveTarget.OTHER: - set = user.getLastXMoves().find(() => true)?.targets || []; + set = [ user.getBattlerIndex() ]; break; case MoveTarget.NEAR_OTHER: + case MoveTarget.OTHER: case MoveTarget.ALL_NEAR_OTHERS: case MoveTarget.ALL_OTHERS: - set = (opponents.concat([ user.getAlly() ])).map(p => p?.getBattleTarget()); - multiple = moveTarget !== MoveTarget.NEAR_OTHER; + set = (opponents.concat([ user.getAlly() ])).map(p => p?.getBattlerIndex()); + multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS break; case MoveTarget.NEAR_ENEMY: case MoveTarget.ALL_NEAR_ENEMIES: case MoveTarget.ALL_ENEMIES: case MoveTarget.ENEMY_SIDE: - set = opponents.map(p => p.getBattleTarget()); + set = opponents.map(p => p.getBattlerIndex()); multiple = moveTarget !== MoveTarget.NEAR_ENEMY; break; case MoveTarget.RANDOM_NEAR_ENEMY: - set = [ opponents[Utils.randInt(opponents.length)].getBattleTarget() ]; + set = [ opponents[Utils.randInt(opponents.length)].getBattlerIndex() ]; break; case MoveTarget.ATTACKER: - set = [ user.scene.getPokemonById(user.turnData.attacksReceived[0].sourceId).getBattleTarget() ]; + set = [ user.scene.getPokemonById(user.turnData.attacksReceived[0].sourceId).getBattlerIndex() ]; break; case MoveTarget.NEAR_ALLY: case MoveTarget.ALLY: - set = [ user.getAlly()?.getBattleTarget() ]; + set = [ user.getAlly()?.getBattlerIndex() ]; break; case MoveTarget.USER_OR_NEAR_ALLY: case MoveTarget.USER_AND_ALLIES: case MoveTarget.USER_SIDE: - set = [ user, user.getAlly() ].map(p => p?.getBattleTarget()); + set = [ user, user.getAlly() ].map(p => p?.getBattlerIndex()); multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY; break; case MoveTarget.ALL: case MoveTarget.BOTH_SIDES: - set = [ user, user.getAlly() ].concat(user.getOpponents()).map(p => p?.getBattleTarget()); + set = [ user, user.getAlly() ].concat(user.getOpponents()).map(p => p?.getBattlerIndex()); multiple = true; break; } - console.log(set, multiple, MoveTarget[moveTarget]); - return { targets: set.filter(t => t !== undefined), multiple }; } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 40af0f2545d..bead50fc74a 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -127,7 +127,7 @@ export class PokemonReviveModifierType extends PokemonHpRestoreModifierType { constructor(name: string, restorePercent: integer, iconImage?: string) { super(name, restorePercent, true, (_type, args) => new Modifiers.PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints, true, true), ((pokemon: PlayerPokemon) => { - if (pokemon.hp) + if (!pokemon.isFainted()) return PartyUiHandler.NoEffectMessage; return null; }), iconImage, 'revive'); @@ -668,15 +668,15 @@ const modifierPool = { return statusEffectPartyMemberCount * 6; }), new WeightedModifierType(modifierTypes.REVIVE, (party: Pokemon[]) => { - const faintedPartyMemberCount = Math.min(party.filter(p => !p.hp).length, 3); + const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); return faintedPartyMemberCount * 9; }), new WeightedModifierType(modifierTypes.MAX_REVIVE, (party: Pokemon[]) => { - const faintedPartyMemberCount = Math.min(party.filter(p => !p.hp).length, 3); + const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); return faintedPartyMemberCount * 3; }), new WeightedModifierType(modifierTypes.SACRED_ASH, (party: Pokemon[]) => { - return party.filter(p => !p.hp).length >= Math.ceil(party.length / 2) ? 1 : 0; + return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0; }), new WeightedModifierType(modifierTypes.HYPER_POTION, (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min(party.filter(p => p.getInverseHp() >= 100 || p.getHpRatio() <= 0.625).length, 3); diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index eefa53e139e..c274b9aecb2 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -480,7 +480,7 @@ export class TurnHealModifier extends PokemonHeldItemModifier { if (pokemon.getHpRatio() < 1) { const scene = pokemon.scene; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), pokemon.getFieldIndex(), + scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), Math.max(Math.floor(pokemon.getMaxHp() / 16) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true)); } @@ -510,7 +510,7 @@ export class HitHealModifier extends PokemonHeldItemModifier { if (pokemon.turnData.damageDealt && pokemon.getHpRatio() < 1) { const scene = pokemon.scene; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), pokemon.getFieldIndex(), + scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), Math.max(Math.floor(pokemon.turnData.damageDealt / 8) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true)); } diff --git a/src/pokemon.ts b/src/pokemon.ts index efe6f22460e..821fb912b0c 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -1,7 +1,7 @@ import Phaser from 'phaser'; import BattleScene from './battle-scene'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './ui/battle-info'; -import Move, { StatChangeAttr, HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariablePowerAttr, Moves, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr } from "./data/move"; +import Move, { StatChangeAttr, HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariablePowerAttr, Moves, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, AttackMove, AddBattlerTagAttr } from "./data/move"; import { pokemonLevelMoves } from './data/pokemon-level-moves'; import { default as PokemonSpecies, PokemonSpeciesForm, getPokemonSpecies } from './data/pokemon-species'; import * as Utils from './utils'; @@ -23,9 +23,9 @@ import { WeatherType } from './data/weather'; import { TempBattleStat } from './data/temp-battle-stat'; import { ArenaTagType, WeakenMoveTypeTag } from './data/arena-tag'; import { Biome } from './data/biome'; -import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, PreApplyBattlerTagAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, abilities, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability'; +import { Abilities, Ability, BattleStatMultiplierAbAttr, BattlerTagImmunityAbAttr, BlockCritAbAttr, PreApplyBattlerTagAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, abilities, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability'; import PokemonData from './system/pokemon-data'; -import { BattleTarget } from './battle'; +import { BattlerIndex } from './battle'; export enum FieldPosition { CENTER, @@ -188,11 +188,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } + isFainted(): boolean { + return !this.hp; + } + + isActive(): boolean { + return !this.isFainted() && !!this.scene; + } + abstract isPlayer(): boolean; abstract getFieldIndex(): integer; - abstract getBattleTarget(): BattleTarget; + abstract getBattlerIndex(): BattlerIndex; loadAssets(): Promise { return new Promise(resolve => { @@ -565,7 +573,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getOpponents(): Pokemon[] { - return this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField(); + return ((this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField()) as Pokemon[]).filter(p => p.isActive()); } getOpponentDescriptor(): string { @@ -660,9 +668,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (damage) { - this.scene.unshiftPhase(new DamagePhase(this.scene, this.isPlayer(), this.getFieldIndex(), result as DamageResult)); + this.scene.unshiftPhase(new DamagePhase(this.scene, this.getBattlerIndex(), result as DamageResult)); if (isCritical) this.scene.queueMessage('A critical hit!'); + this.scene.setPhaseQueueSplice(); this.damage(damage); source.turnData.damageDealt += damage; this.turnData.attacksReceived.unshift({ move: move.id, result: result as DamageResult, damage: damage, sourceId: source.id }); @@ -679,6 +688,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.queueMessage(`It doesn\'t affect ${this.name}!`); break; } + + if (damage) + this.scene.clearPhaseQueueSplice(); } break; case MoveCategory.STATUS: @@ -690,7 +702,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } damage(damage: integer, preventEndure?: boolean): void { - if (!this.hp) + if (this.isFainted()) return; if (this.hp > 1 && this.hp - damage <= 0 && !preventEndure) { @@ -701,8 +713,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.hp = Math.max(this.hp - damage, 0); - if (!this.hp) { - this.scene.pushPhase(new FaintPhase(this.scene, this.isPlayer(), this.getFieldIndex())); + if (this.isFainted()) { + this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex())); this.resetSummonData(); } } @@ -1025,7 +1037,7 @@ export class PlayerPokemon extends Pokemon { return this.scene.getPlayerField().indexOf(this); } - getBattleTarget(): BattleTarget { + getBattlerIndex(): BattlerIndex { return this.getFieldIndex(); } @@ -1124,7 +1136,7 @@ export class EnemyPokemon extends Pokemon { : null; if (queuedMove) { if (queuedMove.isUsable(this.getMoveQueue()[0].ignorePP)) - return { move: queuedMove.moveId, ignorePP: this.getMoveQueue()[0].ignorePP }; + return { move: queuedMove.moveId, targets: this.getMoveQueue()[0].targets, ignorePP: this.getMoveQueue()[0].ignorePP }; else { this.getMoveQueue().shift(); return this.getNextMove(); @@ -1134,52 +1146,26 @@ export class EnemyPokemon extends Pokemon { const movePool = this.getMoveset().filter(m => m.isUsable()); if (movePool.length) { if (movePool.length === 1) - return { move: movePool[0].moveId }; + return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) }; switch (this.aiType) { case AiType.RANDOM: - return { move: movePool[Utils.randInt(movePool.length)].moveId }; + const moveId = movePool[Utils.randInt(movePool.length)].moveId; + return { move: moveId, targets: this.getNextTargets(moveId) }; case AiType.SMART_RANDOM: case AiType.SMART: - const target = this.scene.getPlayerPokemon(); const moveScores = movePool.map(() => 0); + const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ])); for (let m in movePool) { const pokemonMove = movePool[m]; const move = pokemonMove.getMove(); let moveScore = moveScores[m]; - if (move.category === MoveCategory.STATUS) - moveScore++; - else { - const effectiveness = this.getAttackMoveEffectiveness(move.type); - moveScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2; - if (moveScore) { - if (move.category === MoveCategory.PHYSICAL) { - if (this.getBattleStat(Stat.ATK) > this.getBattleStat(Stat.SPATK)) { - const statRatio = this.getBattleStat(Stat.SPATK) / this.getBattleStat(Stat.ATK); - if (statRatio <= 0.75) - moveScore *= 2; - else if (statRatio <= 0.875) - moveScore *= 1.5; - } - } else { - if (this.getBattleStat(Stat.SPATK) > this.getBattleStat(Stat.ATK)) { - const statRatio = this.getBattleStat(Stat.ATK) / this.getBattleStat(Stat.SPATK); - if (statRatio <= 0.75) - moveScore *= 2; - else if (statRatio <= 0.875) - moveScore *= 1.5; - } - } - moveScore += Math.floor(move.power / 5); - } + for (let mt of moveTargets[move.id]) { + const target = this.scene.getField()[mt]; + moveScore += move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); } - const statChangeAttrs = move.getAttrs(StatChangeAttr) as StatChangeAttr[]; - - for (let sc of statChangeAttrs) { - moveScore += ((sc.levels >= 1) === sc.selfTarget ? -2 : 2) + sc.levels * (sc.selfTarget ? 4 : -4); - // TODO: Add awareness of current levels - } + moveScore /= moveTargets[move.id].length // could make smarter by checking opponent def/spdef moveScores[m] = moveScore; @@ -1199,11 +1185,34 @@ export class EnemyPokemon extends Pokemon { r++; } console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName())); - return { move: sortedMovePool[r].moveId }; + return { move: sortedMovePool[r].moveId, targets: moveTargets[sortedMovePool[r].moveId] }; } } - return { move: Moves.STRUGGLE }; + return { move: Moves.STRUGGLE, targets: this.getNextTargets(Moves.STRUGGLE) }; + } + + getNextTargets(moveId: Moves): BattlerIndex[] { + const moveTargets = getMoveTargets(this, moveId); + const targets = this.scene.getField().filter(p => p?.isActive() && moveTargets.targets.indexOf(p.getBattlerIndex()) > -1); + if (moveTargets.multiple) + return targets.map(p => p.getBattlerIndex()); + + const move = allMoves[moveId]; + + let benefitScores = targets + .map(p => [ p.getBattlerIndex(), move.getTargetBenefitScore(this, p, move) * (p.isPlayer() === this.isPlayer() ? 1 : -1) ]); + + const sortedBenefitScores = benefitScores.slice(0); + sortedBenefitScores.sort((a, b) => { + const scoreA = a[1]; + const scoreB = b[1]; + return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0; + }); + + // TODO: Add some randomness + + return [ sortedBenefitScores[0][0] ]; } isPlayer() { @@ -1214,8 +1223,8 @@ export class EnemyPokemon extends Pokemon { return this.scene.getEnemyField().indexOf(this); } - getBattleTarget(): BattleTarget { - return BattleTarget.ENEMY + this.getFieldIndex(); + getBattlerIndex(): BattlerIndex { + return BattlerIndex.ENEMY + this.getFieldIndex(); } addToParty() { @@ -1234,7 +1243,7 @@ export class EnemyPokemon extends Pokemon { export interface TurnMove { move: Moves; - targets?: BattleTarget[]; + targets?: BattlerIndex[]; result: MoveResult; virtual?: boolean; turn?: integer; @@ -1242,6 +1251,7 @@ export interface TurnMove { export interface QueuedMove { move: Moves; + targets: BattlerIndex[]; ignorePP?: boolean; } diff --git a/src/system/auto-play.ts b/src/system/auto-play.ts index 76039ae1b6c..495424ba7f0 100644 --- a/src/system/auto-play.ts +++ b/src/system/auto-play.ts @@ -193,7 +193,7 @@ export function initAutoPlay() { const party = thisArg.getParty(); const modifierTypeOptions = modifierSelectUiHandler.options.map(o => o.modifierTypeOption); - const faintedPartyMemberIndex = party.findIndex(p => !p.hp); + const faintedPartyMemberIndex = party.findIndex(p => p.isFainted()); const lowHpPartyMemberIndex = party.findIndex(p => p.getHpRatio() <= 0.5); const criticalHpPartyMemberIndex = party.findIndex(p => p.getHpRatio() <= 0.25); diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 42a6ce056d8..85402c88da9 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -152,6 +152,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.y -= 12 * (mini ? 1 : -1); } + const offsetElements = [ this.nameText, this.genderText, this.statusIndicator, this.levelContainer ]; + offsetElements.forEach(el => el.y += 1.5 * (mini ? -1 : 1)); + const toggledElements = [ this.hpNumbersContainer, this.expBar ]; toggledElements.forEach(el => el.setVisible(!mini)); } diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 017c639b3fe..acf737fe69d 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -69,7 +69,7 @@ export default class PartyUiHandler extends MessageUiHandler { private static FilterAll = (_pokemon: PlayerPokemon) => null; public static FilterNonFainted = (pokemon: PlayerPokemon) => { - if (!pokemon.hp) + if (pokemon.isFainted()) return `${pokemon.name} has no energy\nleft to battle!`; return null; }; diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index fd4164b8fad..6195f799052 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -1,4 +1,4 @@ -import { BattleTarget } from "../battle"; +import { BattlerIndex } from "../battle"; import BattleScene, { Button } from "../battle-scene"; import { Moves, getMoveTargets } from "../data/move"; import { Mode } from "./ui"; @@ -12,7 +12,7 @@ export default class TargetSelectUiHandler extends UiHandler { private move: Moves; private targetSelectCallback: TargetSelectCallback; - private targets: BattleTarget[]; + private targets: BattlerIndex[]; private targetFlashTween: Phaser.Tweens.Tween; constructor(scene: BattleScene) { @@ -52,12 +52,12 @@ export default class TargetSelectUiHandler extends UiHandler { } else { switch (button) { case Button.UP: - if (this.cursor < BattleTarget.ENEMY && this.targets.find(t => t >= BattleTarget.ENEMY)) - success = this.setCursor(this.targets.find(t => t >= BattleTarget.ENEMY)); + if (this.cursor < BattlerIndex.ENEMY && this.targets.find(t => t >= BattlerIndex.ENEMY)) + success = this.setCursor(this.targets.find(t => t >= BattlerIndex.ENEMY)); break; case Button.DOWN: - if (this.cursor >= BattleTarget.ENEMY && this.targets.find(t => t < BattleTarget.ENEMY)) - success = this.setCursor(this.targets.find(t => t < BattleTarget.ENEMY)); + if (this.cursor >= BattlerIndex.ENEMY && this.targets.find(t => t < BattlerIndex.ENEMY)) + success = this.setCursor(this.targets.find(t => t < BattlerIndex.ENEMY)); break; case Button.LEFT: if (this.cursor % 2 && this.targets.find(t => t === this.cursor - 1))