[QoL] Move Info Overlay (#1585)

* move info implemented for starter selection

a move info box is displayed when editing the starter moveset.

also menus have now onHover triggers.

todo:
- show ui when selecting TMs
- show ui when selecting moves to remember (memory mushroom)

* More Move Info Overlays

Added overlays during Memory Mushroom use and when viewing TMs.
Furthermore a settings option can enable/disable those overlays.

* Added missing ko language entry

... though translation still remains necessary

* updated ui

also added overrides for item rewards

* minor ui update

moved values to the right in the tm move info box

* fixed typedoc issues

* removed settings in to prepare for merge

* updated settings option

added settings option to new settings implementation

* minor changes

removed unused graphic
moved settings option to accessibility
This commit is contained in:
prime 2024-06-06 03:28:12 +02:00 committed by GitHub
parent c5689dfc96
commit 1c98106642
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 429 additions and 44 deletions

View File

@ -71,15 +71,20 @@ const expSpriteKeys: string[] = [];
export let starterColors: StarterColors;
interface StarterColors {
[key: string]: [string, string]
[key: string]: [string, string]
}
export interface PokeballCounts {
[pb: string]: integer;
[pb: string]: integer;
}
export type AnySound = Phaser.Sound.WebAudioSound | Phaser.Sound.HTML5AudioSound | Phaser.Sound.NoAudioSound;
export interface InfoToggle {
toggleInfo(force?: boolean): void;
isActive(): boolean;
}
export default class BattleScene extends SceneBase {
public rexUI: UIPlugin;
public inputController: InputsController;
@ -97,6 +102,7 @@ export default class BattleScene extends SceneBase {
public showArenaFlyout: boolean = true;
public showLevelUpStats: boolean = true;
public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1";
public enableMoveInfo: boolean = true;
public enableRetries: boolean = false;
/**
* Determines the condition for a notification should be shown for Candy Upgrades
@ -120,17 +126,17 @@ export default class BattleScene extends SceneBase {
public skipSeenDialogues: boolean = false;
/**
* Defines the experience gain display mode.
*
* @remarks
* The `expParty` can have several modes:
* - `0` - Default: The normal experience gain display, nothing changed.
* - `1` - Level Up Notification: Displays the level up in the small frame instead of a message.
* - `2` - Skip: No level up frame nor message.
*
* Modes `1` and `2` are still compatible with stats display, level up, new move, etc.
* @default 0 - Uses the default normal experience gain display.
*/
* Defines the experience gain display mode.
*
* @remarks
* The `expParty` can have several modes:
* - `0` - Default: The normal experience gain display, nothing changed.
* - `1` - Level Up Notification: Displays the level up in the small frame instead of a message.
* - `2` - Skip: No level up frame nor message.
*
* Modes `1` and `2` are still compatible with stats display, level up, new move, etc.
* @default 0 - Uses the default normal experience gain display.
*/
public expParty: integer = 0;
public hpBarSpeed: integer = 0;
public fusionPaletteSwaps: boolean = true;
@ -209,6 +215,8 @@ export default class BattleScene extends SceneBase {
public rngSeedOverride: string = "";
public rngOffset: integer = 0;
private infoToggles: InfoToggle[] = [];
/**
* Allows subscribers to listen for events
*
@ -515,7 +523,7 @@ export default class BattleScene extends SceneBase {
this.playTimeTimer = this.time.addEvent({
delay: Utils.fixedInt(1000),
repeat: -1,
callback: () => {
callback: () => {
if (this.gameData) {
this.gameData.gameStats.playTime++;
}
@ -599,25 +607,25 @@ export default class BattleScene extends SceneBase {
/*const loadPokemonAssets: Promise<void>[] = [];
for (let s of Object.keys(speciesStarters)) {
const species = getPokemonSpecies(parseInt(s));
loadPokemonAssets.push(species.loadAssets(this, false, 0, false));
}
for (let s of Object.keys(speciesStarters)) {
const species = getPokemonSpecies(parseInt(s));
loadPokemonAssets.push(species.loadAssets(this, false, 0, false));
}
Promise.all(loadPokemonAssets).then(() => {
const starterCandyColors = {};
const rgbaToHexFunc = (r, g, b) => [r, g, b].map(x => x.toString(16).padStart(2, '0')).join('');
Promise.all(loadPokemonAssets).then(() => {
const starterCandyColors = {};
const rgbaToHexFunc = (r, g, b) => [r, g, b].map(x => x.toString(16).padStart(2, '0')).join('');
for (let s of Object.keys(speciesStarters)) {
const species = getPokemonSpecies(parseInt(s));
for (let s of Object.keys(speciesStarters)) {
const species = getPokemonSpecies(parseInt(s));
starterCandyColors[species.speciesId] = species.generateCandyColors(this).map(c => rgbaToHexFunc(c[0], c[1], c[2]));
}
starterCandyColors[species.speciesId] = species.generateCandyColors(this).map(c => rgbaToHexFunc(c[0], c[1], c[2]));
}
console.log(JSON.stringify(starterCandyColors));
console.log(JSON.stringify(starterCandyColors));
resolve();
});*/
resolve();
});*/
resolve();
});
@ -682,6 +690,16 @@ export default class BattleScene extends SceneBase {
: ret;
}
// store info toggles to be accessible by the ui
addInfoToggle(infoToggle: InfoToggle): void {
this.infoToggles.push(infoToggle);
}
// return the stored info toggles; used by ui-inputs
getInfoToggles(activeOnly: boolean = false): InfoToggle[] {
return activeOnly ? this.infoToggles.filter(t => t?.isActive()) : this.infoToggles;
}
getPokemonById(pokemonId: integer): Pokemon {
const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId);
return findInParty(this.getParty()) || findInParty(this.getEnemyParty());
@ -728,7 +746,7 @@ export default class BattleScene extends SceneBase {
const container = this.add.container(x, y);
const icon = this.add.sprite(0, 0, pokemon.getIconAtlasKey(ignoreOverride));
icon.setFrame(pokemon.getIconId(true));
icon.setFrame(pokemon.getIconId(true));
// Temporary fix to show pokemon's default icon if variant icon doesn't exist
if (icon.frame.name !== pokemon.getIconId(true)) {
console.log(`${pokemon.name}'s variant icon does not exist. Replacing with default.`);
@ -1336,7 +1354,7 @@ export default class BattleScene extends SceneBase {
return;
}
const formattedMoney =
this.moneyFormat === MoneyFormat.ABBREVIATED ? Utils.formatFancyLargeNumber(this.money, 3) : this.money.toLocaleString();
this.moneyFormat === MoneyFormat.ABBREVIATED ? Utils.formatFancyLargeNumber(this.money, 3) : this.money.toLocaleString();
this.moneyText.setText(`${formattedMoney}`);
this.fieldUI.moveAbove(this.moneyText, this.luckText);
if (forceVisible) {
@ -1926,7 +1944,7 @@ export default class BattleScene extends SceneBase {
const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier;
newItemModifier.pokemonId = target.id;
const matchingModifier = target.scene.findModifier(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).matchType(itemModifier) && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier;
&& (m as PokemonHeldItemModifier).matchType(itemModifier) && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier;
let removeOld = true;
if (matchingModifier) {
const maxStackCount = matchingModifier.getMaxStackCount(target.scene);
@ -2030,8 +2048,8 @@ export default class BattleScene extends SceneBase {
}
/**
* Removes all modifiers from enemy of PersistentModifier type
*/
* Removes all modifiers from enemy of PersistentModifier type
*/
clearEnemyModifiers(): void {
const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PersistentModifier);
for (const m of modifiersToRemove) {
@ -2041,8 +2059,8 @@ export default class BattleScene extends SceneBase {
}
/**
* Removes all modifiers from enemy of PokemonHeldItemModifier type
*/
* Removes all modifiers from enemy of PokemonHeldItemModifier type
*/
clearEnemyHeldItemModifiers(): void {
const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier);
for (const m of modifiersToRemove) {

View File

@ -127,6 +127,7 @@ export class LoadingScene extends SceneBase {
this.loadImage("summary_stats_overlay_exp", "ui");
this.loadImage("summary_moves", "ui");
this.loadImage("summary_moves_effect", "ui");
this.loadImage("summary_moves_effect_type", "ui");
this.loadImage("summary_moves_overlay_row", "ui");
this.loadImage("summary_moves_overlay_pp", "ui");
this.loadAtlas("summary_moves_cursor", "ui");

View File

@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = {
name: "TM{{moveId}} - {{moveName}}",
description: "Bringt einem Pokémon {{moveName}} bei",
},
"TmModifierTypeWithInfo": {
name: "TM{{moveId}} - {{moveName}}",
description: "Bringt einem Pokémon {{moveName}} bei\n(Halte C oder Shift für mehr Infos)",
},
"EvolutionItemModifierType": {
description: "Erlaubt es bestimmten Pokémon sich zu entwickeln",
},

View File

@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = {
name: "TM{{moveId}} - {{moveName}}",
description: "Teach {{moveName}} to a Pokémon",
},
"TmModifierTypeWithInfo": {
name: "TM{{moveId}} - {{moveName}}",
description: "Teach {{moveName}} to a Pokémon\n(Hold C or Shift for more info)",
},
"EvolutionItemModifierType": {
description: "Causes certain Pokémon to evolve",
},

View File

@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = {
name: "MT{{moveId}} - {{moveName}}",
description: "Enseña {{moveName}} a un Pokémon",
},
"TmModifierTypeWithInfo": {
name: "MT{{moveId}} - {{moveName}}",
description: "Enseña {{moveName}} a un Pokémon\n(Hold C or Shift for more info)",
},
"EvolutionItemModifierType": {
description: "Hace que ciertos Pokémon evolucionen",
},

View File

@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = {
name: "CT{{moveId}} - {{moveName}}",
description: "Apprend la capacité {{moveName}} à un Pokémon",
},
"TmModifierTypeWithInfo": {
name: "CT{{moveId}} - {{moveName}}",
description: "Apprend la capacité {{moveName}} à un Pokémon\n(Hold C or Shift for more info)",
},
"EvolutionItemModifierType": {
description: "Permet à certains Pokémon dévoluer",
},

View File

@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = {
name: "MT{{moveId}} - {{moveName}}",
description: "Insegna {{moveName}} a un Pokémon",
},
"TmModifierTypeWithInfo": {
name: "MT{{moveId}} - {{moveName}}",
description: "Insegna {{moveName}} a un Pokémon\n(Hold C or Shift for more info)",
},
"EvolutionItemModifierType": {
description: "Fa evolvere determinate specie di Pokémon",
},

View File

@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = {
name: "No.{{moveId}} {{moveName}}",
description: "포켓몬에게 {{moveName}}[[를]] 가르침",
},
"TmModifierTypeWithInfo": {
name: "No.{{moveId}} {{moveName}}",
description: "포켓몬에게 {{moveName}}를(을) 가르침\n(Hold C or Shift for more info)",
},
"EvolutionItemModifierType": {
description: "어느 특정 포켓몬을 진화",
},

View File

@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = {
name: "TM{{moveId}} - {{moveName}}",
description: "Ensina {{moveName}} a um Pokémon",
},
"TmModifierTypeWithInfo": {
name: "TM{{moveId}} - {{moveName}}",
description: "Ensina {{moveName}} a um Pokémon\n(Hold C or Shift for more info)",
},
"EvolutionItemModifierType": {
description: "Faz certos Pokémon evoluírem",
},

View File

@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = {
name: "招式学习器 {{moveId}} - {{moveName}}",
description: "教会一只宝可梦{{moveName}}",
},
"TmModifierTypeWithInfo": {
name: "招式学习器 {{moveId}} - {{moveName}}",
description: "教会一只宝可梦{{moveName}}\n(Hold C or Shift for more info)",
},
"EvolutionItemModifierType": {
description: "使某些宝可梦进化",
},

View File

@ -98,6 +98,10 @@ export const modifierType: ModifierTypeTranslationEntries = {
name: "招式學習器 {{moveId}} - {{moveName}}",
description: "教會一隻寶可夢{{moveName}}",
},
TmModifierTypeWithInfo: {
name: "TM{{moveId}} - {{moveName}}",
description: "教會一隻寶可夢{{moveName}}\n(Hold C or Shift for more info)",
},
EvolutionItemModifierType: { description: "使某些寶可夢進化" },
FormChangeItemModifierType: { description: "使某些寶可夢更改形態" },
FusePokemonModifierType: {

View File

@ -23,6 +23,7 @@ import { ModifierTier } from "./modifier-tier";
import { Nature, getNatureName, getNatureStatMultiplier } from "#app/data/nature";
import i18next from "#app/plugins/i18n";
import { getModifierTierTextTint } from "#app/ui/text";
import * as Overrides from "../overrides";
const outputModifierData = false;
const useMaxWeightForOutput = false;
@ -721,7 +722,7 @@ export class TmModifierType extends PokemonModifierType {
}
getDescription(scene: BattleScene): string {
return i18next.t("modifierType:ModifierType.TmModifierType.description", { moveName: allMoves[this.moveId].name });
return i18next.t(scene.enableMoveInfo ? "modifierType:ModifierType.TmModifierTypeWithInfo.description" : "modifierType:ModifierType.TmModifierType.description", { moveName: allMoves[this.moveId].name });
}
}
@ -1673,6 +1674,14 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo
}
options.push(candidate);
});
// OVERRIDE IF NECESSARY
if (Overrides.ITEM_REWARD_OVERRIDE?.length) {
options.forEach((mod, i) => {
// @ts-ignore: keeps throwing don't use string as index error in typedoc run
const override = modifierTypes[Overrides.ITEM_REWARD_OVERRIDE[i]]?.();
mod.type = (override instanceof ModifierTypeGenerator ? override.generateType(party) : override) || mod.type;
});
}
return options;
}

View File

@ -110,3 +110,11 @@ export const OPP_MODIFIER_OVERRIDE: Array<ModifierOverride> = [];
export const STARTING_HELD_ITEMS_OVERRIDE: Array<ModifierOverride> = [];
export const OPP_HELD_ITEMS_OVERRIDE: Array<ModifierOverride> = [];
/**
* An array of items by keys as defined in the "modifierTypes" object in the "modifier/modifier-type.ts" file.
* Items listed will replace the normal rolls.
* If less items are listed than rolled, only some items will be replaced
* If more items are listed than rolled, only the first X items will be shown, where X is the number of items rolled.
*/
export const ITEM_REWARD_OVERRIDE: Array<String> = [];

View File

@ -44,6 +44,7 @@ export const SettingKeys = {
UI_Theme: "UI_THEME",
Window_Type: "WINDOW_TYPE",
Tutorials: "TUTORIALS",
Move_Info: "MOVE_INFO",
Enable_Retries: "ENABLE_RETRIES",
Skip_Seen_Dialogues: "SKIP_SEEN_DIALOGUES",
Candy_Upgrade_Notification: "CANDY_UPGRADE_NOTIFICATION",
@ -132,6 +133,13 @@ export const Setting: Array<Setting> = [
default: 1,
type: SettingType.GENERAL
},
{
key: SettingKeys.Move_Info,
label: "Move Info",
options: OFF_ON,
default: 1,
type: SettingType.ACCESSIBILITY
},
{
key: SettingKeys.Enable_Retries,
label: "Enable Retries",
@ -312,6 +320,9 @@ export function setSetting(scene: BattleScene, setting: string, value: integer):
case SettingKeys.Tutorials:
scene.enableTutorials = Setting[index].options[value] === "On";
break;
case SettingKeys.Move_Info:
scene.enableMoveInfo = Setting[index].options[value] === "On";
break;
case SettingKeys.Enable_Retries:
scene.enableRetries = Setting[index].options[value] === "On";
break;

View File

@ -114,6 +114,11 @@ export class UiInputs {
}
buttonStats(pressed: boolean = true): void {
// allow access to Button.STATS as a toggle for other elements
for (const t of this.scene.getInfoToggles(true)) {
t.toggleInfo(pressed);
}
// handle normal pokemon battle ui
for (const p of this.scene.getField().filter(p => p?.isActive(true))) {
p.toggleStats(pressed);
}

View File

@ -14,15 +14,17 @@ export interface OptionSelectConfig {
maxOptions?: integer;
delay?: integer;
noCancel?: boolean;
supportHover?: boolean;
}
export interface OptionSelectItem {
label: string;
handler: () => boolean;
onHover?: () => void;
keepOpen?: boolean;
overrideSound?: boolean;
item?: string;
itemArgs?: any[]
itemArgs?: any[];
}
const scrollUpLabel = "↑";
@ -193,6 +195,10 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
}
break;
}
if (this.config?.supportHover) {
// handle hover code if the element supports hover-handlers and the option has the optional hover-handler set.
this.config?.options[this.cursor + (this.scrollCursor - (this.scrollCursor ? 1 : 0))]?.onHover?.();
}
}
if (success && playSound) {

View File

@ -1,5 +1,5 @@
import BattleScene from "../battle-scene";
import { getPlayerShopModifierTypeOptionsForWave, ModifierTypeOption } from "../modifier/modifier-type";
import { getPlayerShopModifierTypeOptionsForWave, ModifierTypeOption, TmModifierType } from "../modifier/modifier-type";
import { getPokeballAtlasKey, PokeballType } from "../data/pokeball";
import { addTextObject, getModifierTierTextTint, getTextColor, TextStyle } from "./text";
import AwaitableUiHandler from "./awaitable-ui-handler";
@ -7,6 +7,8 @@ import { Mode } from "./ui";
import { LockModifierTiersModifier, PokemonHeldItemModifier } from "../modifier/modifier";
import { handleTutorial, Tutorial } from "../tutorial";
import {Button} from "../enums/buttons";
import MoveInfoOverlay from "./move-info-overlay";
import { allMoves } from "../data/move";
export const SHOP_OPTIONS_ROW_LIMIT = 6;
@ -17,6 +19,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
private transferButtonContainer: Phaser.GameObjects.Container;
private rerollCostText: Phaser.GameObjects.Text;
private lockRarityButtonText: Phaser.GameObjects.Text;
private moveInfoOverlay : MoveInfoOverlay;
private rowCursor: integer = 0;
private player: boolean;
@ -73,6 +76,21 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.lockRarityButtonText = addTextObject(this.scene, -4, -2, "Lock Rarities", TextStyle.PARTY);
this.lockRarityButtonText.setOrigin(0, 0);
this.lockRarityButtonContainer.add(this.lockRarityButtonText);
// prepare move overlay
const overlayScale = 1;
this.moveInfoOverlay = new MoveInfoOverlay(this.scene, {
delayVisibility: true,
scale: overlayScale,
onSide: true,
right: true,
x: 1,
y: -MoveInfoOverlay.getHeight(overlayScale, true) -1,
width: (this.scene.game.canvas.width / 6) - 2,
});
ui.add(this.moveInfoOverlay);
// register the overlay to receive toggle events
this.scene.addInfoToggle(this.moveInfoOverlay);
}
show(args: any[]): boolean {
@ -293,6 +311,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.cursorObj.setScale(this.rowCursor === 1 ? 2 : this.rowCursor >= 2 ? 1.5 : 1);
// the modifier selection has been updated, always hide the overlay
this.moveInfoOverlay.clear();
if (this.rowCursor) {
const sliceWidth = (this.scene.game.canvas.width / 6) / (options.length + 2);
if (this.rowCursor < 2) {
@ -300,7 +320,13 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
} else {
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-16 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1))));
}
ui.showText(options[this.cursor].modifierTypeOption.type.getDescription(this.scene));
const type = options[this.cursor].modifierTypeOption.type;
ui.showText(type.getDescription(this.scene));
if (type instanceof TmModifierType) {
// prepare the move overlay to be shown with the toggle
this.moveInfoOverlay.show(allMoves[type.moveId]);
}
} else if (!cursor) {
this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? -72 : -60);
ui.showText("Spend money to reroll your item options.");

194
src/ui/move-info-overlay.ts Normal file
View File

@ -0,0 +1,194 @@
import BattleScene, {InfoToggle} from "../battle-scene";
import { TextStyle, addTextObject } from "./text";
import { addWindow } from "./ui-theme";
import * as Utils from "../utils";
import Move, { MoveCategory } from "../data/move";
import { Type } from "../data/type";
import i18next from "i18next";
export interface MoveInfoOverlaySettings {
delayVisibility?: boolean; // if true, showing the overlay will only set it to active and populate the fields and the handler using this field has to manually call setVisible later.
scale?:number; // scale the box? A scale of 0.5 is recommended
top?: boolean; // should the effect box be on top?
right?: boolean; // should the effect box be on the right?
onSide?: boolean; // should the effect be on the side? ignores top argument if true
//location and width of the component; unaffected by scaling
x?: number;
y?: number;
width?: number; // default is always half the screen, regardless of scale
}
const EFF_HEIGHT = 46;
const EFF_WIDTH = 82;
const DESC_HEIGHT = 46;
const BORDER = 8;
const GLOBAL_SCALE = 6;
export default class MoveInfoOverlay extends Phaser.GameObjects.Container implements InfoToggle {
public active: boolean = false;
private move: Move;
private desc: Phaser.GameObjects.Text;
private descScroll : Phaser.Tweens.Tween = null;
private val: Phaser.GameObjects.Container;
private pp: Phaser.GameObjects.Text;
private pow: Phaser.GameObjects.Text;
private acc: Phaser.GameObjects.Text;
private typ: Phaser.GameObjects.Sprite;
private cat: Phaser.GameObjects.Sprite;
private options : MoveInfoOverlaySettings;
constructor(scene: BattleScene, options?: MoveInfoOverlaySettings) {
if (options?.onSide) {
options.top = false;
}
super(scene, options?.x, options?.y);
const scale = options?.scale || 1; // set up the scale
this.setScale(scale);
this.options = options || {};
// prepare the description box
const width = (options?.width || MoveInfoOverlay.getWidth(scale, scene)) / scale; // divide by scale as we always want this to be half a window wide
const descBg = addWindow(scene, (options?.onSide && !options?.right ? EFF_WIDTH : 0), options?.top ? EFF_HEIGHT : 0, width - (options?.onSide ? EFF_WIDTH : 0), DESC_HEIGHT);
descBg.setOrigin(0, 0);
this.add(descBg);
// set up the description; wordWrap uses true pixels, unaffected by any scaling, while other values are affected
this.desc = addTextObject(scene, (options?.onSide && !options?.right ? EFF_WIDTH : 0) + BORDER, (options?.top ? EFF_HEIGHT : 0) + BORDER - 2, "", TextStyle.BATTLE_INFO, { wordWrap: { width: (width - (BORDER - 2) * 2 - (options?.onSide ? EFF_WIDTH : 0)) * GLOBAL_SCALE } });
// limit the text rendering, required for scrolling later on
const maskPointOrigin = {
x: (options?.x || 0),
y: (options?.y || 0),
};
if (maskPointOrigin.x < 0) {
maskPointOrigin.x += this.scene.game.canvas.width / GLOBAL_SCALE;
}
if (maskPointOrigin.y < 0) {
maskPointOrigin.y += this.scene.game.canvas.height / GLOBAL_SCALE;
}
const moveDescriptionTextMaskRect = this.scene.make.graphics();
moveDescriptionTextMaskRect.fillStyle(0xFF0000);
moveDescriptionTextMaskRect.fillRect(
maskPointOrigin.x + ((options?.onSide && !options?.right ? EFF_WIDTH : 0) + BORDER) * scale, maskPointOrigin.y + ((options?.top ? EFF_HEIGHT : 0) + BORDER - 2) * scale,
width - ((options?.onSide ? EFF_WIDTH : 0) - BORDER * 2) * scale, (DESC_HEIGHT - (BORDER - 2) * 2) * scale);
moveDescriptionTextMaskRect.setScale(6);
const moveDescriptionTextMask = this.createGeometryMask(moveDescriptionTextMaskRect);
this.add(this.desc);
this.desc.setMask(moveDescriptionTextMask);
// prepare the effect box
this.val = new Phaser.GameObjects.Container(scene, options?.right ? width - EFF_WIDTH : 0, options?.top || options?.onSide ? 0 : DESC_HEIGHT);
this.add(this.val);
const valuesBg = addWindow(scene, 0, 0, EFF_WIDTH, EFF_HEIGHT);
valuesBg.setOrigin(0, 0);
this.val.add(valuesBg);
this.typ = this.scene.add.sprite(25, EFF_HEIGHT - 35,`types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}` , "unknown");
this.typ.setScale(0.8);
this.val.add(this.typ);
this.cat = this.scene.add.sprite(57, EFF_HEIGHT - 35, "categories", "physical");
this.val.add(this.cat);
const ppTxt = addTextObject(scene, 12, EFF_HEIGHT - 25, "PP", TextStyle.MOVE_INFO_CONTENT);
ppTxt.setOrigin(0.0, 0.5);
ppTxt.setText(i18next.t("fightUiHandler:pp"));
this.val.add(ppTxt);
this.pp = addTextObject(scene, 70, EFF_HEIGHT - 25, "--", TextStyle.MOVE_INFO_CONTENT);
this.pp.setOrigin(1, 0.5);
this.val.add(this.pp);
const powTxt = addTextObject(scene, 12, EFF_HEIGHT - 17, "POWER", TextStyle.MOVE_INFO_CONTENT);
powTxt.setOrigin(0.0, 0.5);
powTxt.setText(i18next.t("fightUiHandler:power"));
this.val.add(powTxt);
this.pow = addTextObject(scene, 70, EFF_HEIGHT - 17, "---", TextStyle.MOVE_INFO_CONTENT);
this.pow.setOrigin(1, 0.5);
this.val.add(this.pow);
const accTxt = addTextObject(scene, 12, EFF_HEIGHT - 9, "ACC", TextStyle.MOVE_INFO_CONTENT);
accTxt.setOrigin(0.0, 0.5);
accTxt.setText(i18next.t("fightUiHandler:accuracy"));
this.val.add(accTxt);
this.acc = addTextObject(scene, 70, EFF_HEIGHT - 9, "---", TextStyle.MOVE_INFO_CONTENT);
this.acc.setOrigin(1, 0.5);
this.val.add(this.acc);
// hide this component for now
this.setVisible(false);
}
// show this component with infos for the specific move
show(move : Move):boolean {
if (!(this.scene as BattleScene).enableMoveInfo) {
return; // move infos have been disabled
}
this.move = move;
this.pow.setText(move.power >= 0 ? move.power.toString() : "---");
this.acc.setText(move.accuracy >= 0 ? move.accuracy.toString() : "---");
this.pp.setText(move.pp >= 0 ? move.pp.toString() : "---");
this.typ.setTexture(`types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}`, Type[move.type].toLowerCase());
this.cat.setFrame(MoveCategory[move.category].toLowerCase());
this.desc.setText(move?.effect || "");
// stop previous scrolling effects
if (this.descScroll) {
this.descScroll.remove();
this.descScroll = null;
}
// determine if we need to add new scrolling effects
const moveDescriptionLineCount = Math.floor(this.desc.displayHeight * (96 / 72) / 14.83);
if (moveDescriptionLineCount > 3) {
// generate scrolling effects
this.descScroll = this.scene.tweens.add({
targets: this.desc,
delay: Utils.fixedInt(2000),
loop: -1,
hold: Utils.fixedInt(2000),
duration: Utils.fixedInt((moveDescriptionLineCount - 3) * 2000),
y: `-=${14.83 * (72 / 96) * (moveDescriptionLineCount - 3)}`
});
}
if (!this.options.delayVisibility) {
this.setVisible(true);
}
this.active = true;
return true;
}
clear() {
this.setVisible(false);
this.active = false;
}
toggleInfo(force?: boolean): void {
this.setVisible(force ?? !this.visible);
}
isActive(): boolean {
return this.active;
}
// width of this element
static getWidth(scale:number, scene: BattleScene):number {
return scene.game.canvas.width / GLOBAL_SCALE / 2;
}
// height of this element
static getHeight(scale:number, onSide?: boolean):number {
return (onSide ? Math.max(EFF_HEIGHT, DESC_HEIGHT) : (EFF_HEIGHT + DESC_HEIGHT)) * scale;
}
}

View File

@ -17,6 +17,7 @@ import { addWindow } from "./ui-theme";
import { SpeciesFormChangeItemTrigger } from "../data/pokemon-forms";
import { getVariantTint } from "#app/data/variant";
import {Button} from "../enums/buttons";
import MoveInfoOverlay from "./move-info-overlay";
const defaultMessage = "Choose a Pokémon.";
@ -73,6 +74,7 @@ export default class PartyUiHandler extends MessageUiHandler {
private partySlots: PartySlot[];
private partyCancelButton: PartyCancelButton;
private partyMessageBox: Phaser.GameObjects.NineSlice;
private moveInfoOverlay: MoveInfoOverlay;
private optionsMode: boolean;
private optionsScroll: boolean;
@ -179,6 +181,17 @@ export default class PartyUiHandler extends MessageUiHandler {
this.iconAnimHandler = new PokemonIconAnimHandler();
this.iconAnimHandler.setup(this.scene);
// prepare move overlay. in case it appears to be too big, set the overlayScale to .5
const overlayScale = 1;
this.moveInfoOverlay = new MoveInfoOverlay(this.scene, {
scale: overlayScale,
top: true,
x: 1,
y: -MoveInfoOverlay.getHeight(overlayScale) - 1, //this.scene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29,
width: this.scene.game.canvas.width / 12 - 30,
});
ui.add(this.moveInfoOverlay);
this.options = [];
this.partySlots = [];
@ -191,6 +204,9 @@ export default class PartyUiHandler extends MessageUiHandler {
super.show(args);
// reset the infoOverlay
this.moveInfoOverlay.clear();
this.partyUiMode = args[0] as PartyUiMode;
this.fieldIndex = args.length > 1 ? args[1] as integer : -1;
@ -244,6 +260,8 @@ export default class PartyUiHandler extends MessageUiHandler {
ui.playSelect();
return true;
} else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER && option !== PartyOption.CANCEL) {
// clear overlay on cancel
this.moveInfoOverlay.clear();
const filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon);
if (filterResult === null) {
this.selectCallback(this.cursor, option);
@ -408,6 +426,19 @@ export default class PartyUiHandler extends MessageUiHandler {
success = this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0); /** Move cursor */
break;
}
// show move description
if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) {
const option = this.options[this.optionsCursor];
const pokemon = this.scene.getParty()[this.cursor];
const move = allMoves[pokemon.getLearnableLevelMoves()[option]];
if (move) {
this.moveInfoOverlay.show(move);
} else {
// or hide the overlay, in case it's the cancel button
this.moveInfoOverlay.clear();
}
}
}
} else {
if (button === Button.ACTION) {
@ -625,6 +656,11 @@ export default class PartyUiHandler extends MessageUiHandler {
? pokemon.getLearnableLevelMoves()
: null;
if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER && learnableLevelMoves?.length) {
// show the move overlay with info for the first move
this.moveInfoOverlay.show(allMoves[learnableLevelMoves[0]]);
}
const itemModifiers = this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER
? this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === pokemon.id) as PokemonHeldItemModifier[]
@ -874,6 +910,8 @@ export default class PartyUiHandler extends MessageUiHandler {
}
clearOptions() {
// hide the overlay
this.moveInfoOverlay.clear();
this.optionsMode = false;
this.optionsScroll = false;
this.optionsScrollCursor = 0;
@ -895,6 +933,8 @@ export default class PartyUiHandler extends MessageUiHandler {
clear() {
super.clear();
// hide the overlay
this.moveInfoOverlay.clear();
this.partyContainer.setVisible(false);
this.clearPartySlots();
}

View File

@ -31,6 +31,7 @@ import { StatsContainer } from "./stats-container";
import { TextStyle, addBBCodeTextObject, addTextObject } from "./text";
import { Mode } from "./ui";
import { addWindow } from "./ui-theme";
import MoveInfoOverlay from "./move-info-overlay";
export type StarterSelectCallback = (starters: Starter[]) => void;
@ -192,6 +193,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private starterSelectMessageBoxContainer: Phaser.GameObjects.Container;
private statsContainer: StatsContainer;
private pokemonFormText: Phaser.GameObjects.Text;
private moveInfoOverlay : MoveInfoOverlay;
private genMode: boolean;
private statsMode: boolean;
@ -661,6 +663,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.message.setOrigin(0, 0);
this.starterSelectMessageBoxContainer.add(this.message);
const overlayScale = 1; // scale for the move info. "2/3" might be another good option...
this.moveInfoOverlay = new MoveInfoOverlay(this.scene, {
scale: overlayScale,
top: true,
x: 1,
y: this.scene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29,
});
this.starterSelectContainer.add(this.moveInfoOverlay);
const date = new Date();
date.setUTCHours(0, 0, 0, 0);
@ -1058,6 +1069,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
const showSwapOptions = (moveset: StarterMoveset) => {
ui.setMode(Mode.STARTER_SELECT).then(() => {
ui.showText(i18next.t("starterSelectUiHandler:selectMoveSwapOut"), null, () => {
this.moveInfoOverlay.show(allMoves[moveset[0]]);
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: moveset.map((m: Moves, i: number) => {
const option: OptionSelectItem = {
@ -1065,8 +1078,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
handler: () => {
ui.setMode(Mode.STARTER_SELECT).then(() => {
ui.showText(`${i18next.t("starterSelectUiHandler:selectMoveSwapWith")} ${allMoves[m].name}.`, null, () => {
const possibleMoves = this.speciesStarterMoves.filter((sm: Moves) => sm !== m);
this.moveInfoOverlay.show(allMoves[possibleMoves[0]]);
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: this.speciesStarterMoves.filter((sm: Moves) => sm !== m).map(sm => {
options: possibleMoves.map(sm => {
// make an option for each available starter move
const option = {
label: allMoves[sm].name,
@ -1074,7 +1090,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.switchMoveHandler(i, sm, m);
showSwapOptions(this.starterMoveset);
return true;
}
},
onHover: () => {
this.moveInfoOverlay.show(allMoves[sm]);
},
};
return option;
}).concat({
@ -1082,25 +1101,37 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
handler: () => {
showSwapOptions(this.starterMoveset);
return true;
}
},
onHover: () => {
this.moveInfoOverlay.clear();
},
}),
supportHover: true,
maxOptions: 8,
yOffset: 19
});
});
});
return true;
}
},
onHover: () => {
this.moveInfoOverlay.show(allMoves[m]);
},
};
return option;
}).concat({
label: i18next.t("menu:cancel"),
handler: () => {
this.moveInfoOverlay.clear();
this.clearText();
ui.setMode(Mode.STARTER_SELECT);
return true;
}
},
onHover: () => {
this.moveInfoOverlay.clear();
},
}),
supportHover: true,
maxOptions: 8,
yOffset: 19
});