2023-11-04 23:46:48 +00:00
|
|
|
import BattleScene from "./battle-scene";
|
2023-05-18 16:11:06 +01:00
|
|
|
import { EnemyPokemon, PlayerPokemon, QueuedMove } from "./pokemon";
|
|
|
|
import { Command } from "./ui/command-ui-handler";
|
2023-03-31 21:04:39 +01:00
|
|
|
import * as Utils from "./utils";
|
2023-10-07 21:08:33 +01:00
|
|
|
import Trainer from "./trainer";
|
2024-01-13 17:24:24 +00:00
|
|
|
import { Species } from "./data/enums/species";
|
|
|
|
import { Moves } from "./data/enums/moves";
|
|
|
|
import { TrainerType } from "./data/enums/trainer-type";
|
2023-11-04 23:46:48 +00:00
|
|
|
import { GameMode } from "./game-mode";
|
2024-01-13 17:24:24 +00:00
|
|
|
import { BattleSpec } from "./enums/battle-spec";
|
2024-02-06 21:15:35 +00:00
|
|
|
import { PlayerGender } from "./system/game-data";
|
2023-10-07 21:08:33 +01:00
|
|
|
|
|
|
|
export enum BattleType {
|
|
|
|
WILD,
|
|
|
|
TRAINER
|
|
|
|
}
|
2023-03-31 04:02:35 +01:00
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
export enum BattlerIndex {
|
2023-11-27 16:42:03 +00:00
|
|
|
ATTACKER = -1,
|
2023-05-18 16:11:06 +01:00
|
|
|
PLAYER,
|
|
|
|
PLAYER_2,
|
|
|
|
ENEMY,
|
|
|
|
ENEMY_2
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface TurnCommand {
|
|
|
|
command: Command;
|
|
|
|
cursor?: integer;
|
|
|
|
move?: QueuedMove;
|
|
|
|
targets?: BattlerIndex[];
|
2023-10-27 22:43:53 +01:00
|
|
|
skip?: boolean;
|
2023-05-18 16:11:06 +01:00
|
|
|
args?: any[];
|
|
|
|
};
|
|
|
|
|
|
|
|
interface TurnCommands {
|
|
|
|
[key: integer]: TurnCommand
|
|
|
|
}
|
|
|
|
|
|
|
|
export default class Battle {
|
2024-01-13 17:24:24 +00:00
|
|
|
protected gameMode: GameMode;
|
2023-03-31 04:02:35 +01:00
|
|
|
public waveIndex: integer;
|
2023-10-07 21:08:33 +01:00
|
|
|
public battleType: BattleType;
|
2024-01-13 17:24:24 +00:00
|
|
|
public battleSpec: BattleSpec;
|
2023-10-07 21:08:33 +01:00
|
|
|
public trainer: Trainer;
|
2023-05-18 16:11:06 +01:00
|
|
|
public enemyLevels: integer[];
|
2023-10-07 21:08:33 +01:00
|
|
|
public enemyParty: EnemyPokemon[];
|
2023-10-23 18:48:56 +01:00
|
|
|
public seenEnemyPartyMemberIds: Set<integer>;
|
2023-05-18 16:11:06 +01:00
|
|
|
public double: boolean;
|
2023-10-18 23:01:15 +01:00
|
|
|
public started: boolean;
|
2023-04-22 00:30:04 +01:00
|
|
|
public turn: integer;
|
2023-05-18 16:11:06 +01:00
|
|
|
public turnCommands: TurnCommands;
|
2023-03-31 04:02:35 +01:00
|
|
|
public playerParticipantIds: Set<integer> = new Set<integer>();
|
2023-05-07 22:05:19 +01:00
|
|
|
public escapeAttempts: integer = 0;
|
2023-10-10 01:20:02 +01:00
|
|
|
public lastMove: Moves;
|
2024-01-03 02:31:59 +00:00
|
|
|
public battleSeed: string;
|
|
|
|
private battleSeedState: string;
|
2023-03-31 04:02:35 +01:00
|
|
|
|
2024-01-13 17:24:24 +00:00
|
|
|
constructor(gameMode: integer, waveIndex: integer, battleType: BattleType, trainer: Trainer, double: boolean) {
|
|
|
|
this.gameMode = gameMode;
|
2023-03-31 04:02:35 +01:00
|
|
|
this.waveIndex = waveIndex;
|
2023-10-07 21:08:33 +01:00
|
|
|
this.battleType = battleType;
|
|
|
|
this.trainer = trainer;
|
2024-01-13 17:24:24 +00:00
|
|
|
this.initBattleSpec();
|
2023-10-18 23:01:15 +01:00
|
|
|
this.enemyLevels = battleType !== BattleType.TRAINER
|
|
|
|
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
|
|
|
|
: trainer.getPartyLevels(this.waveIndex);
|
2023-10-07 21:08:33 +01:00
|
|
|
this.enemyParty = [];
|
2023-10-23 18:48:56 +01:00
|
|
|
this.seenEnemyPartyMemberIds = new Set<integer>();
|
2023-05-18 16:11:06 +01:00
|
|
|
this.double = double;
|
|
|
|
this.turn = 0;
|
2023-10-18 23:01:15 +01:00
|
|
|
this.started = false;
|
2024-01-03 02:31:59 +00:00
|
|
|
this.battleSeed = Utils.randomString(16, true);
|
|
|
|
this.battleSeedState = null;
|
2023-03-31 04:02:35 +01:00
|
|
|
}
|
|
|
|
|
2024-01-13 17:24:24 +00:00
|
|
|
private initBattleSpec(): void {
|
|
|
|
let spec = BattleSpec.DEFAULT;
|
|
|
|
if (this.gameMode === GameMode.CLASSIC) {
|
|
|
|
if (this.waveIndex === 200)
|
|
|
|
spec = BattleSpec.FINAL_BOSS;
|
|
|
|
}
|
|
|
|
this.battleSpec = spec;
|
|
|
|
}
|
|
|
|
|
2023-10-07 21:08:33 +01:00
|
|
|
private getLevelForWave(): integer {
|
2023-04-19 19:07:38 +01:00
|
|
|
let baseLevel = 1 + this.waveIndex / 2 + Math.pow(this.waveIndex / 25, 2);
|
2023-10-29 20:05:17 +00:00
|
|
|
const bossMultiplier = 1.2;
|
2023-03-31 21:04:39 +01:00
|
|
|
|
2023-04-26 21:07:29 +01:00
|
|
|
if (!(this.waveIndex % 10)) {
|
2023-10-27 02:42:53 +01:00
|
|
|
const ret = Math.floor(baseLevel * bossMultiplier);
|
2024-01-13 17:24:24 +00:00
|
|
|
if (this.battleSpec === BattleSpec.FINAL_BOSS || !(this.waveIndex % 250))
|
2023-10-27 02:42:53 +01:00
|
|
|
return Math.ceil(ret / 25) * 25;
|
2023-10-29 20:05:17 +00:00
|
|
|
return ret + Math.round(Phaser.Math.RND.realInRange(-1, 1) * Math.floor(this.waveIndex / 10));
|
2023-04-26 21:07:29 +01:00
|
|
|
}
|
2023-03-31 21:04:39 +01:00
|
|
|
|
|
|
|
const deviation = 10 / this.waveIndex;
|
|
|
|
|
2024-01-05 05:44:28 +00:00
|
|
|
return Math.max(Math.round(baseLevel + Math.abs(this.randSeedGaussForLevel(deviation))), 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
randSeedGaussForLevel(value: number): number {
|
|
|
|
let rand = 0;
|
|
|
|
for (var i = value; i > 0; i--)
|
|
|
|
rand += Phaser.Math.RND.realInRange(0, 1);
|
|
|
|
return rand / value;
|
2023-03-31 21:04:39 +01:00
|
|
|
}
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
getBattlerCount(): integer {
|
|
|
|
return this.double ? 2 : 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
incrementTurn(scene: BattleScene): void {
|
2023-04-22 00:30:04 +01:00
|
|
|
this.turn++;
|
2023-05-18 16:11:06 +01:00
|
|
|
this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ]));
|
2024-01-03 02:31:59 +00:00
|
|
|
this.battleSeedState = null;
|
2023-04-22 00:30:04 +01:00
|
|
|
}
|
|
|
|
|
2023-03-31 21:04:39 +01:00
|
|
|
addParticipant(playerPokemon: PlayerPokemon): void {
|
2023-03-31 04:02:35 +01:00
|
|
|
this.playerParticipantIds.add(playerPokemon.id);
|
|
|
|
}
|
|
|
|
|
2023-03-31 21:04:39 +01:00
|
|
|
removeFaintedParticipant(playerPokemon: PlayerPokemon): void {
|
2023-03-31 04:02:35 +01:00
|
|
|
this.playerParticipantIds.delete(playerPokemon.id);
|
|
|
|
}
|
2023-10-10 01:20:02 +01:00
|
|
|
|
2023-11-04 23:46:48 +00:00
|
|
|
getBgmOverride(scene: BattleScene): string {
|
2023-10-10 01:20:02 +01:00
|
|
|
const battlers = this.enemyParty.slice(0, this.getBattlerCount());
|
2023-10-18 23:01:15 +01:00
|
|
|
if (this.battleType === BattleType.TRAINER) {
|
2024-02-14 20:33:02 +00:00
|
|
|
if (!this.started && this.trainer.config.encounterBgm && this.trainer.getEncounterMessages()?.length)
|
2023-10-18 23:01:15 +01:00
|
|
|
return `encounter_${this.trainer.getEncounterBgm()}`;
|
|
|
|
return this.trainer.getBattleBgm();
|
2024-01-13 17:24:24 +00:00
|
|
|
} else if (this.gameMode === GameMode.CLASSIC && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS)
|
|
|
|
return 'end_summit';
|
2023-10-10 01:20:02 +01:00
|
|
|
for (let pokemon of battlers) {
|
2024-01-13 17:24:24 +00:00
|
|
|
if (this.battleSpec === BattleSpec.FINAL_BOSS) {
|
|
|
|
if (pokemon.formIndex)
|
|
|
|
return 'battle_final';
|
|
|
|
return 'battle_final_encounter';
|
|
|
|
}
|
2023-10-18 23:01:15 +01:00
|
|
|
if (pokemon.species.legendary || pokemon.species.pseudoLegendary || pokemon.species.mythical) {
|
2023-10-10 01:20:02 +01:00
|
|
|
if (pokemon.species.speciesId === Species.KYUREM)
|
|
|
|
return 'battle_legendary_z';
|
2023-10-18 23:01:15 +01:00
|
|
|
if (pokemon.species.legendary)
|
|
|
|
return 'battle_legendary_rz';
|
2023-10-10 01:20:02 +01:00
|
|
|
return 'battle_legendary';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-04 23:46:48 +00:00
|
|
|
if (scene.gameMode === GameMode.CLASSIC && this.waveIndex <= 4)
|
2023-10-18 23:01:15 +01:00
|
|
|
return 'battle_wild';
|
|
|
|
|
2023-10-10 01:20:02 +01:00
|
|
|
return null;
|
|
|
|
}
|
2024-01-03 02:31:59 +00:00
|
|
|
|
|
|
|
randSeedInt(range: integer, min: integer = 0): integer {
|
|
|
|
let ret: integer;
|
|
|
|
const state = Phaser.Math.RND.state();
|
|
|
|
if (this.battleSeedState)
|
|
|
|
Phaser.Math.RND.state(this.battleSeedState);
|
|
|
|
else
|
|
|
|
Phaser.Math.RND.sow([ Utils.shiftCharCodes(this.battleSeed, this.turn << 6) ]);
|
|
|
|
ret = Utils.randSeedInt(range, min);
|
|
|
|
this.battleSeedState = Phaser.Math.RND.state();
|
|
|
|
Phaser.Math.RND.state(state);
|
|
|
|
return ret;
|
|
|
|
}
|
2023-10-18 23:01:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export class FixedBattle extends Battle {
|
|
|
|
constructor(scene: BattleScene, waveIndex: integer, config: FixedBattleConfig) {
|
2024-01-13 17:24:24 +00:00
|
|
|
super(scene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer(scene) : null, config.double);
|
2023-10-18 23:01:15 +01:00
|
|
|
if (config.getEnemyParty)
|
|
|
|
this.enemyParty = config.getEnemyParty(scene);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type GetTrainerFunc = (scene: BattleScene) => Trainer;
|
|
|
|
type GetEnemyPartyFunc = (scene: BattleScene) => EnemyPokemon[];
|
|
|
|
|
|
|
|
export class FixedBattleConfig {
|
|
|
|
public battleType: BattleType;
|
|
|
|
public double: boolean;
|
|
|
|
public getTrainer: GetTrainerFunc;
|
|
|
|
public getEnemyParty: GetEnemyPartyFunc;
|
2024-01-05 00:37:07 +00:00
|
|
|
public seedOffsetWaveIndex: integer;
|
2023-10-18 23:01:15 +01:00
|
|
|
|
|
|
|
setBattleType(battleType: BattleType): FixedBattleConfig {
|
|
|
|
this.battleType = battleType;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
setDouble(double: boolean): FixedBattleConfig {
|
|
|
|
this.double = double;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
setGetTrainerFunc(getTrainerFunc: GetTrainerFunc): FixedBattleConfig {
|
|
|
|
this.getTrainer = getTrainerFunc;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
setGetEnemyPartyFunc(getEnemyPartyFunc: GetEnemyPartyFunc): FixedBattleConfig {
|
|
|
|
this.getEnemyParty = getEnemyPartyFunc;
|
|
|
|
return this;
|
|
|
|
}
|
2024-01-05 00:37:07 +00:00
|
|
|
|
|
|
|
setSeedOffsetWave(seedOffsetWaveIndex: integer): FixedBattleConfig {
|
|
|
|
this.seedOffsetWaveIndex = seedOffsetWaveIndex;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[]): GetTrainerFunc {
|
|
|
|
return (scene: BattleScene) => {
|
|
|
|
const rand = Utils.randSeedInt(trainerPool.length);
|
|
|
|
const trainerTypes: TrainerType[] = [];
|
|
|
|
for (let trainerPoolEntry of trainerPool) {
|
|
|
|
const trainerType = Array.isArray(trainerPoolEntry)
|
2024-02-17 05:40:03 +00:00
|
|
|
? Utils.randSeedItem(trainerPoolEntry)
|
2024-01-05 00:37:07 +00:00
|
|
|
: trainerPoolEntry;
|
|
|
|
trainerTypes.push(trainerType);
|
|
|
|
}
|
|
|
|
return new Trainer(scene, trainerTypes[rand]);
|
|
|
|
};
|
2023-10-18 23:01:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
interface FixedBattleConfigs {
|
|
|
|
[key: integer]: FixedBattleConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
export const fixedBattles: FixedBattleConfigs = {
|
|
|
|
[5]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
2023-10-20 16:38:41 +01:00
|
|
|
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.YOUNGSTER, !!Utils.randSeedInt(2))),
|
2023-10-20 03:01:12 +01:00
|
|
|
[8]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
2024-02-06 21:15:35 +00:00
|
|
|
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL, scene.gameData.gender === PlayerGender.MALE)),
|
2023-10-18 23:01:15 +01:00
|
|
|
[25]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
2024-02-06 21:15:35 +00:00
|
|
|
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_2, scene.gameData.gender === PlayerGender.MALE)),
|
2023-10-18 23:01:15 +01:00
|
|
|
[55]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
2024-02-06 21:15:35 +00:00
|
|
|
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_3, scene.gameData.gender === PlayerGender.MALE)),
|
2023-10-18 23:01:15 +01:00
|
|
|
[95]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
2024-02-06 21:15:35 +00:00
|
|
|
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_4, scene.gameData.gender === PlayerGender.MALE)),
|
2023-10-18 23:01:15 +01:00
|
|
|
[145]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
2024-02-06 21:15:35 +00:00
|
|
|
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_5, scene.gameData.gender === PlayerGender.MALE)),
|
2024-01-12 19:06:29 +00:00
|
|
|
[182]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
2024-02-06 04:46:45 +00:00
|
|
|
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, [ TrainerType.HALA, TrainerType.MOLAYNE ], TrainerType.RIKA, TrainerType.CRISPIN ])),
|
2024-01-12 19:06:29 +00:00
|
|
|
[184]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182)
|
2024-02-06 04:46:45 +00:00
|
|
|
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.POPPY, TrainerType.AMARYS ])),
|
2024-01-12 19:06:29 +00:00
|
|
|
[186]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182)
|
2024-02-06 04:46:45 +00:00
|
|
|
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, TrainerType.LARRY_ELITE, TrainerType.LACEY ])),
|
2024-01-12 19:06:29 +00:00
|
|
|
[188]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182)
|
2024-02-06 04:46:45 +00:00
|
|
|
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.HASSEL, TrainerType.DRAYTON ])),
|
2024-01-12 19:06:29 +00:00
|
|
|
[190]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182)
|
2024-02-06 04:46:45 +00:00
|
|
|
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BLUE, TrainerType.RED, TrainerType.LANCE_CHAMPION, TrainerType.STEVEN, TrainerType.WALLACE, TrainerType.CYNTHIA, TrainerType.ALDER, TrainerType.IRIS, TrainerType.DIANTHA, TrainerType.LEON, [ TrainerType.GEETA, TrainerType.NEMONA ], TrainerType.KIERAN ])),
|
2023-10-24 04:20:05 +01:00
|
|
|
[195]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
2024-02-06 21:15:35 +00:00
|
|
|
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_6, scene.gameData.gender === PlayerGender.MALE))
|
2024-01-05 00:37:07 +00:00
|
|
|
};
|