Add WiP logic for double battles

This commit is contained in:
Flashfyre 2023-05-11 17:45:39 -04:00
parent 0757fd88a4
commit c123119d48
17 changed files with 616 additions and 420 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, CommonAnim.SUNNY + (weather - 1)));
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, true, 0, 0, CommonAnim.SUNNY + (weather - 1)));
this.scene.queueMessage(getWeatherStartMessage(weather));
} else
this.scene.queueMessage(getWeatherClearMessage(oldWeatherType));

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
import Phaser from 'phaser';
import { Biome } from './data/biome';
import UI from './ui/ui';
import { EncounterPhase, SummonPhase, CommandPhase, NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, CheckLoadPhase } from './battle-phases';
import { EncounterPhase, SummonPhase, NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, CheckLoadPhase, TurnInitPhase } from './battle-phases';
import Pokemon, { PlayerPokemon, EnemyPokemon } from './pokemon';
import PokemonSpecies, { allSpecies, getPokemonSpecies, initSpecies } from './data/pokemon-species';
import * as Utils from './utils';
@ -9,7 +9,6 @@ import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, P
import { PokeballType } from './data/pokeball';
import { Species } from './data/species';
import { initAutoPlay } from './system/auto-play';
import { Battle } from './battle';
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from './data/battle-anims';
import { BattlePhase } from './battle-phase';
import { initGameSpeed } from './system/game-speed';
@ -21,6 +20,7 @@ import { Moves, initMoves } from './data/move';
import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave } from './modifier/modifier-type';
import AbilityBar from './ui/ability-bar';
import { BlockItemTheftAbAttr, applyAbAttrs, initAbilities } from './data/ability';
import Battle from './battle';
const enableAuto = true;
const quickStart = false;
@ -446,21 +446,35 @@ export default class BattleScene extends Phaser.Scene {
return this.party;
}
getEnemyParty(): EnemyPokemon[] {
return this.getEnemyPokemon() ? [ this.getEnemyPokemon() ] : [];
}
getPlayerPokemon(): PlayerPokemon {
return this.getParty()[0];
}
getPlayerField(): PlayerPokemon[] {
const party = this.getParty();
return party.slice(0, Math.min(party.length, this.currentBattle?.double ? 2 : 1));
}
getEnemyPokemon(): EnemyPokemon {
return this.currentBattle?.enemyPokemon;
return this.currentBattle?.enemyField[0];
}
getEnemyField(): EnemyPokemon[] {
return this.currentBattle?.enemyField || [];
}
getField(): Pokemon[] {
const ret = new Array(4).fill(null);
const playerField = this.getPlayerField();
const enemyField = this.getEnemyField();
ret.splice(0, playerField.length, ...playerField);
ret.splice(2, enemyField.length, ...enemyField);
return ret;
}
getPokemonById(pokemonId: integer): Pokemon {
const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId);
return findInParty(this.getParty()) || findInParty(this.getEnemyParty());
return findInParty(this.getParty()) || findInParty(this.getEnemyField());
}
reset(): void {
@ -475,7 +489,7 @@ export default class BattleScene extends Phaser.Scene {
for (let p of this.getParty())
p.destroy();
this.party = [];
for (let p of this.getEnemyParty())
for (let p of this.getEnemyField())
p.destroy();
this.currentBattle = null;
@ -493,10 +507,10 @@ export default class BattleScene extends Phaser.Scene {
this.trainer.setPosition(406, 132);
}
newBattle(waveIndex?: integer): Battle {
newBattle(waveIndex?: integer, double?: boolean): Battle {
if (!waveIndex) {
if (this.currentBattle) {
this.getEnemyPokemon().destroy();
this.getEnemyField().forEach(enemyPokemon => enemyPokemon.destroy());
if (this.currentBattle.waveIndex % 10)
this.pushPhase(new NextEncounterPhase(this));
else {
@ -509,12 +523,14 @@ export default class BattleScene extends Phaser.Scene {
else {
this.arena.playBgm();
this.pushPhase(new EncounterPhase(this));
this.pushPhase(new SummonPhase(this));
this.pushPhase(new SummonPhase(this, 0));
if (double === undefined || double)
this.pushPhase(new SummonPhase(this, 1));
}
}
}
this.currentBattle = new Battle(waveIndex || ((this.currentBattle?.waveIndex || (startingWave - 1)) + 1));
this.currentBattle = new Battle(waveIndex || ((this.currentBattle?.waveIndex || (startingWave - 1)) + 1), double === undefined || double);
return this.currentBattle;
}
@ -699,7 +715,7 @@ export default class BattleScene extends Phaser.Scene {
}
populatePhaseQueue(): void {
this.phaseQueue.push(new CommandPhase(this));
this.phaseQueue.push(new TurnInitPhase(this));
}
addModifier(modifier: Modifier, playSound?: boolean, virtual?: boolean): Promise<void> {
@ -828,7 +844,9 @@ export default class BattleScene extends Phaser.Scene {
}
if (isBoss)
count = Math.max(count, Math.floor(chances / 2));
getEnemyModifierTypesForWave(waveIndex, count, this.getEnemyParty()).map(mt => mt.newModifier(this.getEnemyPokemon()).add(this.enemyModifiers, false));
const enemyField = this.getEnemyField();
getEnemyModifierTypesForWave(waveIndex, count, this.getEnemyField())
.map(mt => mt.newModifier(enemyField[enemyField.length === 1 ? 0 : Utils.randInt(enemyField.length)]).add(this.enemyModifiers, false));
this.updateModifiers(false).then(() => resolve());
});
@ -855,7 +873,7 @@ export default class BattleScene extends Phaser.Scene {
modifiers.splice(modifiers.indexOf(modifier), 1);
}
this.updatePartyForModifiers(player ? this.getParty() : this.getEnemyParty()).then(() => {
this.updatePartyForModifiers(player ? this.getParty() : this.getEnemyField()).then(() => {
(player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers);
if (!player)
this.updateWaveCountPosition();

View File

@ -1,18 +1,44 @@
import { EnemyPokemon, PlayerPokemon } from "./pokemon";
import { EnemyPokemon, PlayerPokemon, QueuedMove } from "./pokemon";
import { Command } from "./ui/command-ui-handler";
import * as Utils from "./utils";
export class Battle {
export enum BattleTarget {
PLAYER,
PLAYER_2,
ENEMY,
ENEMY_2
}
interface TurnCommand {
command: Command;
cursor?: integer;
move?: QueuedMove;
targetIndex?: integer;
args?: any[];
};
interface TurnCommands {
[key: integer]: TurnCommand
}
export default class Battle {
public waveIndex: integer;
public enemyLevel: integer;
public enemyPokemon: EnemyPokemon;
public enemyLevels: integer[];
public enemyField: EnemyPokemon[];
public double: boolean;
public turn: integer;
public turnCommands: TurnCommands;
public playerParticipantIds: Set<integer> = new Set<integer>();
public escapeAttempts: integer = 0;
constructor(waveIndex: integer) {
constructor(waveIndex: integer, double: boolean) {
this.waveIndex = waveIndex;
this.enemyLevel = this.getLevelForWave();
this.turn = 1;
this.enemyLevels = new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave());
this.enemyField = [];
this.double = double;
this.turn = 0;
this.incrementTurn();
}
private getLevelForWave(): number {
@ -31,6 +57,7 @@ export class Battle {
incrementTurn() {
this.turn++;
this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattleTarget).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(), 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.isPlayer(), pokemon.getFieldIndex(), 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(), true, [ this.stat ], this.levels));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), true, [ this.stat ], this.levels));
}
return ret;
@ -406,9 +406,9 @@ export class PostSummonStatChangeAbAttr extends PostSummonAbAttr {
}
applyPostSummon(pokemon: Pokemon, args: any[]): boolean {
const statChangePhase = new StatChangePhase(pokemon.scene, pokemon.isPlayer() === this.selfTarget, this.selfTarget, this.stats, this.levels);
const statChangePhase = new StatChangePhase(pokemon.scene, pokemon.isPlayer() === this.selfTarget, pokemon.getFieldIndex(), this.selfTarget, this.stats, this.levels);
if (!this.selfTarget && !pokemon.getOpponent()?.summonData)
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);
@ -576,7 +576,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(), true, [ BattleStat.SPD ], 1));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), true, [ BattleStat.SPD ], 1));
return true;
}
}
@ -594,7 +594,8 @@ 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(), Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), pokemon.getFieldIndex(),
Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
return true;
}
@ -632,7 +633,7 @@ 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(), 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.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));
return true;
}
@ -988,7 +989,7 @@ function canApplyAttr(pokemon: Pokemon, attr: AbAttr): boolean {
}
function queueShowAbility(pokemon: Pokemon): void {
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer()));
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex()));
pokemon.scene.clearPhaseQueueSplice();
}
@ -1232,8 +1233,8 @@ export function initAbilities() {
new Ability(Abilities.MAGMA_ARMOR, "Magma Armor", "Prevents the POKéMON from becoming frozen.", 3)
.attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE),
new Ability(Abilities.MAGNET_PULL, "Magnet Pull", "Prevents STEEL-type POKéMON from escaping.", 3)
.attr(ArenaTrapAbAttr)
.condition((pokemon: Pokemon) => pokemon.getOpponent()?.isOfType(Type.STEEL)),
/*.attr(ArenaTrapAbAttr)
.condition((pokemon: Pokemon) => pokemon.getOpponent()?.isOfType(Type.STEEL))*/, // TODO: Rework
new Ability(Abilities.MARVEL_SCALE, "Marvel Scale (N)", "Ups DEFENSE if there is a status problem.", 3),
new Ability(Abilities.MINUS, "Minus (N)", "Ups SP. ATK if another POKéMON has PLUS or MINUS.", 3),
new Ability(Abilities.NATURAL_CURE, "Natural Cure (N)", "All status problems heal when it switches out.", 3),

