Updates to double battles

This commit is contained in:
Flashfyre 2023-05-16 14:47:14 -04:00
parent ae8aff0822
commit 05432181b6
17 changed files with 456 additions and 238 deletions

View File

@ -161,7 +161,7 @@ export class Arena {
this.weather = weather ? new Weather(weather, viaMove ? 5 : 0) : null; this.weather = weather ? new Weather(weather, viaMove ? 5 : 0) : null;
if (this.weather) { 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)); this.scene.queueMessage(getWeatherStartMessage(weather));
} else } else
this.scene.queueMessage(getWeatherClearMessage(oldWeatherType)); this.scene.queueMessage(getWeatherClearMessage(oldWeatherType));

View File

@ -1,7 +1,7 @@
import BattleScene, { maxExpLevel, startingLevel, startingWave } from "./battle-scene"; import BattleScene, { maxExpLevel, startingLevel, startingWave } from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult } from "./pokemon"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult } from "./pokemon";
import * as Utils from './utils'; 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 { Mode } from './ui/ui';
import { Command } from "./ui/command-ui-handler"; import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat"; 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 { 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 { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./arena"; import { getBiomeKey } from "./arena";
import { BattleTarget, TurnCommand } from "./battle"; import { BattlerIndex, TurnCommand } from "./battle";
export class CheckLoadPhase extends BattlePhase { export class CheckLoadPhase extends BattlePhase {
private loaded: boolean; private loaded: boolean;
@ -119,9 +119,9 @@ export class SelectStarterPhase extends BattlePhase {
type PokemonFunc = (pokemon: Pokemon) => void; type PokemonFunc = (pokemon: Pokemon) => void;
export abstract class FieldPhase extends BattlePhase { export abstract class FieldPhase extends BattlePhase {
getOrder(): BattleTarget[] { getOrder(): BattlerIndex[] {
const playerField = this.scene.getPlayerField() as Pokemon[]; const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[];
const enemyField = this.scene.getEnemyField() as Pokemon[]; const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[];
let orderedTargets: Pokemon[] = playerField.concat(enemyField).sort((a: Pokemon, b: Pokemon) => { let orderedTargets: Pokemon[] = playerField.concat(enemyField).sort((a: Pokemon, b: Pokemon) => {
const aSpeed = a?.getBattleStat(Stat.SPD) || 0; const aSpeed = a?.getBattleStat(Stat.SPD) || 0;
@ -136,38 +136,47 @@ export abstract class FieldPhase extends BattlePhase {
if (speedReversed.value) if (speedReversed.value)
orderedTargets = orderedTargets.reverse(); 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 { 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)); field.forEach(pokemon => func(pokemon));
} }
} }
export abstract class PokemonPhase extends FieldPhase { export abstract class PokemonPhase extends FieldPhase {
protected battlerIndex: BattlerIndex;
protected player: boolean; protected player: boolean;
protected fieldIndex: integer; protected fieldIndex: integer;
constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { constructor(scene: BattleScene, battlerIndex: BattlerIndex) {
super(scene); super(scene);
this.player = player; if (battlerIndex === undefined)
this.fieldIndex = fieldIndex; battlerIndex = scene.getField().find(p => p?.isActive()).getBattlerIndex();
this.battlerIndex = battlerIndex;
this.player = battlerIndex < 2;
this.fieldIndex = battlerIndex % 2;
} }
getPokemon() { 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 partyMemberIndex: integer;
protected fieldIndex: integer;
constructor(scene: BattleScene, partyMemberIndex: integer) { constructor(scene: BattleScene, partyMemberIndex: integer) {
super(scene, true, partyMemberIndex); super(scene);
this.partyMemberIndex = partyMemberIndex; this.partyMemberIndex = partyMemberIndex;
this.fieldIndex = partyMemberIndex < this.scene.currentBattle.getBattlerCount()
? partyMemberIndex
: -1;
} }
getPokemon() { getPokemon() {
@ -263,7 +272,7 @@ export class EncounterPhase extends BattlePhase {
enemyField.forEach((enemyPokemon, e) => { enemyField.forEach((enemyPokemon, e) => {
if (enemyPokemon.shiny) 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)); enemyField.forEach(enemyPokemon => this.scene.arena.applyTags(ArenaTrapTag, enemyPokemon));
@ -308,7 +317,7 @@ export class NextEncounterPhase extends EncounterPhase {
end() { end() {
this.scene.getEnemyField().forEach((enemyPokemon, e) => { this.scene.getEnemyField().forEach((enemyPokemon, e) => {
if (enemyPokemon.shiny) 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)); this.scene.unshiftPhase(new CheckSwitchPhase(this.scene, 0));
@ -528,7 +537,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
playerField.forEach((pokemon, p) => { playerField.forEach((pokemon, p) => {
if (pokemon.shiny) 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()); playerField.forEach(pokemon => pokemon.resetTurnData());
@ -636,7 +645,7 @@ export class CheckSwitchPhase extends BattlePhase {
return; 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(); super.end();
return; return;
} }
@ -679,19 +688,23 @@ export class TurnInitPhase extends FieldPhase {
super.start(); super.start();
this.scene.getPlayerField().forEach(playerPokemon => { this.scene.getPlayerField().forEach(playerPokemon => {
if (playerPokemon.isActive())
this.scene.currentBattle.addParticipant(playerPokemon); this.scene.currentBattle.addParticipant(playerPokemon);
playerPokemon.resetTurnData(); playerPokemon.resetTurnData();
}); });
this.scene.getEnemyField().forEach(enemyPokemon => enemyPokemon.resetTurnData()); this.scene.getEnemyField().forEach(enemyPokemon => {
if (enemyPokemon.isActive())
enemyPokemon.resetTurnData()
});
const order = this.getOrder(); const order = this.getOrder();
for (let o of order) { for (let o of order) {
if (o < BattleTarget.ENEMY) if (o < BattlerIndex.ENEMY)
this.scene.pushPhase(new CommandPhase(this.scene, o)); this.scene.pushPhase(new CommandPhase(this.scene, o));
else 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)); this.scene.pushPhase(new TurnStartPhase(this.scene));
@ -727,8 +740,10 @@ export class CommandPhase extends FieldPhase {
this.handleCommand(Command.FIGHT, -1, false); this.handleCommand(Command.FIGHT, -1, false);
else { else {
const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move); const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move);
if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex].isUsable(queuedMove.ignorePP)) if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex].isUsable(queuedMove.ignorePP)) {
this.handleCommand(Command.FIGHT, moveIndex, 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 } else
this.scene.ui.setMode(Mode.COMMAND); this.scene.ui.setMode(Mode.COMMAND);
@ -743,10 +758,11 @@ export class CommandPhase extends FieldPhase {
case Command.FIGHT: case Command.FIGHT:
if (cursor === -1 || playerPokemon.trySelectMove(cursor, args[0] as boolean)) { if (cursor === -1 || playerPokemon.trySelectMove(cursor, args[0] as boolean)) {
const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor,
move: cursor > -1 ? { move: playerPokemon.moveset[cursor].moveId } : null, args: args }; // TODO: Struggle logic move: cursor > -1 ? { move: playerPokemon.moveset[cursor].moveId, targets: [] } : null, args: args }; // TODO: Struggle logic
const moveTargets = getMoveTargets(playerPokemon, playerPokemon.moveset[cursor].moveId); 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) if (moveTargets.targets.length <= 1 || moveTargets.multiple)
turnCommand.targets = moveTargets.targets; turnCommand.move.targets = moveTargets.targets;
else else
this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex));
this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand;
@ -827,10 +843,9 @@ export class EnemyCommandPhase extends FieldPhase {
const enemyPokemon = this.scene.getEnemyField()[this.fieldIndex]; const enemyPokemon = this.scene.getEnemyField()[this.fieldIndex];
const nextMove = enemyPokemon.getNextMove(); const nextMove = enemyPokemon.getNextMove();
const moveTargets = getMoveTargets(enemyPokemon, nextMove.move);
this.scene.currentBattle.turnCommands[this.fieldIndex + BattleTarget.ENEMY] = this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] =
{ command: Command.FIGHT, move: nextMove, targets: !moveTargets.multiple ? [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ] : moveTargets.targets }; { command: Command.FIGHT, move: nextMove };
this.end(); this.end();
} }
@ -838,13 +853,13 @@ export class EnemyCommandPhase extends FieldPhase {
export class SelectTargetPhase extends PokemonPhase { export class SelectTargetPhase extends PokemonPhase {
constructor(scene: BattleScene, fieldIndex: integer) { constructor(scene: BattleScene, fieldIndex: integer) {
super(scene, true, fieldIndex); super(scene, fieldIndex);
} }
start() { start() {
super.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.TARGET_SELECT, this.fieldIndex, move, (cursor: integer) => {
this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.setMode(Mode.MESSAGE);
if (cursor === -1) { 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); const move = pokemon.getMoveset().find(m => m.moveId === queuedMove.move) || new PokemonMove(queuedMove.move);
if (pokemon.isPlayer()) { if (pokemon.isPlayer()) {
if (turnCommand.cursor === -1) 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 { 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); this.scene.pushPhase(playerPhase);
} }
} else } 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; break;
case Command.BALL: case Command.BALL:
this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets[0] % 2, turnCommand.cursor)); 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) { for (let o of order) {
if (field[o].status && field[o].status.isPostTurn()) 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)); this.scene.pushPhase(new TurnEndPhase(this.scene));
@ -953,9 +968,6 @@ export class TurnEndPhase extends FieldPhase {
this.scene.currentBattle.incrementTurn(); this.scene.currentBattle.incrementTurn();
const handlePokemon = (pokemon: Pokemon) => { const handlePokemon = (pokemon: Pokemon) => {
if (!pokemon || !pokemon.hp)
return;
pokemon.lapseTags(BattlerTagLapseType.TURN_END); pokemon.lapseTags(BattlerTagLapseType.TURN_END);
const disabledMoves = pokemon.getMoveset().filter(m => m.isDisabled()); 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()); const hasUsableBerry = !!this.scene.findModifier(m => m instanceof BerryModifier && m.shouldApply([ pokemon ]), pokemon.isPlayer());
if (hasUsableBerry) 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); this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);
@ -1017,8 +1029,11 @@ export class CommonAnimPhase extends PokemonPhase {
private anim: CommonAnim; private anim: CommonAnim;
private targetIndex: integer; private targetIndex: integer;
constructor(scene: BattleScene, player: boolean, fieldIndex: integer, targetIndex: integer, anim: CommonAnim) { constructor(scene: BattleScene, battlerIndex: BattlerIndex, targetIndex: BattlerIndex, anim: CommonAnim) {
super(scene, player, fieldIndex); super(scene, battlerIndex);
if (targetIndex === undefined)
targetIndex = this.battlerIndex;
this.anim = anim; this.anim = anim;
this.targetIndex = targetIndex; this.targetIndex = targetIndex;
@ -1033,13 +1048,13 @@ export class CommonAnimPhase extends PokemonPhase {
export class MovePhase extends BattlePhase { export class MovePhase extends BattlePhase {
protected pokemon: Pokemon; protected pokemon: Pokemon;
protected targets: BattleTarget[]; protected targets: BattlerIndex[];
protected move: PokemonMove; protected move: PokemonMove;
protected followUp: boolean; protected followUp: boolean;
protected ignorePp: boolean; protected ignorePp: boolean;
protected cancelled: 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); super(scene);
this.pokemon = pokemon; this.pokemon = pokemon;
@ -1063,8 +1078,6 @@ export class MovePhase extends BattlePhase {
console.log(Moves[this.move.moveId]); console.log(Moves[this.move.moveId]);
console.log(this.scene.currentBattle.turnCommands);
if (!this.canMove()) { if (!this.canMove()) {
if (this.move.isDisabled()) if (this.move.isDisabled())
this.scene.queueMessage(`${this.move.getName()} is disabled!`); this.scene.queueMessage(`${this.move.getName()} is disabled!`);
@ -1074,7 +1087,15 @@ export class MovePhase extends BattlePhase {
console.log(this.targets); 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()) if (!this.followUp && this.canMove())
this.pokemon.lapseTags(BattlerTagLapseType.MOVE); this.pokemon.lapseTags(BattlerTagLapseType.MOVE);
@ -1137,7 +1158,7 @@ export class MovePhase extends BattlePhase {
} }
if (activated) { if (activated) {
this.scene.queueMessage(getPokemonMessage(this.pokemon, getStatusEffectActivationText(this.pokemon.status.effect))); 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(); doMove();
} else { } else {
if (healed) { if (healed) {
@ -1152,12 +1173,12 @@ export class MovePhase extends BattlePhase {
} }
getEffectPhase(): MoveEffectPhase { 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() { end() {
if (!this.followUp && this.canMove()) 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(); super.end();
} }
@ -1165,10 +1186,10 @@ export class MovePhase extends BattlePhase {
class MoveEffectPhase extends PokemonPhase { class MoveEffectPhase extends PokemonPhase {
protected move: PokemonMove; protected move: PokemonMove;
protected targets: BattleTarget[]; protected targets: BattlerIndex[];
constructor(scene: BattleScene, player: boolean, fieldIndex: integer, targets: BattleTarget[], move: PokemonMove) { constructor(scene: BattleScene, battlerIndex: BattlerIndex, targets: BattlerIndex[], move: PokemonMove) {
super(scene, player, fieldIndex); super(scene, battlerIndex);
this.move = move; this.move = move;
this.targets = targets; 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 }; const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
user.pushMoveHistory(moveHistoryEntry); 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]]) { if (targets.length === 1 && !targetHitChecks[this.targets[0]]) {
this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!')); this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!'));
moveHistoryEntry.result = MoveResult.MISS; moveHistoryEntry.result = MoveResult.MISS;
@ -1213,9 +1234,9 @@ class MoveEffectPhase extends PokemonPhase {
} }
// Move animation only needs one target // 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) { for (let target of targets) {
if (!targetHitChecks[target.getBattleTarget()]) { if (!targetHitChecks[target.getBattlerIndex()]) {
this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!')); this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!'));
if (moveHistoryEntry.result === MoveResult.PENDING) if (moveHistoryEntry.result === MoveResult.PENDING)
moveHistoryEntry.result = MoveResult.MISS; moveHistoryEntry.result = MoveResult.MISS;
@ -1242,7 +1263,7 @@ class MoveEffectPhase extends PokemonPhase {
} }
if (!isProtected && !chargeEffect) { if (!isProtected && !chargeEffect) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveHitEffectAttr && (!!target.hp || (attr as MoveHitEffectAttr).selfTarget), user, target, this.move.getMove()); 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); applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult);
if (this.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) if (this.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT))
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex()); this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex());
@ -1256,7 +1277,7 @@ class MoveEffectPhase extends PokemonPhase {
end() { end() {
const user = this.getUserPokemon(); 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()); this.scene.unshiftPhase(this.getNewHitPhase());
else { else {
if (user.turnData.hitCount > 1) if (user.turnData.hitCount > 1)
@ -1272,10 +1293,8 @@ class MoveEffectPhase extends PokemonPhase {
return true; return true;
const hiddenTag = target.getTag(HiddenTag); const hiddenTag = target.getTag(HiddenTag);
if (hiddenTag) { if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length)
if (!this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length)
return false; return false;
}
if (this.getUserPokemon().getTag(BattlerTagType.IGNORE_ACCURACY)) if (this.getUserPokemon().getTag(BattlerTagType.IGNORE_ACCURACY))
return true; return true;
@ -1312,7 +1331,7 @@ class MoveEffectPhase extends PokemonPhase {
} }
getTargets(): Pokemon[] { 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 { getTarget(): Pokemon {
@ -1320,13 +1339,13 @@ class MoveEffectPhase extends PokemonPhase {
} }
getNewHitPhase() { 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 { export class MoveEndPhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { constructor(scene: BattleScene, battlerIndex: BattlerIndex) {
super(scene, player, fieldIndex); super(scene, battlerIndex);
} }
start() { start() {
@ -1375,8 +1394,8 @@ export class MoveAnimTestPhase extends BattlePhase {
} }
export class ShowAbilityPhase extends PokemonPhase { export class ShowAbilityPhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { constructor(scene: BattleScene, battlerIndex: BattlerIndex) {
super(scene, player, fieldIndex); super(scene, battlerIndex);
} }
start() { start() {
@ -1391,10 +1410,8 @@ export class StatChangePhase extends PokemonPhase {
private selfTarget: boolean; private selfTarget: boolean;
private levels: integer; private levels: integer;
constructor(scene: BattleScene, player: boolean, fieldIndex: integer, selfTarget: boolean, stats: BattleStat[], levels: integer) { constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: integer) {
super(scene, player, fieldIndex); super(scene, battlerIndex);
console.log(this.player, this.fieldIndex);
const allStats = Utils.getEnumValues(BattleStat); const allStats = Utils.getEnumValues(BattleStat);
this.selfTarget = selfTarget; this.selfTarget = selfTarget;
@ -1482,7 +1499,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
private weather: Weather; private weather: Weather;
constructor(scene: BattleScene, 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; this.weather = weather;
} }
@ -1503,7 +1520,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
return; return;
this.scene.queueMessage(getWeatherDamageMessage(this.weather.weatherType, pokemon)); 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)); pokemon.damage(Math.ceil(pokemon.getMaxHp() / 16));
}; };
@ -1528,8 +1545,8 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
private cureTurn: integer; private cureTurn: integer;
private sourceText: string; private sourceText: string;
constructor(scene: BattleScene, player: boolean, fieldIndex: integer, statusEffect: StatusEffect, cureTurn?: integer, sourceText?: string) { constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect: StatusEffect, cureTurn?: integer, sourceText?: string) {
super(scene, player, fieldIndex); super(scene, battlerIndex);
this.statusEffect = statusEffect; this.statusEffect = statusEffect;
this.cureTurn = cureTurn; this.cureTurn = cureTurn;
@ -1546,7 +1563,7 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene, () => { new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene, () => {
this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectObtainText(this.statusEffect, this.sourceText))); this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectObtainText(this.statusEffect, this.sourceText)));
if (pokemon.status.isPostTurn()) 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(); this.end();
}); });
return; return;
@ -1558,13 +1575,13 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
} }
export class PostTurnStatusEffectPhase extends PokemonPhase { export class PostTurnStatusEffectPhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { constructor(scene: BattleScene, battlerIndex: BattlerIndex) {
super(scene, player, fieldIndex); super(scene, battlerIndex);
} }
start() { start() {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
if (pokemon?.hp && pokemon.status && pokemon.status.isPostTurn()) { if (pokemon?.isActive() && pokemon.status && pokemon.status.isPostTurn()) {
pokemon.status.incrementTurn(); pokemon.status.incrementTurn();
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => { new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => {
this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect))); this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect)));
@ -1616,8 +1633,8 @@ export class MessagePhase extends BattlePhase {
export class DamagePhase extends PokemonPhase { export class DamagePhase extends PokemonPhase {
private damageResult: DamageResult; private damageResult: DamageResult;
constructor(scene: BattleScene, player: boolean, fieldIndex: integer, damageResult?: DamageResult) { constructor(scene: BattleScene, battlerIndex: BattlerIndex, damageResult?: DamageResult) {
super(scene, player, fieldIndex); super(scene, battlerIndex);
this.damageResult = damageResult || HitResult.EFFECTIVE; this.damageResult = damageResult || HitResult.EFFECTIVE;
} }
@ -1654,8 +1671,8 @@ export class DamagePhase extends PokemonPhase {
} }
export class FaintPhase extends PokemonPhase { export class FaintPhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { constructor(scene: BattleScene, battlerIndex: BattlerIndex) {
super(scene, player, fieldIndex); super(scene, battlerIndex);
} }
start() { start() {
@ -1664,7 +1681,11 @@ export class FaintPhase extends PokemonPhase {
this.scene.queueMessage(getPokemonMessage(this.getPokemon(), ' fainted!'), null, true); this.scene.queueMessage(getPokemonMessage(this.getPokemon(), ' fainted!'), null, true);
if (this.player) { 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 } else
this.scene.unshiftPhase(new VictoryPhase(this.scene, this.fieldIndex)); this.scene.unshiftPhase(new VictoryPhase(this.scene, this.fieldIndex));
@ -1697,7 +1718,7 @@ export class FaintPhase extends PokemonPhase {
export class VictoryPhase extends PokemonPhase { export class VictoryPhase extends PokemonPhase {
constructor(scene: BattleScene, targetIndex: integer) { constructor(scene: BattleScene, targetIndex: integer) {
super(scene, true, targetIndex); super(scene, targetIndex);
} }
start() { 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)); this.scene.pushPhase(new BattleEndPhase(this.scene));
if (this.scene.currentBattle.waveIndex < this.scene.finalWave) { if (this.scene.currentBattle.waveIndex < this.scene.finalWave) {
this.scene.pushPhase(new SelectModifierPhase(this.scene)); this.scene.pushPhase(new SelectModifierPhase(this.scene));
@ -2014,8 +2035,8 @@ export class LearnMovePhase extends PartyMemberPokemonPhase {
} }
export class BerryPhase extends CommonAnimPhase { export class BerryPhase extends CommonAnimPhase {
constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { constructor(scene: BattleScene, battlerIndex: BattlerIndex) {
super(scene, player, fieldIndex, 0, CommonAnim.USE_ITEM); super(scene, battlerIndex, undefined, CommonAnim.USE_ITEM);
} }
start() { start() {
@ -2043,8 +2064,8 @@ export class PokemonHealPhase extends CommonAnimPhase {
private showFullHpMessage: boolean; private showFullHpMessage: boolean;
private skipAnim: boolean; private skipAnim: boolean;
constructor(scene: BattleScene, player: boolean, fieldIndex: integer, hpHealed: integer, message: string, showFullHpMessage: boolean, skipAnim?: boolean) { constructor(scene: BattleScene, battlerIndex: BattlerIndex, hpHealed: integer, message: string, showFullHpMessage: boolean, skipAnim?: boolean) {
super(scene, player, fieldIndex, 0, CommonAnim.HEALTH_UP); super(scene, battlerIndex, undefined, CommonAnim.HEALTH_UP);
this.hpHealed = hpHealed; this.hpHealed = hpHealed;
this.message = message; this.message = message;
@ -2062,7 +2083,7 @@ export class PokemonHealPhase extends CommonAnimPhase {
end() { end() {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
if (!this.getPokemon().hp) { if (!this.getPokemon().isActive()) {
super.end(); super.end();
return; return;
} }
@ -2091,7 +2112,7 @@ export class AttemptCapturePhase extends PokemonPhase {
private originalY: number; private originalY: number;
constructor(scene: BattleScene, targetIndex: integer, pokeballType: PokeballType) { constructor(scene: BattleScene, targetIndex: integer, pokeballType: PokeballType) {
super(scene, false, targetIndex); super(scene, BattlerIndex.ENEMY + targetIndex);
this.pokeballType = pokeballType; this.pokeballType = pokeballType;
} }
@ -2282,7 +2303,7 @@ export class AttemptCapturePhase extends PokemonPhase {
export class AttemptRunPhase extends PokemonPhase { export class AttemptRunPhase extends PokemonPhase {
constructor(scene: BattleScene, fieldIndex: integer) { constructor(scene: BattleScene, fieldIndex: integer) {
super(scene, true, fieldIndex); super(scene, fieldIndex);
} }
start() { start() {
@ -2387,8 +2408,8 @@ export class SelectModifierPhase extends BattlePhase {
} }
export class ShinySparklePhase extends PokemonPhase { export class ShinySparklePhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean, fieldIndex: integer) { constructor(scene: BattleScene, battlerIndex: BattlerIndex) {
super(scene, player, fieldIndex); super(scene, battlerIndex);
} }
start() { start() {

View File

@ -449,7 +449,7 @@ export default class BattleScene extends Phaser.Scene {
} }
getPlayerPokemon(): PlayerPokemon { getPlayerPokemon(): PlayerPokemon {
return this.getPlayerField().find(() => true); return this.getPlayerField().find(p => p.isActive());
} }
getPlayerField(): PlayerPokemon[] { getPlayerField(): PlayerPokemon[] {
@ -458,7 +458,7 @@ export default class BattleScene extends Phaser.Scene {
} }
getEnemyPokemon(): EnemyPokemon { getEnemyPokemon(): EnemyPokemon {
return this.currentBattle?.enemyField.find(() => true); return this.getEnemyField().find(p => p.isActive());
} }
getEnemyField(): EnemyPokemon[] { getEnemyField(): EnemyPokemon[] {

View File

@ -2,7 +2,7 @@ import { EnemyPokemon, PlayerPokemon, QueuedMove } from "./pokemon";
import { Command } from "./ui/command-ui-handler"; import { Command } from "./ui/command-ui-handler";
import * as Utils from "./utils"; import * as Utils from "./utils";
export enum BattleTarget { export enum BattlerIndex {
PLAYER, PLAYER,
PLAYER_2, PLAYER_2,
ENEMY, ENEMY,
@ -13,7 +13,7 @@ export interface TurnCommand {
command: Command; command: Command;
cursor?: integer; cursor?: integer;
move?: QueuedMove; move?: QueuedMove;
targets?: BattleTarget[]; targets?: BattlerIndex[];
args?: any[]; args?: any[];
}; };
@ -61,7 +61,7 @@ export default class Battle {
incrementTurn(): void { incrementTurn(): void {
this.turn++; 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 { addParticipant(playerPokemon: PlayerPokemon): void {

View File

@ -157,7 +157,7 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
if (ret && pokemon.getHpRatio() < 1) { if (ret && pokemon.getHpRatio() < 1) {
const scene = pokemon.scene; 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; return true;
} }
@ -181,7 +181,7 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr {
if (ret) { if (ret) {
cancelled.value = true; 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; return ret;
@ -406,13 +406,21 @@ export class PostSummonStatChangeAbAttr extends PostSummonAbAttr {
} }
applyPostSummon(pokemon: Pokemon, args: any[]): boolean { 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) 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 pokemon.scene.pushPhase(statChangePhase); // TODO: This causes the ability bar to be shown at the wrong time
else else
pokemon.scene.unshiftPhase(statChangePhase); pokemon.scene.unshiftPhase(statChangePhase);
}
return true; return true;
} }
@ -576,7 +584,7 @@ export class PostTurnAbAttr extends AbAttr {
export class PostTurnSpeedBoostAbAttr extends PostTurnAbAttr { export class PostTurnSpeedBoostAbAttr extends PostTurnAbAttr {
applyPostTurn(pokemon: Pokemon, args: any[]): boolean { 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; return true;
} }
} }
@ -594,7 +602,7 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr {
applyPostTurn(pokemon: Pokemon, args: any[]): boolean { applyPostTurn(pokemon: Pokemon, args: any[]): boolean {
if (pokemon.getHpRatio() < 1) { if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene; 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)); Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
return true; return true;
} }
@ -633,7 +641,8 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr {
applyPostWeatherLapse(pokemon: Pokemon, weather: Weather, args: any[]): boolean { applyPostWeatherLapse(pokemon: Pokemon, weather: Weather, args: any[]): boolean {
if (pokemon.getHpRatio() < 1) { if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene; 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; return true;
} }
@ -654,7 +663,7 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr {
if (pokemon.getHpRatio() < 1) { if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene; const scene = pokemon.scene;
scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby its ${pokemon.getAbility()}!`)); 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))); pokemon.damage(Math.ceil(pokemon.getMaxHp() * (16 / this.damageFactor)));
return true; return true;
} }
@ -989,7 +998,7 @@ function canApplyAttr(pokemon: Pokemon, attr: AbAttr): boolean {
} }
function queueShowAbility(pokemon: Pokemon): void { 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(); pokemon.scene.clearPhaseQueueSplice();
} }

View File

@ -151,7 +151,7 @@ class SpikesTag extends ArenaTrapTag {
const damageHpRatio = 1 / (10 - 2 * this.layers); const damageHpRatio = 1 / (10 - 2 * this.layers);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is hurt\nby the spikes!')); 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)); pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio));
return true; 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))) { if (!pokemon.status && (!pokemon.isOfType(Type.FLYING) || pokemon.getTag(BattlerTagType.IGNORE_FLYING) || pokemon.scene.arena.getTag(ArenaTagType.GRAVITY))) {
const toxic = this.layers > 1; 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()}`)); !toxic ? StatusEffect.POISON : StatusEffect.TOXIC, null, `the ${this.getMoveName()}`));
return true; return true;
} }
@ -225,7 +225,7 @@ class StealthRockTag extends ArenaTrapTag {
if (damageHpRatio) { if (damageHpRatio) {
pokemon.scene.queueMessage(`Pointed stones dug into\n${pokemon.name}!`); 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)); pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio));
} }

View File

@ -3,7 +3,7 @@ import BattleScene from "../battle-scene";
import { ChargeAttr, Moves, allMoves } from "./move"; import { ChargeAttr, Moves, allMoves } from "./move";
import Pokemon from "../pokemon"; import Pokemon from "../pokemon";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { BattleTarget } from "../battle"; import { BattlerIndex } from "../battle";
//import fs from 'vite-plugin-fs/browser'; //import fs from 'vite-plugin-fs/browser';
export enum AnimFrameTarget { export enum AnimFrameTarget {
@ -832,7 +832,7 @@ export class CommonBattleAnim extends BattleAnim {
export class MoveAnim extends BattleAnim { export class MoveAnim extends BattleAnim {
public move: Moves; public move: Moves;
constructor(move: Moves, user: Pokemon, target: BattleTarget) { constructor(move: Moves, user: Pokemon, target: BattlerIndex) {
super(user, user.scene.getField()[target]); super(user, user.scene.getField()[target]);
this.move = move; this.move = move;

View File

@ -103,7 +103,7 @@ export class RechargingTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); super.onAdd(pokemon);
pokemon.getMoveQueue().push({ move: Moves.NONE }) pokemon.getMoveQueue().push({ move: Moves.NONE, targets: [] })
} }
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -177,7 +177,7 @@ export class ConfusedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); 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!')); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' became\nconfused!'));
} }
@ -198,14 +198,14 @@ export class ConfusedTag extends BattlerTag {
if (ret) { if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nconfused!')); 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)) { if (Utils.randInt(2)) {
const atk = pokemon.getBattleStat(Stat.ATK); const atk = pokemon.getBattleStat(Stat.ATK);
const def = pokemon.getBattleStat(Stat.DEF); 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)); 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.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.damage(damage);
(pokemon.scene.getCurrentPhase() as MovePhase).cancel(); (pokemon.scene.getCurrentPhase() as MovePhase).cancel();
} }
@ -245,7 +245,7 @@ export class InfatuatedTag extends BattlerTag {
if (ret) { if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is in love\nwith ${pokemon.scene.getPokemonById(this.sourceId).name}!`)); 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)) { if (Utils.randInt(2)) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nimmobilized by love!')); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nimmobilized by love!'));
@ -283,12 +283,12 @@ export class SeedTag extends BattlerTag {
if (ret) { if (ret) {
const source = pokemon.scene.getPokemonById(this.sourceId); 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); 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.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; return ret;
@ -321,10 +321,10 @@ export class NightmareTag extends BattlerTag {
if (ret) { if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is locked\nin a NIGHTMARE!')); 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); 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); pokemon.damage(damage);
} }
@ -345,7 +345,7 @@ export class IngrainTag extends TrappedTag {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) 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)); getPokemonMessage(pokemon, ` absorbed\nnutrients with its roots!`), true));
return ret; return ret;
@ -375,7 +375,7 @@ export class AquaRingTag extends BattlerTag {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) 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)); Math.floor(pokemon.getMaxHp() / 16), `${this.getMoveName()} restored\n${pokemon.name}\'s HP!`, true));
return ret; return ret;
@ -395,7 +395,7 @@ export class DrowsyTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (!super.lapse(pokemon, lapseType)) { 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; return false;
} }
@ -425,10 +425,10 @@ export abstract class DamagingTrapTag extends TrappedTag {
if (ret) { if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby ${this.getMoveName()}!`)); 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); 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); pokemon.damage(damage);
} }
@ -543,7 +543,7 @@ export class TruantTag extends BattlerTag {
if (lastMove && lastMove.move !== Moves.NONE) { if (lastMove && lastMove.move !== Moves.NONE) {
(pokemon.scene.getCurrentPhase() as MovePhase).cancel(); (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!')); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nloafing around!'));
} }

View File

@ -78,7 +78,8 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
case BerryType.SITRUS: case BerryType.SITRUS:
case BerryType.ENIGMA: case BerryType.ENIGMA:
return (pokemon: Pokemon) => { 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: case BerryType.LUM:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
@ -96,13 +97,13 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
case BerryType.APICOT: case BerryType.APICOT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const battleStat = (berryType - BerryType.LIECHI) as BattleStat; 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: case BerryType.LANSAT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
pokemon.addTag(BattlerTagType.CRIT_BOOST); pokemon.addTag(BattlerTagType.CRIT_BOOST);
}; };
case BerryType.STARF: 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));
} }
} }

View File

@ -11,7 +11,8 @@ import { WeatherType } from "./weather";
import { ArenaTagType, ArenaTrapTag } from "./arena-tag"; import { ArenaTagType, ArenaTrapTag } from "./arena-tag";
import { BlockRecoilDamageAttr, applyAbAttrs } from "./ability"; import { BlockRecoilDamageAttr, applyAbAttrs } from "./ability";
import { PokemonHeldItemModifier } from "../modifier/modifier"; import { PokemonHeldItemModifier } from "../modifier/modifier";
import { BattleTarget } from "../battle"; import { BattlerIndex } from "../battle";
import { Stat } from "./pokemon-stat";
export enum MoveCategory { export enum MoveCategory {
PHYSICAL, PHYSICAL,
@ -160,12 +161,67 @@ export default class Move {
return true; 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 { 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) { 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); 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 { export class StatusMove extends Move {
@ -756,6 +812,14 @@ export abstract class MoveAttr {
getCondition(): MoveCondition { getCondition(): MoveCondition {
return null; 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 { export class MoveEffectAttr extends MoveAttr {
@ -793,6 +857,10 @@ export class HighCritAttr extends MoveAttr {
return true; return true;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return 3;
}
} }
export class CritOnlyAttr extends MoveAttr { export class CritOnlyAttr extends MoveAttr {
@ -801,6 +869,10 @@ export class CritOnlyAttr extends MoveAttr {
return true; return true;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return 5;
}
} }
export class FixedDamageAttr extends MoveAttr { export class FixedDamageAttr extends MoveAttr {
@ -890,12 +962,16 @@ export class RecoilAttr extends MoveEffectAttr {
return false; return false;
const recoilDamage = Math.max(Math.floor((!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) / 4), 1); 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.scene.queueMessage(getPokemonMessage(user, ' is hit\nwith recoil!'));
user.damage(recoilDamage); user.damage(recoilDamage);
return true; return true;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return Math.floor((move.power / 5) / -4);
}
} }
export class SacrificialAttr extends MoveEffectAttr { export class SacrificialAttr extends MoveEffectAttr {
@ -907,11 +983,15 @@ export class SacrificialAttr extends MoveEffectAttr {
if (!super.apply(user, target, move, args)) if (!super.apply(user, target, move, args))
return false; 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()); user.damage(user.getMaxHp());
return true; return true;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return Math.ceil((1 - user.getHpRatio()) * 10) - 10;
}
} }
export enum MultiHitType { export enum MultiHitType {
@ -937,9 +1017,13 @@ export class HealAttr extends MoveEffectAttr {
} }
addHealPhase(user: Pokemon, healRatio: number) { 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)); 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 { export class WeatherHealAttr extends HealAttr {
@ -979,10 +1063,14 @@ export class HitHealAttr extends MoveHitEffectAttr {
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { 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)); Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1), getPokemonMessage(target, ` had its\nenergy drained!`), false, true));
return 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 { export class MultiHitAttr extends MoveAttr {
@ -1019,6 +1107,10 @@ export class MultiHitAttr extends MoveAttr {
(args[0] as Utils.IntegerHolder).value = hitTimes; (args[0] as Utils.IntegerHolder).value = hitTimes;
return true; return true;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
return 5;
}
} }
export class StatusEffectAttr extends MoveHitEffectAttr { export class StatusEffectAttr extends MoveHitEffectAttr {
@ -1037,12 +1129,16 @@ export class StatusEffectAttr extends MoveHitEffectAttr {
if (statusCheck) { if (statusCheck) {
const pokemon = this.selfTarget ? user : target; const pokemon = this.selfTarget ? user : target;
if (!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0)) { 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 true;
} }
} }
return false; 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 { export class StealHeldItemAttr extends MoveHitEffectAttr {
@ -1051,8 +1147,7 @@ export class StealHeldItemAttr extends MoveHitEffectAttr {
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const heldItems = user.scene.findModifiers(m => m instanceof PokemonHeldItemModifier const heldItems = this.getTargetHeldItems(target);
&& (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
if (heldItems.length) { if (heldItems.length) {
const stolenItem = heldItems[Utils.randInt(heldItems.length)]; const stolenItem = heldItems[Utils.randInt(heldItems.length)];
user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false); user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false);
@ -1063,6 +1158,21 @@ export class StealHeldItemAttr extends MoveHitEffectAttr {
return false; 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 { export class HealStatusEffectAttr extends MoveEffectAttr {
@ -1089,6 +1199,10 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
return false; return false;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return user.status ? 10 : 0;
}
} }
export class BypassSleepAttr extends MoveAttr { export class BypassSleepAttr extends MoveAttr {
@ -1173,8 +1287,8 @@ export class ChargeAttr extends OverrideMoveEffectAttr {
user.addTag(this.tagType, 1, move.id, user.id); user.addTag(this.tagType, 1, move.id, user.id);
if (this.chargeEffect) if (this.chargeEffect)
applyMoveAttrs(MoveEffectAttr, user, target, move); applyMoveAttrs(MoveEffectAttr, user, target, move);
user.pushMoveHistory({ move: move.id, targets: [ target.getBattleTarget() ], result: MoveResult.OTHER }); user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
user.getMoveQueue().push({ move: move.id, ignorePP: true }); user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true });
resolve(true); resolve(true);
}); });
} else } else
@ -1217,7 +1331,7 @@ export class StatChangeAttr extends MoveEffectAttr {
if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) { if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) {
const levels = this.getLevels(user); 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; return true;
} }
@ -1227,6 +1341,12 @@ export class StatChangeAttr extends MoveEffectAttr {
getLevels(_user: Pokemon): integer { getLevels(_user: Pokemon): integer {
return this.levels; 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 { export class GrowthStatChangeAttr extends StatChangeAttr {
@ -1533,7 +1653,7 @@ export class FrenzyAttr extends MoveEffectAttr {
} }
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) { 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 { 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.getMoveQueue().length) {
if (!user.getTag(BattlerTagType.FRENZY)) { if (!user.getTag(BattlerTagType.FRENZY)) {
const turnCount = Utils.randInt(2) + 1; 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); user.addTag(BattlerTagType.FRENZY, 1, move.id, user.id);
} else { } else {
applyMoveAttrs(AddBattlerTagAttr, user, target, move, args); 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) ? (user: Pokemon, target: Pokemon, move: Move) => !(this.selfTarget ? user : target).getTag(this.tagType)
: null; : 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 { export class LapseBattlerTagAttr extends MoveEffectAttr {
@ -1683,6 +1845,10 @@ export class HitsTagAttr extends MoveAttr {
this.tagType = tagType; this.tagType = tagType;
this.doubleDamage = !!doubleDamage; 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 { export class AddArenaTagAttr extends MoveEffectAttr {
@ -1771,11 +1937,15 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr {
if (moves.length) { if (moves.length) {
const move = moves[Utils.randInt(moves.length)]; const move = moves[Utils.randInt(moves.length)];
const moveIndex = moveset.findIndex(m => m.moveId === move.moveId); const moveIndex = moveset.findIndex(m => m.moveId === move.moveId);
user.getMoveQueue().push({ move: move.moveId, ignorePP: this.enemyMoveset });
const moveTargets = getMoveTargets(user, move.moveId); const moveTargets = getMoveTargets(user, move.moveId);
if (!moveTargets.targets.length) if (!moveTargets.targets.length)
return false; 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)); user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, moveset[moveIndex], true));
return true; return true;
} }
@ -1789,13 +1959,18 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr {
return new Promise(resolve => { return new Promise(resolve => {
const moveIds = Utils.getEnumValues(Moves).filter(m => !allMoves[m].hasFlag(MoveFlags.IGNORE_VIRTUAL)); const moveIds = Utils.getEnumValues(Moves).filter(m => !allMoves[m].hasFlag(MoveFlags.IGNORE_VIRTUAL));
const moveId = moveIds[Utils.randInt(moveIds.length)]; const moveId = moveIds[Utils.randInt(moveIds.length)];
user.getMoveQueue().push({ move: moveId, ignorePP: true });
const moveTargets = getMoveTargets(user, moveId); const moveTargets = getMoveTargets(user, moveId);
if (!moveTargets.targets.length) { if (!moveTargets.targets.length) {
resolve(false); resolve(false);
return; 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)); user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true));
initMoveAnim(moveId).then(() => { initMoveAnim(moveId).then(() => {
loadMoveAnimAssets(user.scene, [ moveId ], true) loadMoveAnimAssets(user.scene, [ moveId ], true)
@ -1831,13 +2006,16 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr {
const copiedMove = targetMoves[0]; const copiedMove = targetMoves[0];
user.getMoveQueue().push({ move: copiedMove.move, ignorePP: true });
const moveTargets = getMoveTargets(user, copiedMove.move); const moveTargets = getMoveTargets(user, copiedMove.move);
if (!moveTargets.targets.length) if (!moveTargets.targets.length)
return false; 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)); 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 = { export type MoveTargetSet = {
targets: BattleTarget[]; targets: BattlerIndex[];
multiple: boolean; multiple: boolean;
} }
export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet { 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(); const opponents = user.getOpponents();
let set: BattleTarget[]; let set: BattlerIndex[] = [];
let multiple = false; let multiple = false;
switch (moveTarget) { switch (moveTarget) {
case MoveTarget.USER: case MoveTarget.USER:
set = [ user.getBattleTarget() ]; set = [ user.getBattlerIndex() ];
break;
case MoveTarget.OTHER:
set = user.getLastXMoves().find(() => true)?.targets || [];
break; break;
case MoveTarget.NEAR_OTHER: case MoveTarget.NEAR_OTHER:
case MoveTarget.OTHER:
case MoveTarget.ALL_NEAR_OTHERS: case MoveTarget.ALL_NEAR_OTHERS:
case MoveTarget.ALL_OTHERS: case MoveTarget.ALL_OTHERS:
set = (opponents.concat([ user.getAlly() ])).map(p => p?.getBattleTarget()); set = (opponents.concat([ user.getAlly() ])).map(p => p?.getBattlerIndex());
multiple = moveTarget !== MoveTarget.NEAR_OTHER; multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS
break; break;
case MoveTarget.NEAR_ENEMY: case MoveTarget.NEAR_ENEMY:
case MoveTarget.ALL_NEAR_ENEMIES: case MoveTarget.ALL_NEAR_ENEMIES:
case MoveTarget.ALL_ENEMIES: case MoveTarget.ALL_ENEMIES:
case MoveTarget.ENEMY_SIDE: case MoveTarget.ENEMY_SIDE:
set = opponents.map(p => p.getBattleTarget()); set = opponents.map(p => p.getBattlerIndex());
multiple = moveTarget !== MoveTarget.NEAR_ENEMY; multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
break; break;
case MoveTarget.RANDOM_NEAR_ENEMY: case MoveTarget.RANDOM_NEAR_ENEMY:
set = [ opponents[Utils.randInt(opponents.length)].getBattleTarget() ]; set = [ opponents[Utils.randInt(opponents.length)].getBattlerIndex() ];
break; break;
case MoveTarget.ATTACKER: case MoveTarget.ATTACKER:
set = [ user.scene.getPokemonById(user.turnData.attacksReceived[0].sourceId).getBattleTarget() ]; set = [ user.scene.getPokemonById(user.turnData.attacksReceived[0].sourceId).getBattlerIndex() ];
break; break;
case MoveTarget.NEAR_ALLY: case MoveTarget.NEAR_ALLY:
case MoveTarget.ALLY: case MoveTarget.ALLY:
set = [ user.getAlly()?.getBattleTarget() ]; set = [ user.getAlly()?.getBattlerIndex() ];
break; break;
case MoveTarget.USER_OR_NEAR_ALLY: case MoveTarget.USER_OR_NEAR_ALLY:
case MoveTarget.USER_AND_ALLIES: case MoveTarget.USER_AND_ALLIES:
case MoveTarget.USER_SIDE: 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; multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
break; break;
case MoveTarget.ALL: case MoveTarget.ALL:
case MoveTarget.BOTH_SIDES: 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; multiple = true;
break; break;
} }
console.log(set, multiple, MoveTarget[moveTarget]);
return { targets: set.filter(t => t !== undefined), multiple }; return { targets: set.filter(t => t !== undefined), multiple };
} }

View File

@ -127,7 +127,7 @@ export class PokemonReviveModifierType extends PokemonHpRestoreModifierType {
constructor(name: string, restorePercent: integer, iconImage?: string) { 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), super(name, restorePercent, true, (_type, args) => new Modifiers.PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints, true, true),
((pokemon: PlayerPokemon) => { ((pokemon: PlayerPokemon) => {
if (pokemon.hp) if (!pokemon.isFainted())
return PartyUiHandler.NoEffectMessage; return PartyUiHandler.NoEffectMessage;
return null; return null;
}), iconImage, 'revive'); }), iconImage, 'revive');
@ -668,15 +668,15 @@ const modifierPool = {
return statusEffectPartyMemberCount * 6; return statusEffectPartyMemberCount * 6;
}), }),
new WeightedModifierType(modifierTypes.REVIVE, (party: Pokemon[]) => { 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; return faintedPartyMemberCount * 9;
}), }),
new WeightedModifierType(modifierTypes.MAX_REVIVE, (party: Pokemon[]) => { 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; return faintedPartyMemberCount * 3;
}), }),
new WeightedModifierType(modifierTypes.SACRED_ASH, (party: Pokemon[]) => { 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[]) => { new WeightedModifierType(modifierTypes.HYPER_POTION, (party: Pokemon[]) => {
const thresholdPartyMemberCount = Math.min(party.filter(p => p.getInverseHp() >= 100 || p.getHpRatio() <= 0.625).length, 3); const thresholdPartyMemberCount = Math.min(party.filter(p => p.getInverseHp() >= 100 || p.getHpRatio() <= 0.625).length, 3);

View File

@ -480,7 +480,7 @@ export class TurnHealModifier extends PokemonHeldItemModifier {
if (pokemon.getHpRatio() < 1) { if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene; 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)); 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) { if (pokemon.turnData.damageDealt && pokemon.getHpRatio() < 1) {
const scene = pokemon.scene; 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)); Math.max(Math.floor(pokemon.turnData.damageDealt / 8) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true));
} }

View File

@ -1,7 +1,7 @@
import Phaser from 'phaser'; import Phaser from 'phaser';
import BattleScene from './battle-scene'; import BattleScene from './battle-scene';
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './ui/battle-info'; 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 { pokemonLevelMoves } from './data/pokemon-level-moves';
import { default as PokemonSpecies, PokemonSpeciesForm, getPokemonSpecies } from './data/pokemon-species'; import { default as PokemonSpecies, PokemonSpeciesForm, getPokemonSpecies } from './data/pokemon-species';
import * as Utils from './utils'; import * as Utils from './utils';
@ -23,9 +23,9 @@ import { WeatherType } from './data/weather';
import { TempBattleStat } from './data/temp-battle-stat'; import { TempBattleStat } from './data/temp-battle-stat';
import { ArenaTagType, WeakenMoveTypeTag } from './data/arena-tag'; import { ArenaTagType, WeakenMoveTypeTag } from './data/arena-tag';
import { Biome } from './data/biome'; 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 PokemonData from './system/pokemon-data';
import { BattleTarget } from './battle'; import { BattlerIndex } from './battle';
export enum FieldPosition { export enum FieldPosition {
CENTER, 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 isPlayer(): boolean;
abstract getFieldIndex(): integer; abstract getFieldIndex(): integer;
abstract getBattleTarget(): BattleTarget; abstract getBattlerIndex(): BattlerIndex;
loadAssets(): Promise<void> { loadAssets(): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
@ -565,7 +573,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
getOpponents(): Pokemon[] { 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 { getOpponentDescriptor(): string {
@ -660,9 +668,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (damage) { 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) if (isCritical)
this.scene.queueMessage('A critical hit!'); this.scene.queueMessage('A critical hit!');
this.scene.setPhaseQueueSplice();
this.damage(damage); this.damage(damage);
source.turnData.damageDealt += damage; source.turnData.damageDealt += damage;
this.turnData.attacksReceived.unshift({ move: move.id, result: result as DamageResult, damage: damage, sourceId: source.id }); 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}!`); this.scene.queueMessage(`It doesn\'t affect ${this.name}!`);
break; break;
} }
if (damage)
this.scene.clearPhaseQueueSplice();
} }
break; break;
case MoveCategory.STATUS: case MoveCategory.STATUS:
@ -690,7 +702,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
damage(damage: integer, preventEndure?: boolean): void { damage(damage: integer, preventEndure?: boolean): void {
if (!this.hp) if (this.isFainted())
return; return;
if (this.hp > 1 && this.hp - damage <= 0 && !preventEndure) { 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); this.hp = Math.max(this.hp - damage, 0);
if (!this.hp) { if (this.isFainted()) {
this.scene.pushPhase(new FaintPhase(this.scene, this.isPlayer(), this.getFieldIndex())); this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex()));
this.resetSummonData(); this.resetSummonData();
} }
} }
@ -1025,7 +1037,7 @@ export class PlayerPokemon extends Pokemon {
return this.scene.getPlayerField().indexOf(this); return this.scene.getPlayerField().indexOf(this);
} }
getBattleTarget(): BattleTarget { getBattlerIndex(): BattlerIndex {
return this.getFieldIndex(); return this.getFieldIndex();
} }
@ -1124,7 +1136,7 @@ export class EnemyPokemon extends Pokemon {
: null; : null;
if (queuedMove) { if (queuedMove) {
if (queuedMove.isUsable(this.getMoveQueue()[0].ignorePP)) 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 { else {
this.getMoveQueue().shift(); this.getMoveQueue().shift();
return this.getNextMove(); return this.getNextMove();
@ -1134,52 +1146,26 @@ export class EnemyPokemon extends Pokemon {
const movePool = this.getMoveset().filter(m => m.isUsable()); const movePool = this.getMoveset().filter(m => m.isUsable());
if (movePool.length) { if (movePool.length) {
if (movePool.length === 1) if (movePool.length === 1)
return { move: movePool[0].moveId }; return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) };
switch (this.aiType) { switch (this.aiType) {
case AiType.RANDOM: 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_RANDOM:
case AiType.SMART: case AiType.SMART:
const target = this.scene.getPlayerPokemon();
const moveScores = movePool.map(() => 0); const moveScores = movePool.map(() => 0);
const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ]));
for (let m in movePool) { for (let m in movePool) {
const pokemonMove = movePool[m]; const pokemonMove = movePool[m];
const move = pokemonMove.getMove(); const move = pokemonMove.getMove();
let moveScore = moveScores[m]; let moveScore = moveScores[m];
if (move.category === MoveCategory.STATUS)
moveScore++; for (let mt of moveTargets[move.id]) {
else { const target = this.scene.getField()[mt];
const effectiveness = this.getAttackMoveEffectiveness(move.type); moveScore += move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1);
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); moveScore /= moveTargets[move.id].length
}
}
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
}
// could make smarter by checking opponent def/spdef // could make smarter by checking opponent def/spdef
moveScores[m] = moveScore; moveScores[m] = moveScore;
@ -1199,11 +1185,34 @@ export class EnemyPokemon extends Pokemon {
r++; r++;
} }
console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName())); 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() { isPlayer() {
@ -1214,8 +1223,8 @@ export class EnemyPokemon extends Pokemon {
return this.scene.getEnemyField().indexOf(this); return this.scene.getEnemyField().indexOf(this);
} }
getBattleTarget(): BattleTarget { getBattlerIndex(): BattlerIndex {
return BattleTarget.ENEMY + this.getFieldIndex(); return BattlerIndex.ENEMY + this.getFieldIndex();
} }
addToParty() { addToParty() {
@ -1234,7 +1243,7 @@ export class EnemyPokemon extends Pokemon {
export interface TurnMove { export interface TurnMove {
move: Moves; move: Moves;
targets?: BattleTarget[]; targets?: BattlerIndex[];
result: MoveResult; result: MoveResult;
virtual?: boolean; virtual?: boolean;
turn?: integer; turn?: integer;
@ -1242,6 +1251,7 @@ export interface TurnMove {
export interface QueuedMove { export interface QueuedMove {
move: Moves; move: Moves;
targets: BattlerIndex[];
ignorePP?: boolean; ignorePP?: boolean;
} }

View File

@ -193,7 +193,7 @@ export function initAutoPlay() {
const party = thisArg.getParty(); const party = thisArg.getParty();
const modifierTypeOptions = modifierSelectUiHandler.options.map(o => o.modifierTypeOption); 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 lowHpPartyMemberIndex = party.findIndex(p => p.getHpRatio() <= 0.5);
const criticalHpPartyMemberIndex = party.findIndex(p => p.getHpRatio() <= 0.25); const criticalHpPartyMemberIndex = party.findIndex(p => p.getHpRatio() <= 0.25);

View File

@ -152,6 +152,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.y -= 12 * (mini ? 1 : -1); 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 ]; const toggledElements = [ this.hpNumbersContainer, this.expBar ];
toggledElements.forEach(el => el.setVisible(!mini)); toggledElements.forEach(el => el.setVisible(!mini));
} }

View File

@ -69,7 +69,7 @@ export default class PartyUiHandler extends MessageUiHandler {
private static FilterAll = (_pokemon: PlayerPokemon) => null; private static FilterAll = (_pokemon: PlayerPokemon) => null;
public static FilterNonFainted = (pokemon: PlayerPokemon) => { public static FilterNonFainted = (pokemon: PlayerPokemon) => {
if (!pokemon.hp) if (pokemon.isFainted())
return `${pokemon.name} has no energy\nleft to battle!`; return `${pokemon.name} has no energy\nleft to battle!`;
return null; return null;
}; };

View File

@ -1,4 +1,4 @@
import { BattleTarget } from "../battle"; import { BattlerIndex } from "../battle";
import BattleScene, { Button } from "../battle-scene"; import BattleScene, { Button } from "../battle-scene";
import { Moves, getMoveTargets } from "../data/move"; import { Moves, getMoveTargets } from "../data/move";
import { Mode } from "./ui"; import { Mode } from "./ui";
@ -12,7 +12,7 @@ export default class TargetSelectUiHandler extends UiHandler {
private move: Moves; private move: Moves;
private targetSelectCallback: TargetSelectCallback; private targetSelectCallback: TargetSelectCallback;
private targets: BattleTarget[]; private targets: BattlerIndex[];
private targetFlashTween: Phaser.Tweens.Tween; private targetFlashTween: Phaser.Tweens.Tween;
constructor(scene: BattleScene) { constructor(scene: BattleScene) {
@ -52,12 +52,12 @@ export default class TargetSelectUiHandler extends UiHandler {
} else { } else {
switch (button) { switch (button) {
case Button.UP: case Button.UP:
if (this.cursor < BattleTarget.ENEMY && 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 >= BattleTarget.ENEMY)); success = this.setCursor(this.targets.find(t => t >= BattlerIndex.ENEMY));
break; break;
case Button.DOWN: case Button.DOWN:
if (this.cursor >= BattleTarget.ENEMY && 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 < BattleTarget.ENEMY)); success = this.setCursor(this.targets.find(t => t < BattlerIndex.ENEMY));
break; break;
case Button.LEFT: case Button.LEFT:
if (this.cursor % 2 && this.targets.find(t => t === this.cursor - 1)) if (this.cursor % 2 && this.targets.find(t => t === this.cursor - 1))