pokerogue/src/ui/party-ui-handler.ts

621 lines
20 KiB
TypeScript
Raw Normal View History

import { CommandPhase, SummonMissingPhase } from "../battle-phases";
import BattleScene, { Button } from "../battle-scene";
2023-04-11 16:04:39 +01:00
import { PlayerPokemon, PokemonMove } from "../pokemon";
2023-03-28 19:54:52 +01:00
import { addTextObject, TextStyle } from "../text";
import { Command } from "./command-ui-handler";
import MessageUiHandler from "./message-ui-handler";
2023-04-06 03:22:03 +01:00
import { Mode } from "./ui";
2023-04-11 06:31:18 +01:00
import * as Utils from "../utils";
2023-03-28 19:54:52 +01:00
const defaultMessage = 'Choose a Pokémon.';
2023-04-06 03:22:03 +01:00
export enum PartyUiMode {
SWITCH,
2023-04-06 05:15:33 +01:00
FAINT_SWITCH,
POST_BATTLE_SWITCH,
2023-04-11 06:31:18 +01:00
MODIFIER,
2023-04-11 16:04:39 +01:00
MOVE_MODIFIER,
2023-04-11 06:31:18 +01:00
RELEASE
2023-04-06 03:22:03 +01:00
}
export enum PartyOption {
SHIFT,
SEND_OUT,
APPLY,
SUMMARY,
2023-04-11 06:31:18 +01:00
RELEASE,
2023-04-11 16:04:39 +01:00
MOVE_1,
MOVE_2,
MOVE_3,
MOVE_4,
2023-04-06 03:22:03 +01:00
CANCEL
}
2023-04-11 16:04:39 +01:00
export type PartySelectCallback = (cursor: integer, option: PartyOption) => void;
export type PokemonSelectFilter = (pokemon: PlayerPokemon) => string;
export type PokemonMoveSelectFilter = (pokemonMove: PokemonMove) => string;
2023-03-28 19:54:52 +01:00
export default class PartyUiHandler extends MessageUiHandler {
2023-04-06 03:22:03 +01:00
private partyUiMode: PartyUiMode;
2023-03-28 19:54:52 +01:00
private partyContainer: Phaser.GameObjects.Container;
private partySlotsContainer: Phaser.GameObjects.Container;
private partySlots: PartySlot[];
private partyCancelButton: PartyCancelButton;
private partyMessageBox: Phaser.GameObjects.Image;
private optionsMode: boolean;
private optionsCursor: integer;
private optionsContainer: Phaser.GameObjects.Container;
private optionsCursorObj: Phaser.GameObjects.Image;
2023-04-06 03:22:03 +01:00
private options: integer[];
2023-03-28 19:54:52 +01:00
private lastCursor: integer = 0;
2023-04-11 16:04:39 +01:00
private selectCallback: PartySelectCallback;
private selectFilter: PokemonSelectFilter;
private moveSelectFilter: PokemonMoveSelectFilter;
2023-03-28 19:54:52 +01:00
private static FilterAll = (_pokemon: PlayerPokemon) => null;
public static FilterNonFainted = (pokemon: PlayerPokemon) => {
if (!pokemon.hp)
return `${pokemon.name} has no energy\nleft to battle!`;
return null;
};
2023-04-11 16:04:39 +01:00
private static FilterAllMoves = (_pokemonMove: PokemonMove) => null;
2023-03-28 19:54:52 +01:00
public static NoEffectMessage = 'It won\'t have any effect.';
constructor(scene: BattleScene) {
super(scene, Mode.PARTY);
}
setup() {
const ui = this.getUi();
const partyContainer = this.scene.add.container(0, 0);
partyContainer.setVisible(false);
ui.add(partyContainer);
this.partyContainer = partyContainer;
const partyBg = this.scene.add.image(0, 0, 'party_bg');
partyContainer.add(partyBg);
partyBg.setOrigin(0, 1);
const partySlotsContainer = this.scene.add.container(0, 0);
partyContainer.add(partySlotsContainer);
this.partySlotsContainer = partySlotsContainer;
const partyMessageBoxContainer = this.scene.add.container(0, -32);
partyContainer.add(partyMessageBoxContainer);
const partyMessageBox = this.scene.add.image(1, 31, 'party_message');
partyMessageBox.setOrigin(0, 1);
partyMessageBoxContainer.add(partyMessageBox);
this.partyMessageBox = partyMessageBox;
const partyMessageText = addTextObject(this.scene, 8, 10, defaultMessage, TextStyle.WINDOW, { maxLines: 2 });
partyMessageText.setOrigin(0, 0);
partyMessageBoxContainer.add(partyMessageText);
this.message = partyMessageText;
const partyCancelButton = new PartyCancelButton(this.scene, 291, -16);
partyContainer.add(partyCancelButton);
this.partyCancelButton = partyCancelButton;
this.optionsContainer = this.scene.add.container((this.scene.game.canvas.width / 6) - 1, -1);
partyContainer.add(this.optionsContainer);
2023-04-06 03:22:03 +01:00
this.options = [];
2023-03-28 19:54:52 +01:00
this.partySlots = [];
}
show(args: any[]) {
2023-04-06 15:05:12 +01:00
if (!args.length || this.active)
2023-04-06 03:22:03 +01:00
return;
2023-03-28 19:54:52 +01:00
super.show(args);
2023-04-06 03:22:03 +01:00
this.partyUiMode = args[0] as PartyUiMode;
2023-03-28 19:54:52 +01:00
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
2023-04-11 16:04:39 +01:00
? args[2] as PokemonSelectFilter
2023-03-28 19:54:52 +01:00
: PartyUiHandler.FilterAll;
2023-04-11 16:04:39 +01:00
this.moveSelectFilter = args.length > 3 && args[3] instanceof Function
? args[3] as PokemonMoveSelectFilter
: PartyUiHandler.FilterAllMoves;
2023-03-28 19:54:52 +01:00
}
processInput(button: Button) {
2023-03-28 19:54:52 +01:00
const ui = this.getUi();
if (this.pendingPrompt)
return;
if (this.awaitingActionInput) {
if (button === Button.ACTION || button === Button.CANCEL) {
2023-03-28 19:54:52 +01:00
if (this.onActionInput) {
ui.playSelect();
const originalOnActionInput = this.onActionInput;
this.onActionInput = null;
originalOnActionInput();
this.awaitingActionInput = false;
}
}
return;
}
let success = false;
if (this.optionsMode) {
if (button === Button.ACTION) {
2023-04-06 03:22:03 +01:00
const option = this.options[this.optionsCursor];
2023-04-11 06:31:18 +01:00
const pokemon = this.scene.getParty()[this.cursor];
2023-04-11 16:04:39 +01:00
if ((option !== PartyOption.SUMMARY && option !== PartyOption.RELEASE && option !== PartyOption.CANCEL)
|| (option === PartyOption.RELEASE && this.partyUiMode === PartyUiMode.RELEASE)) {
2023-04-11 06:31:18 +01:00
let filterResult: string = this.selectFilter(pokemon);
2023-04-11 16:04:39 +01:00
if (filterResult === null && this.partyUiMode === PartyUiMode.MOVE_MODIFIER)
filterResult = this.moveSelectFilter(pokemon.moveset[this.optionsCursor]);
if (filterResult === null) {
this.clearOptions();
if (this.selectCallback) {
2023-04-11 14:41:11 +01:00
if (option === PartyOption.RELEASE)
this.doRelease(this.cursor);
else {
const selectCallback = this.selectCallback;
2023-04-11 06:31:18 +01:00
this.selectCallback = null;
2023-04-11 16:04:39 +01:00
selectCallback(this.cursor, option);
2023-04-11 06:31:18 +01:00
}
} else if (this.cursor)
(this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.POKEMON, this.cursor);
2023-04-11 16:04:39 +01:00
if (this.partyUiMode !== PartyUiMode.MODIFIER && this.partyUiMode !== PartyUiMode.MOVE_MODIFIER)
2023-04-10 00:15:21 +01:00
ui.playSelect();
2023-03-28 19:54:52 +01:00
return;
} else {
this.clearOptions();
this.partyMessageBox.setTexture('party_message_large');
this.message.y -= 15;
this.showText(filterResult as string, null, () => {
this.partyMessageBox.setTexture('party_message');
this.message.setText(defaultMessage);
this.message.y += 15;
}, null, true);
}
2023-04-06 03:22:03 +01:00
} else if (option === PartyOption.SUMMARY) {
2023-03-28 19:54:52 +01:00
ui.playSelect();
2023-04-11 06:31:18 +01:00
ui.setModeWithoutClear(Mode.SUMMARY, pokemon).then(() => this.clearOptions());
2023-04-11 14:41:11 +01:00
} else if (option === PartyOption.RELEASE) {
this.clearOptions();
ui.playSelect();
if (this.cursor) {
this.showText(`Do you really want to release ${pokemon.name}?`, null, () => {
ui.setModeWithoutClear(Mode.CONFIRM, () => {
ui.setMode(Mode.PARTY);
this.doRelease(this.cursor);
}, () => {
ui.setMode(Mode.PARTY);
this.message.setText(defaultMessage);
});
});
} else
this.showText('You can\'t release a POKéMON that\'s in battle!', null, () => this.message.setText(defaultMessage), null, true);
2023-04-06 03:22:03 +01:00
} else if (option === PartyOption.CANCEL)
this.processInput(Button.CANCEL);
} else if (button === Button.CANCEL) {
this.clearOptions();
ui.playSelect();
}
else {
switch (button) {
case Button.UP:
2023-04-06 03:22:03 +01:00
success = this.setCursor(this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1);
break;
case Button.DOWN:
2023-04-06 03:22:03 +01:00
success = this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0);
break;
}
}
} else {
if (button === Button.ACTION) {
if (this.cursor < 6) {
this.showOptions();
2023-03-28 19:54:52 +01:00
ui.playSelect();
2023-04-06 05:15:33 +01:00
} else if (this.partyUiMode === PartyUiMode.FAINT_SWITCH)
ui.playError();
else
this.processInput(Button.CANCEL);
return;
} else if (button === Button.CANCEL) {
2023-04-06 05:15:33 +01:00
if (this.partyUiMode !== PartyUiMode.FAINT_SWITCH) {
if (this.selectCallback) {
const selectCallback = this.selectCallback;
this.selectCallback = null;
2023-04-11 16:04:39 +01:00
selectCallback(6, PartyOption.CANCEL);
ui.playSelect();
} else {
ui.setMode(Mode.COMMAND);
ui.playSelect();
}
2023-03-28 19:54:52 +01:00
}
return;
2023-03-28 19:54:52 +01:00
}
const slotCount = this.partySlots.length;
switch (button) {
case Button.UP:
success = this.setCursor(this.cursor ? this.cursor < 6 ? this.cursor - 1 : slotCount - 1 : 6);
break;
case Button.DOWN:
success = this.setCursor(this.cursor < 6 ? this.cursor < slotCount - 1 ? this.cursor + 1 : 6 : 0);
break;
case Button.LEFT:
if (this.cursor && this.cursor < 6)
success = this.setCursor(0);
break;
case Button.RIGHT:
if (!this.cursor)
success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || 1 : 1);
break;
}
2023-03-28 19:54:52 +01:00
}
if (success)
ui.playSelect();
}
populatePartySlots() {
const party = (this.scene as BattleScene).getParty();
if (this.cursor < 6 && this.cursor >= party.length)
this.cursor = party.length - 1;
else if (this.cursor === 6)
this.partyCancelButton.select();
for (let p in party) {
const slotIndex = parseInt(p);
const partySlot = new PartySlot(this.scene as BattleScene, slotIndex, party[p]);
this.scene.add.existing(partySlot);
this.partySlotsContainer.add(partySlot);
this.partySlots.push(partySlot);
if (this.cursor === slotIndex)
partySlot.select();
}
}
setCursor(cursor: integer): boolean {
let changed: boolean;
if (this.optionsMode) {
changed = this.optionsCursor !== cursor;
this.optionsCursor = cursor;
if (!this.optionsCursorObj) {
this.optionsCursorObj = this.scene.add.image(0, 0, 'cursor');
this.optionsCursorObj.setOrigin(0, 0);
this.optionsContainer.add(this.optionsCursorObj);
}
2023-04-06 03:22:03 +01:00
this.optionsCursorObj.setPosition(-86, -19 - (16 * ((this.options.length - 1) - this.optionsCursor)));
} else {
changed = this.cursor !== cursor;
if (changed) {
this.lastCursor = this.cursor;
this.cursor = cursor;
if (this.lastCursor < 6)
this.partySlots[this.lastCursor].deselect();
else if (this.lastCursor === 6)
this.partyCancelButton.deselect();
if (cursor < 6)
this.partySlots[cursor].select();
else if (cursor === 6)
this.partyCancelButton.select();
}
}
return changed;
}
showOptions() {
if (this.cursor === 6)
return;
this.optionsMode = true;
this.partyMessageBox.setTexture('party_message_options');
this.message.setText('Do what with this Pokémon?');
const optionsBottom = this.scene.add.image(0, 0, 'party_options_bottom');
optionsBottom.setOrigin(1, 1);
this.optionsContainer.add(optionsBottom);
2023-04-11 16:04:39 +01:00
const pokemon = this.scene.getParty()[this.cursor];
2023-04-06 03:22:03 +01:00
2023-04-11 16:04:39 +01:00
if (this.partyUiMode !== PartyUiMode.MOVE_MODIFIER) {
switch (this.partyUiMode) {
case PartyUiMode.SWITCH:
if (this.cursor)
this.options.push(PartyOption.SHIFT);
break;
case PartyUiMode.FAINT_SWITCH:
case PartyUiMode.POST_BATTLE_SWITCH:
if (this.cursor)
this.options.push(PartyOption.SEND_OUT);
break;
case PartyUiMode.MODIFIER:
this.options.push(PartyOption.APPLY);
break;
case PartyUiMode.RELEASE:
this.options.push(PartyOption.RELEASE);
break;
}
2023-04-11 14:41:11 +01:00
2023-04-11 16:04:39 +01:00
this.options.push(PartyOption.SUMMARY);
if (this.partyUiMode === PartyUiMode.SWITCH)
this.options.push(PartyOption.RELEASE);
} else {
for (let m = 0; m < pokemon.moveset.length; m++)
this.options.push(PartyOption.MOVE_1 + m);
}
2023-04-11 14:41:11 +01:00
this.options.push(PartyOption.CANCEL);
2023-04-06 03:22:03 +01:00
for (let o = 0; o < this.options.length; o++) {
2023-04-11 16:04:39 +01:00
const option = this.options[this.options.length - (o + 1)];
let optionName: string;
switch (option) {
case PartyOption.MOVE_1:
case PartyOption.MOVE_2:
case PartyOption.MOVE_3:
case PartyOption.MOVE_4:
optionName = pokemon.moveset[option - PartyOption.MOVE_1].getName();
break;
default:
optionName = PartyOption[option].replace(/\_/g, ' ');
break;
}
const yCoord = -6 - 16 * o;
const optionBg = this.scene.add.image(0, yCoord, 'party_options_center');
2023-04-11 16:04:39 +01:00
const optionText = addTextObject(this.scene, -79, yCoord - 16, optionName, TextStyle.WINDOW);
optionBg.setOrigin(1, 1);
optionText.setOrigin(0, 0);
this.optionsContainer.add(optionBg);
this.optionsContainer.add(optionText);
}
2023-04-06 03:22:03 +01:00
const optionsTop = this.scene.add.image(0, -6 - 16 * this.options.length, 'party_options_top');
optionsTop.setOrigin(1, 1);
this.optionsContainer.add(optionsTop);
this.setCursor(0);
}
2023-04-11 14:41:11 +01:00
doRelease(slotIndex: integer): void {
this.showText(this.getReleaseMessage(this.scene.getParty()[slotIndex].name), null, () => {
this.clearPartySlots();
this.scene.removePartyMemberModifiers(slotIndex);
const releasedPokemon = this.scene.getParty().splice(slotIndex, 1)[0];
releasedPokemon.destroy();
2023-04-11 14:41:11 +01:00
this.populatePartySlots();
if (this.cursor >= this.scene.getParty().length)
this.setCursor(this.cursor - 1);
if (this.partyUiMode === PartyUiMode.RELEASE) {
const selectCallback = this.selectCallback;
this.selectCallback = null;
2023-04-11 16:04:39 +01:00
selectCallback(this.cursor, PartyOption.RELEASE);
2023-04-11 14:41:11 +01:00
} else
this.message.setText(defaultMessage);
}, null, true);
}
2023-04-11 06:31:18 +01:00
getReleaseMessage(pokemonName: string): string {
const rand = Utils.randInt(128);
2023-04-11 14:41:11 +01:00
if (rand < 20)
2023-04-11 06:31:18 +01:00
return `Goodbye, ${pokemonName}!`;
2023-04-11 14:41:11 +01:00
else if (rand < 40)
return `Byebye, ${pokemonName}!`;
else if (rand < 60)
2023-04-11 06:31:18 +01:00
return `Farewell, ${pokemonName}!`;
2023-04-11 14:41:11 +01:00
else if (rand < 80)
return `So long, ${pokemonName}!`;
else if (rand < 100)
2023-04-11 06:31:18 +01:00
return `This is where we part, ${pokemonName}!`;
else if (rand < 108)
return `I'll miss you, ${pokemonName}!`;
else if (rand < 116)
return `I'll never forget you, ${pokemonName}!`;
else if (rand < 124)
return `Until we meet again, ${pokemonName}!`;
else if (rand < 127)
return `Sayonara, ${pokemonName}!`
else
return `Smell ya later, ${pokemonName}!`;
}
clearOptions() {
this.optionsMode = false;
2023-04-06 03:22:03 +01:00
this.options.splice(0, this.options.length);
this.optionsContainer.removeAll(true);
this.eraseOptionsCursor();
this.partyMessageBox.setTexture('party_message');
this.message.setText(defaultMessage);
}
eraseOptionsCursor() {
if (this.optionsCursorObj)
this.optionsCursorObj.destroy();
this.optionsCursorObj = null;
}
2023-03-28 19:54:52 +01:00
clear() {
super.clear();
this.partyContainer.setVisible(false);
this.clearPartySlots();
}
clearPartySlots() {
this.partySlots.splice(0, this.partySlots.length);
this.partySlotsContainer.removeAll(true);
}
}
class PartySlot extends Phaser.GameObjects.Container {
private selected: boolean;
private slotIndex: integer;
private pokemon: PlayerPokemon;
private slotBg: Phaser.GameObjects.Image;
private slotPb: Phaser.GameObjects.Sprite;
private slotPokemonIcon: Phaser.GameObjects.Sprite;
private slotHpOverlay: Phaser.GameObjects.Sprite;
constructor(scene: BattleScene, slotIndex: integer, pokemon: PlayerPokemon) {
super(scene, slotIndex ? 230.5 : 64, slotIndex ? -184 + 28 * slotIndex : -124);
this.slotIndex = slotIndex;
this.pokemon = pokemon;
this.setup();
}
setup() {
const slotKey = `party_slot${this.slotIndex ? '' : '_main'}`;
const slotBg = this.scene.add.sprite(0, 0, slotKey, `${slotKey}${this.pokemon.hp ? '' : '_fnt'}`);
this.slotBg = slotBg;
this.add(slotBg);
const slotPb = this.scene.add.sprite(this.slotIndex ? -85.5 : -51, this.slotIndex ? 0 : -20.5, 'party_pb');
this.slotPb = slotPb;
this.add(slotPb);
2023-04-10 00:15:21 +01:00
const pokemonIcon = this.scene.add.sprite(slotPb.x, slotPb.y, this.pokemon.species.getIconAtlasKey());
2023-03-28 19:54:52 +01:00
console.log(pokemonIcon)
2023-04-10 00:15:21 +01:00
pokemonIcon.play(this.pokemon.species.getIconKey());
2023-03-28 19:54:52 +01:00
this.slotPokemonIcon = pokemonIcon;
this.add(pokemonIcon);
const slotInfoContainer = this.scene.add.container(0, 0);
this.add(slotInfoContainer);
const slotName = addTextObject(this.scene, 0, 0, this.pokemon.name, TextStyle.PARTY);
slotName.setPositionRelative(slotBg, this.slotIndex ? 21 : 24, this.slotIndex ? 3 : 10);
slotName.setOrigin(0, 0);
const slotLevelLabel = this.scene.add.image(0, 0, 'party_slot_overlay_lv');
slotLevelLabel.setPositionRelative(slotName, 8, 12);
slotLevelLabel.setOrigin(0, 0);
const slotLevelText = addTextObject(this.scene, 0, 0, this.pokemon.level.toString(), TextStyle.PARTY);
slotLevelText.setPositionRelative(slotLevelLabel, 9, 0);
slotLevelText.setOrigin(0, 0.25);
const slotHpBar = this.scene.add.image(0, 0, 'party_slot_hp_bar');
slotHpBar.setPositionRelative(slotBg, this.slotIndex ? 72 : 8, this.slotIndex ? 7 : 31);
slotHpBar.setOrigin(0, 0);
2023-03-29 17:23:52 +01:00
const hpRatio = this.pokemon.getHpRatio();
2023-03-28 19:54:52 +01:00
const slotHpOverlay = this.scene.add.sprite(0, 0, 'party_slot_hp_overlay', hpRatio > 0.5 ? 'high' : hpRatio > 0.25 ? 'medium' : 'low');
slotHpOverlay.setPositionRelative(slotHpBar, 16, 2);
slotHpOverlay.setOrigin(0, 0);
slotHpOverlay.setScale(hpRatio, 1);
const slotHpText = addTextObject(this.scene, 0, 0, `${this.pokemon.hp}/${this.pokemon.getMaxHp()}`, TextStyle.PARTY);
slotHpText.setPositionRelative(slotHpBar, slotHpBar.width - 3, slotHpBar.height - 2);
slotHpText.setOrigin(1, 0);
slotInfoContainer.add([ slotName, slotLevelLabel, slotLevelText, slotHpBar, slotHpOverlay, slotHpText ]);
this.slotHpOverlay = slotHpOverlay;
}
select() {
if (this.selected)
return;
this.selected = true;
this.slotBg.setTexture(`party_slot${this.slotIndex ? '' : '_main'}`, `party_slot${this.slotIndex ? '' : '_main'}${this.pokemon.hp ? '' : '_fnt'}_sel`);
this.slotPb.setFrame('party_pb_sel');
}
deselect() {
if (!this.selected)
return;
this.selected = false;
this.slotBg.setTexture(`party_slot${this.slotIndex ? '' : '_main'}`, `party_slot${this.slotIndex ? '' : '_main'}${this.pokemon.hp ? '' : '_fnt'}`);
this.slotPb.setFrame('party_pb');
}
}
class PartyCancelButton extends Phaser.GameObjects.Container {
private selected: boolean;
private partyCancelBg: Phaser.GameObjects.Sprite;
private partyCancelPb: Phaser.GameObjects.Sprite;
constructor(scene: BattleScene, x: number, y: number) {
super(scene, x, y);
this.setup();
}
setup() {
const partyCancelBg = this.scene.add.sprite(0, 0, 'party_cancel');
this.add(partyCancelBg);
this.partyCancelBg = partyCancelBg;
const partyCancelPb = this.scene.add.sprite(-17, 0, 'party_pb');
this.add(partyCancelPb);
this.partyCancelPb = partyCancelPb;
const partyCancelText = addTextObject(this.scene, -7, -6, 'CANCEL', TextStyle.PARTY);
this.add(partyCancelText);
}
select() {
if (this.selected)
return;
this.selected = true;
this.partyCancelBg.setFrame(`party_cancel_sel`);
this.partyCancelPb.setFrame('party_pb_sel');
}
deselect() {
if (!this.selected)
return;
this.selected = false;
this.partyCancelBg.setFrame('party_cancel');
this.partyCancelPb.setFrame('party_pb');
}
}