View File

@ -143,8 +143,7 @@ class SpikesTag extends ArenaTrapTag {
super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId);
const target = source.getOpponent();
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${target.name}'s feet!`);
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${source.getOpponentDescriptor()}'s feet!`);
}
activateTrap(pokemon: Pokemon): boolean {
@ -170,15 +169,14 @@ class ToxicSpikesTag extends ArenaTrapTag {
super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId);
const target = source.getOpponent();
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${target.name}'s feet!`);
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${source.getOpponentDescriptor()}'s feet!`);
}
activateTrap(pokemon: Pokemon): boolean {
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.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(),
!toxic ? StatusEffect.POISON : StatusEffect.TOXIC, null, `the ${this.getMoveName()}`));
return true;
}
@ -196,8 +194,7 @@ class StealthRockTag extends ArenaTrapTag {
super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId);
const target = source.getOpponent();
arena.scene.queueMessage(`Pointed stones float in the air\naround ${target.name}!`);
arena.scene.queueMessage(`Pointed stones float in the air\naround ${source.getOpponentDescriptor()}!`);
}
activateTrap(pokemon: Pokemon): boolean {
@ -242,8 +239,8 @@ export class TrickRoomTag extends ArenaTag {
}
apply(args: any[]): boolean {
const speedDelayed = args[0] as Utils.BooleanHolder;
speedDelayed.value = !speedDelayed.value;
const speedReversed = args[0] as Utils.BooleanHolder;
speedReversed.value = !speedReversed.value;
return true;
}

View File

@ -831,8 +831,8 @@ export class CommonBattleAnim extends BattleAnim {
export class MoveAnim extends BattleAnim {
public move: Moves;
constructor(move: Moves, user: Pokemon) {
super(user, getMoveTarget(user, move));
constructor(move: Moves, user: Pokemon, targetIndex: integer) {
super(user, getMoveTarget(user, targetIndex, move));
this.move = move;
}
@ -852,7 +852,7 @@ export class MoveChargeAnim extends MoveAnim {
private chargeAnim: ChargeAnim;
constructor(chargeAnim: ChargeAnim, move: Moves, user: Pokemon) {
super(move, user);
super(move, user, 0);
this.chargeAnim = chargeAnim;
}

View File

@ -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(), CommonAnim.CONFUSION));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, 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(), CommonAnim.CONFUSION));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, 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.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex()));
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(), CommonAnim.ATTRACT));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, CommonAnim.ATTRACT));
if (Utils.randInt(2)) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nimmobilized by love!'));
@ -282,12 +282,13 @@ export class SeedTag extends BattlerTag {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) {
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, !pokemon.isPlayer(), CommonAnim.LEECH_SEED));
const source = pokemon.scene.getPokemonById(this.sourceId);
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.isPlayer(), source.getFieldIndex(), pokemon.getFieldIndex(), CommonAnim.LEECH_SEED));
const damage = Math.max(Math.floor(pokemon.getMaxHp() / 8), 1);
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer()));
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex()));
pokemon.damage(damage);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, !pokemon.isPlayer(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by LEECH SEED!'), false, true));
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, !source.isPlayer(), source.getFieldIndex(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by LEECH SEED!'), false, true));
}
return ret;
@ -320,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(), CommonAnim.CURSE)); // TODO: Update animation type
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, CommonAnim.CURSE)); // TODO: Update animation type
const damage = Math.ceil(pokemon.getMaxHp() / 4);
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer()));
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex()));
pokemon.damage(damage);
}
@ -344,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(), Math.floor(pokemon.getMaxHp() / 16),
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), Math.floor(pokemon.getMaxHp() / 16),
getPokemonMessage(pokemon, ` absorbed\nnutrients with its roots!`), true));
return ret;
@ -374,7 +375,8 @@ 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(), Math.floor(pokemon.getMaxHp() / 16), `${this.getMoveName()} restored\n${pokemon.name}\'s HP!`, true));
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(),
Math.floor(pokemon.getMaxHp() / 16), `${this.getMoveName()} restored\n${pokemon.name}\'s HP!`, true));
return ret;
}
@ -393,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(), StatusEffect.SLEEP));
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), StatusEffect.SLEEP));
return false;
}
@ -423,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(), this.commonAnim));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 0, this.commonAnim));
const damage = Math.ceil(pokemon.getMaxHp() / 16);
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer()));
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex()));
pokemon.damage(damage);
}
@ -541,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.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex()));
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nloafing around!'));
}

