pokerogue/src/battle-scene.ts

464 lines
13 KiB
TypeScript
Raw Normal View History

2023-03-28 19:54:52 +01:00
import Phaser from 'phaser';
2023-03-31 04:02:35 +01:00
import { Biome, BiomeArena } from './biome';
2023-03-28 19:54:52 +01:00
import UI from './ui/ui';
2023-03-31 21:04:39 +01:00
import { BattlePhase, EncounterPhase, SummonPhase, CommandPhase, NextEncounterPhase, SwitchBiomePhase, NewBiomeEncounterPhase } from './battle-phase';
2023-03-28 19:54:52 +01:00
import { PlayerPokemon, EnemyPokemon } from './pokemon';
2023-03-29 05:31:25 +01:00
import PokemonSpecies, { allSpecies, getPokemonSpecies } from './pokemon-species';
2023-03-28 19:54:52 +01:00
import * as Utils from './utils';
2023-04-02 03:59:07 +01:00
import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonModifier} from './modifier';
2023-03-28 19:54:52 +01:00
import { PokeballType } from './pokeball';
2023-03-29 05:31:25 +01:00
import { Species } from './species';
import { initAutoPlay } from './auto-play';
2023-03-31 04:02:35 +01:00
import { Battle } from './battle';
2023-04-04 01:47:41 +01:00
import { populateAnims } from './battle-anims';
2023-03-28 19:54:52 +01:00
export default class BattleScene extends Phaser.Scene {
2023-03-29 05:31:25 +01:00
private auto: boolean;
2023-03-31 21:04:39 +01:00
private autoSpeed: integer = 10;
2023-03-29 05:31:25 +01:00
2023-03-28 19:54:52 +01:00
private phaseQueue: Array<BattlePhase>;
private phaseQueuePrepend: Array<BattlePhase>;
private currentPhase: BattlePhase;
public field: Phaser.GameObjects.Container;
public fieldUI: Phaser.GameObjects.Container;
public arenaBg: Phaser.GameObjects.Image;
2023-03-31 21:04:39 +01:00
public arenaBgTransition: Phaser.GameObjects.Image;
2023-03-28 19:54:52 +01:00
public arenaPlayer: Phaser.GameObjects.Image;
2023-03-31 21:04:39 +01:00
public arenaPlayerTransition: Phaser.GameObjects.Image;
2023-03-28 19:54:52 +01:00
public arenaEnemy: Phaser.GameObjects.Image;
2023-03-31 21:04:39 +01:00
public arenaEnemyTransition: Phaser.GameObjects.Image;
public arenaNextEnemy: Phaser.GameObjects.Image;
public arena: BiomeArena;
2023-03-28 19:54:52 +01:00
public trainer: Phaser.GameObjects.Sprite;
2023-03-31 04:02:35 +01:00
public currentBattle: Battle;
2023-04-02 03:59:07 +01:00
public pokeballCounts = Object.fromEntries(Utils.getEnumValues(PokeballType).filter(p => p <= PokeballType.MASTER_BALL).map(t => [ t, 0 ]));
2023-03-28 19:54:52 +01:00
private party: PlayerPokemon[];
private modifierBar: ModifierBar;
private modifiers: Modifier[];
public uiContainer: Phaser.GameObjects.Container;
public ui: UI;
2023-04-04 01:47:41 +01:00
//public spritePipeline: SpritePipeline;
2023-03-28 19:54:52 +01:00
private bgm: Phaser.Sound.BaseSound;
private upKey: Phaser.Input.Keyboard.Key;
private downKey: Phaser.Input.Keyboard.Key;
private leftKey: Phaser.Input.Keyboard.Key;
private rightKey: Phaser.Input.Keyboard.Key;
private actionKey: Phaser.Input.Keyboard.Key;
private cancelKey: Phaser.Input.Keyboard.Key;
private blockInput: boolean;
public trainerId: integer = Utils.randInt(65536);
public secretId: integer = Utils.randInt(65536);
constructor() {
super('battle');
this.phaseQueue = [];
this.phaseQueuePrepend = [];
}
loadImage(key: string, folder: string, filename?: string) {
if (!filename)
filename = `${key}.png`;
this.load.image(key, `images/${folder}/${filename}`);
}
loadAtlas(key: string, folder: string, filenameRoot?: string) {
if (!filenameRoot)
filenameRoot = key;
if (folder)
folder += '/';
this.load.atlas(key, `images/${folder}${filenameRoot}.png`, `images/${folder}/${filenameRoot}.json`)
}
2023-04-04 01:47:41 +01:00
loadSpritesheet(key: string, folder: string, size: integer, filename?: string) {
if (!filename)
filename = `${key}.png`;
this.load.spritesheet(key, `images/${folder}/${filename}`, { frameWidth: size, frameHeight: size });
}
2023-03-28 19:54:52 +01:00
loadSe(key: string, folder?: string, filenames?: string | string[]) {
if (!filenames)
filenames = `${key}.wav`;
if (!folder)
folder = '';
else
folder += '/';
if (!Array.isArray(filenames))
filenames = [ filenames ];
for (let f of filenames as string[]) {
this.load.audio(key, `audio/se/${folder}${f}`);
}
}
loadBgm(key: string, filename?: string) {
if (!filename)
filename = `${key}.mp3`;
this.load.audio(key, `audio/bgm/${filename}`);
}
preload() {
// Load menu images
this.loadImage('bg', 'ui');
this.loadImage('bg_command', 'ui');
this.loadImage('bg_fight', 'ui');
this.loadAtlas('prompt', 'ui');
this.loadImage('cursor', 'ui');
this.loadImage('pbinfo_player', 'ui');
this.loadImage('pbinfo_enemy', 'ui');
this.loadImage('overlay_lv', 'ui');
this.loadAtlas('numbers', 'ui');
this.loadAtlas('overlay_hp', 'ui');
this.loadImage('overlay_exp', 'ui');
this.loadImage('level_up_stats', 'ui');
2023-04-02 03:59:07 +01:00
this.loadImage('ball_window', 'ui');
2023-03-28 19:54:52 +01:00
this.loadImage('boolean_window', 'ui');
this.loadImage('party_bg', 'ui');
this.loadAtlas('party_slot_main', 'ui');
this.loadAtlas('party_slot', 'ui');
this.loadImage('party_slot_overlay_lv', 'ui');
this.loadImage('party_slot_hp_bar', 'ui');
this.loadAtlas('party_slot_hp_overlay', 'ui');
this.loadAtlas('party_pb', 'ui');
this.loadImage('party_message', 'ui');
this.loadImage('party_message_large', 'ui');
this.loadAtlas('party_cancel', 'ui');
// Load arena images
2023-03-31 04:02:35 +01:00
Utils.getEnumValues(Biome).map(at => {
const atKey = Biome[at].toLowerCase();
2023-03-29 23:55:41 +01:00
this.loadImage(`${atKey}_bg`, 'arenas', `${atKey}_bg.png`);
this.loadImage(`${atKey}_a`, 'arenas', `${atKey}_a.png`);
this.loadImage(`${atKey}_b`, 'arenas', `${atKey}_b.png`);
});
2023-03-28 19:54:52 +01:00
// Load trainer images
this.loadImage('trainer_m', 'trainer');
this.loadAtlas('trainer_m_pb', 'trainer');
// Load pokemon-related images
this.loadImage(`pkmn__back__sub`, 'pokemon/back', 'sub.png');
this.loadImage(`pkmn__sub`, 'pokemon', 'sub.png');
this.loadAtlas('shiny', 'effects');
this.loadAtlas('pb', '');
this.loadAtlas('items', '');
2023-04-01 01:19:57 +01:00
this.loadAtlas('types', '');
this.loadAtlas('statuses', '');
2023-03-28 19:54:52 +01:00
for (let i = 0; i < 6; i++)
this.loadAtlas(`pokemon_icons_${i}`, 'ui');
this.loadSe('select');
this.loadSe('menu_open');
this.loadSe('hit');
this.loadSe('hit_strong');
this.loadSe('hit_weak');
this.loadSe('faint');
this.loadSe('flee');
this.loadSe('exp');
this.loadSe('level_up');
this.loadSe('shiny');
this.loadSe('restore');
this.loadSe('error');
this.loadSe('pb');
this.loadSe('pb_rel');
this.loadSe('pb_throw');
this.loadSe('pb_bounce_1');
this.loadSe('pb_bounce_2');
this.loadSe('pb_move');
this.loadSe('pb_catch');
this.loadSe('pb_lock');
this.loadBgm('level_up_fanfare');
2023-04-04 01:47:41 +01:00
this.load.glsl('sprite', 'shaders/sprite.frag');
populateAnims();
2023-03-28 19:54:52 +01:00
}
create() {
this.load.setBaseURL();
2023-04-04 01:47:41 +01:00
//this.spritePipeline = (this.renderer as Phaser.Renderer.WebGL.WebGLRenderer).pipelines.get('Sprite') as SpritePipeline;
2023-03-28 19:54:52 +01:00
const field = this.add.container(0, 0);
field.setScale(6);
this.field = field;
2023-03-31 21:04:39 +01:00
this.newBiome();
2023-03-28 19:54:52 +01:00
2023-03-31 21:04:39 +01:00
const biomeKey = this.arena.getBiomeKey();
this.arenaBg = this.add.sprite(0, 0, `${biomeKey}_bg`);
this.arenaBgTransition = this.add.sprite(0, 0, `${biomeKey}_bg`);
this.arenaPlayer = this.add.sprite(340, 20, `${biomeKey}_a`);
this.arenaPlayerTransition = this.add.sprite(40, 20, `${biomeKey}_a`);
this.arenaEnemy = this.add.sprite(-240, 13, `${biomeKey}_b`);
this.arenaNextEnemy = this.add.sprite(-240, 13, `${biomeKey}_b`);
2023-03-28 19:54:52 +01:00
2023-03-31 21:04:39 +01:00
this.arenaBgTransition.setVisible(false);
this.arenaPlayerTransition.setVisible(false);
2023-03-28 19:54:52 +01:00
2023-03-31 21:04:39 +01:00
[this.arenaBg, this.arenaBgTransition, this.arenaPlayer, this.arenaPlayerTransition, this.arenaEnemy, this.arenaNextEnemy].forEach(a => {
2023-03-28 19:54:52 +01:00
a.setOrigin(0, 0);
field.add(a);
});
2023-03-31 21:04:39 +01:00
this.arena.playBgm();
2023-03-28 19:54:52 +01:00
const fieldUI = this.add.container(0, this.game.canvas.height);
fieldUI.setScale(6);
this.fieldUI = fieldUI;
const uiContainer = this.add.container(0, 0);
uiContainer.setScale(6);
this.uiContainer = uiContainer;
this.modifiers = [];
this.modifierBar = new ModifierBar(this);
2023-03-29 05:31:25 +01:00
this.add.existing(this.modifierBar);
uiContainer.add(this.modifierBar);
2023-03-28 19:54:52 +01:00
this.party = [];
2023-03-29 05:31:25 +01:00
let loadPokemonAssets = [];
2023-03-28 19:54:52 +01:00
for (let s = 0; s < 3; s++) {
2023-03-29 05:31:25 +01:00
const playerSpecies = getPokemonSpecies(s === 0 ? Species.TORCHIC : s === 1 ? Species.TREECKO : Species.MUDKIP); //this.randomSpecies();
2023-03-28 19:54:52 +01:00
const playerPokemon = new PlayerPokemon(this, playerSpecies, 5);
playerPokemon.setVisible(false);
2023-03-29 05:31:25 +01:00
loadPokemonAssets.push(playerPokemon.loadAssets());
2023-03-28 19:54:52 +01:00
this.party.push(playerPokemon);
}
console.log(this.getPlayerPokemon().species.name, this.getPlayerPokemon().species.speciesId, this.getPlayerPokemon().stats);
const trainerPbFrameNames = this.anims.generateFrameNames('trainer_m_pb', { zeroPad: 2, start: 1, end: 12 });
this.anims.create({
key: 'trainer_m_pb',
frames: trainerPbFrameNames,
frameRate: 16
});
const trainer = this.add.sprite(406, 132, 'trainer_m');
trainer.setOrigin(0.5, 1);
field.add(trainer);
this.trainer = trainer;
this.anims.create({
key: 'prompt',
frames: this.anims.generateFrameNumbers('prompt', { start: 1, end: 4 }),
frameRate: 6,
repeat: -1,
showOnStart: true
});
const ui = new UI(this);
this.uiContainer.add(ui);
this.ui = ui;
ui.setup();
this.upKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP);
this.downKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN);
this.leftKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT);
this.rightKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT);
this.actionKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z);
this.cancelKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.X);
2023-03-29 05:31:25 +01:00
Promise.all(loadPokemonAssets).then(() => {
if (this.auto)
initAutoPlay.apply(this, [ this.autoSpeed ]);
2023-04-02 03:59:07 +01:00
this.pokeballCounts[PokeballType.POKEBALL] += 5;
2023-03-29 05:31:25 +01:00
2023-03-31 21:04:39 +01:00
this.newBattle();
2023-03-29 05:31:25 +01:00
this.shiftPhase();
});
2023-03-28 19:54:52 +01:00
}
update() {
this.checkInput();
}
getParty(): PlayerPokemon[] {
return this.party;
}
getPlayerPokemon(): PlayerPokemon {
return this.getParty()[0];
}
getEnemyPokemon(): EnemyPokemon {
2023-03-31 04:02:35 +01:00
return this.currentBattle.enemyPokemon;
2023-03-28 19:54:52 +01:00
}
2023-03-31 21:04:39 +01:00
newBattle(): Battle {
if (this.currentBattle) {
console.log(this.getPlayerPokemon(), this.getParty().map(p => p.name), this.getPlayerPokemon().id)
this.getEnemyPokemon().destroy();
if (this.currentBattle.waveIndex % 10)
this.unshiftPhase(new NextEncounterPhase(this));
else {
this.unshiftPhase(new SwitchBiomePhase(this));
this.unshiftPhase(new NewBiomeEncounterPhase(this));
}
} else {
this.pushPhase(new EncounterPhase(this));
this.pushPhase(new SummonPhase(this));
}
this.currentBattle = new Battle((this.currentBattle?.waveIndex || 0) + 1);
2023-03-31 04:02:35 +01:00
return this.currentBattle;
2023-03-28 19:54:52 +01:00
}
2023-03-31 21:04:39 +01:00
newBiome(): BiomeArena {
const biome = Utils.randInt(20) as Biome;
this.arena = new BiomeArena(this, biome, Biome[biome].toLowerCase());
return this.arena;
2023-03-28 19:54:52 +01:00
}
2023-03-31 21:04:39 +01:00
randomSpecies(level: integer, fromArenaPool?: boolean): PokemonSpecies {
return fromArenaPool
? this.arena.randomSpecies(1, level)
: allSpecies[(Utils.randInt(allSpecies.length)) - 1];
2023-03-28 19:54:52 +01:00
}
checkInput(): boolean {
if (this.blockInput)
return;
if (this.upKey.isDown)
this.ui.processInput(this.upKey.keyCode);
else if (this.downKey.isDown)
this.ui.processInput(this.downKey.keyCode);
else if (this.leftKey.isDown)
this.ui.processInput(this.leftKey.keyCode);
else if (this.rightKey.isDown)
this.ui.processInput(this.rightKey.keyCode);
else if (this.actionKey.isDown)
this.ui.processInput(this.actionKey.keyCode);
else if (this.cancelKey.isDown)
this.ui.processInput(this.cancelKey.keyCode);
else
return;
this.blockInput = true;
this.time.delayedCall(250, () => {
this.blockInput = false;
});
}
playBgm(bgmName: string): void {
2023-03-31 21:04:39 +01:00
if (this.bgm && this.bgm.isPlaying)
2023-03-28 19:54:52 +01:00
this.bgm.stop();
this.bgm = this.sound.add(bgmName, { loop: true });
2023-04-01 01:19:57 +01:00
//this.bgm.play();
2023-03-28 19:54:52 +01:00
}
pauseBgm(): void {
if (this.bgm)
this.bgm.pause();
}
resumeBgm(): void {
if (this.bgm && this.bgm.isPaused)
this.bgm.resume();
}
getCurrentPhase(): BattlePhase {
return this.currentPhase;
}
pushPhase(phase: BattlePhase): void {
this.phaseQueue.push(phase);
}
unshiftPhase(phase: BattlePhase): void {
this.phaseQueuePrepend.push(phase);
}
clearPhaseQueue(): void {
this.phaseQueue.splice(0, this.phaseQueue.length);
}
shiftPhase(): void {
if (this.phaseQueuePrepend.length) {
while (this.phaseQueuePrepend.length)
this.phaseQueue.unshift(this.phaseQueuePrepend.pop());
}
if (!this.phaseQueue.length)
this.populatePhaseQueue();
this.currentPhase = this.phaseQueue.shift();
this.currentPhase.start();
}
populatePhaseQueue(): void {
this.phaseQueue.push(new CommandPhase(this));
}
2023-04-04 23:28:21 +01:00
addModifier(modifier: Modifier): Promise<void> {
return new Promise(resolve => {
if (modifier.add(this.modifierBar, this.modifiers))
this.sound.play('restore');
if (modifier instanceof ConsumableModifier) {
const args = [ this ];
if (modifier.shouldApply(args))
modifier.apply(args);
resolve();
return;
}
2023-03-28 19:54:52 +01:00
2023-04-04 23:28:21 +01:00
let pokemonToUpdate = 0;
2023-03-28 19:54:52 +01:00
2023-04-04 23:28:21 +01:00
if (modifier instanceof PokemonModifier) {
for (let p in this.party) {
const pokemon = this.party[p];
if (modifier instanceof ConsumablePokemonModifier) {
const args = [ pokemon ];
if (modifier.shouldApply(args))
modifier.apply(args);
}
2023-03-28 19:54:52 +01:00
2023-04-04 23:28:21 +01:00
pokemonToUpdate++;
pokemon.calculateStats();
pokemon.updateInfo(() => {
if (!(--pokemonToUpdate))
resolve();
});
}
2023-03-28 19:54:52 +01:00
}
2023-04-04 23:28:21 +01:00
if (!pokemonToUpdate)
resolve();
});
2023-03-28 19:54:52 +01:00
}
2023-03-31 04:02:35 +01:00
getModifier(modifierType: { new(...args: any[]): Modifier }): Modifier {
return this.modifiers.find(m => m instanceof modifierType);
}
2023-03-28 19:54:52 +01:00
applyModifiers(modifierType: { new(...args: any[]): Modifier }, ...args: any[]): void {
const modifiers = this.modifiers.filter(m => m instanceof modifierType && m.shouldApply(args));
for (let modifier of modifiers) {
if (modifier.apply(args))
console.log('Applied', modifier.type.name);
}
}
}