This commit is contained in:
Wlowscha 2025-01-30 00:58:46 +00:00 committed by GitHub
commit 80c971439b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 5607 additions and 120 deletions

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

View File

@ -161,6 +161,7 @@ export default class BattleScene extends SceneBase {
public reroll: boolean = false; public reroll: boolean = false;
public shopCursorTarget: number = ShopCursorTarget.REWARDS; public shopCursorTarget: number = ShopCursorTarget.REWARDS;
public commandCursorMemory: boolean = false; public commandCursorMemory: boolean = false;
public dexForDevs: boolean = false;
public showMovesetFlyout: boolean = true; public showMovesetFlyout: boolean = true;
public showArenaFlyout: boolean = true; public showArenaFlyout: boolean = true;
public showTimeOfDayWidget: boolean = true; public showTimeOfDayWidget: boolean = true;

View File

@ -102,6 +102,18 @@ export interface BiomePokemonPools {
[key: integer]: BiomeTierPokemonPools [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 { export interface BiomeTierTrainerPools {
[key: integer]: TrainerType[] [key: integer]: TrainerType[]
} }
@ -7716,6 +7728,10 @@ export function initBiomes() {
uncatchableSpecies.push(speciesId); 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) { for (const b of biomeEntries) {
const biome = b[0]; const biome = b[0];
const tier = b[1]; const tier = b[1];
@ -7725,6 +7741,12 @@ export function initBiomes() {
: [ b[2] ] : [ b[2] ]
: [ TimeOfDay.ALL ]; : [ TimeOfDay.ALL ];
catchableSpecies[speciesId].push({
biome: biome as Biome,
tier: tier as BiomePoolTier,
tod: timesOfDay as TimeOfDay[]
});
for (const tod of timesOfDay) { for (const tod of timesOfDay) {
if (!biomePokemonPools.hasOwnProperty(biome) || !biomePokemonPools[biome].hasOwnProperty(tier) || !biomePokemonPools[biome][tier].hasOwnProperty(tod)) { if (!biomePokemonPools.hasOwnProperty(biome) || !biomePokemonPools[biome].hasOwnProperty(tier) || !biomePokemonPools[biome][tier].hasOwnProperty(tod)) {
continue; continue;

View File

@ -92,6 +92,7 @@ export class SpeciesFormEvolution {
public item: EvolutionItem | null; public item: EvolutionItem | null;
public condition: SpeciesEvolutionCondition | null; public condition: SpeciesEvolutionCondition | null;
public wildDelay: SpeciesWildEvolutionDelay; 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) { constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: integer, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) {
this.speciesId = speciesId; this.speciesId = speciesId;
@ -101,6 +102,23 @@ export class SpeciesFormEvolution {
this.item = item || EvolutionItem.NONE; this.item = item || EvolutionItem.NONE;
this.condition = condition; this.condition = condition;
this.wildDelay = wildDelay ?? SpeciesWildEvolutionDelay.NONE; 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[]) { constructor(weatherTypes: WeatherType[]) {
super(() => weatherTypes.indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1); super(() => weatherTypes.indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1);
this.weatherTypes = weatherTypes; this.weatherTypes = weatherTypes;
this.description = i18next.t("pokemonEvolutions:weather");
} }
} }
@ -1377,7 +1396,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
], ],
[Species.TANDEMAUS]: [ [Species.TANDEMAUS]: [
new SpeciesFormEvolution(Species.MAUSHOLD, "", "three", 25, null, new TandemausEvolutionCondition()), 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]: [ [Species.FIDOUGH]: [
new SpeciesEvolution(Species.DACHSBUN, 26, null, null) new SpeciesEvolution(Species.DACHSBUN, 26, null, null)
@ -1540,7 +1559,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
], ],
[Species.DUNSPARCE]: [ [Species.DUNSPARCE]: [
new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "three-segment", 32, null, new DunsparceEvolutionCondition(), SpeciesWildEvolutionDelay.LONG), 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]: [ [Species.GLIGAR]: [
new SpeciesEvolution(Species.GLISCOR, 1, EvolutionItem.RAZOR_FANG, new TimeOfDayEvolutionCondition("night") /* Razor fang at night*/, SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.GLISCOR, 1, EvolutionItem.RAZOR_FANG, new TimeOfDayEvolutionCondition("night") /* Razor fang at night*/, SpeciesWildEvolutionDelay.VERY_LONG)

View File

@ -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 { interface TmPoolTiers {
[key: integer]: ModifierTier [key: integer]: ModifierTier
} }

View File

@ -328,7 +328,8 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge
this.move = move; this.move = move;
this.known = known; 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; 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 { canChange(pokemon: Pokemon): boolean {

View File

@ -298,8 +298,8 @@ export abstract class PokemonSpeciesForm {
return ret; return ret;
} }
getSpriteAtlasPath(female: boolean, formIndex?: number, shiny?: boolean, variant?: number): string { getSpriteAtlasPath(female: boolean, formIndex?: number, shiny?: boolean, variant?: number, back?: boolean): string {
const spriteId = this.getSpriteId(female, formIndex, shiny, variant).replace(/\_{2}/g, "/"); const spriteId = this.getSpriteId(female, formIndex, shiny, variant, back).replace(/\_{2}/g, "/");
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`; 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}` : ""}`; 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 { getSpriteKey(female: boolean, formIndex?: number, shiny?: boolean, variant?: number, back?: boolean): string {
return `pkmn__${this.getSpriteId(female, formIndex, shiny, variant)}`; return `pkmn__${this.getSpriteId(female, formIndex, shiny, variant, back)}`;
} }
abstract getFormSpriteKey(formIndex?: number): string; abstract getFormSpriteKey(formIndex?: number): string;
@ -494,10 +494,10 @@ export abstract class PokemonSpeciesForm {
return true; 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 => { return new Promise(resolve => {
const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant); const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back);
globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant)); globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back));
globalScene.load.audio(`${this.getCryKey(formIndex)}`, `audio/${this.getCryKey(formIndex)}.m4a`); globalScene.load.audio(`${this.getCryKey(formIndex)}`, `audio/${this.getCryKey(formIndex)}.m4a`);
globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => { globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => {
const originalWarn = console.warn; const originalWarn = console.warn;
@ -507,7 +507,7 @@ export abstract class PokemonSpeciesForm {
console.warn = originalWarn; console.warn = originalWarn;
if (!(globalScene.anims.exists(spriteKey))) { if (!(globalScene.anims.exists(spriteKey))) {
globalScene.anims.create({ globalScene.anims.create({
key: this.getSpriteKey(female, formIndex, shiny, variant), key: this.getSpriteKey(female, formIndex, shiny, variant, back),
frames: frameNames, frames: frameNames,
frameRate: 10, frameRate: 10,
repeat: -1 repeat: -1
@ -515,7 +515,7 @@ export abstract class PokemonSpeciesForm {
} else { } else {
globalScene.anims.get(spriteKey).frameRate = 10; 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()); globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant).then(() => resolve());
}); });
if (startLoad) { 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.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.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 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("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 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 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("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 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.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 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

View File

@ -5,7 +5,7 @@ import { SceneBase } from "#app/scene-base";
import { WindowVariant, getWindowVariantSuffix } from "#app/ui/ui-theme"; import { WindowVariant, getWindowVariantSuffix } from "#app/ui/ui-theme";
import { isMobile } from "#app/touch-controls"; import { isMobile } from "#app/touch-controls";
import * as Utils from "#app/utils"; 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 { initBiomes } from "#app/data/balance/biomes";
import { initEggMoves } from "#app/data/balance/egg-moves"; import { initEggMoves } from "#app/data/balance/egg-moves";
import { initPokemonForms } from "#app/data/pokemon-forms"; import { initPokemonForms } from "#app/data/pokemon-forms";
@ -103,6 +103,8 @@ export class LoadingScene extends SceneBase {
this.loadImage("icon_tera", "ui"); this.loadImage("icon_tera", "ui");
this.loadImage("type_tera", "ui"); this.loadImage("type_tera", "ui");
this.loadAtlas("type_bgs", "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_fg", "ui");
this.loadImage("dawn_icon_mg", "ui"); this.loadImage("dawn_icon_mg", "ui");
@ -154,6 +156,7 @@ export class LoadingScene extends SceneBase {
this.loadImage("scroll_bar_handle", "ui"); this.loadImage("scroll_bar_handle", "ui");
this.loadImage("starter_container_bg", "ui"); this.loadImage("starter_container_bg", "ui");
this.loadImage("starter_select_bg", "ui"); this.loadImage("starter_select_bg", "ui");
this.loadImage("pokedex_summary_bg", "ui");
this.loadImage("select_cursor", "ui"); this.loadImage("select_cursor", "ui");
this.loadImage("select_cursor_highlight", "ui"); this.loadImage("select_cursor_highlight", "ui");
this.loadImage("select_cursor_highlight_thick", "ui"); this.loadImage("select_cursor_highlight_thick", "ui");
@ -354,6 +357,7 @@ export class LoadingScene extends SceneBase {
initVouchers(); initVouchers();
initStatsKeys(); initStatsKeys();
initPokemonPrevolutions(); initPokemonPrevolutions();
initPokemonStarters();
initBiomes(); initBiomes();
initEggMoves(); initEggMoves();
initPokemonForms(); initPokemonForms();

View File

@ -193,6 +193,7 @@ export async function initI18n(): Promise<void> {
"egg", "egg",
"fightUiHandler", "fightUiHandler",
"filterBar", "filterBar",
"filterText",
"gameMode", "gameMode",
"gameStatsUiHandler", "gameStatsUiHandler",
"growth", "growth",
@ -203,6 +204,7 @@ export async function initI18n(): Promise<void> {
"move", "move",
"nature", "nature",
"pokeball", "pokeball",
"pokedexUiHandler",
"pokemon", "pokemon",
"pokemonEvolutions", "pokemonEvolutions",
"pokemonForm", "pokemonForm",

View File

@ -9,6 +9,7 @@ import { EaseType } from "#enums/ease-type";
import { MoneyFormat } from "#enums/money-format"; import { MoneyFormat } from "#enums/money-format";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { ShopCursorTarget } from "#enums/shop-cursor-target"; import { ShopCursorTarget } from "#enums/shop-cursor-target";
import * as Utils from "../../utils";
const VOLUME_OPTIONS: SettingOption[] = new Array(11).fill(null).map((_, i) => i ? { const VOLUME_OPTIONS: SettingOption[] = new Array(11).fill(null).map((_, i) => i ? {
value: (i * 10).toString(), value: (i * 10).toString(),
@ -150,6 +151,7 @@ export const SettingKeys = {
Show_Stats_on_Level_Up: "SHOW_LEVEL_UP_STATS", Show_Stats_on_Level_Up: "SHOW_LEVEL_UP_STATS",
Shop_Cursor_Target: "SHOP_CURSOR_TARGET", Shop_Cursor_Target: "SHOP_CURSOR_TARGET",
Command_Cursor_Memory: "COMMAND_CURSOR_MEMORY", Command_Cursor_Memory: "COMMAND_CURSOR_MEMORY",
Dex_For_Devs: "DEX_FOR_DEVS",
Candy_Upgrade_Notification: "CANDY_UPGRADE_NOTIFICATION", Candy_Upgrade_Notification: "CANDY_UPGRADE_NOTIFICATION",
Candy_Upgrade_Display: "CANDY_UPGRADE_DISPLAY", Candy_Upgrade_Display: "CANDY_UPGRADE_DISPLAY",
Move_Info: "MOVE_INFO", 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 * Return the index of a Setting
* @param key SettingKey * @param key SettingKey
@ -828,6 +840,9 @@ export function setSetting(setting: string, value: integer): boolean {
case SettingKeys.Command_Cursor_Memory: case SettingKeys.Command_Cursor_Memory:
globalScene.commandCursorMemory = Setting[index].options[value].value === "On"; globalScene.commandCursorMemory = Setting[index].options[value].value === "On";
break; break;
case SettingKeys.Dex_For_Devs:
globalScene.dexForDevs = Setting[index].options[value].value === "On";
break;
case SettingKeys.EXP_Gains_Speed: case SettingKeys.EXP_Gains_Speed:
globalScene.expGainsSpeed = value; globalScene.expGainsSpeed = value;
break; break;

View File

@ -174,7 +174,7 @@ describe("Evolution", () => {
for (let f = 1; f < 4; f++) { 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 vi.spyOn(Utils, "randSeedInt").mockReturnValue(f); // setting the random generator to 1, 2 and 3 to force 4 family mausholds
const fourForm = playerPokemon.getEvolution()!; 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"
} }
}); });
}); });

View File

@ -10,6 +10,7 @@ export enum Tutorial {
Access_Menu = "ACCESS_MENU", Access_Menu = "ACCESS_MENU",
Menu = "MENU", Menu = "MENU",
Starter_Select = "STARTER_SELECT", Starter_Select = "STARTER_SELECT",
Pokedex = "POKEDEX",
Pokerus = "POKERUS", Pokerus = "POKERUS",
Stat_Change = "STAT_CHANGE", Stat_Change = "STAT_CHANGE",
Select_Item = "SELECT_ITEM", Select_Item = "SELECT_ITEM",

View File

@ -12,6 +12,8 @@ import { globalScene } from "#app/global-scene";
import SettingsDisplayUiHandler from "./ui/settings/settings-display-ui-handler"; import SettingsDisplayUiHandler from "./ui/settings/settings-display-ui-handler";
import SettingsAudioUiHandler from "./ui/settings/settings-audio-ui-handler"; import SettingsAudioUiHandler from "./ui/settings/settings-audio-ui-handler";
import RunInfoUiHandler from "./ui/run-info-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>; type ActionKeys = Record<Button, () => void>;
@ -140,7 +142,7 @@ export class UiInputs {
} }
buttonGoToFilter(button: Button): void { buttonGoToFilter(button: Button): void {
const whitelist = [ StarterSelectUiHandler ]; const whitelist = [ StarterSelectUiHandler, PokedexUiHandler, PokedexPageUiHandler ];
const uiHandler = globalScene.ui?.getHandler(); const uiHandler = globalScene.ui?.getHandler();
if (whitelist.some(handler => uiHandler instanceof handler)) { if (whitelist.some(handler => uiHandler instanceof handler)) {
globalScene.ui.processInput(button); globalScene.ui.processInput(button);
@ -178,6 +180,7 @@ export class UiInputs {
globalScene.ui.setOverlayMode(Mode.MENU); globalScene.ui.setOverlayMode(Mode.MENU);
break; break;
case Mode.STARTER_SELECT: case Mode.STARTER_SELECT:
case Mode.POKEDEX_PAGE:
this.buttonTouch(); this.buttonTouch();
break; break;
case Mode.MENU: case Mode.MENU:
@ -190,7 +193,7 @@ export class UiInputs {
} }
buttonCycleOption(button: Button): void { 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(); const uiHandler = globalScene.ui?.getHandler();
if (whitelist.some(handler => uiHandler instanceof handler)) { if (whitelist.some(handler => uiHandler instanceof handler)) {
globalScene.ui.processInput(button); globalScene.ui.processInput(button);

View File

@ -1,11 +1,12 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { TextStyle, addTextObject, getTextStyleOptions } from "./text"; import { TextStyle, addBBCodeTextObject, getTextColor, getTextStyleOptions } from "./text";
import { Mode } from "./ui"; import { Mode } from "./ui";
import UiHandler from "./ui-handler"; import UiHandler from "./ui-handler";
import { addWindow } from "./ui-theme"; import { addWindow } from "./ui-theme";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { argbFromRgba } from "@material/material-color-utilities"; import { argbFromRgba } from "@material/material-color-utilities";
import { Button } from "#enums/buttons"; import { Button } from "#enums/buttons";
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
export interface OptionSelectConfig { export interface OptionSelectConfig {
xOffset?: number; xOffset?: number;
@ -21,8 +22,10 @@ export interface OptionSelectItem {
label: string; label: string;
handler: () => boolean; handler: () => boolean;
onHover?: () => void; onHover?: () => void;
skip?: boolean;
keepOpen?: boolean; keepOpen?: boolean;
overrideSound?: boolean; overrideSound?: boolean;
style?: TextStyle;
item?: string; item?: string;
itemArgs?: any[]; itemArgs?: any[];
} }
@ -33,7 +36,7 @@ const scrollDownLabel = "↓";
export default abstract class AbstractOptionSelectUiHandler extends UiHandler { export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
protected optionSelectContainer: Phaser.GameObjects.Container; protected optionSelectContainer: Phaser.GameObjects.Container;
protected optionSelectBg: Phaser.GameObjects.NineSlice; protected optionSelectBg: Phaser.GameObjects.NineSlice;
protected optionSelectText: Phaser.GameObjects.Text; protected optionSelectText: BBCodeText;
protected optionSelectIcons: Phaser.GameObjects.Sprite[]; protected optionSelectIcons: Phaser.GameObjects.Sprite[];
protected config: OptionSelectConfig | null; protected config: OptionSelectConfig | null;
@ -41,11 +44,17 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
protected blockInput: boolean; protected blockInput: boolean;
protected scrollCursor: integer = 0; protected scrollCursor: integer = 0;
protected fullCursor: integer = 0;
protected scale: number = 0.1666666667; protected scale: number = 0.1666666667;
private cursorObj: Phaser.GameObjects.Image | null; private cursorObj: Phaser.GameObjects.Image | null;
protected unskippedIndices: number[] = [];
protected defaultTextStyle: TextStyle = TextStyle.WINDOW;
constructor(mode: Mode | null) { constructor(mode: Mode | null) {
super(mode); super(mode);
} }
@ -79,44 +88,54 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
protected setupOptions() { protected setupOptions() {
const configOptions = this.config?.options ?? []; 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 this.unskippedIndices = this.getUnskippedIndices(configOptions);
// 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;
}
if (this.optionSelectText) { if (this.optionSelectText) {
this.optionSelectText.destroy(); 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) { if (this.optionSelectIcons?.length) {
this.optionSelectIcons.map(i => i.destroy()); this.optionSelectIcons.map(i => i.destroy());
this.optionSelectIcons.splice(0, this.optionSelectIcons.length); 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 }); const optionsWithScroll = (this.config?.options && this.config?.options.length > (this.config?.maxOptions!)) ? this.getOptionsWithScroll() : options;
this.optionSelectText.setLineSpacing(this.scale * 72);
// 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.setName("text-option-select");
this.optionSelectText.setLineSpacing(12);
this.optionSelectContainer.add(this.optionSelectText); this.optionSelectContainer.add(this.optionSelectText);
this.optionSelectContainer.setPosition((globalScene.game.canvas.width / 6) - 1 - (this.config?.xOffset || 0), -48 + (this.config?.yOffset || 0)); 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()); 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.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) => { options.forEach((option: OptionSelectItem, i: integer) => {
if (option.item) { if (option.item) {
@ -160,6 +179,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
this.optionSelectContainer.setVisible(true); this.optionSelectContainer.setVisible(true);
this.scrollCursor = 0; this.scrollCursor = 0;
this.fullCursor = 0;
this.setCursor(0); this.setCursor(0);
if (this.config.delay) { 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()); 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; return true;
} }
@ -177,8 +202,6 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
let success = false; let success = false;
const options = this.getOptionsWithScroll();
let playSound = true; let playSound = true;
if (button === Button.ACTION || button === Button.CANCEL) { if (button === Button.ACTION || button === Button.CANCEL) {
@ -190,15 +213,14 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
success = true; success = true;
if (button === Button.CANCEL) { if (button === Button.CANCEL) {
if (this.config?.maxOptions && this.config.options.length > this.config.maxOptions) { if (this.config?.maxOptions && this.config.options.length > this.config.maxOptions) {
this.scrollCursor = (this.config.options.length - this.config.maxOptions) + 1; this.setCursor(this.unskippedIndices.length - 1);
this.cursor = options.length - 1;
} else if (!this.config?.noCancel) { } else if (!this.config?.noCancel) {
this.setCursor(options.length - 1); this.setCursor(this.unskippedIndices.length - 1);
} else { } else {
return false; 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?.handler()) {
if (!option.keepOpen) { if (!option.keepOpen) {
this.clear(); 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 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 // 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; 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?.handler()) {
if (!option.keepOpen) { if (!option.keepOpen) {
this.clear(); this.clear();
@ -223,15 +245,15 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
} else { } else {
switch (button) { switch (button) {
case Button.UP: case Button.UP:
if (this.cursor) { if (this.fullCursor === 0) {
success = this.setCursor(this.cursor - 1); success = this.setCursor(this.unskippedIndices.length - 1);
} else if (this.cursor === 0) { } else if (this.fullCursor) {
success = this.setCursor(options.length - 1); success = this.setCursor(this.fullCursor - 1);
} }
break; break;
case Button.DOWN: case Button.DOWN:
if (this.cursor < options.length - 1) { if (this.fullCursor < this.unskippedIndices.length - 1) {
success = this.setCursor(this.cursor + 1); success = this.setCursor(this.fullCursor + 1);
} else { } else {
success = this.setCursor(0); success = this.setCursor(0);
} }
@ -239,7 +261,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
} }
if (this.config?.supportHover) { if (this.config?.supportHover) {
// handle hover code if the element supports hover-handlers and the option has the optional hover-handler set. // 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 optionsScrollTotal = options.length;
const optionStartIndex = this.scrollCursor; 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) { if (this.config?.maxOptions && options.length > this.config.maxOptions) {
options.splice(optionEndIndex, optionsScrollTotal); options.splice(optionEndIndex, optionsScrollTotal);
@ -281,13 +305,15 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
if (optionStartIndex) { if (optionStartIndex) {
options.unshift({ options.unshift({
label: scrollUpLabel, label: scrollUpLabel,
handler: () => true handler: () => true,
style: this.defaultTextStyle
}); });
} }
if (optionEndIndex < optionsScrollTotal) { if (optionEndIndex < optionsScrollTotal) {
options.push({ options.push({
label: scrollDownLabel, label: scrollDownLabel,
handler: () => true handler: () => true,
style: this.defaultTextStyle
}); });
} }
} }
@ -295,42 +321,64 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
return options; return options;
} }
setCursor(cursor: integer): boolean { getUnskippedIndices(options: OptionSelectItem[]) {
const changed = this.cursor !== cursor; 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 (changed && this.config?.maxOptions && this.config.options.length > this.config.maxOptions) {
if (Math.abs(cursor - this.cursor) === options.length - 1) {
// Wrap around the list // If the fullCursor is the last possible value, we go to the bottom
const optionsScrollTotal = this.config.options.length; if (fullCursor === this.unskippedIndices.length - 1) {
this.scrollCursor = cursor ? optionsScrollTotal - (this.config.maxOptions - 1) : 0; this.fullCursor = fullCursor;
this.setupOptions(); 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 { } else {
// Move the cursor up or down by 1 const isDown = fullCursor && fullCursor > this.fullCursor;
const isDown = cursor && cursor > this.cursor;
if (isDown) { if (isDown) {
if (options[cursor].label === scrollDownLabel) { // If there are skipped options under the next selection, we show them
isScroll = true; const jumpFromCurrent = this.unskippedIndices[fullCursor] - this.unskippedIndices[this.fullCursor];
this.scrollCursor++; 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 { } else {
if (!cursor && this.scrollCursor) { const jumpFromPrevious = this.unskippedIndices[fullCursor] - this.unskippedIndices[fullCursor - 1];
isScroll = true;
this.scrollCursor--; 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 && this.scrollCursor === 1) {
this.scrollCursor += isDown ? 1 : -1;
}
} }
}
if (isScroll) {
this.setupOptions();
} else { } else {
this.cursor = cursor; this.fullCursor = fullCursor;
this.cursor = this.unskippedIndices[fullCursor];
} }
this.setupOptions();
if (!this.cursorObj) { if (!this.cursorObj) {
this.cursorObj = globalScene.add.image(0, 0, "cursor"); this.cursorObj = globalScene.add.image(0, 0, "cursor");
this.optionSelectContainer.add(this.cursorObj); this.optionSelectContainer.add(this.cursorObj);
@ -346,6 +394,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
super.clear(); super.clear();
this.config = null; this.config = null;
this.optionSelectContainer.setVisible(false); this.optionSelectContainer.setVisible(false);
this.fullCursor = 0;
this.scrollCursor = 0; this.scrollCursor = 0;
this.eraseCursor(); this.eraseCursor();
} }

View File

@ -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;
}
}

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { addTextObject, TextStyle } from "./text"; import { addTextObject, TextStyle } from "./text";
import { addWindow, WindowVariant } from "./ui-theme"; import { addWindow, WindowVariant } from "./ui-theme";
import { ScrollBar } from "#app/ui/scroll-bar";
import i18next from "i18next"; import i18next from "i18next";
export enum DropDownState { export enum DropDownState {
@ -293,21 +294,37 @@ export class DropDown extends Phaser.GameObjects.Container {
private onChange: () => void; private onChange: () => void;
private lastDir: SortDirection = SortDirection.ASC; private lastDir: SortDirection = SortDirection.ASC;
private defaultSettings: any[]; 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) { constructor(x: number, y: number, options: DropDownOption[], onChange: () => void, type: DropDownType = DropDownType.MULTI, optionSpacing: number = 2) {
const windowPadding = 5; const windowPadding = 5;
const optionHeight = 7;
const optionPaddingX = 4;
const optionPaddingY = 6;
const cursorOffset = 7; const cursorOffset = 7;
const optionWidth = 100;
super(globalScene, x - cursorOffset - windowPadding, y); 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.options = options;
this.dropDownType = type; this.dropDownType = type;
this.onChange = onChange; 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.setScale(0.5);
this.cursorObj.setOrigin(0, 0.5); this.cursorObj.setOrigin(0, 0.5);
this.cursorObj.setVisible(false); 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.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(); this.defaultSettings = this.getSettings();
// Place ui elements in the correct spot // Place ui elements in the correct spot
options.forEach((option, index) => { options.forEach((option, index) => {
const toggleVisibility = type !== DropDownType.SINGLE || option.state === DropDownState.ON; const toggleVisibility = type !== DropDownType.SINGLE || option.state === DropDownState.ON;
option.setupToggleIcon(type, toggleVisibility); option.setupToggleIcon(type, toggleVisibility);
option.width = optionWidth; option.width = this.optionWidth;
option.y = index * optionHeight + index * optionSpacing + optionPaddingY; option.y = index * this.optionHeight + index * optionSpacing + this.optionPaddingY;
const baseX = cursorOffset + optionPaddingX + 3; const baseX = cursorOffset + this.optionPaddingX + 3;
const baseY = optionHeight / 2; const baseY = this.optionHeight / 2;
option.setLabelPosition(baseX + 8, baseY); option.setLabelPosition(baseX + 8, baseY);
if (type === DropDownType.SINGLE) { if (type === DropDownType.SINGLE) {
option.setTogglePosition(baseX + 3, baseY + 1); option.setTogglePosition(baseX + 3, baseY + 1);
} else { } else {
option.setTogglePosition(baseX, baseY); 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(this.window);
this.add(options); this.add(options);
this.add(this.cursorObj); this.add(this.cursorObj);
this.setVisible(false); 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 { getWidth(): number {
@ -371,6 +408,11 @@ export class DropDown extends Phaser.GameObjects.Container {
} }
setCursor(cursor: integer): boolean { setCursor(cursor: integer): boolean {
if (this.tooManyOptions) {
this.setLabels(cursor);
}
this.cursor = cursor; this.cursor = cursor;
if (cursor < 0) { if (cursor < 0) {
cursor = 0; cursor = 0;
@ -393,6 +435,41 @@ export class DropDown extends Phaser.GameObjects.Container {
return true; 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 * Switch the option at the provided index to its next state and update visuals
* Update accordingly the other options if needed: * Update accordingly the other options if needed:
@ -597,7 +674,12 @@ export class DropDown extends Phaser.GameObjects.Container {
x = this.options[i].getCurrentLabelX() ?? 0; 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) { if (this.x + this.window.width > this.parentContainer.width) {
this.x = this.parentContainer.width - this.window.width; this.x = this.parentContainer.width - this.window.width;

View File

@ -9,6 +9,7 @@ import { globalScene } from "#app/global-scene";
export enum DropDownColumn { export enum DropDownColumn {
GEN, GEN,
TYPES, TYPES,
BIOME,
CAUGHT, CAUGHT,
UNLOCKS, UNLOCKS,
MISC, MISC,
@ -25,13 +26,20 @@ export class FilterBar extends Phaser.GameObjects.Container {
public openDropDown: boolean = false; public openDropDown: boolean = false;
private lastCursor: number = -1; private lastCursor: number = -1;
private uiTheme: UiTheme; 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); super(globalScene, x, y);
this.width = width; this.width = width;
this.height = height; 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.window = addWindow(0, 0, width, height, false, false, undefined, undefined, WindowVariant.THIN);
this.add(this.window); this.add(this.window);
@ -40,8 +48,6 @@ export class FilterBar extends Phaser.GameObjects.Container {
this.cursorObj.setVisible(false); this.cursorObj.setVisible(false);
this.cursorObj.setOrigin(0, 0); this.cursorObj.setOrigin(0, 0);
this.add(this.cursorObj); this.add(this.cursorObj);
this.uiTheme = globalScene.uiTheme;
} }
/** /**
@ -86,9 +92,9 @@ export class FilterBar extends Phaser.GameObjects.Container {
updateFilterLabels(): void { updateFilterLabels(): void {
for (let i = 0; i < this.numFilters; i++) { for (let i = 0; i < this.numFilters; i++) {
if (this.dropDowns[i].hasDefaultValues()) { 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 { } 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 * Position the filter dropdowns evenly across the width of the container
*/ */
private calcFilterPositions(): void { 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 => { this.labels.forEach(label => {
totalWidth += label.displayWidth + cursorOffset; totalWidth += label.displayWidth + this.cursorOffset;
}); });
const spacing = (this.width - totalWidth) / (this.labels.length - 1); const spacing = (this.width - totalWidth) / (this.labels.length - 1);
for (let i = 0; i < this.labels.length; i++) { for (let i = 0; i < this.labels.length; i++) {
if (i === 0) { if (i === 0) {
this.labels[i].x = paddingX + cursorOffset; this.labels[i].x = this.leftPaddingX + this.cursorOffset;
} else { } else {
const lastRight = this.labels[i - 1].x + this.labels[i - 1].displayWidth; 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; 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 - this.cursorOffset + 2, 6);
this.cursorObj.setPosition(this.labels[cursor].x - cursorOffset + 2, 6);
this.lastCursor = cursor; this.lastCursor = cursor;
} }

230
src/ui/filter-text.ts Normal file
View File

@ -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;
}
}

View File

@ -24,6 +24,7 @@ enum MenuOptions {
RUN_HISTORY, RUN_HISTORY,
EGG_LIST, EGG_LIST,
EGG_GACHA, EGG_GACHA,
POKEDEX,
MANAGE_DATA, MANAGE_DATA,
COMMUNITY, COMMUNITY,
SAVE_AND_QUIT, SAVE_AND_QUIT,
@ -527,6 +528,11 @@ export default class MenuUiHandler extends MessageUiHandler {
ui.setOverlayMode(Mode.EGG_GACHA); ui.setOverlayMode(Mode.EGG_GACHA);
success = true; success = true;
break; break;
case MenuOptions.POKEDEX:
ui.revertMode();
ui.setOverlayMode(Mode.POKEDEX);
success = true;
break;
case MenuOptions.MANAGE_DATA: case MenuOptions.MANAGE_DATA:
if (!bypassLogin && !this.manageDataConfig.options.some(o => o.label === i18next.t("menuUiHandler:linkDiscord") || o.label === i18next.t("menuUiHandler:unlinkDiscord"))) { 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, this.manageDataConfig.options.splice(this.manageDataConfig.options.length - 1, 0,

View File

@ -8,7 +8,7 @@ import { Mode } from "#app/ui/ui";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
import { PokemonFormChangeItemModifier, PokemonHeldItemModifier, SwitchEffectTransferModifier } from "#app/modifier/modifier"; import { PokemonFormChangeItemModifier, PokemonHeldItemModifier, SwitchEffectTransferModifier } from "#app/modifier/modifier";
import { allMoves, ForceSwitchOutAttr } from "#app/data/move"; 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 { StatusEffect } from "#enums/status-effect";
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler";
import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
@ -109,6 +109,7 @@ export enum PartyOption {
TEACH, TEACH,
TRANSFER, TRANSFER,
SUMMARY, SUMMARY,
POKEDEX,
UNPAUSE_EVOLUTION, UNPAUSE_EVOLUTION,
SPLICE, SPLICE,
UNSPLICE, UNSPLICE,
@ -218,7 +219,7 @@ export default class PartyUiHandler extends MessageUiHandler {
public static NoEffectMessage = i18next.t("partyUiHandler:anyEffect"); 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() { constructor() {
super(Mode.PARTY); super(Mode.PARTY);
@ -397,7 +398,7 @@ export default class PartyUiHandler extends MessageUiHandler {
} }
ui.playSelect(); ui.playSelect();
return true; 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)) { || (option === PartyOption.RELEASE && this.partyUiMode === PartyUiMode.RELEASE)) {
let filterResult: string | null; let filterResult: string | null;
const getTransferrableItemsFromPokemon = (pokemon: PlayerPokemon) => const getTransferrableItemsFromPokemon = (pokemon: PlayerPokemon) =>
@ -466,6 +467,16 @@ export default class PartyUiHandler extends MessageUiHandler {
ui.playSelect(); ui.playSelect();
ui.setModeWithoutClear(Mode.SUMMARY, pokemon).then(() => this.clearOptions()); ui.setModeWithoutClear(Mode.SUMMARY, pokemon).then(() => this.clearOptions());
return true; 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) { } else if (option === PartyOption.UNPAUSE_EVOLUTION) {
this.clearOptions(); this.clearOptions();
ui.playSelect(); ui.playSelect();
@ -892,6 +903,7 @@ export default class PartyUiHandler extends MessageUiHandler {
} }
this.options.push(PartyOption.SUMMARY); this.options.push(PartyOption.SUMMARY);
this.options.push(PartyOption.POKEDEX);
this.options.push(PartyOption.RENAME); this.options.push(PartyOption.RENAME);
if ((pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) || (pokemon.isFusion() && pokemon.fusionSpecies && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId)))) { if ((pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) || (pokemon.isFusion() && pokemon.fusionSpecies && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId)))) {

View File

@ -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;
}
}

View File

@ -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

View File

@ -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();
});
}
}

1907
src/ui/pokedex-ui-handler.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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({ options.push({
label: i18next.t("menu:cancel"), label: i18next.t("menu:cancel"),
handler: () => { handler: () => {

View File

@ -42,6 +42,7 @@ export enum TextStyle {
PERFECT_IV, PERFECT_IV,
ME_OPTION_DEFAULT, // Default style for choices in ME ME_OPTION_DEFAULT, // Default style for choices in ME
ME_OPTION_SPECIAL, // Style for choices with special requirements in ME ME_OPTION_SPECIAL, // Style for choices with special requirements in ME
SHADOW_TEXT // To obscure unavailable options
} }
export interface TextStyleOptions { export interface TextStyleOptions {
@ -359,6 +360,12 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui
return !shadow ? "#f8b050" : "#c07800"; // Gold return !shadow ? "#f8b050" : "#c07800"; // Gold
} }
return !shadow ? "#78c850" : "#306850"; // Green 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";
} }
} }

View File

@ -23,6 +23,7 @@ import OptionSelectUiHandler from "./settings/option-select-ui-handler";
import EggHatchSceneHandler from "./egg-hatch-scene-handler"; import EggHatchSceneHandler from "./egg-hatch-scene-handler";
import EggListUiHandler from "./egg-list-ui-handler"; import EggListUiHandler from "./egg-list-ui-handler";
import EggGachaUiHandler from "./egg-gacha-ui-handler"; import EggGachaUiHandler from "./egg-gacha-ui-handler";
import PokedexUiHandler from "./pokedex-ui-handler";
import { addWindow } from "./ui-theme"; import { addWindow } from "./ui-theme";
import LoginFormUiHandler from "./login-form-ui-handler"; import LoginFormUiHandler from "./login-form-ui-handler";
import RegistrationFormUiHandler from "./registration-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 AutoCompleteUiHandler from "./autocomplete-ui-handler";
import { Device } from "#enums/devices"; import { Device } from "#enums/devices";
import MysteryEncounterUiHandler from "./mystery-encounter-ui-handler"; 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"; import { NavigationManager } from "./settings/navigationMenu";
export enum Mode { export enum Mode {
@ -85,6 +88,9 @@ export enum Mode {
GAME_STATS, GAME_STATS,
EGG_LIST, EGG_LIST,
EGG_GACHA, EGG_GACHA,
POKEDEX,
POKEDEX_SCAN,
POKEDEX_PAGE,
LOGIN_FORM, LOGIN_FORM,
REGISTRATION_FORM, REGISTRATION_FORM,
LOADING, LOADING,
@ -109,6 +115,8 @@ const transitionModes = [
Mode.EGG_HATCH_SCENE, Mode.EGG_HATCH_SCENE,
Mode.EGG_LIST, Mode.EGG_LIST,
Mode.EGG_GACHA, Mode.EGG_GACHA,
Mode.POKEDEX,
Mode.POKEDEX_PAGE,
Mode.CHALLENGE_SELECT, Mode.CHALLENGE_SELECT,
Mode.RUN_HISTORY, Mode.RUN_HISTORY,
]; ];
@ -128,6 +136,7 @@ const noTransitionModes = [
Mode.SETTINGS_KEYBOARD, Mode.SETTINGS_KEYBOARD,
Mode.ACHIEVEMENTS, Mode.ACHIEVEMENTS,
Mode.GAME_STATS, Mode.GAME_STATS,
Mode.POKEDEX_SCAN,
Mode.LOGIN_FORM, Mode.LOGIN_FORM,
Mode.REGISTRATION_FORM, Mode.REGISTRATION_FORM,
Mode.LOADING, Mode.LOADING,
@ -193,6 +202,9 @@ export default class UI extends Phaser.GameObjects.Container {
new GameStatsUiHandler(), new GameStatsUiHandler(),
new EggListUiHandler(), new EggListUiHandler(),
new EggGachaUiHandler(), new EggGachaUiHandler(),
new PokedexUiHandler(),
new PokedexScanUiHandler(Mode.TEST_DIALOGUE),
new PokedexPageUiHandler(),
new LoginFormUiHandler(), new LoginFormUiHandler(),
new RegistrationFormUiHandler(), new RegistrationFormUiHandler(),
new LoadingModalUiHandler(), new LoadingModalUiHandler(),
@ -558,6 +570,7 @@ export default class UI extends Phaser.GameObjects.Container {
revertMode(): Promise<boolean> { revertMode(): Promise<boolean> {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
if (!this?.modeChain?.length) { if (!this?.modeChain?.length) {
return resolve(false); return resolve(false);
} }