View File

@ -54,12 +54,7 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
case BerryType.LUM:
return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED);
case BerryType.ENIGMA:
return (pokemon: Pokemon) => {
const opponent = pokemon.getOpponent();
const opponentLastMove = opponent ? opponent.getLastXMoves(1).find(() => true) : null; // TODO: Update so this works even if opponent has fainted
return opponentLastMove && opponentLastMove.turn === pokemon.scene.currentBattle?.turn - 1 && opponentLastMove.result === MoveResult.SUPER_EFFECTIVE;
};
return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === MoveResult.SUPER_EFFECTIVE).length;
case BerryType.LIECHI:
case BerryType.GANLON:
case BerryType.SALAC:
@ -83,7 +78,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
case BerryType.SITRUS:
case BerryType.ENIGMA:
return (pokemon: Pokemon) => {
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true));
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));
};
case BerryType.LUM:
return (pokemon: Pokemon) => {
@ -101,13 +96,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(), true, [ battleStat ], 1));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), 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(), true, [ BattleStat.RAND ], 2));
return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), true, [ BattleStat.RAND ], 2));
}
}

View File

@ -936,7 +936,8 @@ export class HealAttr extends MoveEffectAttr {
}
addHealPhase(user: Pokemon, healRatio: number) {
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), Math.max(Math.floor(user.getMaxHp() * healRatio), 1), getPokemonMessage(user, ' regained\nhealth!'), true, !this.showAnim));
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), user.getFieldIndex(),
Math.max(Math.floor(user.getMaxHp() * healRatio), 1), getPokemonMessage(user, ' regained\nhealth!'), true, !this.showAnim));
}
}
@ -977,7 +978,8 @@ export class HitHealAttr extends MoveHitEffectAttr {
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1), getPokemonMessage(target, ` had its\nenergy drained!`), false, true));
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), user.getFieldIndex(),
Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1), getPokemonMessage(target, ` had its\nenergy drained!`), false, true));
return true;
}
}
@ -1214,7 +1216,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, this.selfTarget, this.stats, levels));
user.scene.unshiftPhase(new StatChangePhase(user.scene, user.isPlayer() === this.selfTarget, user.getFieldIndex(), this.selfTarget, this.stats, levels));
return true;
}
@ -1770,8 +1772,8 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr {
const moveIndex = moveset.findIndex(m => m.moveId === move.moveId);
user.getMoveQueue().push({ move: move.moveId, ignorePP: this.enemyMoveset });
user.scene.unshiftPhase(user.isPlayer()
? new PlayerMovePhase(user.scene, user as PlayerPokemon, moveset[moveIndex], true)
: new EnemyMovePhase(user.scene, user as EnemyPokemon, moveset[moveIndex], true));
? new PlayerMovePhase(user.scene, user as PlayerPokemon, target.getFieldIndex(), moveset[moveIndex], true)
: new EnemyMovePhase(user.scene, user as EnemyPokemon, target.getFieldIndex(), moveset[moveIndex], true));
return true;
}
@ -1786,8 +1788,8 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr {
const moveId = moveIds[Utils.randInt(moveIds.length)];
user.getMoveQueue().push({ move: moveId, ignorePP: true });
user.scene.unshiftPhase(user.isPlayer()
? new PlayerMovePhase(user.scene, user as PlayerPokemon, new PokemonMove(moveId, 0, 0, true), true)
: new EnemyMovePhase(user.scene, user as EnemyPokemon, new PokemonMove(moveId, 0, 0, true), true));
? new PlayerMovePhase(user.scene, user as PlayerPokemon, target.getFieldIndex(), new PokemonMove(moveId, 0, 0, true), true)
: new EnemyMovePhase(user.scene, user as EnemyPokemon, target.getFieldIndex(), new PokemonMove(moveId, 0, 0, true), true));
initMoveAnim(moveId).then(() => {
loadMoveAnimAssets(user.scene, [ moveId ], true)
.then(() => resolve(true));
@ -1824,8 +1826,8 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr {
user.getMoveQueue().push({ move: copiedMove.move, ignorePP: true });
user.scene.unshiftPhase(user.isPlayer()
? new PlayerMovePhase(user.scene, user as PlayerPokemon, new PokemonMove(copiedMove.move, 0, 0, true), true)
: new EnemyMovePhase(user.scene, user as EnemyPokemon, new PokemonMove(copiedMove.move, 0, 0, true), true));
? new PlayerMovePhase(user.scene, user as PlayerPokemon, target.getFieldIndex(), new PokemonMove(copiedMove.move, 0, 0, true), true)
: new EnemyMovePhase(user.scene, user as EnemyPokemon, target.getFieldIndex(), new PokemonMove(copiedMove.move, 0, 0, true), true));
return true;
}
@ -1932,10 +1934,10 @@ export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon
return applyMoveAttrsInternal(attrFilter, user, target, move, args);
}
export function getMoveTarget(user: Pokemon, move: Moves): Pokemon {
export function getMoveTarget(user: Pokemon, targetIndex: integer, move: Moves): Pokemon {
const moveTarget = allMoves[move].moveTarget;
const other = user.getOpponent();
const other = user.getOpponent(targetIndex);
switch (moveTarget) {
case MoveTarget.USER:

View File

@ -100,17 +100,10 @@ export class Weather {
}
isEffectSuppressed(scene: BattleScene): boolean {
const playerPokemon = scene.getPlayerPokemon();
const enemyPokemon = scene.getEnemyPokemon();
const field = scene.getField().filter(p => p);
if (playerPokemon) {
const suppressWeatherEffectAbAttr = playerPokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable))
return true;
}
if (enemyPokemon) {
const suppressWeatherEffectAbAttr = enemyPokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
for (let pokemon of field) {
const suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable))
return true;
}

View File

@ -179,6 +179,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
abstract isPlayer(): boolean;
abstract getFieldIndex(): integer;
loadAssets(): Promise<void> {
return new Promise(resolve => {
const moveIds = this.getMoveset().map(m => m.getMove().id);
@ -496,13 +498,24 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.levelExp = this.exp - getLevelTotalExp(this.level, this.getSpeciesForm().growthRate);
}
getOpponent(): Pokemon {
const ret = this.isPlayer() ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon();
getOpponent(targetIndex: integer): Pokemon {
const ret = this.getOpponents()[targetIndex];
if (ret.summonData)
return ret;
return null;
}
getOpponents(): Pokemon[] {
return this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField();
}
getOpponentDescriptor(): string {
const opponents = this.getOpponents();
if (opponents.length === 1)
return opponents[0].name;
return this.isPlayer() ? 'the opposing team' : 'your team';
}
apply(source: Pokemon, battlerMove: PokemonMove): MoveResult {
let result: MoveResult;
const move = battlerMove.getMove();
@ -626,9 +639,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.scene.pushPhase(new FaintPhase(this.scene, this.isPlayer(), this.getFieldIndex()));
this.resetSummonData();
this.getOpponent()?.resetBattleSummonData();
}
}
@ -946,6 +958,10 @@ export class PlayerPokemon extends Pokemon {
return true;
}
getFieldIndex(): integer {
return this.scene.getPlayerField().indexOf(this);
}
generateCompatibleTms(): void {
this.compatibleTms = [];
@ -1127,6 +1143,10 @@ export class EnemyPokemon extends Pokemon {
return false;
}
getFieldIndex(): integer {
return this.scene.getEnemyField().indexOf(this);
}
addToParty() {
const party = this.scene.getParty();
let ret: PlayerPokemon = null;

View File

@ -21,7 +21,7 @@ interface SystemSaveData {
interface SessionSaveData {
party: PokemonData[];
enemyParty: PokemonData[];
enemyField: PokemonData[];
modifiers: PersistentModifierData[];
enemyModifiers: PersistentModifierData[];
arena: ArenaData;
@ -126,7 +126,7 @@ export class GameData {
saveSession(scene: BattleScene): boolean {
const sessionData = {
party: scene.getParty().map(p => new PokemonData(p)),
enemyParty: scene.getEnemyParty().map(p => new PokemonData(p)),
enemyField: scene.getEnemyField().map(p => new PokemonData(p)),
modifiers: scene.findModifiers(m => true).map(m => new PersistentModifierData(m, true)),
enemyModifiers: scene.findModifiers(m => true, false).map(m => new PersistentModifierData(m, false)),
arena: new ArenaData(scene.arena),
@ -187,17 +187,20 @@ export class GameData {
loadPokemonAssets.push(pokemon.loadAssets());
party.push(pokemon);
}
const enemyPokemon = sessionData.enemyParty[0].toPokemon(scene) as EnemyPokemon;
Object.keys(scene.pokeballCounts).forEach((key: string) => {
scene.pokeballCounts[key] = sessionData.pokeballCounts[key] || 0;
});
scene.newArena(sessionData.arena.biome, true);
scene.newBattle(sessionData.waveIndex).enemyPokemon = enemyPokemon;
scene.newArena(sessionData.arena.biome, sessionData.enemyField.length > 1);
const battle = scene.newBattle(sessionData.waveIndex, sessionData.enemyField.length > 1);
loadPokemonAssets.push(enemyPokemon.loadAssets());
sessionData.enemyField.forEach((enemyData, e) => {
const enemyPokemon = enemyData.toPokemon(scene) as EnemyPokemon;
battle.enemyField[e] = enemyPokemon;
loadPokemonAssets.push(enemyPokemon.loadAssets());
});
scene.arena.weather = sessionData.arena.weather;
// TODO

View File

@ -37,7 +37,7 @@ export default class ModifierData {
type.generatorId = this.typeGeneratorId;
if (type instanceof ModifierTypeGenerator)
type = (type as ModifierTypeGenerator).generateType(this.player ? scene.getParty() : scene.getEnemyParty(), this.typePregenArgs);
type = (type as ModifierTypeGenerator).generateType(this.player ? scene.getParty() : scene.getEnemyField(), this.typePregenArgs);
const ret = Reflect.construct(constructor, ([ type ] as any[]).concat(this.args).concat(this.stackCount)) as PersistentModifier

View File

@ -43,7 +43,7 @@ export default class CommandUiHandler extends UiHandler {
const messageHandler = this.getUi().getMessageHandler();
messageHandler.bg.setTexture('bg_command');
messageHandler.message.setWordWrapWidth(1110);
messageHandler.showText(`What will\n${this.scene.getPlayerPokemon().name} do?`, 0);
messageHandler.showText(`What will\n${(this.scene.getCurrentPhase() as CommandPhase).getPokemon().name} do?`, 0);
this.setCursor(this.cursor);
}
@ -65,7 +65,7 @@ export default class CommandUiHandler extends UiHandler {
success = true;
break;
case 2:
ui.setMode(Mode.PARTY, PartyUiMode.SWITCH);
ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex());
success = true;
break;
case 3:

View File

@ -91,7 +91,7 @@ export default class FightUiHandler extends UiHandler {
ui.add(this.cursorObj);
}
const moveset = this.scene.getPlayerPokemon().getMoveset();
const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset();
const hasMove = cursor < moveset.length;
@ -114,7 +114,7 @@ export default class FightUiHandler extends UiHandler {
}
displayMoves() {
const moveset = this.scene.getPlayerPokemon().getMoveset();
const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset();
for (let m = 0; m < 4; m++) {
const moveText = addTextObject(this.scene, m % 2 === 0 ? 0 : 100, m < 2 ? 0 : 16, '-', TextStyle.WINDOW);
if (m < moveset.length)

View File

@ -42,6 +42,7 @@ export type PokemonMoveSelectFilter = (pokemonMove: PokemonMove) => string;
export default class PartyUiHandler extends MessageUiHandler {
private partyUiMode: PartyUiMode;
private fieldIndex: integer;
private partyContainer: Phaser.GameObjects.Container;
private partySlotsContainer: Phaser.GameObjects.Container;
@ -143,17 +144,19 @@ export default class PartyUiHandler extends MessageUiHandler {
this.partyUiMode = args[0] as PartyUiMode;
this.fieldIndex = args.length > 1 ? args[1] as integer : -1;
this.partyContainer.setVisible(true);
this.populatePartySlots();
this.setCursor(this.cursor < 6 ? this.cursor : 0);
if (args.length > 1 && args[1] instanceof Function)
this.selectCallback = args[1];
this.selectFilter = args.length > 2 && args[2] instanceof Function
? args[2] as PokemonSelectFilter
if (args.length > 2 && args[2] instanceof Function)
this.selectCallback = args[2];
this.selectFilter = args.length > 3 && args[3] instanceof Function
? args[3] as PokemonSelectFilter
: PartyUiHandler.FilterAll;
this.moveSelectFilter = args.length > 3 && args[3] instanceof Function
? args[3] as PokemonMoveSelectFilter
this.moveSelectFilter = args.length > 4 && args[4] instanceof Function
? args[4] as PokemonMoveSelectFilter
: PartyUiHandler.FilterAllMoves;
}
@ -414,7 +417,8 @@ export default class PartyUiHandler extends MessageUiHandler {
if (this.cursor) {
this.options.push(PartyOption.SEND_OUT);
if (this.partyUiMode !== PartyUiMode.FAINT_SWITCH
&& this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === this.scene.getPlayerPokemon().id))
&& this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier
&& (m as SwitchEffectTransferModifier).pokemonId === this.scene.getPlayerField()[this.fieldIndex].id))
this.options.push(PartyOption.PASS_BATON);
}
break;