[Enhancement] [UI/UX] Add ability and passive tooltips to starter select screen (#4023)

* Add ability and passive tooltips to starter select screen

* Remove explicit casts to BattleScene

* Increase tooltip size, reverse y when necessary, and always show passive tooltip

* Add ability name to tooltip title and persist tooltips between Pokemon

* Use vi function mocks
This commit is contained in:
Brandon Bay 2024-09-09 15:07:00 -04:00 committed by GitHub
parent 9afab182e9
commit 9317093044
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 104 additions and 25 deletions

View File

@ -52,9 +52,8 @@ export default class MockContainer implements MockGameObject {
/// Sets the position of this Game Object to be a relative position from the source Game Object. /// Sets the position of this Game Object to be a relative position from the source Game Object.
} }
setInteractive(hitArea?, callback?, dropZone?) { setInteractive = vi.fn();
/// Sets the InteractiveObject to be a drop zone for a drag and drop operation.
}
setOrigin(x, y) { setOrigin(x, y) {
this.x = x; this.x = x;
this.y = y; this.y = y;

View File

@ -1,5 +1,6 @@
import Phaser from "phaser"; import Phaser from "phaser";
import { MockGameObject } from "../mockGameObject"; import { MockGameObject } from "../mockGameObject";
import { vi } from "vitest";
import Sprite = Phaser.GameObjects.Sprite; import Sprite = Phaser.GameObjects.Sprite;
import Frame = Phaser.Textures.Frame; import Frame = Phaser.Textures.Frame;
@ -101,9 +102,7 @@ export default class MockSprite implements MockGameObject {
return this.phaserSprite.stop(); return this.phaserSprite.stop();
} }
setInteractive(hitArea, hitAreaCallback, dropZone) { setInteractive = vi.fn();
return null;
}
on(event, callback, source) { on(event, callback, source) {
return this.phaserSprite.on(event, callback, source); return this.phaserSprite.on(event, callback, source);

View File

@ -197,6 +197,8 @@ export default class MockText implements MockGameObject {
this.color = color; this.color = color;
}); });
setInteractive = vi.fn();
setShadowColor(color) { setShadowColor(color) {
// Sets the shadow color. // Sets the shadow color.
// return this.phaserText.setShadowColor(color); // return this.phaserText.setShadowColor(color);

View File

@ -266,6 +266,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private pokemonPassiveDisabledIcon: Phaser.GameObjects.Sprite; private pokemonPassiveDisabledIcon: Phaser.GameObjects.Sprite;
private pokemonPassiveLockedIcon: Phaser.GameObjects.Sprite; private pokemonPassiveLockedIcon: Phaser.GameObjects.Sprite;
private activeTooltip: "ABILITY" | "PASSIVE" | "CANDY" | undefined;
private instructionsContainer: Phaser.GameObjects.Container; private instructionsContainer: Phaser.GameObjects.Container;
private filterInstructionsContainer: Phaser.GameObjects.Container; private filterInstructionsContainer: Phaser.GameObjects.Container;
private shinyIconElement: Phaser.GameObjects.Sprite; private shinyIconElement: Phaser.GameObjects.Sprite;
@ -561,10 +562,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonAbilityLabelText = addTextObject(this.scene, 6, 127 + starterInfoYOffset, i18next.t("starterSelectUiHandler:ability"), TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); this.pokemonAbilityLabelText = addTextObject(this.scene, 6, 127 + starterInfoYOffset, i18next.t("starterSelectUiHandler:ability"), TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize });
this.pokemonAbilityLabelText.setOrigin(0, 0); this.pokemonAbilityLabelText.setOrigin(0, 0);
this.pokemonAbilityLabelText.setVisible(false); this.pokemonAbilityLabelText.setVisible(false);
this.starterSelectContainer.add(this.pokemonAbilityLabelText); this.starterSelectContainer.add(this.pokemonAbilityLabelText);
this.pokemonAbilityText = addTextObject(this.scene, starterInfoXPos, 127 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); this.pokemonAbilityText = addTextObject(this.scene, starterInfoXPos, 127 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize });
this.pokemonAbilityText.setOrigin(0, 0); this.pokemonAbilityText.setOrigin(0, 0);
this.pokemonAbilityText.setInteractive(new Phaser.Geom.Rectangle(0, 0, 250, 55), Phaser.Geom.Rectangle.Contains);
this.starterSelectContainer.add(this.pokemonAbilityText); this.starterSelectContainer.add(this.pokemonAbilityText);
this.pokemonPassiveLabelText = addTextObject(this.scene, 6, 136 + starterInfoYOffset, i18next.t("starterSelectUiHandler:passive"), TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); this.pokemonPassiveLabelText = addTextObject(this.scene, 6, 136 + starterInfoYOffset, i18next.t("starterSelectUiHandler:passive"), TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize });
@ -574,6 +578,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonPassiveText = addTextObject(this.scene, starterInfoXPos, 136 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); this.pokemonPassiveText = addTextObject(this.scene, starterInfoXPos, 136 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize });
this.pokemonPassiveText.setOrigin(0, 0); this.pokemonPassiveText.setOrigin(0, 0);
this.pokemonPassiveText.setInteractive(new Phaser.Geom.Rectangle(0, 0, 250, 55), Phaser.Geom.Rectangle.Contains);
this.starterSelectContainer.add(this.pokemonPassiveText); this.starterSelectContainer.add(this.pokemonPassiveText);
this.pokemonPassiveDisabledIcon = this.scene.add.sprite(starterInfoXPos, 137 + starterInfoYOffset, "icon_stop"); this.pokemonPassiveDisabledIcon = this.scene.add.sprite(starterInfoXPos, 137 + starterInfoYOffset, "icon_stop");
@ -1921,6 +1926,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
} }
} while (newAbilityIndex !== this.abilityCursor); } while (newAbilityIndex !== this.abilityCursor);
starterAttributes.ability = newAbilityIndex; // store the selected ability starterAttributes.ability = newAbilityIndex; // store the selected ability
const { visible: tooltipVisible } = this.scene.ui.getTooltip();
if (tooltipVisible && this.activeTooltip === "ABILITY") {
const newAbility = allAbilities[this.lastSpecies.getAbility(newAbilityIndex)];
this.scene.ui.editTooltip(`${newAbility.name}`, `${newAbility.description}`);
}
this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, newAbilityIndex, undefined); this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, newAbilityIndex, undefined);
success = true; success = true;
} }
@ -2687,12 +2700,30 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
} }
} }
getFriendship(speciesId: number) {
let currentFriendship = this.scene.gameData.starterData[speciesId].friendship;
if (!currentFriendship || currentFriendship === undefined) {
currentFriendship = 0;
}
const friendshipCap = getStarterValueFriendshipCap(speciesStarters[speciesId]);
return { currentFriendship, friendshipCap };
}
setSpecies(species: PokemonSpecies | null) { setSpecies(species: PokemonSpecies | null) {
this.speciesStarterDexEntry = species ? this.scene.gameData.dexData[species.speciesId] : null; this.speciesStarterDexEntry = species ? this.scene.gameData.dexData[species.speciesId] : null;
this.dexAttrCursor = species ? this.getCurrentDexProps(species.speciesId) : 0n; this.dexAttrCursor = species ? this.getCurrentDexProps(species.speciesId) : 0n;
this.abilityCursor = species ? this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species) : 0; this.abilityCursor = species ? this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species) : 0;
this.natureCursor = species ? this.scene.gameData.getSpeciesDefaultNature(species) : 0; this.natureCursor = species ? this.scene.gameData.getSpeciesDefaultNature(species) : 0;
if (!species && this.scene.ui.getTooltip().visible) {
this.scene.ui.hideTooltip();
}
this.pokemonAbilityText.off("pointerover");
this.pokemonPassiveText.off("pointerover");
const starterAttributes : StarterAttributes | null = species ? {...this.starterPreferences[species.speciesId]} : null; const starterAttributes : StarterAttributes | null = species ? {...this.starterPreferences[species.speciesId]} : null;
if (starterAttributes?.nature) { if (starterAttributes?.nature) {
@ -2807,17 +2838,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonHatchedIcon.setVisible(true); this.pokemonHatchedIcon.setVisible(true);
this.pokemonHatchedCountText.setVisible(true); this.pokemonHatchedCountText.setVisible(true);
let currentFriendship = this.scene.gameData.starterData[this.lastSpecies.speciesId].friendship; const { currentFriendship, friendshipCap } = this.getFriendship(this.lastSpecies.speciesId);
if (!currentFriendship || currentFriendship === undefined) {
currentFriendship = 0;
}
const friendshipCap = getStarterValueFriendshipCap(speciesStarters[this.lastSpecies.speciesId]);
const candyCropY = 16 - (16 * (currentFriendship / friendshipCap)); const candyCropY = 16 - (16 * (currentFriendship / friendshipCap));
if (this.pokemonCandyDarknessOverlay.visible) { if (this.pokemonCandyDarknessOverlay.visible) {
this.pokemonCandyDarknessOverlay.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip("", `${currentFriendship}/${friendshipCap}`, true)); this.pokemonCandyDarknessOverlay.on("pointerover", () => {
this.pokemonCandyDarknessOverlay.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); this.scene.ui.showTooltip("", `${currentFriendship}/${friendshipCap}`, true);
this.activeTooltip = "CANDY";
});
this.pokemonCandyDarknessOverlay.on("pointerout", () => {
this.scene.ui.hideTooltip();
this.activeTooltip = undefined;
});
} }
this.pokemonCandyDarknessOverlay.setCrop(0, 0, 16, candyCropY); this.pokemonCandyDarknessOverlay.setCrop(0, 0, 16, candyCropY);
@ -2932,6 +2964,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.abilityCursor = -1; this.abilityCursor = -1;
this.natureCursor = -1; this.natureCursor = -1;
if (this.activeTooltip === "CANDY") {
const { currentFriendship, friendshipCap } = this.getFriendship(this.lastSpecies.speciesId);
this.scene.ui.editTooltip("", `${currentFriendship}/${friendshipCap}`);
}
if (species?.forms?.find(f => f.formKey === "female")) { if (species?.forms?.find(f => f.formKey === "female")) {
if (female !== undefined) { if (female !== undefined) {
formIndex = female ? 1 : 0; formIndex = female ? 1 : 0;
@ -3081,8 +3118,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
} }
if (dexEntry.caughtAttr) { if (dexEntry.caughtAttr) {
const ability = this.lastSpecies.getAbility(abilityIndex!); // TODO: is this bang correct? const ability = allAbilities[this.lastSpecies.getAbility(abilityIndex!)]; // TODO: is this bang correct?
this.pokemonAbilityText.setText(allAbilities[ability].name); this.pokemonAbilityText.setText(ability.name);
const isHidden = abilityIndex === (this.lastSpecies.ability2 ? 2 : 1); const isHidden = abilityIndex === (this.lastSpecies.ability2 ? 2 : 1);
this.pokemonAbilityText.setColor(this.getTextColor(!isHidden ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GOLD)); this.pokemonAbilityText.setColor(this.getTextColor(!isHidden ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GOLD));
@ -3091,6 +3128,21 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
const passiveAttr = this.scene.gameData.starterData[species.speciesId].passiveAttr; const passiveAttr = this.scene.gameData.starterData[species.speciesId].passiveAttr;
const passiveAbility = allAbilities[starterPassiveAbilities[this.lastSpecies.speciesId]]; const passiveAbility = allAbilities[starterPassiveAbilities[this.lastSpecies.speciesId]];
if (this.pokemonAbilityText.visible) {
if (this.activeTooltip === "ABILITY") {
this.scene.ui.editTooltip(`${ability.name}`, `${ability.description}`);
}
this.pokemonAbilityText.on("pointerover", () => {
this.scene.ui.showTooltip(`${ability.name}`, `${ability.description}`, true);
this.activeTooltip = "ABILITY";
});
this.pokemonAbilityText.on("pointerout", () => {
this.scene.ui.hideTooltip();
this.activeTooltip = undefined;
});
}
if (passiveAbility) { if (passiveAbility) {
const isUnlocked = !!(passiveAttr & PassiveAttr.UNLOCKED); const isUnlocked = !!(passiveAttr & PassiveAttr.UNLOCKED);
const isEnabled = !!(passiveAttr & PassiveAttr.ENABLED); const isEnabled = !!(passiveAttr & PassiveAttr.ENABLED);
@ -3107,6 +3159,21 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonPassiveText.setAlpha(textAlpha); this.pokemonPassiveText.setAlpha(textAlpha);
this.pokemonPassiveText.setShadowColor(this.getTextColor(textStyle, true)); this.pokemonPassiveText.setShadowColor(this.getTextColor(textStyle, true));
if (this.activeTooltip === "PASSIVE") {
this.scene.ui.editTooltip(`${passiveAbility.name}`, `${passiveAbility.description}`);
}
if (this.pokemonPassiveText.visible) {
this.pokemonPassiveText.on("pointerover", () => {
this.scene.ui.showTooltip(`${passiveAbility.name}`, `${passiveAbility.description}`, true);
this.activeTooltip = "PASSIVE";
});
this.pokemonPassiveText.on("pointerout", () => {
this.scene.ui.hideTooltip();
this.activeTooltip = undefined;
});
}
const iconPosition = { const iconPosition = {
x: this.pokemonPassiveText.x + this.pokemonPassiveText.displayWidth + 1, x: this.pokemonPassiveText.x + this.pokemonPassiveText.displayWidth + 1,
y: this.pokemonPassiveText.y + this.pokemonPassiveText.displayHeight / 2 y: this.pokemonPassiveText.y + this.pokemonPassiveText.displayHeight / 2

View File

@ -244,7 +244,7 @@ export default class UI extends Phaser.GameObjects.Container {
this.tooltipContent = addTextObject(this.scene, 6, 16, "", TextStyle.TOOLTIP_CONTENT); this.tooltipContent = addTextObject(this.scene, 6, 16, "", TextStyle.TOOLTIP_CONTENT);
this.tooltipContent.setName("text-tooltip-content"); this.tooltipContent.setName("text-tooltip-content");
this.tooltipContent.setWordWrapWidth(696); this.tooltipContent.setWordWrapWidth(850);
this.tooltipContainer.add(this.tooltipBg); this.tooltipContainer.add(this.tooltipBg);
this.tooltipContainer.add(this.tooltipTitle); this.tooltipContainer.add(this.tooltipTitle);
@ -368,14 +368,13 @@ export default class UI extends Phaser.GameObjects.Container {
return false; return false;
} }
getTooltip(): { visible: boolean; title: string; content: string } {
return { visible: this.tooltipContainer.visible, title: this.tooltipTitle.text, content: this.tooltipContent.text };
}
showTooltip(title: string, content: string, overlap?: boolean): void { showTooltip(title: string, content: string, overlap?: boolean): void {
this.tooltipContainer.setVisible(true); this.tooltipContainer.setVisible(true);
this.tooltipTitle.setText(title || ""); this.editTooltip(title, content);
const wrappedContent = this.tooltipContent.runWordWrap(content);
this.tooltipContent.setText(wrappedContent);
this.tooltipContent.y = title ? 16 : 4;
this.tooltipBg.width = Math.min(Math.max(this.tooltipTitle.displayWidth, this.tooltipContent.displayWidth) + 12, 684);
this.tooltipBg.height = (title ? 31 : 19) + 10.5 * (wrappedContent.split("\n").length - 1);
if (overlap) { if (overlap) {
(this.scene as BattleScene).uiContainer.moveAbove(this.tooltipContainer, this); (this.scene as BattleScene).uiContainer.moveAbove(this.tooltipContainer, this);
} else { } else {
@ -383,6 +382,15 @@ export default class UI extends Phaser.GameObjects.Container {
} }
} }
editTooltip(title: string, content: string): void {
this.tooltipTitle.setText(title || "");
const wrappedContent = this.tooltipContent.runWordWrap(content);
this.tooltipContent.setText(wrappedContent);
this.tooltipContent.y = title ? 16 : 4;
this.tooltipBg.width = Math.min(Math.max(this.tooltipTitle.displayWidth, this.tooltipContent.displayWidth) + 12, 838);
this.tooltipBg.height = (title ? 31 : 19) + 10.5 * (wrappedContent.split("\n").length - 1);
}
hideTooltip(): void { hideTooltip(): void {
this.tooltipContainer.setVisible(false); this.tooltipContainer.setVisible(false);
this.tooltipTitle.clearTint(); this.tooltipTitle.clearTint();
@ -390,8 +398,12 @@ export default class UI extends Phaser.GameObjects.Container {
update(): void { update(): void {
if (this.tooltipContainer.visible) { if (this.tooltipContainer.visible) {
const reverse = this.scene.game.input.mousePointer && this.scene.game.input.mousePointer.x >= this.scene.game.canvas.width - this.tooltipBg.width * 6 - 12; const xReverse = this.scene.game.input.mousePointer && this.scene.game.input.mousePointer.x >= this.scene.game.canvas.width - this.tooltipBg.width * 6 - 12;
this.tooltipContainer.setPosition(!reverse ? this.scene.game.input.mousePointer!.x / 6 + 2 : this.scene.game.input.mousePointer!.x / 6 - this.tooltipBg.width - 2, this.scene.game.input.mousePointer!.y / 6 + 2); // TODO: are these bangs correct? const yReverse = this.scene.game.input.mousePointer && this.scene.game.input.mousePointer.y >= this.scene.game.canvas.height - this.tooltipBg.height * 6 - 12;
this.tooltipContainer.setPosition(
!xReverse ? this.scene.game.input.mousePointer!.x / 6 + 2 : this.scene.game.input.mousePointer!.x / 6 - this.tooltipBg.width - 2,
!yReverse ? this.scene.game.input.mousePointer!.y / 6 + 2 : this.scene.game.input.mousePointer!.y / 6 - this.tooltipBg.height - 2,
);
} }
} }