[UI] Make Egg List and Egg Summary scrollable (#4391)
This commit is contained in:
parent
06331ccdf6
commit
a25ccbcde6
|
@ -1,16 +1,21 @@
|
|||
import BattleScene from "../battle-scene";
|
||||
import { Mode } from "./ui";
|
||||
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler";
|
||||
import { TextStyle, addTextObject } from "./text";
|
||||
import MessageUiHandler from "./message-ui-handler";
|
||||
import { Egg } from "../data/egg";
|
||||
import { addWindow } from "./ui-theme";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler";
|
||||
import { TextStyle, addTextObject } from "#app/ui/text";
|
||||
import MessageUiHandler from "#app/ui/message-ui-handler";
|
||||
import { addWindow } from "#app/ui/ui-theme";
|
||||
import {Button} from "#enums/buttons";
|
||||
import i18next from "i18next";
|
||||
import ScrollableGridUiHandler from "#app/ui/scrollable-grid-handler";
|
||||
import { ScrollBar } from "#app/ui/scroll-bar";
|
||||
|
||||
export default class EggListUiHandler extends MessageUiHandler {
|
||||
private readonly ROWS = 9;
|
||||
private readonly COLUMNS = 11;
|
||||
|
||||
private eggListContainer: Phaser.GameObjects.Container;
|
||||
private eggListIconContainer: Phaser.GameObjects.Container;
|
||||
private eggIcons: Phaser.GameObjects.Sprite[];
|
||||
private eggSprite: Phaser.GameObjects.Sprite;
|
||||
private eggNameText: Phaser.GameObjects.Text;
|
||||
private eggDateText: Phaser.GameObjects.Text;
|
||||
|
@ -19,6 +24,7 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||
private eggListMessageBoxContainer: Phaser.GameObjects.Container;
|
||||
|
||||
private cursorObj: Phaser.GameObjects.Image;
|
||||
private scrollGridHandler : ScrollableGridUiHandler;
|
||||
|
||||
private iconAnimHandler: PokemonIconAnimHandler;
|
||||
|
||||
|
@ -64,7 +70,7 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||
this.eggGachaInfoText.setWordWrapWidth(540);
|
||||
this.eggListContainer.add(this.eggGachaInfoText);
|
||||
|
||||
this.eggListIconContainer = this.scene.add.container(115, 9);
|
||||
this.eggListIconContainer = this.scene.add.container(113, 5);
|
||||
this.eggListContainer.add(this.eggListIconContainer);
|
||||
|
||||
this.cursorObj = this.scene.add.image(0, 0, "select_cursor");
|
||||
|
@ -74,6 +80,14 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||
this.eggSprite = this.scene.add.sprite(54, 37, "egg");
|
||||
this.eggListContainer.add(this.eggSprite);
|
||||
|
||||
const scrollBar = new ScrollBar(this.scene, 310, 5, 4, 170, this.ROWS);
|
||||
this.eggListContainer.add(scrollBar);
|
||||
|
||||
this.scrollGridHandler = new ScrollableGridUiHandler(this, this.ROWS, this.COLUMNS)
|
||||
.withScrollBar(scrollBar)
|
||||
.withUpdateGridCallBack(() => this.updateEggIcons())
|
||||
.withUpdateSingleElementCallback((i:number) => this.setEggDetails(i));
|
||||
|
||||
this.eggListMessageBoxContainer = this.scene.add.container(0, this.scene.game.canvas.height / 6);
|
||||
this.eggListMessageBoxContainer.setVisible(false);
|
||||
this.eggListContainer.add(this.eggListMessageBoxContainer);
|
||||
|
@ -92,76 +106,63 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||
show(args: any[]): boolean {
|
||||
super.show(args);
|
||||
|
||||
this.initEggIcons();
|
||||
|
||||
this.getUi().bringToTop(this.eggListContainer);
|
||||
|
||||
this.eggListContainer.setVisible(true);
|
||||
|
||||
let e = 0;
|
||||
|
||||
for (const egg of this.scene.gameData.eggs) {
|
||||
const x = (e % 11) * 18;
|
||||
const y = Math.floor(e / 11) * 18;
|
||||
const icon = this.scene.add.sprite(x - 2, y + 2, "egg_icons");
|
||||
icon.setScale(0.5);
|
||||
icon.setOrigin(0, 0);
|
||||
icon.setFrame(egg.getKey());
|
||||
this.eggListIconContainer.add(icon);
|
||||
this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.NONE);
|
||||
e++;
|
||||
}
|
||||
this.scrollGridHandler.setTotalElements(this.scene.gameData.eggs.length);
|
||||
|
||||
this.updateEggIcons();
|
||||
this.setCursor(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
processInput(button: Button): boolean {
|
||||
const ui = this.getUi();
|
||||
|
||||
let success = false;
|
||||
const error = false;
|
||||
|
||||
if (button === Button.CANCEL) {
|
||||
ui.revertMode();
|
||||
success = true;
|
||||
} else {
|
||||
const eggCount = this.eggListIconContainer.getAll().length;
|
||||
const rows = Math.ceil(eggCount / 11);
|
||||
const row = Math.floor(this.cursor / 11);
|
||||
switch (button) {
|
||||
case Button.UP:
|
||||
if (row) {
|
||||
success = this.setCursor(this.cursor - 11);
|
||||
}
|
||||
break;
|
||||
case Button.DOWN:
|
||||
if (row < rows - 2 || (row < rows - 1 && this.cursor % 11 <= (eggCount - 1) % 11)) {
|
||||
success = this.setCursor(this.cursor + 11);
|
||||
}
|
||||
break;
|
||||
case Button.LEFT:
|
||||
if (this.cursor % 11) {
|
||||
success = this.setCursor(this.cursor - 1);
|
||||
}
|
||||
break;
|
||||
case Button.RIGHT:
|
||||
if (this.cursor % 11 < (row < rows - 1 ? 10 : (eggCount - 1) % 11)) {
|
||||
success = this.setCursor(this.cursor + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Create the grid of egg icons to display
|
||||
*/
|
||||
private initEggIcons() {
|
||||
this.eggIcons = [];
|
||||
for (let i = 0; i < Math.min(this.ROWS * this.COLUMNS, this.scene.gameData.eggs.length); i++) {
|
||||
const x = (i % this.COLUMNS) * 18;
|
||||
const y = Math.floor(i / this.COLUMNS) * 18;
|
||||
const icon = this.scene.add.sprite(x - 2, y + 2, "egg_icons");
|
||||
icon.setScale(0.5);
|
||||
icon.setOrigin(0, 0);
|
||||
this.eggListIconContainer.add(icon);
|
||||
this.eggIcons.push(icon);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
ui.playSelect();
|
||||
} else if (error) {
|
||||
ui.playError();
|
||||
}
|
||||
|
||||
return success || error;
|
||||
}
|
||||
|
||||
setEggDetails(egg: Egg): void {
|
||||
/**
|
||||
* Show the grid of egg icons
|
||||
*/
|
||||
private updateEggIcons() {
|
||||
const indexOffset = this.scrollGridHandler.getItemOffset();
|
||||
const eggsToShow = Math.min(this.eggIcons.length, this.scene.gameData.eggs.length - indexOffset);
|
||||
|
||||
this.eggIcons.forEach((icon, i) => {
|
||||
if (i !== this.cursor) {
|
||||
this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.NONE);
|
||||
}
|
||||
if (i < eggsToShow) {
|
||||
const egg = this.scene.gameData.eggs[i + indexOffset];
|
||||
icon.setFrame(egg.getKey());
|
||||
icon.setVisible(true);
|
||||
} else {
|
||||
icon.setVisible(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the information panel with the information of the given egg
|
||||
* @param index which egg in the list to display the info for
|
||||
*/
|
||||
private setEggDetails(index: number): void {
|
||||
const egg = this.scene.gameData.eggs[index];
|
||||
this.eggSprite.setFrame(`egg_${egg.getKey()}`);
|
||||
this.eggNameText.setText(`${i18next.t("egg:egg")} (${egg.getEggDescriptor()})`);
|
||||
this.eggDateText.setText(
|
||||
|
@ -176,7 +177,29 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||
this.eggGachaInfoText.setText(egg.getEggTypeDescriptor(this.scene));
|
||||
}
|
||||
|
||||
setCursor(cursor: integer): boolean {
|
||||
processInput(button: Button): boolean {
|
||||
const ui = this.getUi();
|
||||
|
||||
let success = false;
|
||||
const error = false;
|
||||
|
||||
if (button === Button.CANCEL) {
|
||||
ui.revertMode();
|
||||
success = true;
|
||||
} else {
|
||||
success = this.scrollGridHandler.processInput(button);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
ui.playSelect();
|
||||
} else if (error) {
|
||||
ui.playError();
|
||||
}
|
||||
|
||||
return success || error;
|
||||
}
|
||||
|
||||
setCursor(cursor: number): boolean {
|
||||
let changed = false;
|
||||
|
||||
const lastCursor = this.cursor;
|
||||
|
@ -184,14 +207,15 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||
changed = super.setCursor(cursor);
|
||||
|
||||
if (changed) {
|
||||
this.cursorObj.setPosition(114 + 18 * (cursor % 11), 10 + 18 * Math.floor(cursor / 11));
|
||||
const icon = this.eggIcons[cursor];
|
||||
this.cursorObj.setPositionRelative(icon, 114, 5);
|
||||
|
||||
if (lastCursor > -1) {
|
||||
this.iconAnimHandler.addOrUpdate(this.eggListIconContainer.getAt(lastCursor) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.NONE);
|
||||
this.iconAnimHandler.addOrUpdate(this.eggIcons[lastCursor], PokemonIconAnimMode.NONE);
|
||||
}
|
||||
this.iconAnimHandler.addOrUpdate(this.eggListIconContainer.getAt(cursor) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.ACTIVE);
|
||||
this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.ACTIVE);
|
||||
|
||||
this.setEggDetails(this.scene.gameData.eggs[cursor]);
|
||||
this.setEggDetails(cursor + this.scrollGridHandler.getItemOffset());
|
||||
}
|
||||
|
||||
return changed;
|
||||
|
@ -199,9 +223,11 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||
|
||||
clear(): void {
|
||||
super.clear();
|
||||
this.scrollGridHandler.reset();
|
||||
this.cursor = -1;
|
||||
this.eggListContainer.setVisible(false);
|
||||
this.iconAnimHandler.removeAll();
|
||||
this.eggListIconContainer.removeAll(true);
|
||||
this.eggIcons = [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,17 +3,17 @@ import { Mode } from "./ui";
|
|||
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler";
|
||||
import MessageUiHandler from "./message-ui-handler";
|
||||
import { getEggTierForSpecies } from "../data/egg";
|
||||
import {Button} from "#enums/buttons";
|
||||
import { Gender } from "#app/data/gender";
|
||||
import { getVariantTint } from "#app/data/variant";
|
||||
import { EggTier } from "#app/enums/egg-type";
|
||||
import { Button } from "#enums/buttons";
|
||||
import PokemonHatchInfoContainer from "./pokemon-hatch-info-container";
|
||||
import { EggSummaryPhase } from "#app/phases/egg-summary-phase";
|
||||
import { DexAttr } from "#app/system/game-data";
|
||||
import { EggHatchData } from "#app/data/egg-hatch-data";
|
||||
import ScrollableGridUiHandler from "./scrollable-grid-handler";
|
||||
import { HatchedPokemonContainer } from "./hatched-pokemon-container";
|
||||
import { ScrollBar } from "#app/ui/scroll-bar";
|
||||
|
||||
const iconContainerX = 115;
|
||||
const iconContainerX = 112;
|
||||
const iconContainerY = 9;
|
||||
const numRows = 9;
|
||||
const numCols = 11;
|
||||
const iconSize = 18;
|
||||
|
||||
|
@ -27,20 +27,20 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||
private eggHatchContainer: Phaser.GameObjects.Container;
|
||||
/** holds the icon containers and info container */
|
||||
private summaryContainer: Phaser.GameObjects.Container;
|
||||
/** container for the mini pokemon sprites */
|
||||
private pokemonIconSpritesContainer: Phaser.GameObjects.Container;
|
||||
/** container for the icons displayed on top of the mini pokemon sprites (e.g. shiny, HA capsule) */
|
||||
/** container for the each pokemon sprites and icons */
|
||||
private pokemonIconsContainer: Phaser.GameObjects.Container;
|
||||
/** container for the elements displayed behind the mini pokemon sprites (e.g. egg rarity bg) */
|
||||
private pokemonBackgroundContainer: Phaser.GameObjects.Container;
|
||||
/** list of the containers added to pokemonIconsContainer for easier access */
|
||||
private pokemonContainers: HatchedPokemonContainer[];
|
||||
|
||||
/** hatch info container that displays the current pokemon / hatch (main element on left hand side) */
|
||||
private infoContainer: PokemonHatchInfoContainer;
|
||||
/** handles jumping animations for the pokemon sprite icons */
|
||||
private iconAnimHandler: PokemonIconAnimHandler;
|
||||
private eggHatchBg: Phaser.GameObjects.Image;
|
||||
private cursorObj: Phaser.GameObjects.Image;
|
||||
private eggHatchData: EggHatchData[];
|
||||
|
||||
private scrollGridHandler : ScrollableGridUiHandler;
|
||||
private cursorObj: Phaser.GameObjects.Image;
|
||||
|
||||
/**
|
||||
* Allows subscribers to listen for events
|
||||
|
@ -54,7 +54,6 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||
super(scene, Mode.EGG_HATCH_SUMMARY);
|
||||
}
|
||||
|
||||
|
||||
setup() {
|
||||
const ui = this.getUi();
|
||||
|
||||
|
@ -77,11 +76,8 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||
this.cursorObj.setOrigin(0, 0);
|
||||
this.summaryContainer.add(this.cursorObj);
|
||||
|
||||
this.pokemonIconSpritesContainer = this.scene.add.container(iconContainerX, iconContainerY);
|
||||
this.pokemonContainers = [];
|
||||
this.pokemonIconsContainer = this.scene.add.container(iconContainerX, iconContainerY);
|
||||
this.pokemonBackgroundContainer = this.scene.add.container(iconContainerX, iconContainerY);
|
||||
this.summaryContainer.add(this.pokemonBackgroundContainer);
|
||||
this.summaryContainer.add(this.pokemonIconSpritesContainer);
|
||||
this.summaryContainer.add(this.pokemonIconsContainer);
|
||||
|
||||
this.infoContainer = new PokemonHatchInfoContainer(this.scene, this.summaryContainer);
|
||||
|
@ -90,16 +86,24 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||
this.infoContainer.setVisible(true);
|
||||
this.summaryContainer.add(this.infoContainer);
|
||||
|
||||
const scrollBar = new ScrollBar(this.scene, iconContainerX + numCols * iconSize, iconContainerY + 3, 4, this.scene.game.canvas.height / 6 - 20, numRows);
|
||||
this.summaryContainer.add(scrollBar);
|
||||
|
||||
this.scrollGridHandler = new ScrollableGridUiHandler(this, numRows, numCols)
|
||||
.withScrollBar(scrollBar)
|
||||
.withUpdateGridCallBack(() => this.updatePokemonIcons())
|
||||
.withUpdateSingleElementCallback((i: number) => this.infoContainer.showHatchInfo(this.eggHatchData[i]));
|
||||
|
||||
this.cursor = -1;
|
||||
}
|
||||
|
||||
clear() {
|
||||
super.clear();
|
||||
this.cursor = -1;
|
||||
this.scrollGridHandler.reset();
|
||||
this.summaryContainer.setVisible(false);
|
||||
this.pokemonIconSpritesContainer.removeAll(true);
|
||||
this.pokemonIconsContainer.removeAll(true);
|
||||
this.pokemonBackgroundContainer.removeAll(true);
|
||||
this.pokemonContainers = [];
|
||||
this.eggHatchBg.setVisible(false);
|
||||
this.getUi().hideTooltip();
|
||||
|
||||
|
@ -149,111 +153,51 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this.getUi().bringToTop(this.summaryContainer);
|
||||
this.summaryContainer.setVisible(true);
|
||||
this.eggHatchContainer.setVisible(true);
|
||||
this.pokemonIconsContainer.setVisible(true);
|
||||
this.eggHatchBg.setVisible(true);
|
||||
this.infoContainer.hideDisplayPokemon();
|
||||
|
||||
this.eggHatchData.forEach( (value: EggHatchData, i: number) => {
|
||||
const x = (i % numCols) * iconSize;
|
||||
const y = Math.floor(i / numCols) * iconSize;
|
||||
|
||||
const displayPokemon = value.pokemon;
|
||||
const offset = 2;
|
||||
const rightSideX = 12;
|
||||
|
||||
const rarityBg = this.scene.add.image(x + 2, y + 5, "passive_bg");
|
||||
rarityBg.setOrigin(0, 0);
|
||||
rarityBg.setScale(0.75);
|
||||
rarityBg.setVisible(true);
|
||||
this.pokemonBackgroundContainer.add(rarityBg);
|
||||
|
||||
// set tint for passive bg
|
||||
switch (getEggTierForSpecies(displayPokemon.species)) {
|
||||
case EggTier.COMMON:
|
||||
rarityBg.setVisible(false);
|
||||
break;
|
||||
case EggTier.GREAT:
|
||||
rarityBg.setTint(0xabafff);
|
||||
break;
|
||||
case EggTier.ULTRA:
|
||||
rarityBg.setTint(0xffffaa);
|
||||
break;
|
||||
case EggTier.MASTER:
|
||||
rarityBg.setTint(0xdfffaf);
|
||||
break;
|
||||
}
|
||||
const species = displayPokemon.species;
|
||||
const female = displayPokemon.gender === Gender.FEMALE;
|
||||
const formIndex = displayPokemon.formIndex;
|
||||
const variant = displayPokemon.variant;
|
||||
const isShiny = displayPokemon.shiny;
|
||||
|
||||
// set pokemon icon (and replace with base sprite if there is a mismatch)
|
||||
const pokemonIcon = this.scene.add.sprite(x - offset, y + offset, species.getIconAtlasKey(formIndex, isShiny, variant));
|
||||
pokemonIcon.setScale(0.5);
|
||||
pokemonIcon.setOrigin(0, 0);
|
||||
pokemonIcon.setFrame(species.getIconId(female, formIndex, isShiny, variant));
|
||||
|
||||
if (pokemonIcon.frame.name !== species.getIconId(female, formIndex, isShiny, variant)) {
|
||||
console.log(`${species.name}'s variant icon does not exist. Replacing with default.`);
|
||||
pokemonIcon.setTexture(species.getIconAtlasKey(formIndex, false, variant));
|
||||
pokemonIcon.setFrame(species.getIconId(female, formIndex, false, variant));
|
||||
}
|
||||
this.pokemonIconSpritesContainer.add(pokemonIcon);
|
||||
|
||||
const shinyIcon = this.scene.add.image(x + rightSideX, y + offset, "shiny_star_small");
|
||||
shinyIcon.setOrigin(0, 0);
|
||||
shinyIcon.setScale(0.5);
|
||||
shinyIcon.setVisible(displayPokemon.shiny);
|
||||
shinyIcon.setTint(getVariantTint(displayPokemon.variant));
|
||||
this.pokemonIconsContainer.add(shinyIcon);
|
||||
|
||||
const haIcon = this.scene.add.image(x + rightSideX, y + offset * 4, "ha_capsule");
|
||||
haIcon.setOrigin(0, 0);
|
||||
haIcon.setScale(0.5);
|
||||
haIcon.setVisible(displayPokemon.abilityIndex === 2);
|
||||
this.pokemonIconsContainer.add(haIcon);
|
||||
|
||||
const dexEntry = value.dexEntryBeforeUpdate;
|
||||
const caughtAttr = dexEntry.caughtAttr;
|
||||
const newShiny = BigInt(1 << (displayPokemon.shiny ? 1 : 0));
|
||||
const newVariant = BigInt(1 << (displayPokemon.variant + 4));
|
||||
const newShinyOrVariant = ((newShiny & caughtAttr) === BigInt(0)) || ((newVariant & caughtAttr) === BigInt(0));
|
||||
const newForm = (BigInt(1 << displayPokemon.formIndex) * DexAttr.DEFAULT_FORM & caughtAttr) === BigInt(0);
|
||||
|
||||
const pokeballIcon = this.scene.add.image(x + rightSideX, y + offset * 7, "icon_owned");
|
||||
pokeballIcon.setOrigin(0, 0);
|
||||
pokeballIcon.setScale(0.5);
|
||||
pokeballIcon.setVisible(!caughtAttr || newForm);
|
||||
this.pokemonIconsContainer.add(pokeballIcon);
|
||||
|
||||
const eggMoveIcon = this.scene.add.image(x, y + offset, "icon_egg_move");
|
||||
eggMoveIcon.setOrigin(0, 0);
|
||||
eggMoveIcon.setScale(0.5);
|
||||
eggMoveIcon.setVisible(value.eggMoveUnlocked);
|
||||
this.pokemonIconsContainer.add(eggMoveIcon);
|
||||
|
||||
// add animation to the Pokemon sprite for new unlocks (new catch, new shiny or new form)
|
||||
if (!caughtAttr || newShinyOrVariant || newForm) {
|
||||
this.iconAnimHandler.addOrUpdate(pokemonIcon, PokemonIconAnimMode.PASSIVE);
|
||||
} else {
|
||||
this.iconAnimHandler.addOrUpdate(pokemonIcon, PokemonIconAnimMode.NONE);
|
||||
}
|
||||
});
|
||||
this.scrollGridHandler.setTotalElements(this.eggHatchData.length);
|
||||
this.updatePokemonIcons();
|
||||
|
||||
this.setCursor(0);
|
||||
this.scene.playSoundWithoutBgm("evolution_fanfare");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the grid of Pokemon icons
|
||||
*/
|
||||
private updatePokemonIcons(): void {
|
||||
const itemOffset = this.scrollGridHandler.getItemOffset();
|
||||
const eggsToShow = Math.min(numRows * numCols, this.eggHatchData.length - itemOffset);
|
||||
|
||||
for (let i = 0; i < numRows * numCols; i++) {
|
||||
const hatchData = this.eggHatchData[i + itemOffset];
|
||||
let hatchContainer = this.pokemonContainers[i];
|
||||
|
||||
if (i < eggsToShow) {
|
||||
if (!hatchContainer) {
|
||||
const x = (i % numCols) * iconSize;
|
||||
const y = Math.floor(i / numCols) * iconSize;
|
||||
hatchContainer = new HatchedPokemonContainer(this.scene, x, y, hatchData).setVisible(false);
|
||||
this.pokemonContainers.push(hatchContainer);
|
||||
this.pokemonIconsContainer.add(hatchContainer);
|
||||
}
|
||||
hatchContainer.setVisible(true);
|
||||
hatchContainer.updateAndAnimate(hatchData, this.iconAnimHandler);
|
||||
} else if (hatchContainer) {
|
||||
hatchContainer.setVisible(false);
|
||||
this.iconAnimHandler.addOrUpdate(hatchContainer.icon, PokemonIconAnimMode.NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processInput(button: Button): boolean {
|
||||
const ui = this.getUi();
|
||||
|
||||
|
@ -266,31 +210,7 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||
}
|
||||
success = true;
|
||||
} else {
|
||||
const count = this.eggHatchData.length;
|
||||
const rows = Math.ceil(count / numCols);
|
||||
const row = Math.floor(this.cursor / numCols);
|
||||
switch (button) {
|
||||
case Button.UP:
|
||||
if (row) {
|
||||
success = this.setCursor(this.cursor - numCols);
|
||||
}
|
||||
break;
|
||||
case Button.DOWN:
|
||||
if (row < rows - 2 || (row < rows - 1 && this.cursor % numCols <= (count - 1) % numCols)) {
|
||||
success = this.setCursor(this.cursor + numCols);
|
||||
}
|
||||
break;
|
||||
case Button.LEFT:
|
||||
if (this.cursor % numCols) {
|
||||
success = this.setCursor(this.cursor - 1);
|
||||
}
|
||||
break;
|
||||
case Button.RIGHT:
|
||||
if (this.cursor % numCols < (row < rows - 1 ? 10 : (count - 1) % numCols)) {
|
||||
success = this.setCursor(this.cursor + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
this.scrollGridHandler.processInput(button);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
|
@ -313,12 +233,11 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||
this.cursorObj.setPosition(iconContainerX - 1 + iconSize * (cursor % numCols), iconContainerY + 1 + iconSize * Math.floor(cursor / numCols));
|
||||
|
||||
if (lastCursor > -1) {
|
||||
this.iconAnimHandler.addOrUpdate(this.pokemonIconSpritesContainer.getAt(lastCursor) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.NONE);
|
||||
this.iconAnimHandler.addOrUpdate(this.pokemonContainers[lastCursor].icon, PokemonIconAnimMode.NONE);
|
||||
}
|
||||
this.iconAnimHandler.addOrUpdate(this.pokemonIconSpritesContainer.getAt(cursor) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.ACTIVE);
|
||||
|
||||
this.infoContainer.showHatchInfo(this.eggHatchData[cursor]);
|
||||
this.iconAnimHandler.addOrUpdate(this.pokemonContainers[cursor].icon, PokemonIconAnimMode.ACTIVE);
|
||||
|
||||
this.infoContainer.showHatchInfo(this.eggHatchData[cursor + this.scrollGridHandler.getItemOffset()]);
|
||||
}
|
||||
|
||||
return changed;
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
import { EggHatchData } from "#app/data/egg-hatch-data";
|
||||
import { Gender } from "#app/data/gender";
|
||||
import { getVariantTint } from "#app/data/variant";
|
||||
import { DexAttr } from "#app/system/game-data";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import PokemonSpecies from "#app/data/pokemon-species";
|
||||
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler";
|
||||
|
||||
/**
|
||||
* A container for a Pokemon's sprite and icons to get displayed in the egg summary screen
|
||||
* Shows the Pokemon's sprite, surrounded by icons for:
|
||||
* shiny variant, hidden ability, new egg move, new catch
|
||||
*/
|
||||
export class HatchedPokemonContainer extends Phaser.GameObjects.Container {
|
||||
public scene: BattleScene;
|
||||
public species: PokemonSpecies;
|
||||
public icon: Phaser.GameObjects.Sprite;
|
||||
public shinyIcon: Phaser.GameObjects.Image;
|
||||
public hiddenAbilityIcon: Phaser.GameObjects.Image;
|
||||
public pokeballIcon: Phaser.GameObjects.Image;
|
||||
public eggMoveIcon: Phaser.GameObjects.Image;
|
||||
|
||||
/**
|
||||
* @param scene the current {@linkcode BattleScene}
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
* @param hatchData the {@linkcode EggHatchData} to load the icons and sprites for
|
||||
*/
|
||||
constructor(scene: BattleScene, x: number, y: number, hatchData: EggHatchData) {
|
||||
super(scene, x, y);
|
||||
|
||||
const displayPokemon = hatchData.pokemon;
|
||||
this.species = displayPokemon.species;
|
||||
|
||||
const offset = 2;
|
||||
const rightSideX = 12;
|
||||
const species = displayPokemon.species;
|
||||
const female = displayPokemon.gender === Gender.FEMALE;
|
||||
const formIndex = displayPokemon.formIndex;
|
||||
const variant = displayPokemon.variant;
|
||||
const isShiny = displayPokemon.shiny;
|
||||
|
||||
// Pokemon sprite
|
||||
const pokemonIcon = this.scene.add.sprite(-offset, offset, species.getIconAtlasKey(formIndex, isShiny, variant));
|
||||
pokemonIcon.setScale(0.5);
|
||||
pokemonIcon.setOrigin(0, 0);
|
||||
pokemonIcon.setFrame(species.getIconId(female, formIndex, isShiny, variant));
|
||||
this.icon = pokemonIcon;
|
||||
this.checkIconId(female, formIndex, isShiny, variant);
|
||||
this.add(this.icon);
|
||||
|
||||
// Shiny icon
|
||||
this.shinyIcon = this.scene.add.image(rightSideX, offset, "shiny_star_small");
|
||||
this.shinyIcon.setOrigin(0, 0);
|
||||
this.shinyIcon.setScale(0.5);
|
||||
this.add(this.shinyIcon);
|
||||
|
||||
// Hidden ability icon
|
||||
const haIcon = this.scene.add.image(rightSideX, offset * 4, "ha_capsule");
|
||||
haIcon.setOrigin(0, 0);
|
||||
haIcon.setScale(0.5);
|
||||
this.hiddenAbilityIcon = haIcon;
|
||||
this.add(this.hiddenAbilityIcon);
|
||||
|
||||
// Pokeball icon
|
||||
const pokeballIcon = this.scene.add.image(rightSideX, offset * 7, "icon_owned");
|
||||
pokeballIcon.setOrigin(0, 0);
|
||||
pokeballIcon.setScale(0.5);
|
||||
this.pokeballIcon = pokeballIcon;
|
||||
this.add(this.pokeballIcon);
|
||||
|
||||
// Egg move icon
|
||||
const eggMoveIcon = this.scene.add.image(0, offset, "icon_egg_move");
|
||||
eggMoveIcon.setOrigin(0, 0);
|
||||
eggMoveIcon.setScale(0.5);
|
||||
this.eggMoveIcon = eggMoveIcon;
|
||||
this.add(this.eggMoveIcon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the Pokemon's sprite and icons based on new hatch data
|
||||
* Animates the pokemon icon if it has a new form or shiny variant
|
||||
*
|
||||
* @param hatchData the {@linkcode EggHatchData} to base the icons on
|
||||
* @param iconAnimHandler the {@linkcode PokemonIconAnimHandler} to use to animate the sprites
|
||||
*/
|
||||
updateAndAnimate(hatchData: EggHatchData, iconAnimHandler: PokemonIconAnimHandler) {
|
||||
const displayPokemon = hatchData.pokemon;
|
||||
this.species = displayPokemon.species;
|
||||
|
||||
const dexEntry = hatchData.dexEntryBeforeUpdate;
|
||||
const caughtAttr = dexEntry.caughtAttr;
|
||||
const newShiny = BigInt(1 << (displayPokemon.shiny ? 1 : 0));
|
||||
const newVariant = BigInt(1 << (displayPokemon.variant + 4));
|
||||
const newShinyOrVariant = ((newShiny & caughtAttr) === BigInt(0)) || ((newVariant & caughtAttr) === BigInt(0));
|
||||
const newForm = (BigInt(1 << displayPokemon.formIndex) * DexAttr.DEFAULT_FORM & caughtAttr) === BigInt(0);
|
||||
|
||||
const female = displayPokemon.gender === Gender.FEMALE;
|
||||
const formIndex = displayPokemon.formIndex;
|
||||
const variant = displayPokemon.variant;
|
||||
const isShiny = displayPokemon.shiny;
|
||||
|
||||
this.icon.setTexture(this.species.getIconAtlasKey(formIndex, isShiny, variant));
|
||||
this.icon.setFrame(this.species.getIconId(female, formIndex, isShiny, variant));
|
||||
this.checkIconId(female, formIndex, isShiny, variant);
|
||||
|
||||
this.shinyIcon.setVisible(displayPokemon.shiny);
|
||||
this.shinyIcon.setTint(getVariantTint(displayPokemon.variant));
|
||||
|
||||
this.eggMoveIcon.setVisible(hatchData.eggMoveUnlocked);
|
||||
this.hiddenAbilityIcon.setVisible(displayPokemon.abilityIndex === 2);
|
||||
this.pokeballIcon.setVisible(!caughtAttr || newForm);
|
||||
|
||||
// add animation to the Pokemon sprite for new unlocks (new catch, new shiny or new form)
|
||||
if (!caughtAttr || newShinyOrVariant || newForm) {
|
||||
iconAnimHandler.addOrUpdate(this.icon, PokemonIconAnimMode.PASSIVE);
|
||||
} else {
|
||||
iconAnimHandler.addOrUpdate(this.icon, PokemonIconAnimMode.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given Pokemon icon exists, otherwise replace it with a default one
|
||||
* @param female `true` to get the female icon
|
||||
* @param formIndex the form index
|
||||
* @param shiny whether the Pokemon is shiny
|
||||
* @param variant the shiny variant
|
||||
*/
|
||||
private checkIconId(female: boolean, formIndex: number, shiny: boolean, variant: number) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ export class ScrollBar extends Phaser.GameObjects.Container {
|
|||
super(scene, x, y);
|
||||
|
||||
this.maxRows = maxRows;
|
||||
this.totalRows = maxRows;
|
||||
this.currentRow = 0;
|
||||
|
||||
const borderSize = 2;
|
||||
width = Math.max(width, 4);
|
||||
|
@ -46,8 +48,7 @@ export class ScrollBar extends Phaser.GameObjects.Container {
|
|||
*/
|
||||
setScrollCursor(scrollCursor: number): void {
|
||||
this.currentRow = scrollCursor;
|
||||
this.handleBody.y = 1 + (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) / this.totalRows * this.currentRow;
|
||||
this.handleBottom.y = this.handleBody.y + this.handleBody.displayHeight;
|
||||
this.updateHandlePosition();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,7 +60,13 @@ export class ScrollBar extends Phaser.GameObjects.Container {
|
|||
setTotalRows(rows: number): void {
|
||||
this.totalRows = rows;
|
||||
this.handleBody.height = (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) * this.maxRows / this.totalRows;
|
||||
this.updateHandlePosition();
|
||||
|
||||
this.setVisible(this.totalRows > this.maxRows);
|
||||
}
|
||||
|
||||
private updateHandlePosition(): void {
|
||||
this.handleBody.y = 1 + (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) / this.totalRows * this.currentRow;
|
||||
this.handleBottom.y = this.handleBody.y + this.handleBody.displayHeight;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
import { Button } from "#enums/buttons";
|
||||
import UiHandler from "#app/ui/ui-handler";
|
||||
import { ScrollBar } from "#app/ui/scroll-bar";
|
||||
|
||||
type UpdateGridCallbackFunction = () => void;
|
||||
type UpdateDetailsCallbackFunction = (index: number) => void;
|
||||
|
||||
/**
|
||||
* A helper class to handle navigation through a grid of elements that can scroll vertically
|
||||
* Needs to be used by a {@linkcode UiHandler}
|
||||
* How to use:
|
||||
* - in `UiHandler.setup`: Initialize with the {@linkcode UiHandler} that handles the grid,
|
||||
* the number of rows and columns that can be shown at once,
|
||||
* an optional {@linkcode ScrollBar}, and optional callbacks that will get called after scrolling
|
||||
* - in `UiHandler.show`: Set `setTotalElements` to the total number of elements in the list to display
|
||||
* - in `UiHandler.processInput`: call `processNavigationInput` to have it handle the cursor updates while calling the defined callbacks
|
||||
* - in `UiHandler.clear`: call `reset`
|
||||
*/
|
||||
export default class ScrollableGridUiHandler {
|
||||
private readonly ROWS: number;
|
||||
private readonly COLUMNS: number;
|
||||
private handler: UiHandler;
|
||||
private totalElements: number;
|
||||
private cursor: number;
|
||||
private scrollCursor: number;
|
||||
private scrollBar?: ScrollBar;
|
||||
private updateGridCallback?: UpdateGridCallbackFunction;
|
||||
private updateDetailsCallback?: UpdateDetailsCallbackFunction;
|
||||
|
||||
/**
|
||||
* @param scene the {@linkcode UiHandler} that needs its cursor updated based on the scrolling
|
||||
* @param rows the maximum number of rows shown at once
|
||||
* @param columns the maximum number of columns shown at once
|
||||
* @param updateGridCallback optional function that will get called if the whole grid needs to get updated
|
||||
* @param updateDetailsCallback optional function that will get called if a single element's information needs to get updated
|
||||
*/
|
||||
constructor(handler: UiHandler, rows: number, columns: number) {
|
||||
this.handler = handler;
|
||||
this.ROWS = rows;
|
||||
this.COLUMNS = columns;
|
||||
this.scrollCursor = 0;
|
||||
this.cursor = 0;
|
||||
this.totalElements = rows * columns; // default value for the number of elements
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a scrollBar to get updated with the scrolling
|
||||
* @param scrollBar {@linkcode ScrollBar}
|
||||
* @returns this
|
||||
*/
|
||||
withScrollBar(scrollBar: ScrollBar): ScrollableGridUiHandler {
|
||||
this.scrollBar = scrollBar;
|
||||
this.scrollBar.setTotalRows(Math.ceil(this.totalElements / this.COLUMNS));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set function that will get called if the whole grid needs to get updated
|
||||
* @param callback {@linkcode UpdateGridCallbackFunction}
|
||||
* @returns this
|
||||
*/
|
||||
withUpdateGridCallBack(callback: UpdateGridCallbackFunction): ScrollableGridUiHandler {
|
||||
this.updateGridCallback = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set function that will get called if a single element in the grid needs to get updated
|
||||
* @param callback {@linkcode UpdateDetailsCallbackFunction}
|
||||
* @returns this
|
||||
*/
|
||||
withUpdateSingleElementCallback(callback: UpdateDetailsCallbackFunction): ScrollableGridUiHandler {
|
||||
this.updateDetailsCallback = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param totalElements the total number of elements that the grid needs to display
|
||||
*/
|
||||
setTotalElements(totalElements: number) {
|
||||
this.totalElements = totalElements;
|
||||
if (this.scrollBar) {
|
||||
this.scrollBar.setTotalRows(Math.ceil(this.totalElements / this.COLUMNS));
|
||||
}
|
||||
this.setScrollCursor(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns how many elements are hidden due to scrolling
|
||||
*/
|
||||
getItemOffset(): number {
|
||||
return this.scrollCursor * this.COLUMNS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cursor and scrollCursor based on user input
|
||||
* @param button the button that was pressed
|
||||
* @returns `true` if either the cursor or scrollCursor was updated
|
||||
*/
|
||||
processInput(button: Button): boolean {
|
||||
let success = false;
|
||||
const onScreenRows = Math.min(this.ROWS, Math.ceil(this.totalElements / this.COLUMNS));
|
||||
const maxScrollCursor = Math.max(0, Math.ceil(this.totalElements / this.COLUMNS) - onScreenRows);
|
||||
const currentRowIndex = Math.floor(this.cursor / this.COLUMNS);
|
||||
const currentColumnIndex = this.cursor % this.COLUMNS;
|
||||
const itemOffset = this.scrollCursor * this.COLUMNS;
|
||||
const lastVisibleIndex = Math.min(this.totalElements - 1, this.totalElements - maxScrollCursor * this.COLUMNS - 1);
|
||||
switch (button) {
|
||||
case Button.UP:
|
||||
if (currentRowIndex > 0) {
|
||||
success = this.setCursor(this.cursor - this.COLUMNS);
|
||||
} else if (this.scrollCursor > 0) {
|
||||
success = this.setScrollCursor(this.scrollCursor - 1);
|
||||
} else {
|
||||
// wrap around to the last row
|
||||
let newCursor = this.cursor + (onScreenRows - 1) * this.COLUMNS;
|
||||
if (newCursor > lastVisibleIndex) {
|
||||
newCursor -= this.COLUMNS;
|
||||
}
|
||||
success = this.setScrollCursor(maxScrollCursor, newCursor);
|
||||
}
|
||||
break;
|
||||
case Button.DOWN:
|
||||
if (currentRowIndex < onScreenRows - 1) {
|
||||
// Go down one row
|
||||
success = this.setCursor(Math.min(this.cursor + this.COLUMNS, this.totalElements - itemOffset - 1));
|
||||
} else if (this.scrollCursor < maxScrollCursor) {
|
||||
// Scroll down one row
|
||||
success = this.setScrollCursor(this.scrollCursor + 1);
|
||||
} else {
|
||||
// Wrap around to the top row
|
||||
success = this.setScrollCursor(0, this.cursor % this.COLUMNS);
|
||||
}
|
||||
break;
|
||||
case Button.LEFT:
|
||||
if (currentColumnIndex > 0) {
|
||||
success = this.setCursor(this.cursor - 1);
|
||||
} else if (this.scrollCursor === maxScrollCursor && currentRowIndex === onScreenRows - 1) {
|
||||
success = this.setCursor(lastVisibleIndex);
|
||||
} else {
|
||||
success = this.setCursor(this.cursor + this.COLUMNS - 1);
|
||||
}
|
||||
break;
|
||||
case Button.RIGHT:
|
||||
if (currentColumnIndex < this.COLUMNS - 1 && this.cursor + itemOffset < this.totalElements - 1) {
|
||||
success = this.setCursor(this.cursor + 1);
|
||||
} else {
|
||||
success = this.setCursor(this.cursor - currentColumnIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the scrolling
|
||||
*/
|
||||
reset(): void {
|
||||
this.setScrollCursor(0);
|
||||
this.setCursor(0);
|
||||
}
|
||||
|
||||
private setCursor(cursor: number): boolean {
|
||||
this.cursor = cursor;
|
||||
return this.handler.setCursor(cursor);
|
||||
}
|
||||
|
||||
private setScrollCursor(scrollCursor: number, cursor?: number): boolean {
|
||||
const scrollChanged = scrollCursor !== this.scrollCursor;
|
||||
|
||||
// update the scrolling cursor
|
||||
if (scrollChanged) {
|
||||
this.scrollCursor = scrollCursor;
|
||||
if (this.scrollBar) {
|
||||
this.scrollBar.setScrollCursor(scrollCursor);
|
||||
}
|
||||
if (this.updateGridCallback) {
|
||||
this.updateGridCallback();
|
||||
}
|
||||
}
|
||||
|
||||
let cursorChanged = false;
|
||||
const newElementIndex = this.cursor + this.scrollCursor * this.COLUMNS;
|
||||
if (cursor !== undefined) {
|
||||
cursorChanged = this.setCursor(cursor);
|
||||
} else if (newElementIndex >= this.totalElements) {
|
||||
// make sure the cursor does not go past the end of the list
|
||||
cursorChanged = this.setCursor(this.totalElements - this.scrollCursor * this.COLUMNS - 1);
|
||||
} else if (scrollChanged && this.updateDetailsCallback) {
|
||||
// scroll was changed but not the normal cursor, update the selected element
|
||||
this.updateDetailsCallback(newElementIndex);
|
||||
}
|
||||
|
||||
return scrollChanged || cursorChanged;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue