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