From 00d039c65b7380876170287ac7864353e338deae Mon Sep 17 00:00:00 2001 From: Mumble Date: Wed, 7 Aug 2024 16:56:05 -0700 Subject: [PATCH] [Enhancement] Achievements Page is now Scrollable (#3395) * Scrollable Achivements * Update src/ui/achvs-ui-handler.ts Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> * Removed debugging message * Removed redundant object.keys() * Changed constants to be within the class * No more need for mode! See PR #3407 * tRaIlINg SpAcEs NoT aLlOwEd * Added comments as requested. --------- Co-authored-by: Frutescens Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> --- src/ui/achvs-ui-handler.ts | 114 +++++++++++++++++++++++++++++-------- 1 file changed, 89 insertions(+), 25 deletions(-) diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index 46259e52d0d..ad707fb52e1 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -10,6 +10,9 @@ import { ParseKeys } from "i18next"; import { PlayerGender } from "#enums/player-gender"; export default class AchvsUiHandler extends MessageUiHandler { + private readonly ACHV_ROWS = 4; + private readonly ACHV_COLS = 17; + private achvsContainer: Phaser.GameObjects.Container; private achvIconsContainer: Phaser.GameObjects.Container; @@ -19,10 +22,16 @@ export default class AchvsUiHandler extends MessageUiHandler { private scoreText: Phaser.GameObjects.Text; private unlockText: Phaser.GameObjects.Text; + private achvsTotal: number; + private scrollCursor: number; + private cursorObj: Phaser.GameObjects.NineSlice | null; constructor(scene: BattleScene, mode: Mode | null = null) { super(scene, mode); + + this.achvsTotal = Object.keys(achvs).length; + this.scrollCursor = 0; } setup() { @@ -53,9 +62,9 @@ export default class AchvsUiHandler extends MessageUiHandler { this.achvIcons = []; - for (let a = 0; a < Object.keys(achvs).length; a++) { - const x = (a % 17) * 18; - const y = Math.floor(a / 17) * 18; + for (let a = 0; a < this.ACHV_ROWS * this.ACHV_COLS; a++) { + const x = (a % this.ACHV_COLS) * 18; + const y = Math.floor(a / this.ACHV_COLS) * 18; const icon = this.scene.add.sprite(x, y, "items", "unknown"); icon.setOrigin(0, 0); @@ -119,24 +128,11 @@ export default class AchvsUiHandler extends MessageUiHandler { show(args: any[]): boolean { super.show(args); - const achvUnlocks = this.scene.gameData.achvUnlocks; - - Object.values(achvs).forEach((achv: Achv, i: integer) => { - const icon = this.achvIcons[i]; - const unlocked = achvUnlocks.hasOwnProperty(achv.id); - const hidden = !unlocked && achv.secret && (!achv.parentId || !achvUnlocks.hasOwnProperty(achv.parentId)); - const tinted = !hidden && !unlocked; - - icon.setFrame(!hidden ? achv.iconImage : "unknown"); - if (tinted) { - icon.setTintFill(0); - } else { - icon.clearTint(); - } - }); + this.updateAchvIcons(); this.achvsContainer.setVisible(true); this.setCursor(0); + this.setScrollCursor(0); this.getUi().moveTo(this.achvsContainer, this.getUi().length - 1); @@ -173,24 +169,39 @@ export default class AchvsUiHandler extends MessageUiHandler { success = true; this.scene.ui.revertMode(); } else { + const rowIndex = Math.floor(this.cursor / this.ACHV_COLS); + const itemOffset = (this.scrollCursor * this.ACHV_COLS); switch (button) { case Button.UP: - if (this.cursor >= 17) { - success = this.setCursor(this.cursor - 17); + if (this.cursor < this.ACHV_COLS) { + if (this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1); + } + } else { + success = this.setCursor(this.cursor - this.ACHV_COLS); } break; case Button.DOWN: - if (this.cursor + 17 < Object.keys(achvs).length) { - success = this.setCursor(this.cursor + 17); + const canMoveDown = (this.cursor + itemOffset) + this.ACHV_COLS < this.achvsTotal; + if (rowIndex >= this.ACHV_ROWS - 1) { + if (this.scrollCursor < Math.ceil(this.achvsTotal / this.ACHV_COLS) - this.ACHV_ROWS && canMoveDown) { + success = this.setScrollCursor(this.scrollCursor + 1); + } + } else if (canMoveDown) { + success = this.setCursor(this.cursor + this.ACHV_COLS); } break; case Button.LEFT: - if (this.cursor) { + if (!this.cursor && this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1) && this.setCursor(this.cursor + (this.ACHV_COLS - 1)); + } else if (this.cursor) { success = this.setCursor(this.cursor - 1); } break; case Button.RIGHT: - if (this.cursor < Object.keys(achvs).length - 1) { + if (this.cursor + 1 === this.ACHV_ROWS * this.ACHV_COLS && this.scrollCursor < Math.ceil(this.achvsTotal / this.ACHV_COLS) - this.ACHV_ROWS) { + success = this.setScrollCursor(this.scrollCursor + 1) && this.setCursor(this.cursor - (this.ACHV_COLS - 1)); + } else if (this.cursor + itemOffset < this.achvsTotal - 1) { success = this.setCursor(this.cursor + 1); } break; @@ -219,12 +230,65 @@ export default class AchvsUiHandler extends MessageUiHandler { this.cursorObj.setPositionRelative(this.achvIcons[this.cursor], 0, 0); if (updateAchv) { - this.showAchv(achvs[Object.keys(achvs)[cursor]]); + this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.ACHV_COLS]]); } return ret; } + /** + * setScrollCursor(scrollCursor: integer) : boolean + * scrollCursor refers to the page's position within the entire sum of the data, unlike cursor, which refers to a user's position within displayed data + * @param takes a scrollCursor that has been updated based on user behavior + * @returns returns a boolean that indicates whether the updated scrollCursor led to an update in the data displayed. + */ + setScrollCursor(scrollCursor: integer): boolean { + if (scrollCursor === this.scrollCursor) { + return false; + } + + this.scrollCursor = scrollCursor; + + this.updateAchvIcons(); + + this.showAchv(achvs[Object.keys(achvs)[Math.min(this.cursor + this.scrollCursor * this.ACHV_COLS, Object.values(achvs).length - 1)]]); + + return true; + } + + + /** + * updateAchvIcons(): void + * Determines what data is to be displayed on the UI and updates it accordingly based on the current value of this.scrollCursor + */ + updateAchvIcons(): void { + const achvUnlocks = this.scene.gameData.achvUnlocks; + + const itemOffset = this.scrollCursor * this.ACHV_COLS; + const itemLimit = this.ACHV_ROWS * this.ACHV_COLS; + + const achvRange = Object.values(achvs).slice(itemOffset, itemLimit + itemOffset); + + achvRange.forEach((achv: Achv, i: integer) => { + const icon = this.achvIcons[i]; + const unlocked = achvUnlocks.hasOwnProperty(achv.id); + const hidden = !unlocked && achv.secret && (!achv.parentId || !achvUnlocks.hasOwnProperty(achv.parentId)); + const tinted = !hidden && !unlocked; + + icon.setFrame(!hidden ? achv.iconImage : "unknown"); + icon.setVisible(true); + if (tinted) { + icon.setTintFill(0); + } else { + icon.clearTint(); + } + }); + + if (achvRange.length < this.achvIcons.length) { + this.achvIcons.slice(achvRange.length).map(i => i.setVisible(false)); + } + } + clear() { super.clear(); this.achvsContainer.setVisible(false);