Merge e893fcc48f
into ec09186264
This commit is contained in:
commit
80c971439b
Binary file not shown.
After Width: | Height: | Size: 252 B |
Binary file not shown.
After Width: | Height: | Size: 405 B |
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 252 B |
Binary file not shown.
After Width: | Height: | Size: 405 B |
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
|
@ -161,6 +161,7 @@ export default class BattleScene extends SceneBase {
|
|||
public reroll: boolean = false;
|
||||
public shopCursorTarget: number = ShopCursorTarget.REWARDS;
|
||||
public commandCursorMemory: boolean = false;
|
||||
public dexForDevs: boolean = false;
|
||||
public showMovesetFlyout: boolean = true;
|
||||
public showArenaFlyout: boolean = true;
|
||||
public showTimeOfDayWidget: boolean = true;
|
||||
|
|
|
@ -102,6 +102,18 @@ export interface BiomePokemonPools {
|
|||
[key: integer]: BiomeTierPokemonPools
|
||||
}
|
||||
|
||||
export interface BiomeTierTod {
|
||||
biome: Biome,
|
||||
tier: BiomePoolTier,
|
||||
tod: TimeOfDay[]
|
||||
}
|
||||
|
||||
export interface CatchableSpecies{
|
||||
[key: integer]: BiomeTierTod[]
|
||||
}
|
||||
|
||||
export const catchableSpecies: CatchableSpecies = {};
|
||||
|
||||
export interface BiomeTierTrainerPools {
|
||||
[key: integer]: TrainerType[]
|
||||
}
|
||||
|
@ -7716,6 +7728,10 @@ export function initBiomes() {
|
|||
uncatchableSpecies.push(speciesId);
|
||||
}
|
||||
|
||||
// prepares new array in catchableSpecies to host available biomes
|
||||
//TODO: this must be improved to only make arrays for starters
|
||||
catchableSpecies[speciesId] = [];
|
||||
|
||||
for (const b of biomeEntries) {
|
||||
const biome = b[0];
|
||||
const tier = b[1];
|
||||
|
@ -7725,6 +7741,12 @@ export function initBiomes() {
|
|||
: [ b[2] ]
|
||||
: [ TimeOfDay.ALL ];
|
||||
|
||||
catchableSpecies[speciesId].push({
|
||||
biome: biome as Biome,
|
||||
tier: tier as BiomePoolTier,
|
||||
tod: timesOfDay as TimeOfDay[]
|
||||
});
|
||||
|
||||
for (const tod of timesOfDay) {
|
||||
if (!biomePokemonPools.hasOwnProperty(biome) || !biomePokemonPools[biome].hasOwnProperty(tier) || !biomePokemonPools[biome][tier].hasOwnProperty(tod)) {
|
||||
continue;
|
||||
|
|
|
@ -92,6 +92,7 @@ export class SpeciesFormEvolution {
|
|||
public item: EvolutionItem | null;
|
||||
public condition: SpeciesEvolutionCondition | null;
|
||||
public wildDelay: SpeciesWildEvolutionDelay;
|
||||
public description: string = "";
|
||||
|
||||
constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: integer, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) {
|
||||
this.speciesId = speciesId;
|
||||
|
@ -101,6 +102,23 @@ export class SpeciesFormEvolution {
|
|||
this.item = item || EvolutionItem.NONE;
|
||||
this.condition = condition;
|
||||
this.wildDelay = wildDelay ?? SpeciesWildEvolutionDelay.NONE;
|
||||
|
||||
const strings: string[] = [];
|
||||
if (this.level > 1) {
|
||||
strings.push(i18next.t("pokemonEvolutions:level") + ` ${this.level}`);
|
||||
}
|
||||
if (this.item) {
|
||||
const itemDescription = i18next.t(`modifierType:EvolutionItem.${EvolutionItem[this.item].toUpperCase()}`);
|
||||
const rarity = this.item > 50 ? i18next.t("pokemonEvolutions:ULTRA") : i18next.t("pokemonEvolutions:GREAT");
|
||||
strings.push(i18next.t("pokemonEvolutions:using") + itemDescription + ` (${rarity})`);
|
||||
}
|
||||
if (this.condition) {
|
||||
strings.push(this.condition.description);
|
||||
}
|
||||
this.description = strings
|
||||
.filter(str => str !== "")
|
||||
.map((str, index) => index > 0 ? str[0].toLowerCase() + str.slice(1) : str)
|
||||
.join(i18next.t("pokemonEvolutions:connector"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,6 +255,7 @@ class WeatherEvolutionCondition extends SpeciesEvolutionCondition {
|
|||
constructor(weatherTypes: WeatherType[]) {
|
||||
super(() => weatherTypes.indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1);
|
||||
this.weatherTypes = weatherTypes;
|
||||
this.description = i18next.t("pokemonEvolutions:weather");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1377,7 +1396,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
|
|||
],
|
||||
[Species.TANDEMAUS]: [
|
||||
new SpeciesFormEvolution(Species.MAUSHOLD, "", "three", 25, null, new TandemausEvolutionCondition()),
|
||||
new SpeciesEvolution(Species.MAUSHOLD, 25, null, null)
|
||||
new SpeciesFormEvolution(Species.MAUSHOLD, "", "four", 25, null, null)
|
||||
],
|
||||
[Species.FIDOUGH]: [
|
||||
new SpeciesEvolution(Species.DACHSBUN, 26, null, null)
|
||||
|
@ -1540,7 +1559,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
|
|||
],
|
||||
[Species.DUNSPARCE]: [
|
||||
new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "three-segment", 32, null, new DunsparceEvolutionCondition(), SpeciesWildEvolutionDelay.LONG),
|
||||
new SpeciesEvolution(Species.DUDUNSPARCE, 32, null, new MoveEvolutionCondition(Moves.HYPER_DRILL), SpeciesWildEvolutionDelay.LONG)
|
||||
new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "two-segment", 32, null, new MoveEvolutionCondition(Moves.HYPER_DRILL), SpeciesWildEvolutionDelay.LONG)
|
||||
],
|
||||
[Species.GLIGAR]: [
|
||||
new SpeciesEvolution(Species.GLISCOR, 1, EvolutionItem.RAZOR_FANG, new TimeOfDayEvolutionCondition("night") /* Razor fang at night*/, SpeciesWildEvolutionDelay.VERY_LONG)
|
||||
|
|
|
@ -68433,6 +68433,31 @@ export const tmSpecies: TmSpecies = {
|
|||
],
|
||||
};
|
||||
|
||||
interface SpeciesTmMoves {
|
||||
[key: integer]: Moves[]
|
||||
}
|
||||
|
||||
function flipTmSpecies(tmSpecies: TmSpecies): SpeciesTmMoves {
|
||||
const flipped: SpeciesTmMoves = {};
|
||||
|
||||
for (const move in tmSpecies) {
|
||||
const moveKey = Number(move);
|
||||
const speciesList = tmSpecies[move];
|
||||
|
||||
for (const species of speciesList) {
|
||||
const speciesKey = Number(species);
|
||||
if (!flipped[speciesKey]) {
|
||||
flipped[speciesKey] = [];
|
||||
}
|
||||
flipped[speciesKey].push(moveKey);
|
||||
}
|
||||
}
|
||||
return flipped;
|
||||
}
|
||||
|
||||
export const speciesTmMoves: SpeciesTmMoves = flipTmSpecies(tmSpecies);
|
||||
|
||||
|
||||
interface TmPoolTiers {
|
||||
[key: integer]: ModifierTier
|
||||
}
|
||||
|
|
|
@ -328,7 +328,8 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge
|
|||
this.move = move;
|
||||
this.known = known;
|
||||
const moveKey = Moves[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("") as unknown as string;
|
||||
this.description = i18next.t("pokemonEvolutions:Forms.moveLearned", { move: i18next.t(`move:${moveKey}.name`) });
|
||||
this.description = known ? i18next.t("pokemonEvolutions:Forms.moveLearned", { move: i18next.t(`move:${moveKey}.name`) }) :
|
||||
i18next.t("pokemonEvolutions:Forms.moveForgotten", { move: i18next.t(`move:${moveKey}.name`) });
|
||||
}
|
||||
|
||||
canChange(pokemon: Pokemon): boolean {
|
||||
|
|
|
@ -298,8 +298,8 @@ export abstract class PokemonSpeciesForm {
|
|||
return ret;
|
||||
}
|
||||
|
||||
getSpriteAtlasPath(female: boolean, formIndex?: number, shiny?: boolean, variant?: number): string {
|
||||
const spriteId = this.getSpriteId(female, formIndex, shiny, variant).replace(/\_{2}/g, "/");
|
||||
getSpriteAtlasPath(female: boolean, formIndex?: number, shiny?: boolean, variant?: number, back?: boolean): string {
|
||||
const spriteId = this.getSpriteId(female, formIndex, shiny, variant, back).replace(/\_{2}/g, "/");
|
||||
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
|
||||
}
|
||||
|
||||
|
@ -320,8 +320,8 @@ export abstract class PokemonSpeciesForm {
|
|||
return `${back ? "back__" : ""}${shiny && (!variantSet || (!variant && !variantSet[variant || 0])) ? "shiny__" : ""}${baseSpriteKey}${shiny && variantSet && variantSet[variant] === 2 ? `_${variant + 1}` : ""}`;
|
||||
}
|
||||
|
||||
getSpriteKey(female: boolean, formIndex?: number, shiny?: boolean, variant?: number): string {
|
||||
return `pkmn__${this.getSpriteId(female, formIndex, shiny, variant)}`;
|
||||
getSpriteKey(female: boolean, formIndex?: number, shiny?: boolean, variant?: number, back?: boolean): string {
|
||||
return `pkmn__${this.getSpriteId(female, formIndex, shiny, variant, back)}`;
|
||||
}
|
||||
|
||||
abstract getFormSpriteKey(formIndex?: number): string;
|
||||
|
@ -494,10 +494,10 @@ export abstract class PokemonSpeciesForm {
|
|||
return true;
|
||||
}
|
||||
|
||||
loadAssets(female: boolean, formIndex?: number, shiny?: boolean, variant?: Variant, startLoad?: boolean): Promise<void> {
|
||||
loadAssets(female: boolean, formIndex?: number, shiny?: boolean, variant?: Variant, startLoad?: boolean, back?: boolean): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant);
|
||||
globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant));
|
||||
const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back);
|
||||
globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back));
|
||||
globalScene.load.audio(`${this.getCryKey(formIndex)}`, `audio/${this.getCryKey(formIndex)}.m4a`);
|
||||
globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => {
|
||||
const originalWarn = console.warn;
|
||||
|
@ -507,7 +507,7 @@ export abstract class PokemonSpeciesForm {
|
|||
console.warn = originalWarn;
|
||||
if (!(globalScene.anims.exists(spriteKey))) {
|
||||
globalScene.anims.create({
|
||||
key: this.getSpriteKey(female, formIndex, shiny, variant),
|
||||
key: this.getSpriteKey(female, formIndex, shiny, variant, back),
|
||||
frames: frameNames,
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
|
@ -515,7 +515,7 @@ export abstract class PokemonSpeciesForm {
|
|||
} else {
|
||||
globalScene.anims.get(spriteKey).frameRate = 10;
|
||||
}
|
||||
const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, "");
|
||||
const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant, back).replace("variant/", "").replace(/_[1-3]$/, "");
|
||||
globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant).then(() => resolve());
|
||||
});
|
||||
if (startLoad) {
|
||||
|
@ -2637,18 +2637,10 @@ export function initSpecies() {
|
|||
new PokemonSpecies(Species.ROARING_MOON, 9, false, false, false, "Paradox Pokémon", Type.DRAGON, Type.DARK, 2, 380, Abilities.PROTOSYNTHESIS, Abilities.NONE, Abilities.NONE, 590, 105, 139, 71, 55, 101, 119, 10, 0, 295, GrowthRate.SLOW, null, false),
|
||||
new PokemonSpecies(Species.IRON_VALIANT, 9, false, false, false, "Paradox Pokémon", Type.FAIRY, Type.FIGHTING, 1.4, 35, Abilities.QUARK_DRIVE, Abilities.NONE, Abilities.NONE, 590, 74, 130, 90, 120, 60, 116, 10, 0, 295, GrowthRate.SLOW, null, false),
|
||||
new PokemonSpecies(Species.KORAIDON, 9, false, true, false, "Paradox Pokémon", Type.FIGHTING, Type.DRAGON, 2.5, 303, Abilities.ORICHALCUM_PULSE, Abilities.NONE, Abilities.NONE, 670, 100, 135, 115, 85, 100, 135, 3, 0, 335, GrowthRate.SLOW, null, false, false,
|
||||
new PokemonForm("Apex Build", "apex-build", Type.FIGHTING, Type.DRAGON, 2.5, 303, Abilities.ORICHALCUM_PULSE, Abilities.NONE, Abilities.NONE, 670, 100, 135, 115, 85, 100, 135, 3, 0, 335, false, null, true),
|
||||
new PokemonForm("Limited Build", "limited-build", Type.FIGHTING, Type.DRAGON, 3.5, 303, Abilities.ORICHALCUM_PULSE, Abilities.NONE, Abilities.NONE, 670, 100, 135, 115, 85, 100, 135, 3, 0, 335, false, null, true),
|
||||
new PokemonForm("Sprinting Build", "sprinting-build", Type.FIGHTING, Type.DRAGON, 3.5, 303, Abilities.ORICHALCUM_PULSE, Abilities.NONE, Abilities.NONE, 670, 100, 135, 115, 85, 100, 135, 3, 0, 335, false, null, true),
|
||||
new PokemonForm("Swimming Build", "swimming-build", Type.FIGHTING, Type.DRAGON, 3.5, 303, Abilities.ORICHALCUM_PULSE, Abilities.NONE, Abilities.NONE, 670, 100, 135, 115, 85, 100, 135, 3, 0, 335, false, null, true),
|
||||
new PokemonForm("Gliding Build", "gliding-build", Type.FIGHTING, Type.DRAGON, 3.5, 303, Abilities.ORICHALCUM_PULSE, Abilities.NONE, Abilities.NONE, 670, 100, 135, 115, 85, 100, 135, 3, 0, 335, false, null, true),
|
||||
new PokemonForm("Apex Build", "apex-build", Type.FIGHTING, Type.DRAGON, 2.5, 303, Abilities.ORICHALCUM_PULSE, Abilities.NONE, Abilities.NONE, 670, 100, 135, 115, 85, 100, 135, 3, 0, 335, false, null, true)
|
||||
),
|
||||
new PokemonSpecies(Species.MIRAIDON, 9, false, true, false, "Paradox Pokémon", Type.ELECTRIC, Type.DRAGON, 3.5, 240, Abilities.HADRON_ENGINE, Abilities.NONE, Abilities.NONE, 670, 100, 85, 100, 135, 115, 135, 3, 0, 335, GrowthRate.SLOW, null, false, false,
|
||||
new PokemonForm("Ultimate Mode", "ultimate-mode", Type.ELECTRIC, Type.DRAGON, 3.5, 240, Abilities.HADRON_ENGINE, Abilities.NONE, Abilities.NONE, 670, 100, 85, 100, 135, 115, 135, 3, 0, 335, false, null, true),
|
||||
new PokemonForm("Low-Power Mode", "low-power-mode", Type.ELECTRIC, Type.DRAGON, 2.8, 240, Abilities.HADRON_ENGINE, Abilities.NONE, Abilities.NONE, 670, 100, 85, 100, 135, 115, 135, 3, 0, 335, false, null, true),
|
||||
new PokemonForm("Drive Mode", "drive-mode", Type.ELECTRIC, Type.DRAGON, 2.8, 240, Abilities.HADRON_ENGINE, Abilities.NONE, Abilities.NONE, 670, 100, 85, 100, 135, 115, 135, 3, 0, 335, false, null, true),
|
||||
new PokemonForm("Aquatic Mode", "aquatic-mode", Type.ELECTRIC, Type.DRAGON, 2.8, 240, Abilities.HADRON_ENGINE, Abilities.NONE, Abilities.NONE, 670, 100, 85, 100, 135, 115, 135, 3, 0, 335, false, null, true),
|
||||
new PokemonForm("Glide Mode", "glide-mode", Type.ELECTRIC, Type.DRAGON, 2.8, 240, Abilities.HADRON_ENGINE, Abilities.NONE, Abilities.NONE, 670, 100, 85, 100, 135, 115, 135, 3, 0, 335, false, null, true),
|
||||
new PokemonForm("Ultimate Mode", "ultimate-mode", Type.ELECTRIC, Type.DRAGON, 3.5, 240, Abilities.HADRON_ENGINE, Abilities.NONE, Abilities.NONE, 670, 100, 85, 100, 135, 115, 135, 3, 0, 335, false, null, true)
|
||||
),
|
||||
new PokemonSpecies(Species.WALKING_WAKE, 9, false, false, false, "Paradox Pokémon", Type.WATER, Type.DRAGON, 3.5, 280, Abilities.PROTOSYNTHESIS, Abilities.NONE, Abilities.NONE, 590, 99, 83, 91, 125, 83, 109, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Gouging Fire and Raging Bolt
|
||||
new PokemonSpecies(Species.IRON_LEAVES, 9, false, false, false, "Paradox Pokémon", Type.GRASS, Type.PSYCHIC, 1.5, 125, Abilities.QUARK_DRIVE, Abilities.NONE, Abilities.NONE, 590, 90, 130, 88, 70, 108, 104, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Iron Boulder and Iron Crown
|
||||
|
|
|
@ -5,7 +5,7 @@ import { SceneBase } from "#app/scene-base";
|
|||
import { WindowVariant, getWindowVariantSuffix } from "#app/ui/ui-theme";
|
||||
import { isMobile } from "#app/touch-controls";
|
||||
import * as Utils from "#app/utils";
|
||||
import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
|
||||
import { initPokemonPrevolutions, initPokemonStarters } from "#app/data/balance/pokemon-evolutions";
|
||||
import { initBiomes } from "#app/data/balance/biomes";
|
||||
import { initEggMoves } from "#app/data/balance/egg-moves";
|
||||
import { initPokemonForms } from "#app/data/pokemon-forms";
|
||||
|
@ -103,6 +103,8 @@ export class LoadingScene extends SceneBase {
|
|||
this.loadImage("icon_tera", "ui");
|
||||
this.loadImage("type_tera", "ui");
|
||||
this.loadAtlas("type_bgs", "ui");
|
||||
this.loadImage("mystery_egg", "ui");
|
||||
this.loadImage("normal_memory", "ui");
|
||||
|
||||
this.loadImage("dawn_icon_fg", "ui");
|
||||
this.loadImage("dawn_icon_mg", "ui");
|
||||
|
@ -154,6 +156,7 @@ export class LoadingScene extends SceneBase {
|
|||
this.loadImage("scroll_bar_handle", "ui");
|
||||
this.loadImage("starter_container_bg", "ui");
|
||||
this.loadImage("starter_select_bg", "ui");
|
||||
this.loadImage("pokedex_summary_bg", "ui");
|
||||
this.loadImage("select_cursor", "ui");
|
||||
this.loadImage("select_cursor_highlight", "ui");
|
||||
this.loadImage("select_cursor_highlight_thick", "ui");
|
||||
|
@ -354,6 +357,7 @@ export class LoadingScene extends SceneBase {
|
|||
initVouchers();
|
||||
initStatsKeys();
|
||||
initPokemonPrevolutions();
|
||||
initPokemonStarters();
|
||||
initBiomes();
|
||||
initEggMoves();
|
||||
initPokemonForms();
|
||||
|
|
|
@ -193,6 +193,7 @@ export async function initI18n(): Promise<void> {
|
|||
"egg",
|
||||
"fightUiHandler",
|
||||
"filterBar",
|
||||
"filterText",
|
||||
"gameMode",
|
||||
"gameStatsUiHandler",
|
||||
"growth",
|
||||
|
@ -203,6 +204,7 @@ export async function initI18n(): Promise<void> {
|
|||
"move",
|
||||
"nature",
|
||||
"pokeball",
|
||||
"pokedexUiHandler",
|
||||
"pokemon",
|
||||
"pokemonEvolutions",
|
||||
"pokemonForm",
|
||||
|
|
|
@ -9,6 +9,7 @@ import { EaseType } from "#enums/ease-type";
|
|||
import { MoneyFormat } from "#enums/money-format";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { ShopCursorTarget } from "#enums/shop-cursor-target";
|
||||
import * as Utils from "../../utils";
|
||||
|
||||
const VOLUME_OPTIONS: SettingOption[] = new Array(11).fill(null).map((_, i) => i ? {
|
||||
value: (i * 10).toString(),
|
||||
|
@ -150,6 +151,7 @@ export const SettingKeys = {
|
|||
Show_Stats_on_Level_Up: "SHOW_LEVEL_UP_STATS",
|
||||
Shop_Cursor_Target: "SHOP_CURSOR_TARGET",
|
||||
Command_Cursor_Memory: "COMMAND_CURSOR_MEMORY",
|
||||
Dex_For_Devs: "DEX_FOR_DEVS",
|
||||
Candy_Upgrade_Notification: "CANDY_UPGRADE_NOTIFICATION",
|
||||
Candy_Upgrade_Display: "CANDY_UPGRADE_DISPLAY",
|
||||
Move_Info: "MOVE_INFO",
|
||||
|
@ -691,6 +693,16 @@ export const Setting: Array<Setting> = [
|
|||
}
|
||||
];
|
||||
|
||||
if (Utils.isLocal) {
|
||||
Setting.push({
|
||||
key: SettingKeys.Dex_For_Devs,
|
||||
label: i18next.t("settings:dexForDevs"),
|
||||
options: OFF_ON,
|
||||
default: 0,
|
||||
type: SettingType.GENERAL
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index of a Setting
|
||||
* @param key SettingKey
|
||||
|
@ -828,6 +840,9 @@ export function setSetting(setting: string, value: integer): boolean {
|
|||
case SettingKeys.Command_Cursor_Memory:
|
||||
globalScene.commandCursorMemory = Setting[index].options[value].value === "On";
|
||||
break;
|
||||
case SettingKeys.Dex_For_Devs:
|
||||
globalScene.dexForDevs = Setting[index].options[value].value === "On";
|
||||
break;
|
||||
case SettingKeys.EXP_Gains_Speed:
|
||||
globalScene.expGainsSpeed = value;
|
||||
break;
|
||||
|
|
|
@ -174,7 +174,7 @@ describe("Evolution", () => {
|
|||
for (let f = 1; f < 4; f++) {
|
||||
vi.spyOn(Utils, "randSeedInt").mockReturnValue(f); // setting the random generator to 1, 2 and 3 to force 4 family mausholds
|
||||
const fourForm = playerPokemon.getEvolution()!;
|
||||
expect(fourForm.evoFormKey).toBe(null); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is null
|
||||
expect(fourForm.evoFormKey).toBe("four"); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is "four"
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ export enum Tutorial {
|
|||
Access_Menu = "ACCESS_MENU",
|
||||
Menu = "MENU",
|
||||
Starter_Select = "STARTER_SELECT",
|
||||
Pokedex = "POKEDEX",
|
||||
Pokerus = "POKERUS",
|
||||
Stat_Change = "STAT_CHANGE",
|
||||
Select_Item = "SELECT_ITEM",
|
||||
|
|
|
@ -12,6 +12,8 @@ import { globalScene } from "#app/global-scene";
|
|||
import SettingsDisplayUiHandler from "./ui/settings/settings-display-ui-handler";
|
||||
import SettingsAudioUiHandler from "./ui/settings/settings-audio-ui-handler";
|
||||
import RunInfoUiHandler from "./ui/run-info-ui-handler";
|
||||
import PokedexUiHandler from "./ui/pokedex-ui-handler";
|
||||
import PokedexPageUiHandler from "./ui/pokedex-page-ui-handler";
|
||||
|
||||
type ActionKeys = Record<Button, () => void>;
|
||||
|
||||
|
@ -140,7 +142,7 @@ export class UiInputs {
|
|||
}
|
||||
|
||||
buttonGoToFilter(button: Button): void {
|
||||
const whitelist = [ StarterSelectUiHandler ];
|
||||
const whitelist = [ StarterSelectUiHandler, PokedexUiHandler, PokedexPageUiHandler ];
|
||||
const uiHandler = globalScene.ui?.getHandler();
|
||||
if (whitelist.some(handler => uiHandler instanceof handler)) {
|
||||
globalScene.ui.processInput(button);
|
||||
|
@ -178,6 +180,7 @@ export class UiInputs {
|
|||
globalScene.ui.setOverlayMode(Mode.MENU);
|
||||
break;
|
||||
case Mode.STARTER_SELECT:
|
||||
case Mode.POKEDEX_PAGE:
|
||||
this.buttonTouch();
|
||||
break;
|
||||
case Mode.MENU:
|
||||
|
@ -190,7 +193,7 @@ export class UiInputs {
|
|||
}
|
||||
|
||||
buttonCycleOption(button: Button): void {
|
||||
const whitelist = [ StarterSelectUiHandler, SettingsUiHandler, RunInfoUiHandler, SettingsDisplayUiHandler, SettingsAudioUiHandler, SettingsGamepadUiHandler, SettingsKeyboardUiHandler ];
|
||||
const whitelist = [ StarterSelectUiHandler, PokedexUiHandler, PokedexPageUiHandler, SettingsUiHandler, RunInfoUiHandler, SettingsDisplayUiHandler, SettingsAudioUiHandler, SettingsGamepadUiHandler, SettingsKeyboardUiHandler ];
|
||||
const uiHandler = globalScene.ui?.getHandler();
|
||||
if (whitelist.some(handler => uiHandler instanceof handler)) {
|
||||
globalScene.ui.processInput(button);
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { globalScene } from "#app/global-scene";
|
||||
import { TextStyle, addTextObject, getTextStyleOptions } from "./text";
|
||||
import { TextStyle, addBBCodeTextObject, getTextColor, getTextStyleOptions } from "./text";
|
||||
import { Mode } from "./ui";
|
||||
import UiHandler from "./ui-handler";
|
||||
import { addWindow } from "./ui-theme";
|
||||
import * as Utils from "../utils";
|
||||
import { argbFromRgba } from "@material/material-color-utilities";
|
||||
import { Button } from "#enums/buttons";
|
||||
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
|
||||
|
||||
export interface OptionSelectConfig {
|
||||
xOffset?: number;
|
||||
|
@ -21,8 +22,10 @@ export interface OptionSelectItem {
|
|||
label: string;
|
||||
handler: () => boolean;
|
||||
onHover?: () => void;
|
||||
skip?: boolean;
|
||||
keepOpen?: boolean;
|
||||
overrideSound?: boolean;
|
||||
style?: TextStyle;
|
||||
item?: string;
|
||||
itemArgs?: any[];
|
||||
}
|
||||
|
@ -33,7 +36,7 @@ const scrollDownLabel = "↓";
|
|||
export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
||||
protected optionSelectContainer: Phaser.GameObjects.Container;
|
||||
protected optionSelectBg: Phaser.GameObjects.NineSlice;
|
||||
protected optionSelectText: Phaser.GameObjects.Text;
|
||||
protected optionSelectText: BBCodeText;
|
||||
protected optionSelectIcons: Phaser.GameObjects.Sprite[];
|
||||
|
||||
protected config: OptionSelectConfig | null;
|
||||
|
@ -41,11 +44,17 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
protected blockInput: boolean;
|
||||
|
||||
protected scrollCursor: integer = 0;
|
||||
protected fullCursor: integer = 0;
|
||||
|
||||
protected scale: number = 0.1666666667;
|
||||
|
||||
private cursorObj: Phaser.GameObjects.Image | null;
|
||||
|
||||
protected unskippedIndices: number[] = [];
|
||||
|
||||
protected defaultTextStyle: TextStyle = TextStyle.WINDOW;
|
||||
|
||||
|
||||
constructor(mode: Mode | null) {
|
||||
super(mode);
|
||||
}
|
||||
|
@ -79,44 +88,54 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
protected setupOptions() {
|
||||
const configOptions = this.config?.options ?? [];
|
||||
|
||||
let options: OptionSelectItem[];
|
||||
const options: OptionSelectItem[] = configOptions;
|
||||
|
||||
// for performance reasons, this limits how many options we can see at once. Without this, it would try to make text options for every single options
|
||||
// which makes the performance take a hit. If there's not enough options to do this (set to 10 at the moment) and the ui mode !== Mode.AUTO_COMPLETE,
|
||||
// this is ignored and the original code is untouched, with the options array being all the options from the config
|
||||
if (configOptions.length >= 10 && globalScene.ui.getMode() === Mode.AUTO_COMPLETE) {
|
||||
const optionsScrollTotal = configOptions.length;
|
||||
const optionStartIndex = this.scrollCursor;
|
||||
const optionEndIndex = Math.min(optionsScrollTotal, optionStartIndex + (!optionStartIndex || this.scrollCursor + (this.config?.maxOptions! - 1) >= optionsScrollTotal ? this.config?.maxOptions! - 1 : this.config?.maxOptions! - 2));
|
||||
options = configOptions.slice(optionStartIndex, optionEndIndex + 2);
|
||||
} else {
|
||||
options = configOptions;
|
||||
}
|
||||
this.unskippedIndices = this.getUnskippedIndices(configOptions);
|
||||
|
||||
if (this.optionSelectText) {
|
||||
if (this.optionSelectText instanceof BBCodeText) {
|
||||
try {
|
||||
this.optionSelectText.destroy();
|
||||
} catch (error) {
|
||||
console.error("Error while destroying optionSelectText:", error);
|
||||
}
|
||||
} else {
|
||||
console.warn("optionSelectText is not an instance of BBCodeText.");
|
||||
}
|
||||
}
|
||||
|
||||
if (this.optionSelectIcons?.length) {
|
||||
this.optionSelectIcons.map(i => i.destroy());
|
||||
this.optionSelectIcons.splice(0, this.optionSelectIcons.length);
|
||||
}
|
||||
|
||||
this.optionSelectText = addTextObject(0, 0, options.map(o => o.item ? ` ${o.label}` : o.label).join("\n"), TextStyle.WINDOW, { maxLines: options.length });
|
||||
this.optionSelectText.setLineSpacing(this.scale * 72);
|
||||
const optionsWithScroll = (this.config?.options && this.config?.options.length > (this.config?.maxOptions!)) ? this.getOptionsWithScroll() : options;
|
||||
|
||||
// Setting the initial text to establish the width of the select object. We consider all options, even ones that are not displayed,
|
||||
// Except in the case of autocomplete, where we don't want to set up a text element with potentially hundreds of lines.
|
||||
const optionsForWidth = globalScene.ui.getMode() === Mode.AUTO_COMPLETE ? optionsWithScroll : options;
|
||||
this.optionSelectText = addBBCodeTextObject(
|
||||
0, 0, optionsForWidth.map(o => o.item
|
||||
? `[shadow=${getTextColor(o.style ?? this.defaultTextStyle, true, globalScene.uiTheme)}][color=${getTextColor(o.style ?? TextStyle.WINDOW, false, globalScene.uiTheme)}] ${o.label}[/color][/shadow]`
|
||||
: `[shadow=${getTextColor(o.style ?? this.defaultTextStyle, true, globalScene.uiTheme)}][color=${getTextColor(o.style ?? TextStyle.WINDOW, false, globalScene.uiTheme)}]${o.label}[/color][/shadow]`
|
||||
).join("\n"),
|
||||
TextStyle.WINDOW, { maxLines: options.length, lineSpacing: 12 }
|
||||
);
|
||||
this.optionSelectText.setOrigin(0, 0);
|
||||
this.optionSelectText.setName("text-option-select");
|
||||
this.optionSelectText.setLineSpacing(12);
|
||||
this.optionSelectContainer.add(this.optionSelectText);
|
||||
this.optionSelectContainer.setPosition((globalScene.game.canvas.width / 6) - 1 - (this.config?.xOffset || 0), -48 + (this.config?.yOffset || 0));
|
||||
|
||||
this.optionSelectBg.width = Math.max(this.optionSelectText.displayWidth + 24, this.getWindowWidth());
|
||||
|
||||
if (this.config?.options && this.config?.options.length > (this.config?.maxOptions!)) { // TODO: is this bang correct?
|
||||
this.optionSelectText.setText(this.getOptionsWithScroll().map(o => o.label).join("\n"));
|
||||
}
|
||||
|
||||
this.optionSelectBg.height = this.getWindowHeight();
|
||||
this.optionSelectText.setPosition(this.optionSelectBg.x - this.optionSelectBg.width + 12 + 24 * this.scale, this.optionSelectBg.y - this.optionSelectBg.height + 2 + 42 * this.scale);
|
||||
|
||||
this.optionSelectText.setPositionRelative(this.optionSelectBg, 12 + 24 * this.scale, 2 + 42 * this.scale);
|
||||
// Now that the container and background widths are established, we can set up the proper text restricted to visible options
|
||||
this.optionSelectText.setText(optionsWithScroll.map(o => o.item
|
||||
? `[shadow=${getTextColor(o.style ?? this.defaultTextStyle, true, globalScene.uiTheme)}][color=${getTextColor(o.style ?? TextStyle.WINDOW, false, globalScene.uiTheme)}] ${o.label}[/color][/shadow]`
|
||||
: `[shadow=${getTextColor(o.style ?? this.defaultTextStyle, true, globalScene.uiTheme)}][color=${getTextColor(o.style ?? TextStyle.WINDOW, false, globalScene.uiTheme)}]${o.label}[/color][/shadow]`
|
||||
).join("\n")
|
||||
|
||||
);
|
||||
|
||||
options.forEach((option: OptionSelectItem, i: integer) => {
|
||||
if (option.item) {
|
||||
|
@ -160,6 +179,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
|
||||
this.optionSelectContainer.setVisible(true);
|
||||
this.scrollCursor = 0;
|
||||
this.fullCursor = 0;
|
||||
this.setCursor(0);
|
||||
|
||||
if (this.config.delay) {
|
||||
|
@ -169,6 +189,11 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
globalScene.time.delayedCall(Utils.fixedInt(this.config.delay), () => this.unblockInput());
|
||||
}
|
||||
|
||||
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.unskippedIndices[this.fullCursor]]?.onHover?.();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -177,8 +202,6 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
|
||||
let success = false;
|
||||
|
||||
const options = this.getOptionsWithScroll();
|
||||
|
||||
let playSound = true;
|
||||
|
||||
if (button === Button.ACTION || button === Button.CANCEL) {
|
||||
|
@ -190,15 +213,14 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
success = true;
|
||||
if (button === Button.CANCEL) {
|
||||
if (this.config?.maxOptions && this.config.options.length > this.config.maxOptions) {
|
||||
this.scrollCursor = (this.config.options.length - this.config.maxOptions) + 1;
|
||||
this.cursor = options.length - 1;
|
||||
this.setCursor(this.unskippedIndices.length - 1);
|
||||
} else if (!this.config?.noCancel) {
|
||||
this.setCursor(options.length - 1);
|
||||
this.setCursor(this.unskippedIndices.length - 1);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const option = this.config?.options[this.cursor + (this.scrollCursor - (this.scrollCursor ? 1 : 0))];
|
||||
const option = this.config?.options[this.unskippedIndices[this.fullCursor]];
|
||||
if (option?.handler()) {
|
||||
if (!option.keepOpen) {
|
||||
this.clear();
|
||||
|
@ -211,7 +233,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
// this is here to differentiate between a Button.SUBMIT vs Button.ACTION within the autocomplete handler
|
||||
// this is here because Button.ACTION is picked up as z on the keyboard, meaning if you're typing and hit z, it'll select the option you've chosen
|
||||
success = true;
|
||||
const option = this.config?.options[this.cursor + (this.scrollCursor - (this.scrollCursor ? 1 : 0))];
|
||||
const option = this.config?.options[this.unskippedIndices[this.fullCursor]];
|
||||
if (option?.handler()) {
|
||||
if (!option.keepOpen) {
|
||||
this.clear();
|
||||
|
@ -223,15 +245,15 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
} else {
|
||||
switch (button) {
|
||||
case Button.UP:
|
||||
if (this.cursor) {
|
||||
success = this.setCursor(this.cursor - 1);
|
||||
} else if (this.cursor === 0) {
|
||||
success = this.setCursor(options.length - 1);
|
||||
if (this.fullCursor === 0) {
|
||||
success = this.setCursor(this.unskippedIndices.length - 1);
|
||||
} else if (this.fullCursor) {
|
||||
success = this.setCursor(this.fullCursor - 1);
|
||||
}
|
||||
break;
|
||||
case Button.DOWN:
|
||||
if (this.cursor < options.length - 1) {
|
||||
success = this.setCursor(this.cursor + 1);
|
||||
if (this.fullCursor < this.unskippedIndices.length - 1) {
|
||||
success = this.setCursor(this.fullCursor + 1);
|
||||
} else {
|
||||
success = this.setCursor(0);
|
||||
}
|
||||
|
@ -239,7 +261,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
}
|
||||
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?.();
|
||||
this.config?.options[this.unskippedIndices[this.fullCursor]]?.onHover?.();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -273,7 +295,9 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
|
||||
const optionsScrollTotal = options.length;
|
||||
const optionStartIndex = this.scrollCursor;
|
||||
const optionEndIndex = Math.min(optionsScrollTotal, optionStartIndex + (!optionStartIndex || this.scrollCursor + (this.config.maxOptions - 1) >= optionsScrollTotal ? this.config.maxOptions - 1 : this.config.maxOptions - 2));
|
||||
const optionEndIndex = Math.min(optionsScrollTotal, optionStartIndex +
|
||||
(!optionStartIndex || this.scrollCursor + (this.config.maxOptions - 1) >= optionsScrollTotal ? this.config.maxOptions - 1 : this.config.maxOptions - 2)
|
||||
);
|
||||
|
||||
if (this.config?.maxOptions && options.length > this.config.maxOptions) {
|
||||
options.splice(optionEndIndex, optionsScrollTotal);
|
||||
|
@ -281,13 +305,15 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
if (optionStartIndex) {
|
||||
options.unshift({
|
||||
label: scrollUpLabel,
|
||||
handler: () => true
|
||||
handler: () => true,
|
||||
style: this.defaultTextStyle
|
||||
});
|
||||
}
|
||||
if (optionEndIndex < optionsScrollTotal) {
|
||||
options.push({
|
||||
label: scrollDownLabel,
|
||||
handler: () => true
|
||||
handler: () => true,
|
||||
style: this.defaultTextStyle
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -295,41 +321,63 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
return options;
|
||||
}
|
||||
|
||||
setCursor(cursor: integer): boolean {
|
||||
const changed = this.cursor !== cursor;
|
||||
getUnskippedIndices(options: OptionSelectItem[]) {
|
||||
const unskippedIndices = options
|
||||
.map((option, index) => (option.skip ? null : index)) // Map to index or null if skipped
|
||||
.filter(index => index !== null) as number[];
|
||||
return unskippedIndices;
|
||||
}
|
||||
|
||||
setCursor(fullCursor: integer): boolean {
|
||||
const changed = this.fullCursor !== fullCursor;
|
||||
|
||||
let isScroll = false;
|
||||
const options = this.getOptionsWithScroll();
|
||||
if (changed && this.config?.maxOptions && this.config.options.length > this.config.maxOptions) {
|
||||
if (Math.abs(cursor - this.cursor) === options.length - 1) {
|
||||
// Wrap around the list
|
||||
const optionsScrollTotal = this.config.options.length;
|
||||
this.scrollCursor = cursor ? optionsScrollTotal - (this.config.maxOptions - 1) : 0;
|
||||
this.setupOptions();
|
||||
|
||||
// If the fullCursor is the last possible value, we go to the bottom
|
||||
if (fullCursor === this.unskippedIndices.length - 1) {
|
||||
this.fullCursor = fullCursor;
|
||||
this.cursor = this.config.maxOptions - (this.config.options.length - this.unskippedIndices[fullCursor]);
|
||||
this.scrollCursor = this.config.options.length - this.config.maxOptions + 1;
|
||||
// If the fullCursor is the first possible value, we go to the top
|
||||
} else if (fullCursor === 0) {
|
||||
this.fullCursor = fullCursor;
|
||||
this.cursor = this.unskippedIndices[fullCursor];
|
||||
this.scrollCursor = 0;
|
||||
} else {
|
||||
// Move the cursor up or down by 1
|
||||
const isDown = cursor && cursor > this.cursor;
|
||||
const isDown = fullCursor && fullCursor > this.fullCursor;
|
||||
|
||||
if (isDown) {
|
||||
if (options[cursor].label === scrollDownLabel) {
|
||||
isScroll = true;
|
||||
this.scrollCursor++;
|
||||
// If there are skipped options under the next selection, we show them
|
||||
const jumpFromCurrent = this.unskippedIndices[fullCursor] - this.unskippedIndices[this.fullCursor];
|
||||
const skipsFromNext = this.unskippedIndices[fullCursor + 1] - this.unskippedIndices[fullCursor] - 1;
|
||||
|
||||
if (this.cursor + jumpFromCurrent + skipsFromNext >= this.config.maxOptions - 1) {
|
||||
this.fullCursor = fullCursor;
|
||||
this.cursor = this.config.maxOptions - 2 - skipsFromNext;
|
||||
this.scrollCursor = this.unskippedIndices[this.fullCursor] - this.cursor + 1;
|
||||
} else {
|
||||
this.fullCursor = fullCursor;
|
||||
this.cursor = this.unskippedIndices[fullCursor] - this.scrollCursor + (this.scrollCursor ? 1 : 0);
|
||||
}
|
||||
} else {
|
||||
if (!cursor && this.scrollCursor) {
|
||||
isScroll = true;
|
||||
this.scrollCursor--;
|
||||
}
|
||||
}
|
||||
if (isScroll && this.scrollCursor === 1) {
|
||||
this.scrollCursor += isDown ? 1 : -1;
|
||||
const jumpFromPrevious = this.unskippedIndices[fullCursor] - this.unskippedIndices[fullCursor - 1];
|
||||
|
||||
if (this.cursor - jumpFromPrevious < 1) {
|
||||
this.fullCursor = fullCursor;
|
||||
this.cursor = 1;
|
||||
this.scrollCursor = this.unskippedIndices[this.fullCursor] - this.cursor + 1;
|
||||
} else {
|
||||
this.fullCursor = fullCursor;
|
||||
this.cursor = this.unskippedIndices[fullCursor] - this.scrollCursor + (this.scrollCursor ? 1 : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isScroll) {
|
||||
} else {
|
||||
this.fullCursor = fullCursor;
|
||||
this.cursor = this.unskippedIndices[fullCursor];
|
||||
}
|
||||
|
||||
this.setupOptions();
|
||||
} else {
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
if (!this.cursorObj) {
|
||||
this.cursorObj = globalScene.add.image(0, 0, "cursor");
|
||||
|
@ -346,6 +394,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
super.clear();
|
||||
this.config = null;
|
||||
this.optionSelectContainer.setVisible(false);
|
||||
this.fullCursor = 0;
|
||||
this.scrollCursor = 0;
|
||||
this.eraseCursor();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
import type { InfoToggle } from "../battle-scene";
|
||||
import { TextStyle, addTextObject } from "./text";
|
||||
import { addWindow } from "./ui-theme";
|
||||
import * as Utils from "../utils";
|
||||
import i18next from "i18next";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
export interface BaseStatsOverlaySettings {
|
||||
scale?:number; // scale the box? A scale of 0.5 is recommended
|
||||
x?: number;
|
||||
y?: number;
|
||||
/** Default is always half the screen, regardless of scale */
|
||||
width?: number;
|
||||
}
|
||||
|
||||
const HEIGHT = 120;
|
||||
const BORDER = 8;
|
||||
const GLOBAL_SCALE = 6;
|
||||
const shortStats = [ "HP", "ATK", "DEF", "SPATK", "SPDEF", "SPD" ];
|
||||
|
||||
export default class BaseStatsOverlay extends Phaser.GameObjects.Container implements InfoToggle {
|
||||
|
||||
public active: boolean = false;
|
||||
|
||||
private statsLabels: Phaser.GameObjects.Text[] = [];
|
||||
private statsRectangles: Phaser.GameObjects.Rectangle[] = [];
|
||||
private statsShadows: Phaser.GameObjects.Rectangle[] = [];
|
||||
private statsTotalLabel: Phaser.GameObjects.Text;
|
||||
|
||||
private statsBg: Phaser.GameObjects.NineSlice;
|
||||
|
||||
private options: BaseStatsOverlaySettings;
|
||||
|
||||
public scale: number;
|
||||
public width: number;
|
||||
|
||||
constructor(options?: BaseStatsOverlaySettings) {
|
||||
super(globalScene, options?.x, options?.y);
|
||||
this.scale = options?.scale || 1; // set up the scale
|
||||
this.setScale(this.scale);
|
||||
this.options = options || {};
|
||||
|
||||
// prepare the description box
|
||||
this.width = (options?.width || BaseStatsOverlay.getWidth(this.scale)) / this.scale; // divide by scale as we always want this to be half a window wide
|
||||
this.statsBg = addWindow(0, 0, this.width, HEIGHT);
|
||||
this.statsBg.setOrigin(0, 0);
|
||||
this.add(this.statsBg);
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const shadow = globalScene.add.rectangle(this.width - BORDER + 1, BORDER + 3 + i * 15, 100, 5, 0x006860);
|
||||
shadow.setOrigin(1, 0);
|
||||
this.statsShadows.push(shadow);
|
||||
this.add(shadow);
|
||||
|
||||
const rectangle = globalScene.add.rectangle(this.width - BORDER, BORDER + 2 + i * 15, 100, 5, 0x66aa99);
|
||||
rectangle.setOrigin(1, 0);
|
||||
this.statsRectangles.push(rectangle);
|
||||
this.add(rectangle);
|
||||
|
||||
const label = addTextObject(BORDER, BORDER - 2 + i * 15, "A", TextStyle.BATTLE_INFO);
|
||||
this.statsLabels.push(label);
|
||||
this.add(label);
|
||||
}
|
||||
|
||||
this.statsTotalLabel = addTextObject(BORDER, BORDER + 6 * 15, "A", TextStyle.MONEY_WINDOW);
|
||||
this.add(this.statsTotalLabel);
|
||||
|
||||
// hide this component for now
|
||||
this.setVisible(false);
|
||||
}
|
||||
|
||||
// show this component with infos for the specific move
|
||||
show(values: number[], total: number):boolean {
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
this.statsLabels[i].setText(i18next.t(`pokemonInfo:Stat.${shortStats[i]}shortened`) + ": " + `${values[i]}`);
|
||||
// This accounts for base stats up to 200, might not be enough.
|
||||
// TODO: change color based on value.
|
||||
this.statsShadows[i].setSize(values[i] / 2, 5);
|
||||
this.statsRectangles[i].setSize(values[i] / 2, 5);
|
||||
}
|
||||
|
||||
this.statsTotalLabel.setText(i18next.t("pokedexUiHandler:baseTotal") + ": " + `${total}`);
|
||||
|
||||
|
||||
this.setVisible(true);
|
||||
this.active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.setVisible(false);
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
toggleInfo(visible: boolean): void {
|
||||
if (visible) {
|
||||
this.setVisible(true);
|
||||
}
|
||||
globalScene.tweens.add({
|
||||
targets: this.statsLabels,
|
||||
duration: Utils.fixedInt(125),
|
||||
ease: "Sine.easeInOut",
|
||||
alpha: visible ? 1 : 0
|
||||
});
|
||||
if (!visible) {
|
||||
this.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
// width of this element
|
||||
static getWidth(scale:number):number {
|
||||
return globalScene.game.canvas.width / GLOBAL_SCALE / 2;
|
||||
}
|
||||
|
||||
// height of this element
|
||||
static getHeight(scale:number, onSide?: boolean):number {
|
||||
return HEIGHT * scale;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { globalScene } from "#app/global-scene";
|
||||
import { addTextObject, TextStyle } from "./text";
|
||||
import { addWindow, WindowVariant } from "./ui-theme";
|
||||
import { ScrollBar } from "#app/ui/scroll-bar";
|
||||
import i18next from "i18next";
|
||||
|
||||
export enum DropDownState {
|
||||
|
@ -293,21 +294,37 @@ export class DropDown extends Phaser.GameObjects.Container {
|
|||
private onChange: () => void;
|
||||
private lastDir: SortDirection = SortDirection.ASC;
|
||||
private defaultSettings: any[];
|
||||
private dropDownScrollBar: ScrollBar;
|
||||
private totalOptions: number = 0;
|
||||
private maxOptions: number = 0;
|
||||
private shownOptions: number = 0;
|
||||
private tooManyOptions: Boolean = false;
|
||||
private firstShown: number = 0;
|
||||
private optionHeight: number = 0;
|
||||
private optionSpacing: number = 0;
|
||||
private optionPaddingX: number = 4;
|
||||
private optionPaddingY: number = 6;
|
||||
private optionWidth: number = 100;
|
||||
private cursorOffset: number = 0;
|
||||
|
||||
constructor(x: number, y: number, options: DropDownOption[], onChange: () => void, type: DropDownType = DropDownType.MULTI, optionSpacing: number = 2) {
|
||||
const windowPadding = 5;
|
||||
const optionHeight = 7;
|
||||
const optionPaddingX = 4;
|
||||
const optionPaddingY = 6;
|
||||
const cursorOffset = 7;
|
||||
const optionWidth = 100;
|
||||
|
||||
super(globalScene, x - cursorOffset - windowPadding, y);
|
||||
|
||||
this.optionWidth = 100;
|
||||
this.optionHeight = 7;
|
||||
this.optionSpacing = optionSpacing;
|
||||
this.optionPaddingX = 4;
|
||||
this.optionPaddingY = 6;
|
||||
this.cursorOffset = cursorOffset;
|
||||
|
||||
this.options = options;
|
||||
this.dropDownType = type;
|
||||
this.onChange = onChange;
|
||||
|
||||
this.cursorObj = globalScene.add.image(optionPaddingX + 3, 0, "cursor");
|
||||
this.cursorObj = globalScene.add.image(this.optionPaddingX + 3, 0, "cursor");
|
||||
this.cursorObj.setScale(0.5);
|
||||
this.cursorObj.setOrigin(0, 0.5);
|
||||
this.cursorObj.setVisible(false);
|
||||
|
@ -317,31 +334,51 @@ export class DropDown extends Phaser.GameObjects.Container {
|
|||
this.options.unshift(new DropDownOption("ALL", new DropDownLabel(i18next.t("filterBar:all"), undefined, this.checkForAllOn() ? DropDownState.ON : DropDownState.OFF)));
|
||||
}
|
||||
|
||||
this.maxOptions = 19;
|
||||
this.totalOptions = this.options.length;
|
||||
this.tooManyOptions = this.totalOptions > this.maxOptions;
|
||||
this.shownOptions = this.tooManyOptions ? this.maxOptions : this.totalOptions;
|
||||
|
||||
this.defaultSettings = this.getSettings();
|
||||
|
||||
// Place ui elements in the correct spot
|
||||
options.forEach((option, index) => {
|
||||
|
||||
const toggleVisibility = type !== DropDownType.SINGLE || option.state === DropDownState.ON;
|
||||
option.setupToggleIcon(type, toggleVisibility);
|
||||
|
||||
option.width = optionWidth;
|
||||
option.y = index * optionHeight + index * optionSpacing + optionPaddingY;
|
||||
option.width = this.optionWidth;
|
||||
option.y = index * this.optionHeight + index * optionSpacing + this.optionPaddingY;
|
||||
|
||||
const baseX = cursorOffset + optionPaddingX + 3;
|
||||
const baseY = optionHeight / 2;
|
||||
const baseX = cursorOffset + this.optionPaddingX + 3;
|
||||
const baseY = this.optionHeight / 2;
|
||||
option.setLabelPosition(baseX + 8, baseY);
|
||||
if (type === DropDownType.SINGLE) {
|
||||
option.setTogglePosition(baseX + 3, baseY + 1);
|
||||
} else {
|
||||
option.setTogglePosition(baseX, baseY);
|
||||
}
|
||||
|
||||
if (index >= this.shownOptions) {
|
||||
option.visible = false;
|
||||
}
|
||||
|
||||
this.firstShown = 0;
|
||||
});
|
||||
|
||||
this.window = addWindow(0, 0, optionWidth, options[options.length - 1].y + optionHeight + optionPaddingY, false, false, undefined, undefined, WindowVariant.XTHIN);
|
||||
this.window = addWindow(0, 0, this.optionWidth, options[this.shownOptions - 1].y + this.optionHeight + this.optionPaddingY, false, false, undefined, undefined, WindowVariant.XTHIN);
|
||||
this.add(this.window);
|
||||
this.add(options);
|
||||
this.add(this.cursorObj);
|
||||
this.setVisible(false);
|
||||
|
||||
if (this.tooManyOptions) {
|
||||
// Setting the last parameter to 1 turns out to be optimal in all cases.
|
||||
this.dropDownScrollBar = new ScrollBar(this.window.width - 3, 5, 5, this.window.height - 10, 1);
|
||||
this.add(this.dropDownScrollBar);
|
||||
this.dropDownScrollBar.setTotalRows(this.totalOptions);
|
||||
this.dropDownScrollBar.setScrollCursor(0);
|
||||
}
|
||||
}
|
||||
|
||||
getWidth(): number {
|
||||
|
@ -371,6 +408,11 @@ export class DropDown extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
setCursor(cursor: integer): boolean {
|
||||
|
||||
if (this.tooManyOptions) {
|
||||
this.setLabels(cursor);
|
||||
}
|
||||
|
||||
this.cursor = cursor;
|
||||
if (cursor < 0) {
|
||||
cursor = 0;
|
||||
|
@ -393,6 +435,41 @@ export class DropDown extends Phaser.GameObjects.Container {
|
|||
return true;
|
||||
}
|
||||
|
||||
setLabels(cursor: integer) {
|
||||
|
||||
if ((cursor === 0) && (this.lastCursor === this.totalOptions - 1)) {
|
||||
this.firstShown = 0;
|
||||
} else if ((cursor === this.totalOptions - 1) && (this.lastCursor === 0)) {
|
||||
this.firstShown = this.totalOptions - this.shownOptions;
|
||||
} else if ((cursor - this.firstShown >= this.shownOptions) && (cursor > this.lastCursor)) {
|
||||
this.firstShown += 1;
|
||||
} else if ((cursor < this.firstShown) && (cursor < this.lastCursor)) {
|
||||
this.firstShown -= 1;
|
||||
}
|
||||
|
||||
this.options.forEach((option, index) => {
|
||||
|
||||
option.y = (index - this.firstShown) * (this.optionHeight + this.optionSpacing) + this.optionPaddingY;
|
||||
|
||||
const baseX = this.cursorOffset + this.optionPaddingX + 3;
|
||||
const baseY = this.optionHeight / 2;
|
||||
option.setLabelPosition(baseX + 8, baseY);
|
||||
if (this.dropDownType === DropDownType.SINGLE) {
|
||||
option.setTogglePosition(baseX + 3, baseY + 1);
|
||||
} else {
|
||||
option.setTogglePosition(baseX, baseY);
|
||||
}
|
||||
|
||||
if ((index < this.firstShown) || ( index >= this.firstShown + this.shownOptions)) {
|
||||
option.visible = false;
|
||||
} else {
|
||||
option.visible = true;
|
||||
}
|
||||
});
|
||||
|
||||
this.dropDownScrollBar.setScrollCursor(cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the option at the provided index to its next state and update visuals
|
||||
* Update accordingly the other options if needed:
|
||||
|
@ -597,7 +674,12 @@ export class DropDown extends Phaser.GameObjects.Container {
|
|||
x = this.options[i].getCurrentLabelX() ?? 0;
|
||||
}
|
||||
}
|
||||
this.window.width = maxWidth + x - this.window.x + 6;
|
||||
this.window.width = maxWidth + x - this.window.x + 9;
|
||||
|
||||
if (this.tooManyOptions) {
|
||||
this.window.width += 6;
|
||||
this.dropDownScrollBar.x = this.window.width - 9;
|
||||
}
|
||||
|
||||
if (this.x + this.window.width > this.parentContainer.width) {
|
||||
this.x = this.parentContainer.width - this.window.width;
|
||||
|
|
|
@ -9,6 +9,7 @@ import { globalScene } from "#app/global-scene";
|
|||
export enum DropDownColumn {
|
||||
GEN,
|
||||
TYPES,
|
||||
BIOME,
|
||||
CAUGHT,
|
||||
UNLOCKS,
|
||||
MISC,
|
||||
|
@ -25,13 +26,20 @@ export class FilterBar extends Phaser.GameObjects.Container {
|
|||
public openDropDown: boolean = false;
|
||||
private lastCursor: number = -1;
|
||||
private uiTheme: UiTheme;
|
||||
private leftPaddingX: number;
|
||||
private rightPaddingX: number;
|
||||
private cursorOffset: number;
|
||||
|
||||
constructor(x: number, y: number, width: number, height: number) {
|
||||
constructor(x: number, y: number, width: number, height: number, leftPaddingX: number = 6, rightPaddingX: number = 6, cursorOffset: number = 8) {
|
||||
super(globalScene, x, y);
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
this.leftPaddingX = leftPaddingX;
|
||||
this.rightPaddingX = rightPaddingX;
|
||||
this.cursorOffset = cursorOffset;
|
||||
|
||||
this.window = addWindow(0, 0, width, height, false, false, undefined, undefined, WindowVariant.THIN);
|
||||
this.add(this.window);
|
||||
|
||||
|
@ -40,8 +48,6 @@ export class FilterBar extends Phaser.GameObjects.Container {
|
|||
this.cursorObj.setVisible(false);
|
||||
this.cursorObj.setOrigin(0, 0);
|
||||
this.add(this.cursorObj);
|
||||
|
||||
this.uiTheme = globalScene.uiTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,9 +92,9 @@ export class FilterBar extends Phaser.GameObjects.Container {
|
|||
updateFilterLabels(): void {
|
||||
for (let i = 0; i < this.numFilters; i++) {
|
||||
if (this.dropDowns[i].hasDefaultValues()) {
|
||||
this.labels[i].setColor(getTextColor(TextStyle.TOOLTIP_CONTENT, false, this.uiTheme));
|
||||
this.labels[i].setColor(getTextColor(TextStyle.TOOLTIP_CONTENT, false, globalScene.uiTheme));
|
||||
} else {
|
||||
this.labels[i].setColor(getTextColor(TextStyle.STATS_LABEL, false, this.uiTheme));
|
||||
this.labels[i].setColor(getTextColor(TextStyle.STATS_LABEL, false, globalScene.uiTheme));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,23 +103,21 @@ export class FilterBar extends Phaser.GameObjects.Container {
|
|||
* Position the filter dropdowns evenly across the width of the container
|
||||
*/
|
||||
private calcFilterPositions(): void {
|
||||
const paddingX = 6;
|
||||
const cursorOffset = 8;
|
||||
|
||||
let totalWidth = paddingX * 2 + cursorOffset;
|
||||
let totalWidth = this.leftPaddingX + this.rightPaddingX + this.cursorOffset;
|
||||
this.labels.forEach(label => {
|
||||
totalWidth += label.displayWidth + cursorOffset;
|
||||
totalWidth += label.displayWidth + this.cursorOffset;
|
||||
});
|
||||
const spacing = (this.width - totalWidth) / (this.labels.length - 1);
|
||||
for (let i = 0; i < this.labels.length; i++) {
|
||||
if (i === 0) {
|
||||
this.labels[i].x = paddingX + cursorOffset;
|
||||
this.labels[i].x = this.leftPaddingX + this.cursorOffset;
|
||||
} else {
|
||||
const lastRight = this.labels[i - 1].x + this.labels[i - 1].displayWidth;
|
||||
this.labels[i].x = lastRight + spacing + cursorOffset;
|
||||
this.labels[i].x = lastRight + spacing + this.cursorOffset;
|
||||
}
|
||||
|
||||
this.dropDowns[i].x = this.labels[i].x - cursorOffset - paddingX;
|
||||
this.dropDowns[i].x = this.labels[i].x - this.cursorOffset - this.leftPaddingX;
|
||||
this.dropDowns[i].y = this.height;
|
||||
}
|
||||
}
|
||||
|
@ -140,8 +144,7 @@ export class FilterBar extends Phaser.GameObjects.Container {
|
|||
}
|
||||
}
|
||||
|
||||
const cursorOffset = 8;
|
||||
this.cursorObj.setPosition(this.labels[cursor].x - cursorOffset + 2, 6);
|
||||
this.cursorObj.setPosition(this.labels[cursor].x - this.cursorOffset + 2, 6);
|
||||
this.lastCursor = cursor;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
import type { StarterContainer } from "./starter-container";
|
||||
import { addTextObject, getTextColor, TextStyle } from "./text";
|
||||
import type { UiTheme } from "#enums/ui-theme";
|
||||
import { addWindow, WindowVariant } from "./ui-theme";
|
||||
import i18next from "i18next";
|
||||
import type AwaitableUiHandler from "./awaitable-ui-handler";
|
||||
import type UI from "./ui";
|
||||
import { Mode } from "./ui";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
export enum FilterTextRow{
|
||||
NAME,
|
||||
MOVE_1,
|
||||
MOVE_2,
|
||||
ABILITY_1,
|
||||
ABILITY_2,
|
||||
}
|
||||
|
||||
export class FilterText extends Phaser.GameObjects.Container {
|
||||
private window: Phaser.GameObjects.NineSlice;
|
||||
private labels: Phaser.GameObjects.Text[] = [];
|
||||
private selections: Phaser.GameObjects.Text[] = [];
|
||||
private selectionStrings: string[] = [];
|
||||
// private dropDowns: DropDown[] = [];
|
||||
private rows: FilterTextRow[] = [];
|
||||
public cursorObj: Phaser.GameObjects.Image;
|
||||
public numFilters: number = 0;
|
||||
// public openDropDown: boolean = false;
|
||||
private lastCursor: number = -1;
|
||||
private uiTheme: UiTheme;
|
||||
|
||||
private menuMessageBoxContainer: Phaser.GameObjects.Container;
|
||||
private dialogueMessageBox: Phaser.GameObjects.NineSlice;
|
||||
message: any;
|
||||
private readonly textPadding = 8;
|
||||
private readonly defaultWordWrapWidth = 1224;
|
||||
|
||||
private onChange: () => void;
|
||||
|
||||
public defaultText: string = "---";
|
||||
|
||||
constructor(x: number, y: number, width: number, height: number, onChange: () => void,) {
|
||||
super(globalScene, x, y);
|
||||
|
||||
this.onChange = onChange;
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
this.window = addWindow(0, 0, width, height, false, false, undefined, undefined, WindowVariant.THIN);
|
||||
this.add(this.window);
|
||||
|
||||
this.cursorObj = globalScene.add.image(1, 1, "cursor");
|
||||
this.cursorObj.setScale(0.5);
|
||||
this.cursorObj.setVisible(false);
|
||||
this.cursorObj.setOrigin(0, 0);
|
||||
this.add(this.cursorObj);
|
||||
|
||||
this.menuMessageBoxContainer = globalScene.add.container(0, 130);
|
||||
this.menuMessageBoxContainer.setName("menu-message-box");
|
||||
this.menuMessageBoxContainer.setVisible(false);
|
||||
|
||||
// Full-width window used for testing dialog messages in debug mode
|
||||
this.dialogueMessageBox = addWindow(-this.textPadding, 0, globalScene.game.canvas.width / 6 + this.textPadding * 2, 49, false, false, 0, 0, WindowVariant.THIN);
|
||||
this.dialogueMessageBox.setOrigin(0, 0);
|
||||
this.menuMessageBoxContainer.add(this.dialogueMessageBox);
|
||||
|
||||
const menuMessageText = addTextObject(this.textPadding, this.textPadding, "", TextStyle.WINDOW, { maxLines: 2 });
|
||||
menuMessageText.setName("menu-message");
|
||||
menuMessageText.setOrigin(0, 0);
|
||||
this.menuMessageBoxContainer.add(menuMessageText);
|
||||
|
||||
// this.initTutorialOverlay(this.menuContainer);
|
||||
// this.initPromptSprite(this.menuMessageBoxContainer);
|
||||
|
||||
this.message = menuMessageText;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new filter to the FilterBar, as long that a unique DropDownColumn is provided
|
||||
* @param column the DropDownColumn that will be used to access the filter values
|
||||
* @param title the string that will get displayed in the filter bar
|
||||
* @param dropDown the DropDown with all options for this filter
|
||||
* @returns true if successful, false if the provided column was already in use for another filter
|
||||
*/
|
||||
addFilter(row: FilterTextRow, title: string): boolean {
|
||||
// The column should be unique to each filter,
|
||||
|
||||
const paddingX = 6;
|
||||
const cursorOffset = 8;
|
||||
const extraSpaceX = 40;
|
||||
|
||||
if (this.rows.includes(row)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.rows.push(row);
|
||||
|
||||
const filterTypesLabel = addTextObject(paddingX + cursorOffset, 3, title, TextStyle.TOOLTIP_CONTENT);
|
||||
this.labels.push(filterTypesLabel);
|
||||
this.add(filterTypesLabel);
|
||||
|
||||
const filterTypesSelection = addTextObject(paddingX + cursorOffset + extraSpaceX, 3, this.defaultText, TextStyle.TOOLTIP_CONTENT);
|
||||
this.selections.push(filterTypesSelection);
|
||||
this.add(filterTypesSelection);
|
||||
|
||||
this.selectionStrings.push("");
|
||||
|
||||
this.calcFilterPositions();
|
||||
this.numFilters++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
resetSelection(index: number): void {
|
||||
this.selections[index].setText(this.defaultText);
|
||||
this.selectionStrings[index] = "";
|
||||
this.onChange();
|
||||
}
|
||||
|
||||
setValsToDefault(): void {
|
||||
for (let i = 0; i < this.numFilters; i++) {
|
||||
this.resetSelection(i);
|
||||
}
|
||||
}
|
||||
|
||||
startSearch(index: number, ui: UI): void {
|
||||
|
||||
ui.playSelect();
|
||||
const prefilledText = "";
|
||||
const buttonAction: any = {};
|
||||
buttonAction["buttonActions"] = [
|
||||
(sanitizedName: string) => {
|
||||
// ui.revertMode();
|
||||
ui.playSelect();
|
||||
const dialogueTestName = sanitizedName;
|
||||
//TODO: Is it really necessary to encode and decode?
|
||||
const dialogueName = decodeURIComponent(escape(atob(dialogueTestName)));
|
||||
const handler = ui.getHandler() as AwaitableUiHandler;
|
||||
handler.tutorialActive = true;
|
||||
// Switch to the dialog test window
|
||||
this.selections[index].setText(String(i18next.t(dialogueName)));
|
||||
ui.revertMode();
|
||||
this.onChange();
|
||||
},
|
||||
() => {
|
||||
ui.revertMode();
|
||||
this.onChange;
|
||||
}
|
||||
];
|
||||
ui.setOverlayMode(Mode.POKEDEX_SCAN, buttonAction, prefilledText, index);
|
||||
}
|
||||
|
||||
|
||||
setCursor(cursor: number): void {
|
||||
const cursorOffset = 8;
|
||||
|
||||
this.cursorObj.setPosition(cursorOffset, this.labels[cursor].y + 3);
|
||||
this.lastCursor = cursor;
|
||||
}
|
||||
|
||||
|
||||
/////////////////From here down changes must be made
|
||||
/**
|
||||
* Highlight the labels of the FilterBar if the filters are different from their default values
|
||||
*/
|
||||
updateFilterLabels(): void {
|
||||
for (let i = 0; i < this.numFilters; i++) {
|
||||
if (this.selections[i].text === this.defaultText) {
|
||||
this.labels[i].setColor(getTextColor(TextStyle.TOOLTIP_CONTENT, false, globalScene.uiTheme));
|
||||
} else {
|
||||
this.labels[i].setColor(getTextColor(TextStyle.STATS_LABEL, false, globalScene.uiTheme));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the filter dropdowns evenly across the width of the container
|
||||
*/
|
||||
private calcFilterPositions(): void {
|
||||
const paddingY = 8;
|
||||
|
||||
let totalHeight = paddingY * 2;
|
||||
this.labels.forEach(label => {
|
||||
totalHeight += label.displayHeight;
|
||||
});
|
||||
const spacing = (this.height - totalHeight) / (this.labels.length - 1);
|
||||
for (let i = 0; i < this.labels.length; i++) {
|
||||
if (i === 0) {
|
||||
this.labels[i].y = paddingY;
|
||||
this.selections[i].y = paddingY;
|
||||
} else {
|
||||
const lastBottom = this.labels[i - 1].y + this.labels[i - 1].displayHeight;
|
||||
this.labels[i].y = lastBottom + spacing;
|
||||
this.selections[i].y = lastBottom + spacing;
|
||||
}
|
||||
// Uncomment and adjust if necessary to position dropdowns vertically
|
||||
// this.dropDowns[i].y = this.labels[i].y + this.labels[i].displayHeight + paddingY;
|
||||
// this.dropDowns[i].x = this.width;
|
||||
}
|
||||
}
|
||||
|
||||
getValue(row: number): string {
|
||||
return this.selections[row].getWrappedText()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest filter to the provided container on the y-axis
|
||||
* @param container the StarterContainer to compare position against
|
||||
* @returns the index of the closest filter
|
||||
*/
|
||||
getNearestFilter(container: StarterContainer): number {
|
||||
|
||||
const midy = container.y + container.icon.displayHeight / 2;
|
||||
let nearest = 0;
|
||||
let nearestDist = 1000;
|
||||
for (let i = 0; i < this.labels.length; i++) {
|
||||
const dist = Math.abs(midy - (this.labels[i].y + this.labels[i].displayHeight / 3));
|
||||
if (dist < nearestDist) {
|
||||
nearest = i;
|
||||
nearestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
return nearest;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -24,6 +24,7 @@ enum MenuOptions {
|
|||
RUN_HISTORY,
|
||||
EGG_LIST,
|
||||
EGG_GACHA,
|
||||
POKEDEX,
|
||||
MANAGE_DATA,
|
||||
COMMUNITY,
|
||||
SAVE_AND_QUIT,
|
||||
|
@ -527,6 +528,11 @@ export default class MenuUiHandler extends MessageUiHandler {
|
|||
ui.setOverlayMode(Mode.EGG_GACHA);
|
||||
success = true;
|
||||
break;
|
||||
case MenuOptions.POKEDEX:
|
||||
ui.revertMode();
|
||||
ui.setOverlayMode(Mode.POKEDEX);
|
||||
success = true;
|
||||
break;
|
||||
case MenuOptions.MANAGE_DATA:
|
||||
if (!bypassLogin && !this.manageDataConfig.options.some(o => o.label === i18next.t("menuUiHandler:linkDiscord") || o.label === i18next.t("menuUiHandler:unlinkDiscord"))) {
|
||||
this.manageDataConfig.options.splice(this.manageDataConfig.options.length - 1, 0,
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Mode } from "#app/ui/ui";
|
|||
import * as Utils from "#app/utils";
|
||||
import { PokemonFormChangeItemModifier, PokemonHeldItemModifier, SwitchEffectTransferModifier } from "#app/modifier/modifier";
|
||||
import { allMoves, ForceSwitchOutAttr } from "#app/data/move";
|
||||
import { getGenderColor, getGenderSymbol } from "#app/data/gender";
|
||||
import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler";
|
||||
import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
|
||||
|
@ -109,6 +109,7 @@ export enum PartyOption {
|
|||
TEACH,
|
||||
TRANSFER,
|
||||
SUMMARY,
|
||||
POKEDEX,
|
||||
UNPAUSE_EVOLUTION,
|
||||
SPLICE,
|
||||
UNSPLICE,
|
||||
|
@ -218,7 +219,7 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
|
||||
public static NoEffectMessage = i18next.t("partyUiHandler:anyEffect");
|
||||
|
||||
private localizedOptions = [ PartyOption.SEND_OUT, PartyOption.SUMMARY, PartyOption.CANCEL, PartyOption.APPLY, PartyOption.RELEASE, PartyOption.TEACH, PartyOption.SPLICE, PartyOption.UNSPLICE, PartyOption.REVIVE, PartyOption.TRANSFER, PartyOption.UNPAUSE_EVOLUTION, PartyOption.PASS_BATON, PartyOption.RENAME, PartyOption.SELECT ];
|
||||
private localizedOptions = [ PartyOption.SEND_OUT, PartyOption.SUMMARY, PartyOption.POKEDEX, PartyOption.CANCEL, PartyOption.APPLY, PartyOption.RELEASE, PartyOption.TEACH, PartyOption.SPLICE, PartyOption.UNSPLICE, PartyOption.REVIVE, PartyOption.TRANSFER, PartyOption.UNPAUSE_EVOLUTION, PartyOption.PASS_BATON, PartyOption.RENAME, PartyOption.SELECT ];
|
||||
|
||||
constructor() {
|
||||
super(Mode.PARTY);
|
||||
|
@ -397,7 +398,7 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
}
|
||||
ui.playSelect();
|
||||
return true;
|
||||
} else if ((option !== PartyOption.SUMMARY && option !== PartyOption.UNPAUSE_EVOLUTION && option !== PartyOption.UNSPLICE && option !== PartyOption.RELEASE && option !== PartyOption.CANCEL && option !== PartyOption.RENAME)
|
||||
} else if ((option !== PartyOption.SUMMARY && option !== PartyOption.POKEDEX && option !== PartyOption.UNPAUSE_EVOLUTION && option !== PartyOption.UNSPLICE && option !== PartyOption.RELEASE && option !== PartyOption.CANCEL && option !== PartyOption.RENAME)
|
||||
|| (option === PartyOption.RELEASE && this.partyUiMode === PartyUiMode.RELEASE)) {
|
||||
let filterResult: string | null;
|
||||
const getTransferrableItemsFromPokemon = (pokemon: PlayerPokemon) =>
|
||||
|
@ -466,6 +467,16 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
ui.playSelect();
|
||||
ui.setModeWithoutClear(Mode.SUMMARY, pokemon).then(() => this.clearOptions());
|
||||
return true;
|
||||
} else if (option === PartyOption.POKEDEX) {
|
||||
ui.playSelect();
|
||||
const attributes = {
|
||||
shiny: pokemon.shiny,
|
||||
variant: pokemon.variant,
|
||||
form: pokemon.formIndex,
|
||||
female: pokemon.gender === Gender.FEMALE ? true : false
|
||||
};
|
||||
ui.setOverlayMode(Mode.POKEDEX_PAGE, pokemon.species, pokemon.formIndex, attributes).then(() => this.clearOptions());
|
||||
return true;
|
||||
} else if (option === PartyOption.UNPAUSE_EVOLUTION) {
|
||||
this.clearOptions();
|
||||
ui.playSelect();
|
||||
|
@ -892,6 +903,7 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
}
|
||||
|
||||
this.options.push(PartyOption.SUMMARY);
|
||||
this.options.push(PartyOption.POKEDEX);
|
||||
this.options.push(PartyOption.RENAME);
|
||||
|
||||
if ((pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) || (pokemon.isFusion() && pokemon.fusionSpecies && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId)))) {
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
import type { InfoToggle } from "../battle-scene";
|
||||
import { TextStyle, addTextObject } from "./text";
|
||||
import { addWindow } from "./ui-theme";
|
||||
import * as Utils from "../utils";
|
||||
import i18next from "i18next";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
export interface PokedexInfoOverlaySettings {
|
||||
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
|
||||
//location and width of the component; unaffected by scaling
|
||||
x?: number;
|
||||
y?: number;
|
||||
/** Default is always half the screen, regardless of scale */
|
||||
width?: number;
|
||||
/** Determines whether to display the small secondary box */
|
||||
hideEffectBox?: boolean;
|
||||
hideBg?: boolean;
|
||||
}
|
||||
|
||||
const DESC_HEIGHT = 48;
|
||||
const BORDER = 8;
|
||||
const GLOBAL_SCALE = 6;
|
||||
|
||||
export default class PokedexInfoOverlay extends Phaser.GameObjects.Container implements InfoToggle {
|
||||
public active: boolean = false;
|
||||
|
||||
private desc: Phaser.GameObjects.Text;
|
||||
private descScroll : Phaser.Tweens.Tween | null = null;
|
||||
|
||||
private descBg: Phaser.GameObjects.NineSlice;
|
||||
|
||||
private options: PokedexInfoOverlaySettings;
|
||||
|
||||
private textMaskRect: Phaser.GameObjects.Graphics;
|
||||
|
||||
private maskPointOriginX: number;
|
||||
private maskPointOriginY: number;
|
||||
public scale: number;
|
||||
public width: number;
|
||||
|
||||
constructor(options?: PokedexInfoOverlaySettings) {
|
||||
super(globalScene, options?.x, options?.y);
|
||||
this.scale = options?.scale || 1; // set up the scale
|
||||
this.setScale(this.scale);
|
||||
this.options = options || {};
|
||||
|
||||
// prepare the description box
|
||||
this.width = (options?.width || PokedexInfoOverlay.getWidth(this.scale)) / this.scale; // divide by scale as we always want this to be half a window wide
|
||||
this.descBg = addWindow(0, 0, this.width, DESC_HEIGHT);
|
||||
this.descBg.setOrigin(0, 0);
|
||||
this.add(this.descBg);
|
||||
|
||||
// set up the description; wordWrap uses true pixels, unaffected by any scaling, while other values are affected
|
||||
this.desc = addTextObject(BORDER, BORDER - 2, "", TextStyle.BATTLE_INFO, { wordWrap: { width: (this.width - (BORDER - 2) * 2) * GLOBAL_SCALE }});
|
||||
this.desc.setLineSpacing(i18next.resolvedLanguage === "ja" ? 25 : 5);
|
||||
|
||||
// limit the text rendering, required for scrolling later on
|
||||
this.maskPointOriginX = options?.x || 0;
|
||||
this.maskPointOriginY = options?.y || 0;
|
||||
|
||||
if (this.maskPointOriginX < 0) {
|
||||
this.maskPointOriginX += globalScene.game.canvas.width / GLOBAL_SCALE;
|
||||
}
|
||||
if (this.maskPointOriginY < 0) {
|
||||
this.maskPointOriginY += globalScene.game.canvas.height / GLOBAL_SCALE;
|
||||
}
|
||||
|
||||
this.textMaskRect = globalScene.make.graphics();
|
||||
this.textMaskRect.fillStyle(0xFF0000);
|
||||
this.textMaskRect.fillRect(
|
||||
this.maskPointOriginX + BORDER * this.scale, this.maskPointOriginY + (BORDER - 2) * this.scale,
|
||||
this.width - (BORDER * 2) * this.scale, (DESC_HEIGHT - (BORDER - 2) * 2) * this.scale);
|
||||
this.textMaskRect.setScale(6);
|
||||
const textMask = this.createGeometryMask(this.textMaskRect);
|
||||
|
||||
this.add(this.desc);
|
||||
this.desc.setMask(textMask);
|
||||
|
||||
if (options?.hideBg) {
|
||||
this.descBg.setVisible(false);
|
||||
}
|
||||
|
||||
// hide this component for now
|
||||
this.setVisible(false);
|
||||
}
|
||||
|
||||
// show this component with infos for the specific move
|
||||
show(text: string):boolean {
|
||||
if (!globalScene.enableMoveInfo) {
|
||||
return false; // move infos have been disabled // TODO:: is `false` correct? i used to be `undeefined`
|
||||
}
|
||||
|
||||
this.desc.setText(text ?? "");
|
||||
|
||||
// stop previous scrolling effects and reset y position
|
||||
if (this.descScroll) {
|
||||
this.descScroll.remove();
|
||||
this.descScroll = null;
|
||||
this.desc.y = BORDER - 2;
|
||||
}
|
||||
|
||||
// determine if we need to add new scrolling effects
|
||||
const lineCount = Math.floor(this.desc.displayHeight * (96 / 72) / 14.83);
|
||||
|
||||
const newHeight = lineCount >= 3 ? 48 : (lineCount === 2 ? 36 : 24);
|
||||
this.textMaskRect.clear();
|
||||
this.textMaskRect.fillStyle(0xFF0000);
|
||||
this.textMaskRect.fillRect(
|
||||
this.maskPointOriginX + BORDER * this.scale,
|
||||
this.maskPointOriginY + (BORDER - 2) * this.scale + (48 - newHeight),
|
||||
this.width - (BORDER * 2) * this.scale,
|
||||
(newHeight - (BORDER - 2) * 2) * this.scale
|
||||
);
|
||||
const updatedMask = this.createGeometryMask(this.textMaskRect);
|
||||
this.desc.setMask(updatedMask);
|
||||
|
||||
this.descBg.setSize(this.descBg.width, newHeight);
|
||||
this.descBg.setY(48 - newHeight);
|
||||
this.desc.setY(BORDER - 2 + (48 - newHeight));
|
||||
|
||||
if (lineCount > 3) {
|
||||
// generate scrolling effects
|
||||
this.descScroll = globalScene.tweens.add({
|
||||
targets: this.desc,
|
||||
delay: Utils.fixedInt(2000),
|
||||
loop: -1,
|
||||
hold: Utils.fixedInt(2000),
|
||||
duration: Utils.fixedInt((lineCount - 3) * 2000),
|
||||
y: `-=${14.83 * (72 / 96) * (lineCount - 3)}`
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.options.delayVisibility) {
|
||||
this.setVisible(true);
|
||||
}
|
||||
this.active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.setVisible(false);
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
toggleInfo(visible: boolean): void {
|
||||
if (visible) {
|
||||
this.setVisible(true);
|
||||
}
|
||||
globalScene.tweens.add({
|
||||
targets: this.desc,
|
||||
duration: Utils.fixedInt(125),
|
||||
ease: "Sine.easeInOut",
|
||||
alpha: visible ? 1 : 0
|
||||
});
|
||||
if (!visible) {
|
||||
this.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
// width of this element
|
||||
static getWidth(scale:number):number {
|
||||
return globalScene.game.canvas.width / GLOBAL_SCALE / 2;
|
||||
}
|
||||
|
||||
// height of this element
|
||||
static getHeight(scale:number, onSide?: boolean):number {
|
||||
return DESC_HEIGHT * scale;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
import { globalScene } from "#app/global-scene";
|
||||
import type PokemonSpecies from "../data/pokemon-species";
|
||||
import { addTextObject, TextStyle } from "./text";
|
||||
|
||||
export class PokedexMonContainer extends Phaser.GameObjects.Container {
|
||||
public species: PokemonSpecies;
|
||||
public icon: Phaser.GameObjects.Sprite;
|
||||
public shinyIcons: Phaser.GameObjects.Image[] = [];
|
||||
public label: Phaser.GameObjects.Text;
|
||||
public starterPassiveBgs: Phaser.GameObjects.Image;
|
||||
public hiddenAbilityIcon: Phaser.GameObjects.Image;
|
||||
public favoriteIcon: Phaser.GameObjects.Image;
|
||||
public classicWinIcon: Phaser.GameObjects.Image;
|
||||
public candyUpgradeIcon: Phaser.GameObjects.Image;
|
||||
public candyUpgradeOverlayIcon: Phaser.GameObjects.Image;
|
||||
public eggMove1Icon: Phaser.GameObjects.Image;
|
||||
public tmMove1Icon: Phaser.GameObjects.Image;
|
||||
public eggMove2Icon: Phaser.GameObjects.Image;
|
||||
public tmMove2Icon: Phaser.GameObjects.Image;
|
||||
public passive1Icon: Phaser.GameObjects.Image;
|
||||
public passive2Icon: Phaser.GameObjects.Image;
|
||||
public cost: number = 0;
|
||||
|
||||
constructor(species: PokemonSpecies) {
|
||||
super(globalScene, 0, 0);
|
||||
|
||||
this.species = species;
|
||||
|
||||
const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, false, true);
|
||||
const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
||||
|
||||
// starter passive bg
|
||||
const starterPassiveBg = globalScene.add.image(2, 5, "passive_bg");
|
||||
starterPassiveBg.setOrigin(0, 0);
|
||||
starterPassiveBg.setScale(0.75);
|
||||
starterPassiveBg.setVisible(false);
|
||||
this.add(starterPassiveBg);
|
||||
this.starterPassiveBgs = starterPassiveBg;
|
||||
|
||||
// icon
|
||||
this.icon = globalScene.add.sprite(-2, 2, species.getIconAtlasKey(defaultProps.formIndex, defaultProps.shiny, defaultProps.variant));
|
||||
this.icon.setScale(0.5);
|
||||
this.icon.setOrigin(0, 0);
|
||||
this.icon.setFrame(species.getIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant));
|
||||
this.checkIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant);
|
||||
this.icon.setTint(0);
|
||||
this.add(this.icon);
|
||||
|
||||
// shiny icons
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const shinyIcon = globalScene.add.image(i * -3 + 12, 2, "shiny_star_small");
|
||||
shinyIcon.setScale(0.5);
|
||||
shinyIcon.setOrigin(0, 0);
|
||||
shinyIcon.setVisible(false);
|
||||
this.shinyIcons.push(shinyIcon);
|
||||
}
|
||||
this.add(this.shinyIcons);
|
||||
|
||||
// value label
|
||||
const label = addTextObject(1, 2, "0", TextStyle.WINDOW, { fontSize: "32px" });
|
||||
label.setShadowOffset(2, 2);
|
||||
label.setOrigin(0, 0);
|
||||
label.setVisible(false);
|
||||
this.add(label);
|
||||
this.label = label;
|
||||
|
||||
// hidden ability icon
|
||||
const abilityIcon = globalScene.add.image(12, 7, "ha_capsule");
|
||||
abilityIcon.setOrigin(0, 0);
|
||||
abilityIcon.setScale(0.5);
|
||||
abilityIcon.setVisible(false);
|
||||
this.add(abilityIcon);
|
||||
this.hiddenAbilityIcon = abilityIcon;
|
||||
|
||||
// favorite icon
|
||||
const favoriteIcon = globalScene.add.image(0, 7, "favorite");
|
||||
favoriteIcon.setOrigin(0, 0);
|
||||
favoriteIcon.setScale(0.5);
|
||||
favoriteIcon.setVisible(false);
|
||||
this.add(favoriteIcon);
|
||||
this.favoriteIcon = favoriteIcon;
|
||||
|
||||
// classic win icon
|
||||
const classicWinIcon = globalScene.add.image(0, 12, "champion_ribbon");
|
||||
classicWinIcon.setOrigin(0, 0);
|
||||
classicWinIcon.setScale(0.5);
|
||||
classicWinIcon.setVisible(false);
|
||||
this.add(classicWinIcon);
|
||||
this.classicWinIcon = classicWinIcon;
|
||||
|
||||
// candy upgrade icon
|
||||
const candyUpgradeIcon = globalScene.add.image(12, 12, "candy");
|
||||
candyUpgradeIcon.setOrigin(0, 0);
|
||||
candyUpgradeIcon.setScale(0.25);
|
||||
candyUpgradeIcon.setVisible(false);
|
||||
this.add(candyUpgradeIcon);
|
||||
this.candyUpgradeIcon = candyUpgradeIcon;
|
||||
|
||||
// candy upgrade overlay icon
|
||||
const candyUpgradeOverlayIcon = globalScene.add.image(12, 12, "candy_overlay");
|
||||
candyUpgradeOverlayIcon.setOrigin(0, 0);
|
||||
candyUpgradeOverlayIcon.setScale(0.25);
|
||||
candyUpgradeOverlayIcon.setVisible(false);
|
||||
this.add(candyUpgradeOverlayIcon);
|
||||
this.candyUpgradeOverlayIcon = candyUpgradeOverlayIcon;
|
||||
|
||||
// move icons
|
||||
const eggMove1Icon = globalScene.add.image(0, 12, "mystery_egg");
|
||||
eggMove1Icon.setOrigin(0, 0);
|
||||
eggMove1Icon.setScale(0.25);
|
||||
eggMove1Icon.setVisible(false);
|
||||
this.add(eggMove1Icon);
|
||||
this.eggMove1Icon = eggMove1Icon;
|
||||
|
||||
// move icons
|
||||
const tmMove1Icon = globalScene.add.image(0, 12, "normal_memory");
|
||||
tmMove1Icon.setOrigin(0, 0);
|
||||
tmMove1Icon.setScale(0.25);
|
||||
tmMove1Icon.setVisible(false);
|
||||
this.add(tmMove1Icon);
|
||||
this.tmMove1Icon = tmMove1Icon;
|
||||
|
||||
// move icons
|
||||
const eggMove2Icon = globalScene.add.image(7, 12, "mystery_egg");
|
||||
eggMove2Icon.setOrigin(0, 0);
|
||||
eggMove2Icon.setScale(0.25);
|
||||
eggMove2Icon.setVisible(false);
|
||||
this.add(eggMove2Icon);
|
||||
this.eggMove2Icon = eggMove2Icon;
|
||||
|
||||
// move icons
|
||||
const tmMove2Icon = globalScene.add.image(7, 12, "normal_memory");
|
||||
tmMove2Icon.setOrigin(0, 0);
|
||||
tmMove2Icon.setScale(0.25);
|
||||
tmMove2Icon.setVisible(false);
|
||||
this.add(tmMove2Icon);
|
||||
this.tmMove2Icon = tmMove2Icon;
|
||||
|
||||
|
||||
// move icons
|
||||
const passive1Icon = globalScene.add.image(3, 3, "candy");
|
||||
passive1Icon.setOrigin(0, 0);
|
||||
passive1Icon.setScale(0.25);
|
||||
passive1Icon.setVisible(false);
|
||||
this.add(passive1Icon);
|
||||
this.passive1Icon = passive1Icon;
|
||||
|
||||
// move icons
|
||||
const passive2Icon = globalScene.add.image(12, 3, "candy");
|
||||
passive2Icon.setOrigin(0, 0);
|
||||
passive2Icon.setScale(0.25);
|
||||
passive2Icon.setVisible(false);
|
||||
this.add(passive2Icon);
|
||||
this.passive2Icon = passive2Icon;
|
||||
}
|
||||
|
||||
checkIconId(female, formIndex, shiny, variant) {
|
||||
if (this.icon.frame.name !== this.species.getIconId(female, formIndex, shiny, variant)) {
|
||||
console.log(`${this.species.name}'s variant icon does not exist. Replacing with default.`);
|
||||
this.icon.setTexture(this.species.getIconAtlasKey(formIndex, false, variant));
|
||||
this.icon.setFrame(this.species.getIconId(female, formIndex, false, variant));
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,191 @@
|
|||
import type { InputFieldConfig } from "./form-modal-ui-handler";
|
||||
import { FormModalUiHandler } from "./form-modal-ui-handler";
|
||||
import type { ModalConfig } from "./modal-ui-handler";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import type { OptionSelectItem } from "./abstact-option-select-ui-handler";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import { Mode } from "./ui";
|
||||
import { FilterTextRow } from "./filter-text";
|
||||
import { allAbilities } from "#app/data/ability";
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { allSpecies } from "#app/data/pokemon-species";
|
||||
import i18next from "i18next";
|
||||
|
||||
export default class PokedexScanUiHandler extends FormModalUiHandler {
|
||||
|
||||
keys: string[];
|
||||
reducedKeys: string[];
|
||||
parallelKeys: string[];
|
||||
nameKeys: string[];
|
||||
moveKeys: string[];
|
||||
abilityKeys: string[];
|
||||
row: number;
|
||||
|
||||
constructor(mode) {
|
||||
super(mode);
|
||||
}
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
this.nameKeys = allSpecies.map(a => a.name).filter((value, index, self) => self.indexOf(value) === index);
|
||||
this.moveKeys = allMoves.map(a => a.name);
|
||||
this.abilityKeys = allAbilities.map(a => a.name);
|
||||
}
|
||||
|
||||
getModalTitle(config?: ModalConfig): string {
|
||||
return i18next.t("pokedexUiHandler:scanChooseOption");
|
||||
}
|
||||
|
||||
getWidth(config?: ModalConfig): number {
|
||||
return 300;
|
||||
}
|
||||
|
||||
getMargin(config?: ModalConfig): [number, number, number, number] {
|
||||
return [ 0, 0, 48, 0 ];
|
||||
}
|
||||
|
||||
getButtonLabels(config?: ModalConfig): string[] {
|
||||
return [ i18next.t("pokedexUiHandler:scanSelect"), i18next.t("pokedexUiHandler:scanCancel") ];
|
||||
}
|
||||
|
||||
getReadableErrorMessage(error: string): string {
|
||||
const colonIndex = error?.indexOf(":");
|
||||
if (colonIndex > 0) {
|
||||
error = error.slice(0, colonIndex);
|
||||
}
|
||||
|
||||
return super.getReadableErrorMessage(error);
|
||||
}
|
||||
|
||||
override getInputFieldConfigs(): InputFieldConfig[] {
|
||||
switch (this.row) {
|
||||
case FilterTextRow.NAME: {
|
||||
return [{ label: i18next.t("pokedexUiHandler:scanLabelName") }];
|
||||
}
|
||||
case FilterTextRow.MOVE_1:
|
||||
case FilterTextRow.MOVE_2: {
|
||||
return [{ label: i18next.t("pokedexUiHandler:scanLabelMove") }];
|
||||
}
|
||||
case FilterTextRow.ABILITY_1:{
|
||||
return [{ label: i18next.t("pokedexUiHandler:scanLabelAbility") }];
|
||||
}
|
||||
case FilterTextRow.ABILITY_2: {
|
||||
return [{ label: i18next.t("pokedexUiHandler:scanLabelPassive") }];
|
||||
}
|
||||
default: {
|
||||
return [{ label: "" }];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
reduceKeys(): void {
|
||||
switch (this.row) {
|
||||
case FilterTextRow.NAME: {
|
||||
this.reducedKeys = this.nameKeys;
|
||||
break;
|
||||
}
|
||||
case FilterTextRow.MOVE_1:
|
||||
case FilterTextRow.MOVE_2: {
|
||||
this.reducedKeys = this.moveKeys;
|
||||
break;
|
||||
}
|
||||
case FilterTextRow.ABILITY_1:
|
||||
case FilterTextRow.ABILITY_2: {
|
||||
this.reducedKeys = this.abilityKeys;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this.reducedKeys = this.keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// args[2] is an index of FilterTextRow
|
||||
show(args: any[]): boolean {
|
||||
this.row = args[2];
|
||||
const ui = this.getUi();
|
||||
const hasTitle = !!this.getModalTitle();
|
||||
this.updateFields(this.getInputFieldConfigs(), hasTitle);
|
||||
this.updateContainer(args[0] as ModalConfig);
|
||||
const input = this.inputs[0];
|
||||
input.setMaxLength(255);
|
||||
|
||||
this.reduceKeys();
|
||||
|
||||
input.on("keydown", (inputObject, evt: KeyboardEvent) => {
|
||||
if ([ "escape", "space" ].some((v) => v === evt.key.toLowerCase() || v === evt.code.toLowerCase()) && ui.getMode() === Mode.AUTO_COMPLETE) {
|
||||
// Delete autocomplete list and recovery focus.
|
||||
inputObject.on("blur", () => inputObject.node.focus(), { once: true });
|
||||
ui.revertMode();
|
||||
}
|
||||
});
|
||||
|
||||
input.on("textchange", (inputObject, evt: InputEvent) => {
|
||||
// Delete autocomplete.
|
||||
if (ui.getMode() === Mode.AUTO_COMPLETE) {
|
||||
ui.revertMode();
|
||||
}
|
||||
|
||||
let options: OptionSelectItem[] = [];
|
||||
const filteredKeys = this.reducedKeys.filter((command) => command.toLowerCase().includes(inputObject.text.toLowerCase()));
|
||||
if (inputObject.text !== "" && filteredKeys.length > 0) {
|
||||
options = filteredKeys.slice(0).map((value) => {
|
||||
return {
|
||||
label: value,
|
||||
handler: () => {
|
||||
if (!isNullOrUndefined(evt.data) || evt.inputType?.toLowerCase() === "deletecontentbackward") {
|
||||
inputObject.setText(value);
|
||||
}
|
||||
ui.revertMode();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (options.length > 0) {
|
||||
const modalOpts = {
|
||||
options: options,
|
||||
maxOptions: 5,
|
||||
modalContainer: this.modalContainer
|
||||
};
|
||||
ui.setOverlayMode(Mode.AUTO_COMPLETE, modalOpts);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (super.show(args)) {
|
||||
const config = args[0] as ModalConfig;
|
||||
this.inputs[0].resize(1150, 116);
|
||||
this.inputContainers[0].list[0].width = 200;
|
||||
if (args[1] && typeof (args[1] as PlayerPokemon).getNameToRender === "function") {
|
||||
this.inputs[0].text = (args[1] as PlayerPokemon).getNameToRender();
|
||||
} else {
|
||||
this.inputs[0].text = args[1];
|
||||
}
|
||||
this.submitAction = (_) => {
|
||||
if (ui.getMode() === Mode.POKEDEX_SCAN) {
|
||||
this.sanitizeInputs();
|
||||
const sanitizedName = btoa(unescape(encodeURIComponent(this.inputs[0].text)));
|
||||
config.buttonActions[0](sanitizedName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
super.clear();
|
||||
|
||||
// Clearing the labels so they don't appear again and overlap
|
||||
this.formLabels.forEach(label => {
|
||||
label.destroy();
|
||||
});
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1981,6 +1981,21 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||
}
|
||||
});
|
||||
}
|
||||
options.push({
|
||||
label: i18next.t("menuUiHandler:POKEDEX"),
|
||||
handler: () => {
|
||||
ui.setMode(Mode.STARTER_SELECT).then(() => {
|
||||
const attributes = {
|
||||
shiny: starterAttributes.shiny,
|
||||
variant: starterAttributes.variant,
|
||||
form: starterAttributes.form,
|
||||
female: starterAttributes.female
|
||||
};
|
||||
ui.setOverlayMode(Mode.POKEDEX_PAGE, this.lastSpecies, starterAttributes.form, attributes);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
});
|
||||
options.push({
|
||||
label: i18next.t("menu:cancel"),
|
||||
handler: () => {
|
||||
|
|
|
@ -42,6 +42,7 @@ export enum TextStyle {
|
|||
PERFECT_IV,
|
||||
ME_OPTION_DEFAULT, // Default style for choices in ME
|
||||
ME_OPTION_SPECIAL, // Style for choices with special requirements in ME
|
||||
SHADOW_TEXT // To obscure unavailable options
|
||||
}
|
||||
|
||||
export interface TextStyleOptions {
|
||||
|
@ -359,6 +360,12 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui
|
|||
return !shadow ? "#f8b050" : "#c07800"; // Gold
|
||||
}
|
||||
return !shadow ? "#78c850" : "#306850"; // Green
|
||||
// Leaving the logic in place, in case someone wants to pick an even darker hue for the shadow down the line
|
||||
case TextStyle.SHADOW_TEXT:
|
||||
if (isLegacyTheme) {
|
||||
return !shadow ? "#d0d0c8" : "#d0d0c8";
|
||||
}
|
||||
return !shadow ? "#6b5a73" : "#6b5a73";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
13
src/ui/ui.ts
13
src/ui/ui.ts
|
@ -23,6 +23,7 @@ import OptionSelectUiHandler from "./settings/option-select-ui-handler";
|
|||
import EggHatchSceneHandler from "./egg-hatch-scene-handler";
|
||||
import EggListUiHandler from "./egg-list-ui-handler";
|
||||
import EggGachaUiHandler from "./egg-gacha-ui-handler";
|
||||
import PokedexUiHandler from "./pokedex-ui-handler";
|
||||
import { addWindow } from "./ui-theme";
|
||||
import LoginFormUiHandler from "./login-form-ui-handler";
|
||||
import RegistrationFormUiHandler from "./registration-form-ui-handler";
|
||||
|
@ -53,6 +54,8 @@ import TestDialogueUiHandler from "#app/ui/test-dialogue-ui-handler";
|
|||
import AutoCompleteUiHandler from "./autocomplete-ui-handler";
|
||||
import { Device } from "#enums/devices";
|
||||
import MysteryEncounterUiHandler from "./mystery-encounter-ui-handler";
|
||||
import PokedexScanUiHandler from "./pokedex-scan-ui-handler";
|
||||
import PokedexPageUiHandler from "./pokedex-page-ui-handler";
|
||||
import { NavigationManager } from "./settings/navigationMenu";
|
||||
|
||||
export enum Mode {
|
||||
|
@ -85,6 +88,9 @@ export enum Mode {
|
|||
GAME_STATS,
|
||||
EGG_LIST,
|
||||
EGG_GACHA,
|
||||
POKEDEX,
|
||||
POKEDEX_SCAN,
|
||||
POKEDEX_PAGE,
|
||||
LOGIN_FORM,
|
||||
REGISTRATION_FORM,
|
||||
LOADING,
|
||||
|
@ -109,6 +115,8 @@ const transitionModes = [
|
|||
Mode.EGG_HATCH_SCENE,
|
||||
Mode.EGG_LIST,
|
||||
Mode.EGG_GACHA,
|
||||
Mode.POKEDEX,
|
||||
Mode.POKEDEX_PAGE,
|
||||
Mode.CHALLENGE_SELECT,
|
||||
Mode.RUN_HISTORY,
|
||||
];
|
||||
|
@ -128,6 +136,7 @@ const noTransitionModes = [
|
|||
Mode.SETTINGS_KEYBOARD,
|
||||
Mode.ACHIEVEMENTS,
|
||||
Mode.GAME_STATS,
|
||||
Mode.POKEDEX_SCAN,
|
||||
Mode.LOGIN_FORM,
|
||||
Mode.REGISTRATION_FORM,
|
||||
Mode.LOADING,
|
||||
|
@ -193,6 +202,9 @@ export default class UI extends Phaser.GameObjects.Container {
|
|||
new GameStatsUiHandler(),
|
||||
new EggListUiHandler(),
|
||||
new EggGachaUiHandler(),
|
||||
new PokedexUiHandler(),
|
||||
new PokedexScanUiHandler(Mode.TEST_DIALOGUE),
|
||||
new PokedexPageUiHandler(),
|
||||
new LoginFormUiHandler(),
|
||||
new RegistrationFormUiHandler(),
|
||||
new LoadingModalUiHandler(),
|
||||
|
@ -558,6 +570,7 @@ export default class UI extends Phaser.GameObjects.Container {
|
|||
|
||||
revertMode(): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
|
||||
if (!this?.modeChain?.length) {
|
||||
return resolve(false);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue