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;
if (this.weather) {
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, true, 0, 0, CommonAnim.SUNNY + (weather - 1)));
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1)));
this.scene.queueMessage(getWeatherStartMessage(weather));
} else
this.scene.queueMessage(getWeatherClearMessage(oldWeatherType));

View File

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

View File

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

View File

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

View File

@ -157,7 +157,7 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
if (ret && pokemon.getHpRatio() < 1) {
const scene = pokemon.scene;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), pokemon.getFieldIndex(), Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
return true;
}
@ -181,7 +181,7 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr {
if (ret) {
cancelled.value = true;
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), true, [ this.stat ], this.levels));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels));
}
return ret;
@ -406,13 +406,21 @@ export class PostSummonStatChangeAbAttr extends PostSummonAbAttr {
}
applyPostSummon(pokemon: Pokemon, args: any[]): boolean {
const statChangePhase = new StatChangePhase(pokemon.scene, pokemon.isPlayer() === this.selfTarget, pokemon.getFieldIndex(), this.selfTarget, this.stats, this.levels);
const statChangePhases: StatChangePhase[] = [];
if (!this.selfTarget && !pokemon.getOpponent(0)?.summonData)
pokemon.scene.pushPhase(statChangePhase); // TODO: This causes the ability bar to be shown at the wrong time
else
pokemon.scene.unshiftPhase(statChangePhase);
if (this.selfTarget)
statChangePhases.push(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels));
else {
for (let opponent of pokemon.getOpponents())
statChangePhases.push(new StatChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.levels));
}
for (let statChangePhase of statChangePhases) {
if (!this.selfTarget && !statChangePhase.getPokemon().summonData)
pokemon.scene.pushPhase(statChangePhase); // TODO: This causes the ability bar to be shown at the wrong time
else
pokemon.scene.unshiftPhase(statChangePhase);
}
return true;
}
@ -576,7 +584,7 @@ export class PostTurnAbAttr extends AbAttr {
export class PostTurnSpeedBoostAbAttr extends PostTurnAbAttr {
applyPostTurn(pokemon: Pokemon, args: any[]): boolean {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), true, [ BattleStat.SPD ], 1));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.SPD ], 1));
return true;
}
}
@ -594,7 +602,7 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr {
applyPostTurn(pokemon: Pokemon, args: any[]): boolean {
if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), pokemon.getFieldIndex(),
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
return true;
}
@ -633,7 +641,8 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr {
applyPostWeatherLapse(pokemon: Pokemon, weather: Weather, args: any[]): boolean {
if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), pokemon.getFieldIndex(), Math.max(Math.floor(pokemon.getMaxHp() / (16 / this.healFactor)), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.getMaxHp() / (16 / this.healFactor)), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
return true;
}
@ -654,7 +663,7 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr {
if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene;
scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby its ${pokemon.getAbility()}!`));
scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), HitResult.OTHER));
scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER));
pokemon.damage(Math.ceil(pokemon.getMaxHp() * (16 / this.damageFactor)));
return true;
}
@ -989,7 +998,7 @@ function canApplyAttr(pokemon: Pokemon, attr: AbAttr): boolean {
}
function queueShowAbility(pokemon: Pokemon): void {
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex()));
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.scene.clearPhaseQueueSplice();
}

View File

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

View File

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

View File

@ -103,7 +103,7 @@ export class RechargingTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.getMoveQueue().push({ move: Moves.NONE })
pokemon.getMoveQueue().push({ move: Moves.NONE, targets: [] })
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -177,7 +177,7 @@ export class ConfusedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, CommonAnim.CONFUSION));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION));
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' became\nconfused!'));
}
@ -198,14 +198,14 @@ export class ConfusedTag extends BattlerTag {
if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nconfused!'));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, CommonAnim.CONFUSION));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION));
if (Utils.randInt(2)) {
const atk = pokemon.getBattleStat(Stat.ATK);
const def = pokemon.getBattleStat(Stat.DEF);
const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * ((Utils.randInt(15) + 85) / 100));
pokemon.scene.queueMessage('It hurt itself in its\nconfusion!');
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex()));
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.damage(damage);
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
}
@ -245,7 +245,7 @@ export class InfatuatedTag extends BattlerTag {
if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is in love\nwith ${pokemon.scene.getPokemonById(this.sourceId).name}!`));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, CommonAnim.ATTRACT));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT));
if (Utils.randInt(2)) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nimmobilized by love!'));
@ -283,12 +283,12 @@ export class SeedTag extends BattlerTag {
if (ret) {
const source = pokemon.scene.getPokemonById(this.sourceId);
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.isPlayer(), source.getFieldIndex(), pokemon.getFieldIndex(), CommonAnim.LEECH_SEED));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED));
const damage = Math.max(Math.floor(pokemon.getMaxHp() / 8), 1);
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex()));
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.damage(damage);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, !source.isPlayer(), source.getFieldIndex(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by LEECH SEED!'), false, true));
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by LEECH SEED!'), false, true));
}
return ret;
@ -321,10 +321,10 @@ export class NightmareTag extends BattlerTag {
if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is locked\nin a NIGHTMARE!'));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, CommonAnim.CURSE)); // TODO: Update animation type
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type
const damage = Math.ceil(pokemon.getMaxHp() / 4);
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex()));
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.damage(damage);
}
@ -345,7 +345,7 @@ export class IngrainTag extends TrappedTag {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret)
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), Math.floor(pokemon.getMaxHp() / 16),
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), Math.floor(pokemon.getMaxHp() / 16),
getPokemonMessage(pokemon, ` absorbed\nnutrients with its roots!`), true));
return ret;
@ -375,7 +375,7 @@ export class AquaRingTag extends BattlerTag {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret)
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(),
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 16), `${this.getMoveName()} restored\n${pokemon.name}\'s HP!`, true));
return ret;
@ -395,7 +395,7 @@ export class DrowsyTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (!super.lapse(pokemon, lapseType)) {
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), StatusEffect.SLEEP));
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.getBattlerIndex(), StatusEffect.SLEEP));
return false;
}
@ -425,10 +425,10 @@ export abstract class DamagingTrapTag extends TrappedTag {
if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby ${this.getMoveName()}!`));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, this.commonAnim));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim));
const damage = Math.ceil(pokemon.getMaxHp() / 16);
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex()));
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.damage(damage);
}
@ -543,7 +543,7 @@ export class TruantTag extends BattlerTag {
if (lastMove && lastMove.move !== Moves.NONE) {
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex()));
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nloafing around!'));
}

View File

@ -78,7 +78,8 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
case BerryType.SITRUS:
case BerryType.ENIGMA:
return (pokemon: Pokemon) => {
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true));
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true));
};
case BerryType.LUM:
return (pokemon: Pokemon) => {
@ -96,13 +97,13 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
case BerryType.APICOT:
return (pokemon: Pokemon) => {
const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), true, [ battleStat ], 1));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], 1));
};
case BerryType.LANSAT:
return (pokemon: Pokemon) => {
pokemon.addTag(BattlerTagType.CRIT_BOOST);
};
case BerryType.STARF:
return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), true, [ BattleStat.RAND ], 2));
return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.RAND ], 2));
}
}

View File

@ -11,7 +11,8 @@ import { WeatherType } from "./weather";
import { ArenaTagType, ArenaTrapTag } from "./arena-tag";
import { BlockRecoilDamageAttr, applyAbAttrs } from "./ability";
import { PokemonHeldItemModifier } from "../modifier/modifier";
import { BattleTarget } from "../battle";
import { BattlerIndex } from "../battle";
import { Stat } from "./pokemon-stat";
export enum MoveCategory {
PHYSICAL,
@ -160,12 +161,67 @@ export default class Move {
return true;
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
let score = 0;
for (let attr of this.attrs)
score += attr.getUserBenefitScore(user, target, move);
return score;
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
let score = 0;
for (let attr of this.attrs)
score += attr.getTargetBenefitScore(user, target, move);
return score;
}
}
export class AttackMove extends Move {
constructor(id: Moves, name: string, type: Type, category: MoveCategory, power: integer, accuracy: integer, pp: integer, tm: integer, effect: string, chance: integer, priority: integer, generation: integer) {
super(id, name, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, tm, effect, chance, priority, generation);
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
let ret = super.getTargetBenefitScore(user, target, move);
let attackScore = 0;
const effectiveness = target.getAttackMoveEffectiveness(this.type);
attackScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2;
if (attackScore) {
if (this.category === MoveCategory.PHYSICAL) {
if (user.getBattleStat(Stat.ATK) > user.getBattleStat(Stat.SPATK)) {
const statRatio = user.getBattleStat(Stat.SPATK) / user.getBattleStat(Stat.ATK);
if (statRatio <= 0.75)
attackScore *= 2;
else if (statRatio <= 0.875)
attackScore *= 1.5;
}
} else {
if (user.getBattleStat(Stat.SPATK) > user.getBattleStat(Stat.ATK)) {
const statRatio = user.getBattleStat(Stat.ATK) / user.getBattleStat(Stat.SPATK);
if (statRatio <= 0.75)
attackScore *= 2;
else if (statRatio <= 0.875)
attackScore *= 1.5;
}
}
const power = new Utils.NumberHolder(this.power);
applyMoveAttrs(VariablePowerAttr, user, target, move, power);
attackScore += Math.floor(power.value / 5);
}
ret -= attackScore;
return ret;
}
}
export class StatusMove extends Move {
@ -756,6 +812,14 @@ export abstract class MoveAttr {
getCondition(): MoveCondition {
return null;
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return 0;
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return 0;
}
}
export class MoveEffectAttr extends MoveAttr {
@ -793,6 +857,10 @@ export class HighCritAttr extends MoveAttr {
return true;
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return 3;
}
}
export class CritOnlyAttr extends MoveAttr {
@ -801,6 +869,10 @@ export class CritOnlyAttr extends MoveAttr {
return true;
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return 5;
}
}
export class FixedDamageAttr extends MoveAttr {
@ -890,12 +962,16 @@ export class RecoilAttr extends MoveEffectAttr {
return false;
const recoilDamage = Math.max(Math.floor((!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) / 4), 1);
user.scene.unshiftPhase(new DamagePhase(user.scene, user.isPlayer(), user.getFieldIndex(), HitResult.OTHER));
user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER));
user.scene.queueMessage(getPokemonMessage(user, ' is hit\nwith recoil!'));
user.damage(recoilDamage);
return true;
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return Math.floor((move.power / 5) / -4);
}
}
export class SacrificialAttr extends MoveEffectAttr {
@ -907,11 +983,15 @@ export class SacrificialAttr extends MoveEffectAttr {
if (!super.apply(user, target, move, args))
return false;
user.scene.unshiftPhase(new DamagePhase(user.scene, user.isPlayer(), user.getFieldIndex(), HitResult.OTHER));
user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER));
user.damage(user.getMaxHp());
return true;
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return Math.ceil((1 - user.getHpRatio()) * 10) - 10;
}
}
export enum MultiHitType {
@ -937,9 +1017,13 @@ export class HealAttr extends MoveEffectAttr {
}
addHealPhase(user: Pokemon, healRatio: number) {
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), user.getFieldIndex(),
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(),
Math.max(Math.floor(user.getMaxHp() * healRatio), 1), getPokemonMessage(user, ' regained\nhealth!'), true, !this.showAnim));
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return (1 - (this.selfTarget ? user : target).getHpRatio()) * 20;
}
}
export class WeatherHealAttr extends HealAttr {
@ -979,10 +1063,14 @@ export class HitHealAttr extends MoveHitEffectAttr {
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), user.getFieldIndex(),
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(),
Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1), getPokemonMessage(target, ` had its\nenergy drained!`), false, true));
return true;
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return Math.floor(Math.max((1 - user.getHpRatio()) - 0.33, 0) * ((move.power / 5) / 4));
}
}
export class MultiHitAttr extends MoveAttr {
@ -1019,6 +1107,10 @@ export class MultiHitAttr extends MoveAttr {
(args[0] as Utils.IntegerHolder).value = hitTimes;
return true;
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
return 5;
}
}
export class StatusEffectAttr extends MoveHitEffectAttr {
@ -1037,12 +1129,16 @@ export class StatusEffectAttr extends MoveHitEffectAttr {
if (statusCheck) {
const pokemon = this.selfTarget ? user : target;
if (!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0)) {
user.scene.unshiftPhase(new ObtainStatusEffectPhase(user.scene, pokemon.isPlayer(), this.effect, this.cureTurn));
user.scene.unshiftPhase(new ObtainStatusEffectPhase(user.scene, pokemon.getBattlerIndex(), this.effect, this.cureTurn));
return true;
}
}
return false;
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
return !(this.selfTarget ? user : target).status ? Math.floor(move.chance * -0.1) : 0;
}
}
export class StealHeldItemAttr extends MoveHitEffectAttr {
@ -1051,8 +1147,7 @@ export class StealHeldItemAttr extends MoveHitEffectAttr {
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const heldItems = user.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
const heldItems = this.getTargetHeldItems(target);
if (heldItems.length) {
const stolenItem = heldItems[Utils.randInt(heldItems.length)];
user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false);
@ -1063,6 +1158,21 @@ export class StealHeldItemAttr extends MoveHitEffectAttr {
return false;
}
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
const heldItems = this.getTargetHeldItems(target);
return heldItems.length ? 5 : 0;
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
const heldItems = this.getTargetHeldItems(target);
return heldItems.length ? -5 : 0;
}
}
export class HealStatusEffectAttr extends MoveEffectAttr {
@ -1089,6 +1199,10 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
return false;
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return user.status ? 10 : 0;
}
}
export class BypassSleepAttr extends MoveAttr {
@ -1173,8 +1287,8 @@ export class ChargeAttr extends OverrideMoveEffectAttr {
user.addTag(this.tagType, 1, move.id, user.id);
if (this.chargeEffect)
applyMoveAttrs(MoveEffectAttr, user, target, move);
user.pushMoveHistory({ move: move.id, targets: [ target.getBattleTarget() ], result: MoveResult.OTHER });
user.getMoveQueue().push({ move: move.id, ignorePP: true });
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true });
resolve(true);
});
} else
@ -1217,7 +1331,7 @@ export class StatChangeAttr extends MoveEffectAttr {
if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) {
const levels = this.getLevels(user);
user.scene.unshiftPhase(new StatChangePhase(user.scene, user.isPlayer() === this.selfTarget, user.getFieldIndex(), this.selfTarget, this.stats, levels));
user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels));
return true;
}
@ -1227,6 +1341,12 @@ export class StatChangeAttr extends MoveEffectAttr {
getLevels(_user: Pokemon): integer {
return this.levels;
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
// TODO: Add awareness of level limits
const levels = this.getLevels(user);
return (levels * 4) + (levels > 0 ? -2 : 2);
}
}
export class GrowthStatChangeAttr extends StatChangeAttr {
@ -1533,7 +1653,7 @@ export class FrenzyAttr extends MoveEffectAttr {
}
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
return !!(this.selfTarget ? user.hp : target.hp);
return !(this.selfTarget ? user : target).isFainted();
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
@ -1543,7 +1663,7 @@ export class FrenzyAttr extends MoveEffectAttr {
if (!user.getMoveQueue().length) {
if (!user.getTag(BattlerTagType.FRENZY)) {
const turnCount = Utils.randInt(2) + 1;
new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, ignorePP: true }));
new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }));
user.addTag(BattlerTagType.FRENZY, 1, move.id, user.id);
} else {
applyMoveAttrs(AddBattlerTagAttr, user, target, move, args);
@ -1599,6 +1719,48 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
? (user: Pokemon, target: Pokemon, move: Move) => !(this.selfTarget ? user : target).getTag(this.tagType)
: null;
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
switch (this.tagType) {
case BattlerTagType.FLINCHED:
return -5;
case BattlerTagType.CONFUSED:
return -5;
case BattlerTagType.INFATUATED:
return -5;
case BattlerTagType.SEEDED:
return -3;
case BattlerTagType.NIGHTMARE:
return -5;
case BattlerTagType.FRENZY:
return -2;
case BattlerTagType.INGRAIN:
return 3;
case BattlerTagType.AQUA_RING:
return 3;
case BattlerTagType.DROWSY:
return -5;
case BattlerTagType.TRAPPED:
case BattlerTagType.BIND:
case BattlerTagType.WRAP:
case BattlerTagType.FIRE_SPIN:
case BattlerTagType.WHIRLPOOL:
case BattlerTagType.CLAMP:
case BattlerTagType.SAND_TOMB:
case BattlerTagType.MAGMA_STORM:
return -3;
case BattlerTagType.PROTECTED:
return 10;
case BattlerTagType.FLYING:
return 5;
case BattlerTagType.CRIT_BOOST:
return 5;
case BattlerTagType.NO_CRIT:
return -5;
case BattlerTagType.IGNORE_ACCURACY:
return 3;
}
}
}
export class LapseBattlerTagAttr extends MoveEffectAttr {
@ -1683,6 +1845,10 @@ export class HitsTagAttr extends MoveAttr {
this.tagType = tagType;
this.doubleDamage = !!doubleDamage;
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return target.getTag(this.tagType) ? this.doubleDamage ? 10 : 5 : 0;
}
}
export class AddArenaTagAttr extends MoveEffectAttr {
@ -1771,11 +1937,15 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr {
if (moves.length) {
const move = moves[Utils.randInt(moves.length)];
const moveIndex = moveset.findIndex(m => m.moveId === move.moveId);
user.getMoveQueue().push({ move: move.moveId, ignorePP: this.enemyMoveset });
const moveTargets = getMoveTargets(user, move.moveId);
if (!moveTargets.targets.length)
return false;
const targets = moveTargets.multiple ? moveTargets.targets : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
const targets = moveTargets.multiple || moveTargets.targets.length === 1
? moveTargets.targets
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
? [ target.getBattlerIndex() ]
: [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
user.getMoveQueue().push({ move: move.moveId, targets: targets, ignorePP: this.enemyMoveset });
user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, moveset[moveIndex], true));
return true;
}
@ -1789,13 +1959,18 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr {
return new Promise(resolve => {
const moveIds = Utils.getEnumValues(Moves).filter(m => !allMoves[m].hasFlag(MoveFlags.IGNORE_VIRTUAL));
const moveId = moveIds[Utils.randInt(moveIds.length)];
user.getMoveQueue().push({ move: moveId, ignorePP: true });
const moveTargets = getMoveTargets(user, moveId);
if (!moveTargets.targets.length) {
resolve(false);
return;
}
const targets = moveTargets.multiple ? moveTargets.targets : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
const targets = moveTargets.multiple || moveTargets.targets.length === 1
? moveTargets.targets
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
? [ target.getBattlerIndex() ]
: [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true });
user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true));
initMoveAnim(moveId).then(() => {
loadMoveAnimAssets(user.scene, [ moveId ], true)
@ -1831,13 +2006,16 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr {
const copiedMove = targetMoves[0];
user.getMoveQueue().push({ move: copiedMove.move, ignorePP: true });
const moveTargets = getMoveTargets(user, copiedMove.move);
if (!moveTargets.targets.length)
return false;
const targets = moveTargets.multiple ? moveTargets.targets : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
const targets = moveTargets.multiple || moveTargets.targets.length === 1
? moveTargets.targets
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
? [ target.getBattlerIndex() ]
: [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
user.getMoveQueue().push({ move: copiedMove.move, targets: targets, ignorePP: true });
user.scene.unshiftPhase(new MovePhase(user.scene, user as PlayerPokemon, targets, new PokemonMove(copiedMove.move, 0, 0, true), true));
@ -1947,62 +2125,58 @@ export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon
}
export type MoveTargetSet = {
targets: BattleTarget[];
targets: BattlerIndex[];
multiple: boolean;
}
export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet {
const moveTarget = move ? allMoves[move].moveTarget : MoveTarget.NEAR_ENEMY;
const moveTarget = move ? allMoves[move].moveTarget : move === undefined ? undefined : MoveTarget.NEAR_ENEMY;
const opponents = user.getOpponents();
let set: BattleTarget[];
let set: BattlerIndex[] = [];
let multiple = false;
switch (moveTarget) {
case MoveTarget.USER:
set = [ user.getBattleTarget() ];
break;
case MoveTarget.OTHER:
set = user.getLastXMoves().find(() => true)?.targets || [];
set = [ user.getBattlerIndex() ];
break;
case MoveTarget.NEAR_OTHER:
case MoveTarget.OTHER:
case MoveTarget.ALL_NEAR_OTHERS:
case MoveTarget.ALL_OTHERS:
set = (opponents.concat([ user.getAlly() ])).map(p => p?.getBattleTarget());
multiple = moveTarget !== MoveTarget.NEAR_OTHER;
set = (opponents.concat([ user.getAlly() ])).map(p => p?.getBattlerIndex());
multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS
break;
case MoveTarget.NEAR_ENEMY:
case MoveTarget.ALL_NEAR_ENEMIES:
case MoveTarget.ALL_ENEMIES:
case MoveTarget.ENEMY_SIDE:
set = opponents.map(p => p.getBattleTarget());
set = opponents.map(p => p.getBattlerIndex());
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
break;
case MoveTarget.RANDOM_NEAR_ENEMY:
set = [ opponents[Utils.randInt(opponents.length)].getBattleTarget() ];
set = [ opponents[Utils.randInt(opponents.length)].getBattlerIndex() ];
break;
case MoveTarget.ATTACKER:
set = [ user.scene.getPokemonById(user.turnData.attacksReceived[0].sourceId).getBattleTarget() ];
set = [ user.scene.getPokemonById(user.turnData.attacksReceived[0].sourceId).getBattlerIndex() ];
break;
case MoveTarget.NEAR_ALLY:
case MoveTarget.ALLY:
set = [ user.getAlly()?.getBattleTarget() ];
set = [ user.getAlly()?.getBattlerIndex() ];
break;
case MoveTarget.USER_OR_NEAR_ALLY:
case MoveTarget.USER_AND_ALLIES:
case MoveTarget.USER_SIDE:
set = [ user, user.getAlly() ].map(p => p?.getBattleTarget());
set = [ user, user.getAlly() ].map(p => p?.getBattlerIndex());
multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
break;
case MoveTarget.ALL:
case MoveTarget.BOTH_SIDES:
set = [ user, user.getAlly() ].concat(user.getOpponents()).map(p => p?.getBattleTarget());
set = [ user, user.getAlly() ].concat(user.getOpponents()).map(p => p?.getBattlerIndex());
multiple = true;
break;
}
console.log(set, multiple, MoveTarget[moveTarget]);
return { targets: set.filter(t => t !== undefined), multiple };
}

View File

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

View File

@ -480,7 +480,7 @@ export class TurnHealModifier extends PokemonHeldItemModifier {
if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), pokemon.getFieldIndex(),
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.getMaxHp() / 16) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true));
}
@ -510,7 +510,7 @@ export class HitHealModifier extends PokemonHeldItemModifier {
if (pokemon.turnData.damageDealt && pokemon.getHpRatio() < 1) {
const scene = pokemon.scene;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), pokemon.getFieldIndex(),
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.turnData.damageDealt / 8) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true));
}

View File

@ -1,7 +1,7 @@
import Phaser from 'phaser';
import BattleScene from './battle-scene';
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './ui/battle-info';
import Move, { StatChangeAttr, HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariablePowerAttr, Moves, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr } from "./data/move";
import Move, { StatChangeAttr, HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariablePowerAttr, Moves, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, AttackMove, AddBattlerTagAttr } from "./data/move";
import { pokemonLevelMoves } from './data/pokemon-level-moves';
import { default as PokemonSpecies, PokemonSpeciesForm, getPokemonSpecies } from './data/pokemon-species';
import * as Utils from './utils';
@ -23,9 +23,9 @@ import { WeatherType } from './data/weather';
import { TempBattleStat } from './data/temp-battle-stat';
import { ArenaTagType, WeakenMoveTypeTag } from './data/arena-tag';
import { Biome } from './data/biome';
import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, PreApplyBattlerTagAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, abilities, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability';
import { Abilities, Ability, BattleStatMultiplierAbAttr, BattlerTagImmunityAbAttr, BlockCritAbAttr, PreApplyBattlerTagAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, abilities, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability';
import PokemonData from './system/pokemon-data';
import { BattleTarget } from './battle';
import { BattlerIndex } from './battle';
export enum FieldPosition {
CENTER,
@ -188,11 +188,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
isFainted(): boolean {
return !this.hp;
}
isActive(): boolean {
return !this.isFainted() && !!this.scene;
}
abstract isPlayer(): boolean;
abstract getFieldIndex(): integer;
abstract getBattleTarget(): BattleTarget;
abstract getBattlerIndex(): BattlerIndex;
loadAssets(): Promise<void> {
return new Promise(resolve => {
@ -565,7 +573,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
getOpponents(): Pokemon[] {
return this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField();
return ((this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField()) as Pokemon[]).filter(p => p.isActive());
}
getOpponentDescriptor(): string {
@ -660,9 +668,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
if (damage) {
this.scene.unshiftPhase(new DamagePhase(this.scene, this.isPlayer(), this.getFieldIndex(), result as DamageResult));
this.scene.unshiftPhase(new DamagePhase(this.scene, this.getBattlerIndex(), result as DamageResult));
if (isCritical)
this.scene.queueMessage('A critical hit!');
this.scene.setPhaseQueueSplice();
this.damage(damage);
source.turnData.damageDealt += damage;
this.turnData.attacksReceived.unshift({ move: move.id, result: result as DamageResult, damage: damage, sourceId: source.id });
@ -679,6 +688,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.scene.queueMessage(`It doesn\'t affect ${this.name}!`);
break;
}
if (damage)
this.scene.clearPhaseQueueSplice();
}
break;
case MoveCategory.STATUS:
@ -690,7 +702,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
damage(damage: integer, preventEndure?: boolean): void {
if (!this.hp)
if (this.isFainted())
return;
if (this.hp > 1 && this.hp - damage <= 0 && !preventEndure) {
@ -701,8 +713,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
this.hp = Math.max(this.hp - damage, 0);
if (!this.hp) {
this.scene.pushPhase(new FaintPhase(this.scene, this.isPlayer(), this.getFieldIndex()));
if (this.isFainted()) {
this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex()));
this.resetSummonData();
}
}
@ -1025,7 +1037,7 @@ export class PlayerPokemon extends Pokemon {
return this.scene.getPlayerField().indexOf(this);
}
getBattleTarget(): BattleTarget {
getBattlerIndex(): BattlerIndex {
return this.getFieldIndex();
}
@ -1124,7 +1136,7 @@ export class EnemyPokemon extends Pokemon {
: null;
if (queuedMove) {
if (queuedMove.isUsable(this.getMoveQueue()[0].ignorePP))
return { move: queuedMove.moveId, ignorePP: this.getMoveQueue()[0].ignorePP };
return { move: queuedMove.moveId, targets: this.getMoveQueue()[0].targets, ignorePP: this.getMoveQueue()[0].ignorePP };
else {
this.getMoveQueue().shift();
return this.getNextMove();
@ -1134,52 +1146,26 @@ export class EnemyPokemon extends Pokemon {
const movePool = this.getMoveset().filter(m => m.isUsable());
if (movePool.length) {
if (movePool.length === 1)
return { move: movePool[0].moveId };
return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) };
switch (this.aiType) {
case AiType.RANDOM:
return { move: movePool[Utils.randInt(movePool.length)].moveId };
const moveId = movePool[Utils.randInt(movePool.length)].moveId;
return { move: moveId, targets: this.getNextTargets(moveId) };
case AiType.SMART_RANDOM:
case AiType.SMART:
const target = this.scene.getPlayerPokemon();
const moveScores = movePool.map(() => 0);
const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ]));
for (let m in movePool) {
const pokemonMove = movePool[m];
const move = pokemonMove.getMove();
let moveScore = moveScores[m];
if (move.category === MoveCategory.STATUS)
moveScore++;
else {
const effectiveness = this.getAttackMoveEffectiveness(move.type);
moveScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2;
if (moveScore) {
if (move.category === MoveCategory.PHYSICAL) {
if (this.getBattleStat(Stat.ATK) > this.getBattleStat(Stat.SPATK)) {
const statRatio = this.getBattleStat(Stat.SPATK) / this.getBattleStat(Stat.ATK);
if (statRatio <= 0.75)
moveScore *= 2;
else if (statRatio <= 0.875)
moveScore *= 1.5;
}
} else {
if (this.getBattleStat(Stat.SPATK) > this.getBattleStat(Stat.ATK)) {
const statRatio = this.getBattleStat(Stat.ATK) / this.getBattleStat(Stat.SPATK);
if (statRatio <= 0.75)
moveScore *= 2;
else if (statRatio <= 0.875)
moveScore *= 1.5;
}
}
moveScore += Math.floor(move.power / 5);
}
for (let mt of moveTargets[move.id]) {
const target = this.scene.getField()[mt];
moveScore += move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1);
}
const statChangeAttrs = move.getAttrs(StatChangeAttr) as StatChangeAttr[];
for (let sc of statChangeAttrs) {
moveScore += ((sc.levels >= 1) === sc.selfTarget ? -2 : 2) + sc.levels * (sc.selfTarget ? 4 : -4);
// TODO: Add awareness of current levels
}
moveScore /= moveTargets[move.id].length
// could make smarter by checking opponent def/spdef
moveScores[m] = moveScore;
@ -1199,11 +1185,34 @@ export class EnemyPokemon extends Pokemon {
r++;
}
console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName()));
return { move: sortedMovePool[r].moveId };
return { move: sortedMovePool[r].moveId, targets: moveTargets[sortedMovePool[r].moveId] };
}
}
return { move: Moves.STRUGGLE };
return { move: Moves.STRUGGLE, targets: this.getNextTargets(Moves.STRUGGLE) };
}
getNextTargets(moveId: Moves): BattlerIndex[] {
const moveTargets = getMoveTargets(this, moveId);
const targets = this.scene.getField().filter(p => p?.isActive() && moveTargets.targets.indexOf(p.getBattlerIndex()) > -1);
if (moveTargets.multiple)
return targets.map(p => p.getBattlerIndex());
const move = allMoves[moveId];
let benefitScores = targets
.map(p => [ p.getBattlerIndex(), move.getTargetBenefitScore(this, p, move) * (p.isPlayer() === this.isPlayer() ? 1 : -1) ]);
const sortedBenefitScores = benefitScores.slice(0);
sortedBenefitScores.sort((a, b) => {
const scoreA = a[1];
const scoreB = b[1];
return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0;
});
// TODO: Add some randomness
return [ sortedBenefitScores[0][0] ];
}
isPlayer() {
@ -1214,8 +1223,8 @@ export class EnemyPokemon extends Pokemon {
return this.scene.getEnemyField().indexOf(this);
}
getBattleTarget(): BattleTarget {
return BattleTarget.ENEMY + this.getFieldIndex();
getBattlerIndex(): BattlerIndex {
return BattlerIndex.ENEMY + this.getFieldIndex();
}
addToParty() {
@ -1234,7 +1243,7 @@ export class EnemyPokemon extends Pokemon {
export interface TurnMove {
move: Moves;
targets?: BattleTarget[];
targets?: BattlerIndex[];
result: MoveResult;
virtual?: boolean;
turn?: integer;
@ -1242,6 +1251,7 @@ export interface TurnMove {
export interface QueuedMove {
move: Moves;
targets: BattlerIndex[];
ignorePP?: boolean;
}

View File

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

View File

@ -152,6 +152,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.y -= 12 * (mini ? 1 : -1);
}
const offsetElements = [ this.nameText, this.genderText, this.statusIndicator, this.levelContainer ];
offsetElements.forEach(el => el.y += 1.5 * (mini ? -1 : 1));
const toggledElements = [ this.hpNumbersContainer, this.expBar ];
toggledElements.forEach(el => el.setVisible(!mini));
}

View File

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

View File

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