diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 0736831a01e..87b31e4d0f8 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,5 +1,5 @@ import * as ModifierTypes from './modifier-type'; -import { LearnMovePhase, LevelUpPhase, PokemonHealPhase } from "../phases"; +import { LearnMovePhase, LevelUpPhase, PokemonHealPhase, ScanIvsPhase, CommandPhase } from "../phases"; import BattleScene from "../battle-scene"; import { getLevelTotalExp } from "../data/exp"; import { PokeballType } from "../data/pokeball"; @@ -21,6 +21,13 @@ import { Nature } from '#app/data/nature'; import { BattlerTagType } from '#app/data/enums/battler-tag-type'; import * as Overrides from '../overrides'; import { ModifierType, modifierTypes } from './modifier-type'; +import { Mode } from '#app/ui/ui.js'; +import i18next from 'i18next'; +import { BattleType } from '#app/battle.js'; +import { battleMessageUiHandler } from '#app/locales/de/battle-message-ui-handler.js'; +import BattleMessageUiHandler from '#app/ui/battle-message-ui-handler.js'; +import Button from 'phaser3-rex-plugins/plugins/button'; +import { CommonAnim, CommonBattleAnim } from '#app/data/battle-anims.js'; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -70,6 +77,16 @@ export class ModifierBar extends Phaser.GameObjects.Container { if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) thisArg.updateModifierOverflowVisibility(false); }); + + /* + Pseudo-Click Events. + Pseudo because POINTER_DOWN is when input is down. + TODO: A real click is when mouse is down, and goes up + while still inside the element's X and Y. + */ + icon.on(Phaser.Input.Events.POINTER_DOWN, () => { + modifier.onClick(this.scene); + }) }); for (let icon of this.getAll()) @@ -110,6 +127,8 @@ export abstract class Modifier { } abstract apply(args: any[]): boolean | Promise; + + onClick(scene: Phaser.Scene) {}; } export abstract class PersistentModifier extends Modifier { @@ -1925,6 +1944,10 @@ export class IvScannerModifier extends PersistentModifier { super(type, stackCount); } + // Whether the IV-Scanner, should auto-prompt each wave. + public shouldAutoPrompt: boolean = true; + private isForceScanning: boolean = false; + match(modifier: Modifier): boolean { return modifier instanceof IvScannerModifier; } @@ -1940,6 +1963,133 @@ export class IvScannerModifier extends PersistentModifier { getMaxStackCount(scene: BattleScene): integer { return 3; } + + // Opens the menu for the IV Scanner + // Does not implement own sanity checks. + openIvScannerMenu(scene: BattleScene): void { + // Only if not a Trainer + if (scene.currentBattle.battleType !== BattleType.WILD) { + return; + } + + // We shall prevent this phase being stacked through clicks. + if (scene.getCurrentPhase() instanceof ScanIvsPhase) + return; + + + // Interrupt the current phase, if it is our turn + if (!(scene.getCurrentPhase() instanceof CommandPhase)) { + //console.log("Not our turn!") + return; + } + + var commandPhase = scene.getCurrentPhase() as CommandPhase; + const enemyField = scene.getEnemyField(); + + var scanOptions = []; + + // Create buttons for every enemy Pokemon + enemyField.map(pokemon => { + scanOptions.push({ + label: `Scan: ${pokemon.name}`, + handler: () => { + scene.ui.revertMode(); // hide commandPhase controls + + // Show prompt + // Handler to close the IV Prompt + + scene.ui.setMode(Mode.MESSAGE); + scene.ui.clearText(); + + scene.ui.showText(`${pokemon.name}`, null, () => { + new CommonBattleAnim(CommonAnim.LOCK_ON, pokemon, pokemon).play(scene, () => { + scene.ui.getMessageHandler().promptIvs(pokemon.id, pokemon.ivs, Math.min(this.getStackCount() * 2, 6)) + + // promptIvs has its own Action Handler, and once that is executed, the promise is resolved + .then(() => { + scene.ui.showText(null, 0); + // go back to our commandPhase prompt + scene.ui.setMode(Mode.COMMAND, commandPhase.getFieldIndex()); + }); + }); + }); + + return true; + }, + + keepOpen: false, + }) + }) + + const ivOptions = [ + { + // Label automatically changes text. + label: `Toggle ${this.shouldAutoPrompt ? "OFF" : "ON"}`, + handler: () => { + scene.ui.revertMode(); + // Message Confirmation Mode + scene.ui.setMode(Mode.MESSAGE); + + // Toggle + this.shouldAutoPrompt = !this.shouldAutoPrompt; + + let textToShow = `IV-Scanner auto-prompt\nhas been toggled ${this.shouldAutoPrompt ? "ON" : "OFF"}`; + + scene.ui.showText(textToShow, null, () => { + scene.ui.showText(null, 0); + // go back to our commandPhase prompt + scene.ui.setMode(Mode.COMMAND, commandPhase.getFieldIndex()); + }, null, true); + return true; + }, + + // Prevents the container from closing after selecting + keepOpen: false, + }, + + // Auto put in the Pokemon Scan options right here! + // Thanks to the --> ... + ...scanOptions.map(option => ({ + ...option, + })), + + { + label: i18next.t('menuUiHandler:cancel'), + handler: () => { + scene.ui.revertMode(); // closes the container ui + + scene.ui.setMode(Mode.COMMAND, commandPhase.getFieldIndex()); + return true; + } + } + ]; + + const ivOptionsConfig = { + xOffset: 98, + options: ivOptions + }; + + scene.ui.setOverlayMode(Mode.MENU_OPTION_SELECT, ivOptionsConfig); + } + + // Click Event for the IV-Scanner + onClick(scene: BattleScene): void { + this.openIvScannerMenu(scene); + + // Put in queue at the top. + //scene.unshiftPhase(scene.getCurrentPhase()); + + // Add a Phase for every enemy. + /*enemyField.map( + p => { + var iv = new ScanIvsPhase(scene, p.getBattlerIndex(), Math.min(this.getStackCount() * 2, 6)) + scene.unshiftPhase(iv); + } + );*/ + + // Interrupt the CommandPhase + //scene.shiftPhase(); + } } export class ExtraModifierModifier extends PersistentModifier { diff --git a/src/phases.ts b/src/phases.ts index 1137c85afa6..283d07abd8a 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -4828,6 +4828,12 @@ export class ScanIvsPhase extends PokemonPhase { const pokemon = this.getPokemon(); + const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier) as IvScannerModifier; + + // Only show if auto-prompt is enabled. + if (!ivScannerModifier.shouldAutoPrompt) + return this.end() + this.scene.ui.showText(i18next.t('battle:ivScannerUseQuestion', { pokemonName: pokemon.name }), null, () => { this.scene.ui.setMode(Mode.CONFIRM, () => { this.scene.ui.setMode(Mode.MESSAGE); diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts index 06729151d44..e63413fa625 100644 --- a/src/ui/ball-ui-handler.ts +++ b/src/ui/ball-ui-handler.ts @@ -7,12 +7,16 @@ import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import { addWindow } from "./ui-theme"; import {Button} from "../enums/buttons"; +import { IvScannerModifier } from "#app/modifier/modifier.js"; export default class BallUiHandler extends UiHandler { private pokeballSelectContainer: Phaser.GameObjects.Container; private pokeballSelectBg: Phaser.GameObjects.NineSlice; + private optionsText: Phaser.GameObjects.Text; private countsText: Phaser.GameObjects.Text; + private ivScannerAdded = false; + private cursorObj: Phaser.GameObjects.Image; constructor(scene: BattleScene) { @@ -32,14 +36,17 @@ export default class BallUiHandler extends UiHandler { let optionsTextContent = ''; - for (let pb = 0; pb < Object.keys(this.scene.pokeballCounts).length; pb++) + // For every Pokeball, add them to the text content. + for (let pb = 0; pb < Object.keys(this.scene.pokeballCounts).length; pb++) { optionsTextContent += `${getPokeballName(pb)}\n`; - optionsTextContent += 'Cancel'; - const optionsText = addTextObject(this.scene, 0, 0, optionsTextContent, TextStyle.WINDOW, { align: 'right', maxLines: 6 }); - optionsText.setOrigin(0, 0); - optionsText.setPositionRelative(this.pokeballSelectBg, 42, 9); - optionsText.setLineSpacing(12); - this.pokeballSelectContainer.add(optionsText); + } + optionsTextContent += 'Cancel'; // Warning, "Cancel", is used in a RegExp. + + this.optionsText = addTextObject(this.scene, 0, 0, optionsTextContent, TextStyle.WINDOW, { align: 'right', maxLines: 6 }); + this.optionsText.setOrigin(0, 0); + this.optionsText.setPositionRelative(this.pokeballSelectBg, 42, 9); + this.optionsText.setLineSpacing(12); + this.pokeballSelectContainer.add(this.optionsText); this.countsText = addTextObject(this.scene, 0, 0, '', TextStyle.WINDOW, { maxLines: 5 }); this.countsText.setPositionRelative(this.pokeballSelectBg, 18, 9); @@ -49,9 +56,33 @@ export default class BallUiHandler extends UiHandler { this.setCursor(0); } + updatePositions(): void { + this.optionsText.setPositionRelative(this.pokeballSelectBg, 42, 9); + this.countsText.setPositionRelative(this.pokeballSelectBg, 18, 9); + } + + addIvScanner(): void { + if (this.ivScannerAdded) + return; + + this.ivScannerAdded = true; + this.optionsText.setText( + this.optionsText.text.replace(new RegExp("Cancel", "g"), "IV-Scanner\nCancel") + ); + this.optionsText.setMaxLines(7); + + this.pokeballSelectBg.height = 112 + 12; + this.updatePositions(); + } + show(args: any[]): boolean { super.show(args); + const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier) as IvScannerModifier; + if (ivScannerModifier) { + this.addIvScanner(); + } + this.updateCounts(); this.pokeballSelectContainer.setVisible(true); this.setCursor(this.cursor); @@ -65,6 +96,7 @@ export default class BallUiHandler extends UiHandler { let success = false; const pokeballTypeCount = Object.keys(this.scene.pokeballCounts).length; + let maxLines = this.optionsText.style.maxLines; if (button === Button.ACTION || button === Button.CANCEL) { const commandPhase = this.scene.getCurrentPhase() as CommandPhase; @@ -78,6 +110,15 @@ export default class BallUiHandler extends UiHandler { } } else ui.playError(); + + } else if (button === Button.ACTION && this.cursor < (pokeballTypeCount+1) && this.ivScannerAdded) { + this.scene.ui.revertMode(); + ui.setMode(Mode.COMMAND, commandPhase.getFieldIndex()); + + const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier) as IvScannerModifier; + ivScannerModifier.openIvScannerMenu(this.scene); + + success = true; } else { ui.setMode(Mode.COMMAND, commandPhase.getFieldIndex()); success = true; @@ -85,10 +126,10 @@ export default class BallUiHandler extends UiHandler { } else { switch (button) { case Button.UP: - success = this.setCursor(this.cursor ? this.cursor - 1 : pokeballTypeCount); + success = this.setCursor(this.cursor ? this.cursor - 1 : (maxLines-1)); break; case Button.DOWN: - success = this.setCursor(this.cursor < pokeballTypeCount ? this.cursor + 1 : 0); + success = this.setCursor(this.cursor < (maxLines-1) ? this.cursor + 1 : 0); break; } }