mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-12-14 22:05:34 +01:00
[Misc][UI/UX] See stats of other users in admin panel (#6579)
* Game stats ui handler takes save data as input * Make admin panel functional for local testing * Added button to show stats; mocking for local testing with current save data * Adding pokédex to admin panel * Many nice things * Fixed typo * Add backend support * Fixed button width in admin panel * Apply suggestions from code review --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com>
This commit is contained in:
parent
4349ee82b9
commit
f8edaeb1ae
@ -1,31 +1,32 @@
|
||||
export interface LinkAccountToDiscordIdRequest {
|
||||
username: string;
|
||||
discordId: string;
|
||||
}
|
||||
|
||||
export interface UnlinkAccountFromDiscordIdRequest {
|
||||
username: string;
|
||||
discordId: string;
|
||||
}
|
||||
|
||||
export interface LinkAccountToGoogledIdRequest {
|
||||
username: string;
|
||||
googleId: string;
|
||||
}
|
||||
|
||||
export interface UnlinkAccountFromGoogledIdRequest {
|
||||
username: string;
|
||||
googleId: string;
|
||||
}
|
||||
import type { SystemSaveData } from "#types/save-data";
|
||||
|
||||
export interface SearchAccountRequest {
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface DiscordRequest extends SearchAccountRequest {
|
||||
discordId: string;
|
||||
}
|
||||
|
||||
export interface GoogleRequest extends SearchAccountRequest {
|
||||
googleId: string;
|
||||
}
|
||||
|
||||
export interface SearchAccountResponse {
|
||||
username: string;
|
||||
discordId: string;
|
||||
googleId: string;
|
||||
lastLoggedIn: string;
|
||||
registered: string;
|
||||
systemData?: SystemSaveData;
|
||||
}
|
||||
|
||||
/** Third party login services */
|
||||
export type AdminUiHandlerService = "discord" | "google";
|
||||
/** Mode for the admin UI handler */
|
||||
export type AdminUiHandlerServiceMode = "Link" | "Unlink";
|
||||
|
||||
export interface PokerogueAdminApiParams extends Record<AdminUiHandlerService, SearchAccountRequest> {
|
||||
discord: DiscordRequest;
|
||||
google: GoogleRequest;
|
||||
}
|
||||
|
||||
23
src/enums/admin-mode.ts
Normal file
23
src/enums/admin-mode.ts
Normal file
@ -0,0 +1,23 @@
|
||||
export const AdminMode = Object.freeze({
|
||||
LINK: 0,
|
||||
SEARCH: 1,
|
||||
ADMIN: 2,
|
||||
});
|
||||
|
||||
export type AdminMode = (typeof AdminMode)[keyof typeof AdminMode];
|
||||
|
||||
/**
|
||||
* Get the name of the admin mode.
|
||||
* @param adminMode - The admin mode.
|
||||
* @returns The Uppercase name of the admin mode.
|
||||
*/
|
||||
export function getAdminModeName(adminMode: AdminMode): string {
|
||||
switch (adminMode) {
|
||||
case AdminMode.LINK:
|
||||
return "Link";
|
||||
case AdminMode.SEARCH:
|
||||
return "Search";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@ -1,112 +1,47 @@
|
||||
import { ApiBase } from "#api/api-base";
|
||||
import type {
|
||||
LinkAccountToDiscordIdRequest,
|
||||
LinkAccountToGoogledIdRequest,
|
||||
AdminUiHandlerService,
|
||||
AdminUiHandlerServiceMode,
|
||||
PokerogueAdminApiParams,
|
||||
SearchAccountRequest,
|
||||
SearchAccountResponse,
|
||||
UnlinkAccountFromDiscordIdRequest,
|
||||
UnlinkAccountFromGoogledIdRequest,
|
||||
} from "#types/api/pokerogue-admin-api";
|
||||
|
||||
export class PokerogueAdminApi extends ApiBase {
|
||||
public readonly ERR_USERNAME_NOT_FOUND: string = "Username not found!";
|
||||
|
||||
/**
|
||||
* Links an account to a discord id.
|
||||
* @param params The {@linkcode LinkAccountToDiscordIdRequest} to send
|
||||
* @returns `null` if successful, error message if not
|
||||
* Link or unlink a third party service to/from a user account
|
||||
* @param mode - The mode, either "Link" or "Unlink"
|
||||
* @param service - The third party service to perform the action with
|
||||
* @param params - The parameters for the user to perform the action on
|
||||
* @returns `null` if successful, otherwise an error message
|
||||
*/
|
||||
public async linkAccountToDiscord(params: LinkAccountToDiscordIdRequest) {
|
||||
public async linkUnlinkRequest(
|
||||
mode: AdminUiHandlerServiceMode,
|
||||
service: AdminUiHandlerService,
|
||||
params: PokerogueAdminApiParams[typeof service],
|
||||
): Promise<string | null> {
|
||||
const endpoint = "/admin/account/" + service + mode;
|
||||
const preposition = mode === "Link" ? "with " : "from ";
|
||||
const errMsg = "Could not " + mode.toLowerCase() + " account " + preposition + service + "!";
|
||||
try {
|
||||
const response = await this.doPost("/admin/account/discordLink", params, "form-urlencoded");
|
||||
const response = await this.doPost(endpoint, params, "form-urlencoded");
|
||||
|
||||
if (response.ok) {
|
||||
return null;
|
||||
}
|
||||
console.warn("Could not link account with discord!", response.status, response.statusText);
|
||||
console.warn(errMsg, response.status, response.statusText);
|
||||
|
||||
if (response.status === 404) {
|
||||
return this.ERR_USERNAME_NOT_FOUND;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("Could not link account with discord!", err);
|
||||
console.warn(errMsg, err);
|
||||
}
|
||||
|
||||
return this.ERR_GENERIC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlinks an account from a discord id.
|
||||
* @param params The {@linkcode UnlinkAccountFromDiscordIdRequest} to send
|
||||
* @returns `null` if successful, error message if not
|
||||
*/
|
||||
public async unlinkAccountFromDiscord(params: UnlinkAccountFromDiscordIdRequest) {
|
||||
try {
|
||||
const response = await this.doPost("/admin/account/discordUnlink", params, "form-urlencoded");
|
||||
|
||||
if (response.ok) {
|
||||
return null;
|
||||
}
|
||||
console.warn("Could not unlink account from discord!", response.status, response.statusText);
|
||||
|
||||
if (response.status === 404) {
|
||||
return this.ERR_USERNAME_NOT_FOUND;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("Could not unlink account from discord!", err);
|
||||
}
|
||||
|
||||
return this.ERR_GENERIC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Links an account to a google id.
|
||||
* @param params The {@linkcode LinkAccountToGoogledIdRequest} to send
|
||||
* @returns `null` if successful, error message if not
|
||||
*/
|
||||
public async linkAccountToGoogleId(params: LinkAccountToGoogledIdRequest) {
|
||||
try {
|
||||
const response = await this.doPost("/admin/account/googleLink", params, "form-urlencoded");
|
||||
|
||||
if (response.ok) {
|
||||
return null;
|
||||
}
|
||||
console.warn("Could not link account with google!", response.status, response.statusText);
|
||||
|
||||
if (response.status === 404) {
|
||||
return this.ERR_USERNAME_NOT_FOUND;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("Could not link account with google!", err);
|
||||
}
|
||||
|
||||
return this.ERR_GENERIC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlinks an account from a google id.
|
||||
* @param params The {@linkcode UnlinkAccountFromGoogledIdRequest} to send
|
||||
* @returns `null` if successful, error message if not
|
||||
*/
|
||||
public async unlinkAccountFromGoogleId(params: UnlinkAccountFromGoogledIdRequest) {
|
||||
try {
|
||||
const response = await this.doPost("/admin/account/googleUnlink", params, "form-urlencoded");
|
||||
|
||||
if (response.ok) {
|
||||
return null;
|
||||
}
|
||||
console.warn("Could not unlink account from google!", response.status, response.statusText);
|
||||
|
||||
if (response.status === 404) {
|
||||
return this.ERR_USERNAME_NOT_FOUND;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("Could not unlink account from google!", err);
|
||||
}
|
||||
|
||||
return this.ERR_GENERIC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search an account.
|
||||
* @param params The {@linkcode SearchAccountRequest} to send
|
||||
|
||||
@ -146,12 +146,20 @@ export class GameData {
|
||||
public eggPity: number[];
|
||||
public unlockPity: number[];
|
||||
|
||||
constructor() {
|
||||
this.loadSettings();
|
||||
this.loadGamepadSettings();
|
||||
this.loadMappingConfigs();
|
||||
this.trainerId = randInt(65536);
|
||||
this.secretId = randInt(65536);
|
||||
/**
|
||||
* @param fromRaw - If true, will skip initialization of fields that are normally randomized on new game start. Used for the admin panel; default `false`
|
||||
*/
|
||||
constructor(fromRaw = false) {
|
||||
if (fromRaw) {
|
||||
this.trainerId = 0;
|
||||
this.secretId = 0;
|
||||
} else {
|
||||
this.loadSettings();
|
||||
this.loadGamepadSettings();
|
||||
this.loadMappingConfigs();
|
||||
this.trainerId = randInt(65536);
|
||||
this.secretId = randInt(65536);
|
||||
}
|
||||
this.starterData = {};
|
||||
this.gameStats = new GameStats();
|
||||
this.runHistory = {};
|
||||
@ -288,13 +296,115 @@ export class GameData {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dataStr - The raw JSON string of the `SystemSaveData`
|
||||
* @returns - A new `GameData` instance initialized with the parsed `SystemSaveData`
|
||||
*/
|
||||
public static fromRawSystem(dataStr: string): GameData {
|
||||
const gameData = new GameData(true);
|
||||
const systemData = GameData.parseSystemData(dataStr);
|
||||
gameData.initParsedSystem(systemData);
|
||||
return gameData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize system data _after_ it has been parsed from JSON.
|
||||
* @param systemData The parsed `SystemSaveData` to initialize from
|
||||
*/
|
||||
private initParsedSystem(systemData: SystemSaveData): void {
|
||||
applySystemVersionMigration(systemData);
|
||||
|
||||
this.trainerId = systemData.trainerId;
|
||||
this.secretId = systemData.secretId;
|
||||
|
||||
this.gender = systemData.gender;
|
||||
|
||||
this.saveSetting(SettingKeys.Player_Gender, systemData.gender === PlayerGender.FEMALE ? 1 : 0);
|
||||
|
||||
if (systemData.starterData) {
|
||||
this.starterData = systemData.starterData;
|
||||
} else {
|
||||
this.initStarterData();
|
||||
|
||||
if (systemData["starterMoveData"]) {
|
||||
const starterMoveData = systemData["starterMoveData"];
|
||||
for (const s of Object.keys(starterMoveData)) {
|
||||
this.starterData[s].moveset = starterMoveData[s];
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData["starterEggMoveData"]) {
|
||||
const starterEggMoveData = systemData["starterEggMoveData"];
|
||||
for (const s of Object.keys(starterEggMoveData)) {
|
||||
this.starterData[s].eggMoves = starterEggMoveData[s];
|
||||
}
|
||||
}
|
||||
|
||||
this.migrateStarterAbilities(systemData, this.starterData);
|
||||
|
||||
const starterIds = Object.keys(this.starterData).map(s => Number.parseInt(s) as SpeciesId);
|
||||
for (const s of starterIds) {
|
||||
this.starterData[s].candyCount += systemData.dexData[s].caughtCount;
|
||||
this.starterData[s].candyCount += systemData.dexData[s].hatchedCount * 2;
|
||||
if (systemData.dexData[s].caughtAttr & DexAttr.SHINY) {
|
||||
this.starterData[s].candyCount += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.gameStats) {
|
||||
this.gameStats = systemData.gameStats;
|
||||
}
|
||||
|
||||
if (systemData.unlocks) {
|
||||
for (const key of Object.keys(systemData.unlocks)) {
|
||||
if (this.unlocks.hasOwnProperty(key)) {
|
||||
this.unlocks[key] = systemData.unlocks[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.achvUnlocks) {
|
||||
for (const a of Object.keys(systemData.achvUnlocks)) {
|
||||
if (achvs.hasOwnProperty(a)) {
|
||||
this.achvUnlocks[a] = systemData.achvUnlocks[a];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.voucherUnlocks) {
|
||||
for (const v of Object.keys(systemData.voucherUnlocks)) {
|
||||
if (vouchers.hasOwnProperty(v)) {
|
||||
this.voucherUnlocks[v] = systemData.voucherUnlocks[v];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.voucherCounts) {
|
||||
getEnumKeys(VoucherType).forEach(key => {
|
||||
const index = VoucherType[key];
|
||||
this.voucherCounts[index] = systemData.voucherCounts[index] || 0;
|
||||
});
|
||||
}
|
||||
|
||||
this.eggs = systemData.eggs ? systemData.eggs.map(e => e.toEgg()) : [];
|
||||
|
||||
this.eggPity = systemData.eggPity ? systemData.eggPity.slice(0) : [0, 0, 0, 0];
|
||||
this.unlockPity = systemData.unlockPity ? systemData.unlockPity.slice(0) : [0, 0, 0, 0];
|
||||
|
||||
this.dexData = Object.assign(this.dexData, systemData.dexData);
|
||||
this.consolidateDexData(this.dexData);
|
||||
this.defaultDexData = null;
|
||||
}
|
||||
|
||||
public initSystem(systemDataStr: string, cachedSystemDataStr?: string): Promise<boolean> {
|
||||
const { promise, resolve } = Promise.withResolvers<boolean>();
|
||||
try {
|
||||
let systemData = this.parseSystemData(systemDataStr);
|
||||
let systemData = GameData.parseSystemData(systemDataStr);
|
||||
|
||||
if (cachedSystemDataStr) {
|
||||
const cachedSystemData = this.parseSystemData(cachedSystemDataStr);
|
||||
const cachedSystemData = GameData.parseSystemData(cachedSystemDataStr);
|
||||
if (cachedSystemData.timestamp > systemData.timestamp) {
|
||||
console.debug("Use cached system");
|
||||
systemData = cachedSystemData;
|
||||
@ -307,7 +417,9 @@ export class GameData {
|
||||
if (isLocal || isBeta) {
|
||||
try {
|
||||
console.debug(
|
||||
this.parseSystemData(JSON.stringify(systemData, (_, v: any) => (typeof v === "bigint" ? v.toString() : v))),
|
||||
GameData.parseSystemData(
|
||||
JSON.stringify(systemData, (_, v: any) => (typeof v === "bigint" ? v.toString() : v)),
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
console.debug("Attempt to log system data failed:", err);
|
||||
@ -322,90 +434,7 @@ export class GameData {
|
||||
localStorage.setItem(lsItemKey, "");
|
||||
}
|
||||
|
||||
applySystemVersionMigration(systemData);
|
||||
|
||||
this.trainerId = systemData.trainerId;
|
||||
this.secretId = systemData.secretId;
|
||||
|
||||
this.gender = systemData.gender;
|
||||
|
||||
this.saveSetting(SettingKeys.Player_Gender, systemData.gender === PlayerGender.FEMALE ? 1 : 0);
|
||||
|
||||
if (!systemData.starterData) {
|
||||
this.initStarterData();
|
||||
|
||||
if (systemData["starterMoveData"]) {
|
||||
const starterMoveData = systemData["starterMoveData"];
|
||||
for (const s of Object.keys(starterMoveData)) {
|
||||
this.starterData[s].moveset = starterMoveData[s];
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData["starterEggMoveData"]) {
|
||||
const starterEggMoveData = systemData["starterEggMoveData"];
|
||||
for (const s of Object.keys(starterEggMoveData)) {
|
||||
this.starterData[s].eggMoves = starterEggMoveData[s];
|
||||
}
|
||||
}
|
||||
|
||||
this.migrateStarterAbilities(systemData, this.starterData);
|
||||
|
||||
const starterIds = Object.keys(this.starterData).map(s => Number.parseInt(s) as SpeciesId);
|
||||
for (const s of starterIds) {
|
||||
this.starterData[s].candyCount += systemData.dexData[s].caughtCount;
|
||||
this.starterData[s].candyCount += systemData.dexData[s].hatchedCount * 2;
|
||||
if (systemData.dexData[s].caughtAttr & DexAttr.SHINY) {
|
||||
this.starterData[s].candyCount += 4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.starterData = systemData.starterData;
|
||||
}
|
||||
|
||||
if (systemData.gameStats) {
|
||||
this.gameStats = systemData.gameStats;
|
||||
}
|
||||
|
||||
if (systemData.unlocks) {
|
||||
for (const key of Object.keys(systemData.unlocks)) {
|
||||
if (this.unlocks.hasOwnProperty(key)) {
|
||||
this.unlocks[key] = systemData.unlocks[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.achvUnlocks) {
|
||||
for (const a of Object.keys(systemData.achvUnlocks)) {
|
||||
if (achvs.hasOwnProperty(a)) {
|
||||
this.achvUnlocks[a] = systemData.achvUnlocks[a];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.voucherUnlocks) {
|
||||
for (const v of Object.keys(systemData.voucherUnlocks)) {
|
||||
if (vouchers.hasOwnProperty(v)) {
|
||||
this.voucherUnlocks[v] = systemData.voucherUnlocks[v];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.voucherCounts) {
|
||||
getEnumKeys(VoucherType).forEach(key => {
|
||||
const index = VoucherType[key];
|
||||
this.voucherCounts[index] = systemData.voucherCounts[index] || 0;
|
||||
});
|
||||
}
|
||||
|
||||
this.eggs = systemData.eggs ? systemData.eggs.map(e => e.toEgg()) : [];
|
||||
|
||||
this.eggPity = systemData.eggPity ? systemData.eggPity.slice(0) : [0, 0, 0, 0];
|
||||
this.unlockPity = systemData.unlockPity ? systemData.unlockPity.slice(0) : [0, 0, 0, 0];
|
||||
|
||||
this.dexData = Object.assign(this.dexData, systemData.dexData);
|
||||
this.consolidateDexData(this.dexData);
|
||||
this.defaultDexData = null;
|
||||
|
||||
this.initParsedSystem(systemData);
|
||||
resolve(true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@ -507,7 +536,7 @@ export class GameData {
|
||||
return true;
|
||||
}
|
||||
|
||||
parseSystemData(dataStr: string): SystemSaveData {
|
||||
static parseSystemData(dataStr: string): SystemSaveData {
|
||||
return JSON.parse(dataStr, (k: string, v: any) => {
|
||||
if (k === "gameStats") {
|
||||
return new GameStats(v);
|
||||
@ -1296,7 +1325,7 @@ export class GameData {
|
||||
: this.getSessionSaveData();
|
||||
const maxIntAttrValue = 0x80000000;
|
||||
const systemData = useCachedSystem
|
||||
? this.parseSystemData(decrypt(localStorage.getItem(`data_${loggedInUser?.username}`)!, bypassLogin))
|
||||
? GameData.parseSystemData(decrypt(localStorage.getItem(`data_${loggedInUser?.username}`)!, bypassLogin))
|
||||
: this.getSystemSaveData(); // TODO: is this bang correct?
|
||||
|
||||
const request = {
|
||||
@ -1426,7 +1455,7 @@ export class GameData {
|
||||
case GameDataType.SYSTEM: {
|
||||
dataStr = this.convertSystemDataStr(dataStr);
|
||||
dataStr = dataStr.replace(/"playTime":\d+/, `"playTime":${this.gameStats.playTime + 60}`);
|
||||
const systemData = this.parseSystemData(dataStr);
|
||||
const systemData = GameData.parseSystemData(dataStr);
|
||||
valid = !!systemData.dexData && !!systemData.timestamp;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1,33 +1,41 @@
|
||||
import { pokerogueApi } from "#api/pokerogue-api";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { bypassLogin } from "#app/global-vars/bypass-login";
|
||||
import { AdminMode } from "#enums/admin-mode";
|
||||
import { Button } from "#enums/buttons";
|
||||
import { TextStyle } from "#enums/text-style";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { GameData } from "#system/game-data";
|
||||
import type {
|
||||
AdminUiHandlerService,
|
||||
AdminUiHandlerServiceMode,
|
||||
SearchAccountResponse,
|
||||
} from "#types/api/pokerogue-admin-api";
|
||||
import type { InputFieldConfig } from "#ui/form-modal-ui-handler";
|
||||
import { FormModalUiHandler } from "#ui/form-modal-ui-handler";
|
||||
import type { ModalConfig } from "#ui/modal-ui-handler";
|
||||
import { getTextColor } from "#ui/text";
|
||||
import { toTitleCase } from "#utils/strings";
|
||||
|
||||
type AdminUiHandlerService = "discord" | "google";
|
||||
type AdminUiHandlerServiceMode = "Link" | "Unlink";
|
||||
|
||||
export class AdminUiHandler extends FormModalUiHandler {
|
||||
private adminMode: AdminMode;
|
||||
private adminResult: AdminSearchInfo;
|
||||
private adminResult: SearchAccountResponse;
|
||||
private config: ModalConfig;
|
||||
|
||||
private tempGameData: GameData | null = null;
|
||||
|
||||
private readonly buttonGap = 10;
|
||||
private readonly ERR_REQUIRED_FIELD = (field: string) => {
|
||||
/** @returns "[field] is required" */
|
||||
private static ERR_REQUIRED_FIELD(field: string) {
|
||||
if (field === "username") {
|
||||
return `${toTitleCase(field)} is required`;
|
||||
}
|
||||
return `${toTitleCase(field)} Id is required`;
|
||||
};
|
||||
// returns a string saying whether a username has been successfully linked/unlinked to discord/google
|
||||
private readonly SUCCESS_SERVICE_MODE = (service: string, mode: string) => {
|
||||
}
|
||||
/** @returns "Username and [service] successfully [mode]ed" */
|
||||
private static SUCCESS_SERVICE_MODE(service: string, mode: string) {
|
||||
return `Username and ${service} successfully ${mode.toLowerCase()}ed`;
|
||||
};
|
||||
}
|
||||
|
||||
constructor(mode: UiMode | null = null) {
|
||||
super(mode);
|
||||
@ -48,50 +56,41 @@ export class AdminUiHandler extends FormModalUiHandler {
|
||||
override getButtonLabels(): string[] {
|
||||
switch (this.adminMode) {
|
||||
case AdminMode.LINK:
|
||||
return ["Link Account", "Cancel"];
|
||||
return ["Link Account", "Cancel", "", ""];
|
||||
case AdminMode.SEARCH:
|
||||
return ["Find account", "Cancel"];
|
||||
return ["Find account", "Cancel", "", ""];
|
||||
case AdminMode.ADMIN:
|
||||
return ["Back to search", "Cancel"];
|
||||
return ["Back to search", "Cancel", "Stats", "Pokedex"];
|
||||
default:
|
||||
return ["Activate ADMIN", "Cancel"];
|
||||
return ["Activate ADMIN", "Cancel", "Stats", "Pokedex"];
|
||||
}
|
||||
}
|
||||
|
||||
override getInputFieldConfigs(): InputFieldConfig[] {
|
||||
const inputFieldConfigs: InputFieldConfig[] = [];
|
||||
switch (this.adminMode) {
|
||||
case AdminMode.LINK:
|
||||
inputFieldConfigs.push({ label: "Username" });
|
||||
inputFieldConfigs.push({ label: "Discord ID" });
|
||||
break;
|
||||
return [{ label: "Username" }, { label: "Discord ID" }];
|
||||
case AdminMode.SEARCH:
|
||||
inputFieldConfigs.push({ label: "Username" });
|
||||
break;
|
||||
return [{ label: "Username" }];
|
||||
case AdminMode.ADMIN: {
|
||||
const adminResult = this.adminResult ?? {
|
||||
username: "",
|
||||
discordId: "",
|
||||
googleId: "",
|
||||
lastLoggedIn: "",
|
||||
registered: "",
|
||||
};
|
||||
// Discord and Google ID fields that are not empty get locked, other fields are all locked
|
||||
inputFieldConfigs.push({ label: "Username", isReadOnly: true });
|
||||
inputFieldConfigs.push({
|
||||
label: "Discord ID",
|
||||
isReadOnly: adminResult.discordId !== "",
|
||||
});
|
||||
inputFieldConfigs.push({
|
||||
label: "Google ID",
|
||||
isReadOnly: adminResult.googleId !== "",
|
||||
});
|
||||
inputFieldConfigs.push({ label: "Last played", isReadOnly: true });
|
||||
inputFieldConfigs.push({ label: "Registered", isReadOnly: true });
|
||||
break;
|
||||
return [
|
||||
{ label: "Username", isReadOnly: true },
|
||||
{
|
||||
label: "Discord ID",
|
||||
isReadOnly: (this.adminResult?.discordId ?? "") !== "",
|
||||
},
|
||||
{
|
||||
label: "Google ID",
|
||||
isReadOnly: (this.adminResult?.googleId ?? "") !== "",
|
||||
},
|
||||
{ label: "Last played", isReadOnly: true },
|
||||
{ label: "Registered", isReadOnly: true },
|
||||
];
|
||||
}
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
return inputFieldConfigs;
|
||||
}
|
||||
|
||||
processInput(button: Button): boolean {
|
||||
@ -126,36 +125,41 @@ export class AdminUiHandler extends FormModalUiHandler {
|
||||
this.buttonLabels[i].setText(labels[i]); // sets the label text
|
||||
}
|
||||
|
||||
this.errorMessage.setPosition(10, (hasTitle ? 31 : 5) + 20 * (fields.length - 1) + 16 + this.getButtonTopMargin()); // sets the position of the message dynamically
|
||||
if (isMessageError) {
|
||||
this.errorMessage.setColor(getTextColor(TextStyle.SUMMARY_PINK));
|
||||
this.errorMessage.setShadowColor(getTextColor(TextStyle.SUMMARY_PINK, true));
|
||||
} else {
|
||||
this.errorMessage.setColor(getTextColor(TextStyle.SUMMARY_GREEN));
|
||||
this.errorMessage.setShadowColor(getTextColor(TextStyle.SUMMARY_GREEN, true));
|
||||
const msgColor = isMessageError ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_GREEN;
|
||||
|
||||
this.errorMessage
|
||||
.setPosition(10, (hasTitle ? 31 : 5) + 20 * (fields.length - 1) + 16 + this.getButtonTopMargin())
|
||||
.setColor(getTextColor(msgColor))
|
||||
.setShadowColor(getTextColor(msgColor, true));
|
||||
|
||||
if (!super.show(args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (super.show(args)) {
|
||||
this.populateFields(this.adminMode, this.adminResult);
|
||||
const originalSubmitAction = this.submitAction;
|
||||
this.submitAction = _ => {
|
||||
this.submitAction = originalSubmitAction;
|
||||
const adminSearchResult: AdminSearchInfo = this.convertInputsToAdmin(); // this converts the input texts into a single object for use later
|
||||
const validFields = this.areFieldsValid(this.adminMode);
|
||||
if (validFields.error) {
|
||||
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); // this is here to force a loading screen to allow the admin tool to reopen again if there's an error
|
||||
return this.showMessage(validFields.errorMessage ?? "", adminSearchResult, true);
|
||||
}
|
||||
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] });
|
||||
if (this.adminMode === AdminMode.LINK) {
|
||||
this.hideLastButtons(this.adminMode === AdminMode.ADMIN ? 0 : 2);
|
||||
|
||||
this.populateFields(this.adminMode, this.adminResult);
|
||||
const originalSubmitAction = this.submitAction;
|
||||
this.submitAction = () => {
|
||||
this.submitAction = originalSubmitAction;
|
||||
const adminSearchResult: SearchAccountResponse = this.convertInputsToAdmin(); // this converts the input texts into a single object for use later
|
||||
const validFields = this.areFieldsValid(this.adminMode);
|
||||
if (validFields.error) {
|
||||
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); // this is here to force a loading screen to allow the admin tool to reopen again if there's an error
|
||||
return this.showMessage(validFields.errorMessage ?? "", adminSearchResult, true);
|
||||
}
|
||||
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] });
|
||||
switch (this.adminMode) {
|
||||
case AdminMode.LINK:
|
||||
this.adminLinkUnlink(adminSearchResult, "discord", "Link") // calls server to link discord
|
||||
.then(response => {
|
||||
if (response.error) {
|
||||
return this.showMessage(response.errorType, adminSearchResult, true); // error or some kind
|
||||
}
|
||||
return this.showMessage(this.SUCCESS_SERVICE_MODE("discord", "link"), adminSearchResult, false); // success
|
||||
return this.showMessage(AdminUiHandler.SUCCESS_SERVICE_MODE("discord", "link"), adminSearchResult, false); // success
|
||||
});
|
||||
} else if (this.adminMode === AdminMode.SEARCH) {
|
||||
break;
|
||||
case AdminMode.SEARCH:
|
||||
this.adminSearch(adminSearchResult) // admin search for username
|
||||
.then(response => {
|
||||
if (response.error) {
|
||||
@ -163,16 +167,16 @@ export class AdminUiHandler extends FormModalUiHandler {
|
||||
}
|
||||
this.updateAdminPanelInfo(response.adminSearchResult ?? adminSearchResult); // success
|
||||
});
|
||||
} else if (this.adminMode === AdminMode.ADMIN) {
|
||||
break;
|
||||
case AdminMode.ADMIN:
|
||||
this.updateAdminPanelInfo(adminSearchResult, AdminMode.SEARCH);
|
||||
}
|
||||
};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
showMessage(message: string, adminResult: AdminSearchInfo, isError: boolean) {
|
||||
showMessage(message: string, adminResult: SearchAccountResponse, isError: boolean) {
|
||||
globalScene.ui.setMode(
|
||||
UiMode.ADMIN,
|
||||
Object.assign(this.config, { errorMessage: message?.trim() }),
|
||||
@ -187,13 +191,65 @@ export class AdminUiHandler extends FormModalUiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private populateAdminFields(adminResult: SearchAccountResponse) {
|
||||
for (const [i, aR] of Object.keys(adminResult).entries()) {
|
||||
if (aR === "systemData") {
|
||||
continue;
|
||||
}
|
||||
this.inputs[i].setText(adminResult[aR]);
|
||||
if (aR === "discordId" || aR === "googleId") {
|
||||
// this is here to add the icons for linking/unlinking of google/discord IDs
|
||||
const nineSlice = this.inputContainers[i].list.find(iC => iC.type === "NineSlice");
|
||||
const img = globalScene.add.image(
|
||||
this.inputContainers[i].x + nineSlice!.width + this.buttonGap,
|
||||
this.inputContainers[i].y + Math.floor(nineSlice!.height / 2),
|
||||
adminResult[aR] === "" ? "link_icon" : "unlink_icon",
|
||||
);
|
||||
img
|
||||
.setName(`adminBtn_${aR}`)
|
||||
.setOrigin()
|
||||
.setInteractive()
|
||||
.on("pointerdown", () => {
|
||||
const service = aR.toLowerCase().replace("id", ""); // this takes our key (discordId or googleId) and removes the "Id" at the end to make it more url friendly
|
||||
const mode = adminResult[aR] === "" ? "Link" : "Unlink"; // this figures out if we're linking or unlinking a service
|
||||
const validFields = this.areFieldsValid(this.adminMode, service);
|
||||
if (validFields.error) {
|
||||
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); // this is here to force a loading screen to allow the admin tool to reopen again if there's an error
|
||||
return this.showMessage(validFields.errorMessage ?? "", adminResult, true);
|
||||
}
|
||||
this.adminLinkUnlink(this.convertInputsToAdmin(), service as AdminUiHandlerService, mode).then(response => {
|
||||
// attempts to link/unlink depending on the service
|
||||
if (response.error) {
|
||||
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] });
|
||||
return this.showMessage(response.errorType, adminResult, true); // fail
|
||||
}
|
||||
// success, reload panel with new results
|
||||
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] });
|
||||
this.adminSearch(adminResult).then(searchResponse => {
|
||||
if (searchResponse.error) {
|
||||
return this.showMessage(searchResponse.errorType, adminResult, true);
|
||||
}
|
||||
return this.showMessage(
|
||||
AdminUiHandler.SUCCESS_SERVICE_MODE(service, mode),
|
||||
searchResponse.adminSearchResult ?? adminResult,
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
this.addInteractionHoverEffect(img);
|
||||
this.modalContainer.add(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to update the fields' text when loading in a new admin ui handler. It uses the {@linkcode adminResult}
|
||||
* to update the input text based on the {@linkcode adminMode}. For a linking adminMode, it sets the username and discord.
|
||||
* For a search adminMode, it sets the username. For an admin adminMode, it sets all the info from adminResult in the
|
||||
* appropriate text boxes, and also sets the link/unlink icons for discord/google depending on the result
|
||||
*/
|
||||
private populateFields(adminMode: AdminMode, adminResult: AdminSearchInfo) {
|
||||
private populateFields(adminMode: AdminMode, adminResult: SearchAccountResponse) {
|
||||
switch (adminMode) {
|
||||
case AdminMode.LINK:
|
||||
this.inputs[0].setText(adminResult.username);
|
||||
@ -203,53 +259,7 @@ export class AdminUiHandler extends FormModalUiHandler {
|
||||
this.inputs[0].setText(adminResult.username);
|
||||
break;
|
||||
case AdminMode.ADMIN:
|
||||
Object.keys(adminResult).forEach((aR, i) => {
|
||||
this.inputs[i].setText(adminResult[aR]);
|
||||
if (aR === "discordId" || aR === "googleId") {
|
||||
// this is here to add the icons for linking/unlinking of google/discord IDs
|
||||
const nineSlice = this.inputContainers[i].list.find(iC => iC.type === "NineSlice");
|
||||
const img = globalScene.add.image(
|
||||
this.inputContainers[i].x + nineSlice!.width + this.buttonGap,
|
||||
this.inputContainers[i].y + Math.floor(nineSlice!.height / 2),
|
||||
adminResult[aR] === "" ? "link_icon" : "unlink_icon",
|
||||
);
|
||||
img.setName(`adminBtn_${aR}`);
|
||||
img.setOrigin(0.5, 0.5);
|
||||
img.setInteractive();
|
||||
img.on("pointerdown", () => {
|
||||
const service = aR.toLowerCase().replace("id", ""); // this takes our key (discordId or googleId) and removes the "Id" at the end to make it more url friendly
|
||||
const mode = adminResult[aR] === "" ? "Link" : "Unlink"; // this figures out if we're linking or unlinking a service
|
||||
const validFields = this.areFieldsValid(this.adminMode, service);
|
||||
if (validFields.error) {
|
||||
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); // this is here to force a loading screen to allow the admin tool to reopen again if there's an error
|
||||
return this.showMessage(validFields.errorMessage ?? "", adminResult, true);
|
||||
}
|
||||
this.adminLinkUnlink(this.convertInputsToAdmin(), service as AdminUiHandlerService, mode).then(
|
||||
response => {
|
||||
// attempts to link/unlink depending on the service
|
||||
if (response.error) {
|
||||
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] });
|
||||
return this.showMessage(response.errorType, adminResult, true); // fail
|
||||
}
|
||||
// success, reload panel with new results
|
||||
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] });
|
||||
this.adminSearch(adminResult).then(response => {
|
||||
if (response.error) {
|
||||
return this.showMessage(response.errorType, adminResult, true);
|
||||
}
|
||||
return this.showMessage(
|
||||
this.SUCCESS_SERVICE_MODE(service, mode),
|
||||
response.adminSearchResult ?? adminResult,
|
||||
false,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
this.addInteractionHoverEffect(img);
|
||||
this.modalContainer.add(img);
|
||||
}
|
||||
});
|
||||
this.populateAdminFields(adminResult);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -261,23 +271,23 @@ export class AdminUiHandler extends FormModalUiHandler {
|
||||
// username missing from link panel
|
||||
return {
|
||||
error: true,
|
||||
errorMessage: this.ERR_REQUIRED_FIELD("username"),
|
||||
errorMessage: AdminUiHandler.ERR_REQUIRED_FIELD("username"),
|
||||
};
|
||||
}
|
||||
if (!this.inputs[1].text) {
|
||||
// discordId missing from linking panel
|
||||
return {
|
||||
error: true,
|
||||
errorMessage: this.ERR_REQUIRED_FIELD("discord"),
|
||||
errorMessage: AdminUiHandler.ERR_REQUIRED_FIELD("discord"),
|
||||
};
|
||||
}
|
||||
break;
|
||||
case AdminMode.SEARCH:
|
||||
if (!this.inputs[0].text) {
|
||||
// username missing from search panel
|
||||
if (!this.inputs[0].text && !bypassLogin) {
|
||||
// username missing from search panel, skip check for local testing
|
||||
return {
|
||||
error: true,
|
||||
errorMessage: this.ERR_REQUIRED_FIELD("username"),
|
||||
errorMessage: AdminUiHandler.ERR_REQUIRED_FIELD("username"),
|
||||
};
|
||||
}
|
||||
break;
|
||||
@ -286,14 +296,14 @@ export class AdminUiHandler extends FormModalUiHandler {
|
||||
// discordId missing from admin panel
|
||||
return {
|
||||
error: true,
|
||||
errorMessage: this.ERR_REQUIRED_FIELD(service),
|
||||
errorMessage: AdminUiHandler.ERR_REQUIRED_FIELD(service),
|
||||
};
|
||||
}
|
||||
if (!this.inputs[2].text && service === "google") {
|
||||
// googleId missing from admin panel
|
||||
return {
|
||||
error: true,
|
||||
errorMessage: this.ERR_REQUIRED_FIELD(service),
|
||||
errorMessage: AdminUiHandler.ERR_REQUIRED_FIELD(service),
|
||||
};
|
||||
}
|
||||
break;
|
||||
@ -303,17 +313,32 @@ export class AdminUiHandler extends FormModalUiHandler {
|
||||
};
|
||||
}
|
||||
|
||||
private convertInputsToAdmin(): AdminSearchInfo {
|
||||
private convertInputsToAdmin(): SearchAccountResponse {
|
||||
const inputs = this.inputs;
|
||||
return {
|
||||
username: this.inputs[0]?.node ? this.inputs[0].text : "",
|
||||
discordId: this.inputs[1]?.node ? this.inputs[1]?.text : "",
|
||||
googleId: this.inputs[2]?.node ? this.inputs[2]?.text : "",
|
||||
lastLoggedIn: this.inputs[3]?.node ? this.inputs[3]?.text : "",
|
||||
registered: this.inputs[4]?.node ? this.inputs[4]?.text : "",
|
||||
username: inputs[0]?.node ? inputs[0].text : "",
|
||||
discordId: inputs[1]?.node ? inputs[1]?.text : "",
|
||||
googleId: inputs[2]?.node ? inputs[2]?.text : "",
|
||||
lastLoggedIn: inputs[3]?.node ? inputs[3]?.text : "",
|
||||
registered: inputs[4]?.node ? inputs[4]?.text : "",
|
||||
};
|
||||
}
|
||||
|
||||
private async adminSearch(adminSearchResult: AdminSearchInfo) {
|
||||
private async adminSearch(adminSearchResult: SearchAccountResponse) {
|
||||
this.tempGameData = null;
|
||||
// Mocking response, solely for local testing
|
||||
if (bypassLogin) {
|
||||
const fakeResponse: SearchAccountResponse = {
|
||||
username: adminSearchResult.username,
|
||||
discordId: "",
|
||||
googleId: "",
|
||||
lastLoggedIn: "",
|
||||
registered: "",
|
||||
};
|
||||
this.tempGameData = globalScene.gameData;
|
||||
return { adminSearchResult: fakeResponse, error: false };
|
||||
}
|
||||
|
||||
try {
|
||||
const [adminInfo, errorType] = await pokerogueApi.admin.searchAccount({
|
||||
username: adminSearchResult.username,
|
||||
@ -322,7 +347,14 @@ export class AdminUiHandler extends FormModalUiHandler {
|
||||
// error - if adminInfo.status === this.httpUserNotFoundErrorCode that means the username can't be found in the db
|
||||
return { adminSearchResult, error: true, errorType };
|
||||
}
|
||||
// success
|
||||
if (adminInfo.systemData) {
|
||||
const rawSystem = JSON.stringify(adminInfo.systemData);
|
||||
try {
|
||||
this.tempGameData = GameData.fromRawSystem(rawSystem);
|
||||
} catch {
|
||||
console.warn("Could not parse system data for admin panel, stats/pokedex will be unavailable!");
|
||||
}
|
||||
}
|
||||
return { adminSearchResult: adminInfo, error: false };
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@ -331,58 +363,23 @@ export class AdminUiHandler extends FormModalUiHandler {
|
||||
}
|
||||
|
||||
private async adminLinkUnlink(
|
||||
adminSearchResult: AdminSearchInfo,
|
||||
adminSearchResult: SearchAccountResponse,
|
||||
service: AdminUiHandlerService,
|
||||
mode: AdminUiHandlerServiceMode,
|
||||
) {
|
||||
try {
|
||||
let errorType: string | null = null;
|
||||
|
||||
if (service === "discord") {
|
||||
if (mode === "Link") {
|
||||
errorType = await pokerogueApi.admin.linkAccountToDiscord({
|
||||
discordId: adminSearchResult.discordId,
|
||||
username: adminSearchResult.username,
|
||||
});
|
||||
} else if (mode === "Unlink") {
|
||||
errorType = await pokerogueApi.admin.unlinkAccountFromDiscord({
|
||||
discordId: adminSearchResult.discordId,
|
||||
username: adminSearchResult.username,
|
||||
});
|
||||
} else {
|
||||
console.warn("Unknown mode", mode, "for service", service);
|
||||
}
|
||||
} else if (service === "google") {
|
||||
if (mode === "Link") {
|
||||
errorType = await pokerogueApi.admin.linkAccountToGoogleId({
|
||||
googleId: adminSearchResult.googleId,
|
||||
username: adminSearchResult.username,
|
||||
});
|
||||
} else if (mode === "Unlink") {
|
||||
errorType = await pokerogueApi.admin.unlinkAccountFromGoogleId({
|
||||
googleId: adminSearchResult.googleId,
|
||||
username: adminSearchResult.username,
|
||||
});
|
||||
} else {
|
||||
console.warn("Unknown mode", mode, "for service", service);
|
||||
}
|
||||
} else {
|
||||
console.warn("Unknown service", service);
|
||||
const error = await pokerogueApi.admin.linkUnlinkRequest(mode, service, adminSearchResult);
|
||||
if (error != null) {
|
||||
return { error: true, errorType: error };
|
||||
}
|
||||
|
||||
if (errorType) {
|
||||
// error - if response.status === this.httpUserNotFoundErrorCode that means the username can't be found in the db
|
||||
return { adminSearchResult, error: true, errorType };
|
||||
}
|
||||
// success!
|
||||
return { adminSearchResult, error: false };
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return { error: true, errorType: err };
|
||||
}
|
||||
return { adminSearchResult, error: false };
|
||||
}
|
||||
|
||||
private updateAdminPanelInfo(adminSearchResult: AdminSearchInfo, mode?: AdminMode) {
|
||||
private updateAdminPanelInfo(adminSearchResult: SearchAccountResponse, mode?: AdminMode) {
|
||||
mode = mode ?? AdminMode.ADMIN;
|
||||
globalScene.ui.setMode(
|
||||
UiMode.ADMIN,
|
||||
@ -397,6 +394,27 @@ export class AdminUiHandler extends FormModalUiHandler {
|
||||
globalScene.ui.revertMode();
|
||||
globalScene.ui.revertMode();
|
||||
},
|
||||
() => {
|
||||
if (this.tempGameData == null) {
|
||||
globalScene.ui.playError();
|
||||
return;
|
||||
}
|
||||
this.hide();
|
||||
globalScene.ui.setOverlayMode(
|
||||
UiMode.GAME_STATS,
|
||||
adminSearchResult.username,
|
||||
this.tempGameData,
|
||||
this.unhide.bind(this),
|
||||
);
|
||||
},
|
||||
() => {
|
||||
if (this.tempGameData == null) {
|
||||
globalScene.ui.playError();
|
||||
return;
|
||||
}
|
||||
this.hide();
|
||||
globalScene.ui.setOverlayMode(UiMode.POKEDEX, this.tempGameData, this.unhide.bind(this));
|
||||
},
|
||||
],
|
||||
},
|
||||
mode,
|
||||
@ -432,28 +450,3 @@ export class AdminUiHandler extends FormModalUiHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export enum AdminMode {
|
||||
LINK,
|
||||
SEARCH,
|
||||
ADMIN,
|
||||
}
|
||||
|
||||
export function getAdminModeName(adminMode: AdminMode): string {
|
||||
switch (adminMode) {
|
||||
case AdminMode.LINK:
|
||||
return "Link";
|
||||
case AdminMode.SEARCH:
|
||||
return "Search";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
interface AdminSearchInfo {
|
||||
username: string;
|
||||
discordId: string;
|
||||
googleId: string;
|
||||
lastLoggedIn: string;
|
||||
registered: string;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { Button } from "#enums/buttons";
|
||||
import { TextStyle } from "#enums/text-style";
|
||||
import type { UiMode } from "#enums/ui-mode";
|
||||
import type { AnyFn } from "#types/type-helpers";
|
||||
import type { ModalConfig } from "#ui/modal-ui-handler";
|
||||
import { ModalUiHandler } from "#ui/modal-ui-handler";
|
||||
import { addTextInputObject, addTextObject, getTextColor } from "#ui/text";
|
||||
@ -14,23 +14,14 @@ export interface FormModalConfig extends ModalConfig {
|
||||
}
|
||||
|
||||
export abstract class FormModalUiHandler extends ModalUiHandler {
|
||||
protected editing: boolean;
|
||||
protected inputContainers: Phaser.GameObjects.Container[];
|
||||
protected inputs: InputText[];
|
||||
protected editing = false;
|
||||
protected inputContainers: Phaser.GameObjects.Container[] = [];
|
||||
protected inputs: InputText[] = [];
|
||||
protected errorMessage: Phaser.GameObjects.Text;
|
||||
protected submitAction: Function | null;
|
||||
protected cancelAction: (() => void) | null;
|
||||
protected tween: Phaser.Tweens.Tween;
|
||||
protected formLabels: Phaser.GameObjects.Text[];
|
||||
|
||||
constructor(mode: UiMode | null = null) {
|
||||
super(mode);
|
||||
|
||||
this.editing = false;
|
||||
this.inputContainers = [];
|
||||
this.inputs = [];
|
||||
this.formLabels = [];
|
||||
}
|
||||
protected submitAction: AnyFn | undefined;
|
||||
protected cancelAction: (() => void) | undefined;
|
||||
protected tween: Phaser.Tweens.Tween | undefined;
|
||||
protected formLabels: Phaser.GameObjects.Text[] = [];
|
||||
|
||||
/**
|
||||
* Get configuration for all fields that should be part of the modal
|
||||
@ -77,18 +68,18 @@ export abstract class FormModalUiHandler extends ModalUiHandler {
|
||||
fontSize: "42px",
|
||||
wordWrap: { width: 850 },
|
||||
},
|
||||
);
|
||||
this.errorMessage.setColor(getTextColor(TextStyle.SUMMARY_PINK));
|
||||
this.errorMessage.setShadowColor(getTextColor(TextStyle.SUMMARY_PINK, true));
|
||||
this.errorMessage.setVisible(false);
|
||||
)
|
||||
.setColor(getTextColor(TextStyle.SUMMARY_PINK))
|
||||
.setShadowColor(getTextColor(TextStyle.SUMMARY_PINK, true))
|
||||
.setVisible(false);
|
||||
this.modalContainer.add(this.errorMessage);
|
||||
}
|
||||
|
||||
protected updateFields(fieldsConfig: InputFieldConfig[], hasTitle: boolean) {
|
||||
this.inputContainers = [];
|
||||
this.inputs = [];
|
||||
this.formLabels = [];
|
||||
fieldsConfig.forEach((config, f) => {
|
||||
const inputContainers = (this.inputContainers = new Array(fieldsConfig.length));
|
||||
const inputs = (this.inputs = new Array(fieldsConfig.length));
|
||||
const formLabels = (this.formLabels = new Array(fieldsConfig.length));
|
||||
for (const [f, config] of fieldsConfig.entries()) {
|
||||
// The Pokédex Scan Window uses width `300` instead of `160` like the other forms
|
||||
// Therefore, the label does not need to be shortened
|
||||
const label = addTextObject(
|
||||
@ -99,12 +90,13 @@ export abstract class FormModalUiHandler extends ModalUiHandler {
|
||||
);
|
||||
label.name = "formLabel" + f;
|
||||
|
||||
this.formLabels.push(label);
|
||||
formLabels[f] = label;
|
||||
this.modalContainer.add(label);
|
||||
|
||||
const inputWidth = label.width < 320 ? 80 : 80 - (label.width - 320) / 5.5;
|
||||
const inputContainer = globalScene.add.container(70 + (80 - inputWidth), (hasTitle ? 28 : 2) + 20 * f);
|
||||
inputContainer.setVisible(false);
|
||||
const inputContainer = globalScene.add
|
||||
.container(70 + (80 - inputWidth), (hasTitle ? 28 : 2) + 20 * f)
|
||||
.setVisible(false);
|
||||
|
||||
const inputBg = addWindow(0, 0, inputWidth, 16, false, false, 0, 0, WindowVariant.XTHIN);
|
||||
|
||||
@ -114,27 +106,27 @@ export abstract class FormModalUiHandler extends ModalUiHandler {
|
||||
type: isPassword ? "password" : "text",
|
||||
maxLength: isPassword ? 64 : 20,
|
||||
readOnly: isReadOnly,
|
||||
});
|
||||
input.setOrigin(0, 0);
|
||||
}).setOrigin(0);
|
||||
|
||||
inputContainer.add(inputBg);
|
||||
inputContainer.add(input);
|
||||
inputContainer.add([inputBg, input]);
|
||||
|
||||
this.inputContainers.push(inputContainer);
|
||||
inputContainers[f] = inputContainer;
|
||||
this.modalContainer.add(inputContainer);
|
||||
|
||||
this.inputs.push(input);
|
||||
});
|
||||
inputs[f] = input;
|
||||
}
|
||||
}
|
||||
|
||||
override show(args: any[]): boolean {
|
||||
if (super.show(args)) {
|
||||
this.inputContainers.map(ic => ic.setVisible(true));
|
||||
for (const ic of this.inputContainers) {
|
||||
ic.setActive(true).setVisible(true);
|
||||
}
|
||||
|
||||
const config = args[0] as FormModalConfig;
|
||||
const buttonActions = config.buttonActions ?? [];
|
||||
|
||||
this.submitAction = config.buttonActions.length > 0 ? config.buttonActions[0] : null;
|
||||
this.cancelAction = config.buttonActions[1] ?? null;
|
||||
[this.submitAction, this.cancelAction] = buttonActions;
|
||||
|
||||
// Auto focus the first input field after a short delay, to prevent accidental inputs
|
||||
setTimeout(() => {
|
||||
@ -146,26 +138,24 @@ export abstract class FormModalUiHandler extends ModalUiHandler {
|
||||
// properties that we set above, allowing their behavior to change after this method terminates
|
||||
// Some subclasses use this to add behavior to the submit and cancel action
|
||||
|
||||
this.buttonBgs[0].off("pointerdown");
|
||||
this.buttonBgs[0].on("pointerdown", () => {
|
||||
if (this.submitAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) {
|
||||
this.submitAction();
|
||||
}
|
||||
});
|
||||
const cancelBg = this.buttonBgs[1];
|
||||
if (cancelBg) {
|
||||
cancelBg.off("pointerdown");
|
||||
cancelBg.on("pointerdown", () => {
|
||||
this.buttonBgs[0] // formatting
|
||||
.off("pointerdown")
|
||||
.on("pointerdown", () => {
|
||||
if (this.submitAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) {
|
||||
this.submitAction();
|
||||
}
|
||||
});
|
||||
this.buttonBgs[1] // formatting
|
||||
?.off("pointerdown")
|
||||
.on("pointerdown", () => {
|
||||
// The seemingly redundant cancelAction check is intentionally left in as a defensive programming measure
|
||||
if (this.cancelAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) {
|
||||
this.cancelAction();
|
||||
}
|
||||
});
|
||||
}
|
||||
//#endregion: Override pointerDown events
|
||||
|
||||
this.modalContainer.y += 24;
|
||||
this.modalContainer.setAlpha(0);
|
||||
this.modalContainer.setAlpha(0).y += 24;
|
||||
|
||||
this.tween = globalScene.tweens.add({
|
||||
targets: this.modalContainer,
|
||||
@ -199,21 +189,37 @@ export abstract class FormModalUiHandler extends ModalUiHandler {
|
||||
updateContainer(config?: ModalConfig): void {
|
||||
super.updateContainer(config);
|
||||
|
||||
this.errorMessage.setText(this.getReadableErrorMessage((config as FormModalConfig)?.errorMessage || ""));
|
||||
this.errorMessage.setVisible(!!this.errorMessage.text);
|
||||
this.errorMessage
|
||||
.setText(this.getReadableErrorMessage((config as FormModalConfig)?.errorMessage || ""))
|
||||
.setVisible(!!this.errorMessage.text);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.modalContainer.setVisible(false).setActive(false);
|
||||
for (const ic of this.inputContainers) {
|
||||
ic.setVisible(false).setActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
unhide(): void {
|
||||
this.modalContainer.setActive(true).setVisible(true);
|
||||
for (const ic of this.inputContainers) {
|
||||
ic.setActive(true).setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
super.clear();
|
||||
this.modalContainer.setVisible(false);
|
||||
|
||||
this.inputContainers.map(ic => ic.setVisible(false));
|
||||
|
||||
this.submitAction = null;
|
||||
|
||||
if (this.tween) {
|
||||
this.tween.remove();
|
||||
for (const ic of this.inputContainers) {
|
||||
ic.setVisible(false).setActive(false);
|
||||
}
|
||||
|
||||
this.submitAction = undefined;
|
||||
|
||||
this.tween?.remove().destroy();
|
||||
this.tween = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import { PlayerGender } from "#enums/player-gender";
|
||||
import { TextStyle } from "#enums/text-style";
|
||||
import { UiTheme } from "#enums/ui-theme";
|
||||
import type { GameData } from "#system/game-data";
|
||||
import type { AnyFn } from "#types/type-helpers";
|
||||
import { addTextObject } from "#ui/text";
|
||||
import { UiHandler } from "#ui/ui-handler";
|
||||
import { addWindow } from "#ui/ui-theme";
|
||||
@ -239,6 +240,12 @@ export class GameStatsUiHandler extends UiHandler {
|
||||
/** Logged in username */
|
||||
private headerText: Phaser.GameObjects.Text;
|
||||
|
||||
/** The game data to display */
|
||||
private gameData: GameData;
|
||||
|
||||
/** A callback invoked when {@linkcode clear} is called */
|
||||
private exitCallback?: AnyFn | undefined;
|
||||
|
||||
/** Whether the UI is single column mode */
|
||||
private get singleCol(): boolean {
|
||||
const resolvedLang = i18next.resolvedLanguage ?? "en";
|
||||
@ -318,9 +325,9 @@ export class GameStatsUiHandler extends UiHandler {
|
||||
? i18next.t("trainerNames:playerF")
|
||||
: i18next.t("trainerNames:playerM");
|
||||
|
||||
const displayName = !globalScene.hideUsername
|
||||
? (loggedInUser?.username ?? i18next.t("common:guest"))
|
||||
: usernameReplacement;
|
||||
const displayName = globalScene.hideUsername
|
||||
? usernameReplacement
|
||||
: (loggedInUser?.username ?? i18next.t("common:guest"));
|
||||
|
||||
return i18next.t("gameStatsUiHandler:stats", { username: displayName });
|
||||
}
|
||||
@ -395,11 +402,19 @@ export class GameStatsUiHandler extends UiHandler {
|
||||
this.gameStatsContainer.setVisible(false);
|
||||
}
|
||||
|
||||
show(args: any[]): boolean {
|
||||
super.show(args);
|
||||
show([username, data, callback]: [] | [username: string, data: GameData, callback?: AnyFn]): boolean {
|
||||
super.show([]);
|
||||
|
||||
// show updated username on every render
|
||||
this.headerText.setText(this.getUsername());
|
||||
if (username != null && data != null) {
|
||||
this.gameData = data;
|
||||
this.exitCallback = callback;
|
||||
this.headerText.setText(username);
|
||||
} else {
|
||||
this.gameData = globalScene.gameData;
|
||||
this.exitCallback = undefined;
|
||||
// show updated username on every render
|
||||
this.headerText.setText(this.getUsername());
|
||||
}
|
||||
|
||||
this.gameStatsContainer.setActive(true).setVisible(true);
|
||||
|
||||
@ -436,7 +451,7 @@ export class GameStatsUiHandler extends UiHandler {
|
||||
const statKeys = Object.keys(displayStats).slice(this.cursor * columns, this.cursor * columns + perPage);
|
||||
statKeys.forEach((key, s) => {
|
||||
const stat = displayStats[key] as DisplayStat;
|
||||
const value = stat.sourceFunc?.(globalScene.gameData) ?? "-";
|
||||
const value = stat.sourceFunc?.(this.gameData) ?? "-";
|
||||
const valAsInt = Number.parseInt(value);
|
||||
this.statLabels[s].setText(
|
||||
!stat.hidden || Number.isNaN(value) || valAsInt ? i18next.t(`gameStatsUiHandler:${stat.label_key}`) : "???",
|
||||
@ -512,6 +527,12 @@ export class GameStatsUiHandler extends UiHandler {
|
||||
clear() {
|
||||
super.clear();
|
||||
this.gameStatsContainer.setVisible(false).setActive(false);
|
||||
|
||||
const callback = this.exitCallback;
|
||||
if (callback != null) {
|
||||
this.exitCallback = undefined;
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import { loggedInUser, updateUserInfo } from "#app/account";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { bypassLogin } from "#app/global-vars/bypass-login";
|
||||
import { handleTutorial, Tutorial } from "#app/tutorial";
|
||||
import { AdminMode, getAdminModeName } from "#enums/admin-mode";
|
||||
import { Button } from "#enums/buttons";
|
||||
import { GameDataType } from "#enums/game-data-type";
|
||||
import { TextStyle } from "#enums/text-style";
|
||||
@ -19,7 +20,6 @@ import { getEnumValues } from "#utils/enums";
|
||||
import { toCamelCase } from "#utils/strings";
|
||||
import { isBeta } from "#utils/utility-vars";
|
||||
import i18next from "i18next";
|
||||
import { AdminMode, getAdminModeName } from "./admin-ui-handler";
|
||||
|
||||
enum MenuOptions {
|
||||
GAME_SETTINGS,
|
||||
@ -452,7 +452,7 @@ export class MenuUiHandler extends MessageUiHandler {
|
||||
keepOpen: true,
|
||||
},
|
||||
];
|
||||
if (!bypassLogin && loggedInUser?.hasAdminRole) {
|
||||
if (bypassLogin || loggedInUser?.hasAdminRole) {
|
||||
communityOptions.push({
|
||||
label: "Admin",
|
||||
handler: () => {
|
||||
|
||||
@ -182,6 +182,32 @@ export abstract class ModalUiHandler extends UiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
hideLastButtons(hideCount = 0) {
|
||||
const visibleCount = this.buttonBgs.length - hideCount;
|
||||
|
||||
const totalButtonWidth = this.buttonBgs.slice(0, visibleCount).reduce((sum, bg) => sum + bg.width, 0);
|
||||
|
||||
// Clamping the button spacing between 2 and 12
|
||||
// Dividing by visibleCount rather than visibleCount-1 to leave space at the edge
|
||||
// -8 is to take the border of the background into account
|
||||
const spacing = Math.max(2, Math.min(12, (this.modalBg.width - 8 - totalButtonWidth) / visibleCount));
|
||||
|
||||
const totalVisibleWidth = totalButtonWidth + spacing * Math.max(visibleCount - 1, 0);
|
||||
|
||||
let x = (this.modalBg.width - totalVisibleWidth) / 2;
|
||||
|
||||
this.buttonContainers.forEach((container, i) => {
|
||||
const visible = i < visibleCount;
|
||||
|
||||
container.setActive(visible).setVisible(visible);
|
||||
|
||||
if (visible) {
|
||||
container.setPosition(x + this.buttonBgs[i].width / 2, this.modalBg.height - (this.buttonBgs[i].height + 8));
|
||||
x += this.buttonBgs[i].width + spacing;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
processInput(_button: Button): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -31,9 +31,11 @@ import { UiMode } from "#enums/ui-mode";
|
||||
import { UiTheme } from "#enums/ui-theme";
|
||||
import type { Variant } from "#sprites/variant";
|
||||
import { getVariantIcon, getVariantTint } from "#sprites/variant";
|
||||
import type { GameData } from "#system/game-data";
|
||||
import { SettingKeyboard } from "#system/settings-keyboard";
|
||||
import type { DexEntry } from "#types/dex-data";
|
||||
import type { DexAttrProps, StarterAttributes } from "#types/save-data";
|
||||
import type { AnyFn } from "#types/type-helpers";
|
||||
import type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler";
|
||||
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#ui/dropdown";
|
||||
import { FilterBar } from "#ui/filter-bar";
|
||||
@ -237,6 +239,10 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
private canShowFormTray: boolean;
|
||||
private filteredIndices: SpeciesId[];
|
||||
|
||||
private gameData: GameData;
|
||||
private exitCallback?: AnyFn;
|
||||
private blockOpenPage = false;
|
||||
|
||||
constructor() {
|
||||
super(UiMode.POKEDEX);
|
||||
}
|
||||
@ -642,8 +648,15 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
this.pokerusSpecies = getPokerusStarters();
|
||||
|
||||
// When calling with "refresh", we do not reset the cursor and filters
|
||||
if (args.length > 0 && args[0] === "refresh") {
|
||||
return false;
|
||||
if (args.length > 0) {
|
||||
if (args[0] === "refresh") {
|
||||
return false;
|
||||
}
|
||||
[this.gameData, this.exitCallback] = args;
|
||||
this.blockOpenPage = true;
|
||||
} else {
|
||||
this.gameData = globalScene.gameData;
|
||||
this.blockOpenPage = false;
|
||||
}
|
||||
|
||||
super.show(args);
|
||||
@ -685,8 +698,8 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
*/
|
||||
initStarterPrefs(species: PokemonSpecies): StarterAttributes {
|
||||
const starterAttributes = this.starterPreferences[species.speciesId];
|
||||
const dexEntry = globalScene.gameData.dexData[species.speciesId];
|
||||
const starterData = globalScene.gameData.starterData[species.speciesId];
|
||||
const dexEntry = this.gameData.dexData[species.speciesId];
|
||||
const starterData = this.gameData.starterData[species.speciesId];
|
||||
|
||||
// no preferences or Pokemon wasn't caught, return empty attribute
|
||||
if (!starterAttributes || !dexEntry.caughtAttr) {
|
||||
@ -753,15 +766,14 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
const selectedForm = starterAttributes.form;
|
||||
if (
|
||||
selectedForm !== undefined
|
||||
&& (!species.forms[selectedForm]?.isStarterSelectable
|
||||
|| !(caughtAttr & globalScene.gameData.getFormAttr(selectedForm)))
|
||||
&& (!species.forms[selectedForm]?.isStarterSelectable || !(caughtAttr & this.gameData.getFormAttr(selectedForm)))
|
||||
) {
|
||||
// requested form wasn't unlocked/isn't a starter form, purging setting
|
||||
starterAttributes.form = undefined;
|
||||
}
|
||||
|
||||
if (starterAttributes.nature !== undefined) {
|
||||
const unlockedNatures = globalScene.gameData.getNaturesForAttr(dexEntry.natureAttr);
|
||||
const unlockedNatures = this.gameData.getNaturesForAttr(dexEntry.natureAttr);
|
||||
if (unlockedNatures.indexOf(starterAttributes.nature as unknown as Nature) < 0) {
|
||||
// requested nature wasn't unlocked, purging setting
|
||||
starterAttributes.nature = undefined;
|
||||
@ -812,7 +824,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
return true;
|
||||
}
|
||||
if (!seenFilter) {
|
||||
const starterDexEntry = globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)];
|
||||
const starterDexEntry = this.gameData.dexData[this.getStarterSpeciesId(species.speciesId)];
|
||||
return !!starterDexEntry?.caughtAttr;
|
||||
}
|
||||
return false;
|
||||
@ -851,7 +863,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
*/
|
||||
isPassiveAvailable(speciesId: number): boolean {
|
||||
// Get this species ID's starter data
|
||||
const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)];
|
||||
const starterData = this.gameData.starterData[this.getStarterSpeciesId(speciesId)];
|
||||
|
||||
return (
|
||||
starterData.candyCount >= getPassiveCandyCount(speciesStarterCosts[this.getStarterSpeciesId(speciesId)])
|
||||
@ -866,7 +878,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
*/
|
||||
isValueReductionAvailable(speciesId: number): boolean {
|
||||
// Get this species ID's starter data
|
||||
const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)];
|
||||
const starterData = this.gameData.starterData[this.getStarterSpeciesId(speciesId)];
|
||||
|
||||
return (
|
||||
starterData.candyCount
|
||||
@ -883,7 +895,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
*/
|
||||
isSameSpeciesEggAvailable(speciesId: number): boolean {
|
||||
// Get this species ID's starter data
|
||||
const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)];
|
||||
const starterData = this.gameData.starterData[this.getStarterSpeciesId(speciesId)];
|
||||
|
||||
return (
|
||||
starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[this.getStarterSpeciesId(speciesId)])
|
||||
@ -1161,8 +1173,13 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
} else if (this.showingTray) {
|
||||
if (button === Button.ACTION) {
|
||||
const formIndex = this.trayForms[this.trayCursor].formIndex;
|
||||
ui.setOverlayMode(UiMode.POKEDEX_PAGE, this.lastSpecies, { form: formIndex }, this.filteredIndices);
|
||||
success = true;
|
||||
if (this.blockOpenPage) {
|
||||
success = false;
|
||||
error = true;
|
||||
} else {
|
||||
ui.setOverlayMode(UiMode.POKEDEX_PAGE, this.lastSpecies, { form: formIndex }, this.filteredIndices);
|
||||
success = true;
|
||||
}
|
||||
} else {
|
||||
const numberOfForms = this.trayContainers.length;
|
||||
const numOfRows = Math.ceil(numberOfForms / maxColumns);
|
||||
@ -1209,8 +1226,13 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
}
|
||||
}
|
||||
} else if (button === Button.ACTION) {
|
||||
ui.setOverlayMode(UiMode.POKEDEX_PAGE, this.lastSpecies, null, this.filteredIndices);
|
||||
success = true;
|
||||
if (this.blockOpenPage) {
|
||||
success = false;
|
||||
error = true;
|
||||
} else {
|
||||
ui.setOverlayMode(UiMode.POKEDEX_PAGE, this.lastSpecies, null, this.filteredIndices);
|
||||
success = true;
|
||||
}
|
||||
} else {
|
||||
switch (button) {
|
||||
case Button.UP:
|
||||
@ -1372,21 +1394,21 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
const starterId = this.getStarterSpeciesId(species.speciesId);
|
||||
|
||||
const currentDexAttr = this.getCurrentDexProps(species.speciesId);
|
||||
const props = this.getSanitizedProps(globalScene.gameData.getSpeciesDexAttrProps(species, currentDexAttr));
|
||||
const props = this.getSanitizedProps(this.gameData.getSpeciesDexAttrProps(species, currentDexAttr));
|
||||
|
||||
const data: ContainerData = {
|
||||
species,
|
||||
cost: globalScene.gameData.getSpeciesStarterValue(starterId),
|
||||
cost: this.gameData.getSpeciesStarterValue(starterId),
|
||||
props,
|
||||
};
|
||||
|
||||
// First, ensure you have the caught attributes for the species else default to bigint 0
|
||||
// TODO: This might be removed depending on how accessible we want the pokedex function to be
|
||||
const caughtAttr =
|
||||
(globalScene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0))
|
||||
& (globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)]?.caughtAttr || BigInt(0))
|
||||
(this.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0))
|
||||
& (this.gameData.dexData[this.getStarterSpeciesId(species.speciesId)]?.caughtAttr || BigInt(0))
|
||||
& species.getFullUnlocksData();
|
||||
const starterData = globalScene.gameData.starterData[starterId];
|
||||
const starterData = this.gameData.starterData[starterId];
|
||||
const isStarterProgressable = speciesEggMoves.hasOwnProperty(starterId);
|
||||
|
||||
// Name filter
|
||||
@ -1635,7 +1657,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
});
|
||||
|
||||
// Seen Filter
|
||||
const dexEntry = globalScene.gameData.dexData[species.speciesId];
|
||||
const dexEntry = this.gameData.dexData[species.speciesId];
|
||||
const isItSeen = this.isSeen(species, dexEntry, true) || !!dexEntry.caughtAttr;
|
||||
const fitsSeen = this.filterBar.getVals(DropDownColumn.MISC).some(misc => {
|
||||
if (misc.val === "SEEN_SPECIES" && misc.state === DropDownState.ON) {
|
||||
@ -1725,33 +1747,31 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
case SortCriteria.COST:
|
||||
return (a.cost - b.cost) * -sort.dir;
|
||||
case SortCriteria.CANDY: {
|
||||
const candyCountA =
|
||||
globalScene.gameData.starterData[this.getStarterSpeciesId(a.species.speciesId)].candyCount;
|
||||
const candyCountB =
|
||||
globalScene.gameData.starterData[this.getStarterSpeciesId(b.species.speciesId)].candyCount;
|
||||
const candyCountA = this.gameData.starterData[this.getStarterSpeciesId(a.species.speciesId)].candyCount;
|
||||
const candyCountB = this.gameData.starterData[this.getStarterSpeciesId(b.species.speciesId)].candyCount;
|
||||
return (candyCountA - candyCountB) * -sort.dir;
|
||||
}
|
||||
case SortCriteria.IV: {
|
||||
const avgIVsA =
|
||||
globalScene.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0)
|
||||
/ globalScene.gameData.dexData[a.species.speciesId].ivs.length;
|
||||
this.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0)
|
||||
/ this.gameData.dexData[a.species.speciesId].ivs.length;
|
||||
const avgIVsB =
|
||||
globalScene.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0)
|
||||
/ globalScene.gameData.dexData[b.species.speciesId].ivs.length;
|
||||
this.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0)
|
||||
/ this.gameData.dexData[b.species.speciesId].ivs.length;
|
||||
return (avgIVsA - avgIVsB) * -sort.dir;
|
||||
}
|
||||
case SortCriteria.NAME:
|
||||
return a.species.name.localeCompare(b.species.name) * -sort.dir;
|
||||
case SortCriteria.CAUGHT:
|
||||
return (
|
||||
(globalScene.gameData.dexData[a.species.speciesId].caughtCount
|
||||
- globalScene.gameData.dexData[b.species.speciesId].caughtCount)
|
||||
(this.gameData.dexData[a.species.speciesId].caughtCount
|
||||
- this.gameData.dexData[b.species.speciesId].caughtCount)
|
||||
* -sort.dir
|
||||
);
|
||||
case SortCriteria.HATCHED:
|
||||
return (
|
||||
(globalScene.gameData.dexData[this.getStarterSpeciesId(a.species.speciesId)].hatchedCount
|
||||
- globalScene.gameData.dexData[this.getStarterSpeciesId(b.species.speciesId)].hatchedCount)
|
||||
(this.gameData.dexData[this.getStarterSpeciesId(a.species.speciesId)].hatchedCount
|
||||
- this.gameData.dexData[this.getStarterSpeciesId(b.species.speciesId)].hatchedCount)
|
||||
* -sort.dir
|
||||
);
|
||||
default:
|
||||
@ -1795,10 +1815,10 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
container.checkIconId(props.female, props.formIndex, props.shiny, props.variant);
|
||||
|
||||
const speciesId = data.species.speciesId;
|
||||
const dexEntry = globalScene.gameData.dexData[speciesId];
|
||||
const dexEntry = this.gameData.dexData[speciesId];
|
||||
const caughtAttr =
|
||||
dexEntry.caughtAttr
|
||||
& globalScene.gameData.dexData[this.getStarterSpeciesId(speciesId)].caughtAttr
|
||||
& this.gameData.dexData[this.getStarterSpeciesId(speciesId)].caughtAttr
|
||||
& data.species.getFullUnlocksData();
|
||||
|
||||
if (caughtAttr & data.species.getFullUnlocksData() || globalScene.dexForDevs) {
|
||||
@ -1857,13 +1877,13 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
}
|
||||
|
||||
container.starterPassiveBgs.setVisible(
|
||||
!!globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].passiveAttr,
|
||||
!!this.gameData.starterData[this.getStarterSpeciesId(speciesId)].passiveAttr,
|
||||
);
|
||||
container.hiddenAbilityIcon.setVisible(
|
||||
!!caughtAttr && !!(globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].abilityAttr & 4),
|
||||
!!caughtAttr && !!(this.gameData.starterData[this.getStarterSpeciesId(speciesId)].abilityAttr & 4),
|
||||
);
|
||||
container.classicWinIcon.setVisible(
|
||||
globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].classicWinCount > 0,
|
||||
this.gameData.starterData[this.getStarterSpeciesId(speciesId)].classicWinCount > 0,
|
||||
);
|
||||
container.favoriteIcon.setVisible(this.starterPreferences[speciesId]?.favorite ?? false);
|
||||
|
||||
@ -1989,15 +2009,15 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
this.formTrayContainer.setX((goLeft ? boxPos.x - 18 * (this.trayColumns - spaceRight) : boxPos.x) - 3);
|
||||
this.formTrayContainer.setY(goUp ? boxPos.y - this.trayBg.height : boxPos.y + 17);
|
||||
|
||||
const dexEntry = globalScene.gameData.dexData[species.speciesId];
|
||||
const dexEntry = this.gameData.dexData[species.speciesId];
|
||||
const dexAttr = this.getCurrentDexProps(species.speciesId);
|
||||
const props = this.getSanitizedProps(globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, dexAttr));
|
||||
const props = this.getSanitizedProps(this.gameData.getSpeciesDexAttrProps(this.lastSpecies, dexAttr));
|
||||
|
||||
this.trayContainers = [];
|
||||
const isFormSeen = this.isSeen(species, dexEntry);
|
||||
this.trayForms.map((f, index) => {
|
||||
const isFormCaught = dexEntry
|
||||
? (dexEntry.caughtAttr & species.getFullUnlocksData() & globalScene.gameData.getFormAttr(f.formIndex ?? 0)) > 0n
|
||||
? (dexEntry.caughtAttr & species.getFullUnlocksData() & this.gameData.getFormAttr(f.formIndex ?? 0)) > 0n
|
||||
: false;
|
||||
const formContainer = new PokedexMonContainer(species, {
|
||||
formIndex: f.formIndex,
|
||||
@ -2066,7 +2086,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
}
|
||||
|
||||
getFriendship(speciesId: number) {
|
||||
let currentFriendship = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].friendship;
|
||||
let currentFriendship = this.gameData.starterData[this.getStarterSpeciesId(speciesId)].friendship;
|
||||
if (!currentFriendship || currentFriendship === undefined) {
|
||||
currentFriendship = 0;
|
||||
}
|
||||
@ -2094,7 +2114,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
if (container) {
|
||||
const lastSpeciesIcon = container.icon;
|
||||
const dexAttr = this.getCurrentDexProps(container.species.speciesId);
|
||||
const props = this.getSanitizedProps(globalScene.gameData.getSpeciesDexAttrProps(container.species, dexAttr));
|
||||
const props = this.getSanitizedProps(this.gameData.getSpeciesDexAttrProps(container.species, dexAttr));
|
||||
this.checkIconId(lastSpeciesIcon, container.species, props.female, props.formIndex, props.shiny, props.variant);
|
||||
this.iconAnimHandler.addOrUpdate(lastSpeciesIcon, PokemonIconAnimMode.NONE);
|
||||
// Resume the animation for the previously selected species
|
||||
@ -2103,7 +2123,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
}
|
||||
|
||||
setSpecies(species: PokemonSpecies | null) {
|
||||
this.speciesStarterDexEntry = species ? globalScene.gameData.dexData[species.speciesId] : null;
|
||||
this.speciesStarterDexEntry = species ? this.gameData.dexData[species.speciesId] : null;
|
||||
|
||||
if (!species && globalScene.ui.getTooltip().visible) {
|
||||
globalScene.ui.hideTooltip();
|
||||
@ -2182,15 +2202,15 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
}
|
||||
|
||||
if (species) {
|
||||
const dexEntry = globalScene.gameData.dexData[species.speciesId];
|
||||
const dexEntry = this.gameData.dexData[species.speciesId];
|
||||
const caughtAttr =
|
||||
dexEntry.caughtAttr
|
||||
& globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)].caughtAttr
|
||||
& this.gameData.dexData[this.getStarterSpeciesId(species.speciesId)].caughtAttr
|
||||
& species.getFullUnlocksData();
|
||||
|
||||
if (caughtAttr) {
|
||||
const props = this.getSanitizedProps(
|
||||
globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)),
|
||||
this.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)),
|
||||
);
|
||||
|
||||
if (shiny === undefined) {
|
||||
@ -2207,7 +2227,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
const isFormCaught = dexEntry ? (caughtAttr & globalScene.gameData.getFormAttr(formIndex ?? 0)) > 0n : false;
|
||||
const isFormCaught = dexEntry ? (caughtAttr & this.gameData.getFormAttr(formIndex ?? 0)) > 0n : false;
|
||||
const isFormSeen = this.isSeen(species, dexEntry);
|
||||
|
||||
const assetLoadCancelled = new BooleanHolder(false);
|
||||
@ -2291,7 +2311,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
updateStarterValueLabel(starter: PokedexMonContainer): void {
|
||||
const speciesId = starter.species.speciesId;
|
||||
const baseStarterValue = speciesStarterCosts[speciesId];
|
||||
const starterValue = globalScene.gameData.getSpeciesStarterValue(this.getStarterSpeciesId(speciesId));
|
||||
const starterValue = this.gameData.getSpeciesStarterValue(this.getStarterSpeciesId(speciesId));
|
||||
starter.cost = starterValue;
|
||||
let valueStr = starterValue.toString();
|
||||
if (valueStr.startsWith("0.")) {
|
||||
@ -2356,8 +2376,8 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
let props = 0n;
|
||||
const species = allSpecies.find(sp => sp.speciesId === speciesId);
|
||||
const caughtAttr =
|
||||
globalScene.gameData.dexData[speciesId].caughtAttr
|
||||
& globalScene.gameData.dexData[this.getStarterSpeciesId(speciesId)].caughtAttr
|
||||
this.gameData.dexData[speciesId].caughtAttr
|
||||
& this.gameData.dexData[this.getStarterSpeciesId(speciesId)].caughtAttr
|
||||
& (species?.getFullUnlocksData() ?? 0n);
|
||||
|
||||
/* this checks the gender of the pokemon; this works by checking a) that the starter preferences for the species exist, and if so, is it female. If so, it'll add DexAttr.FEMALE to our temp props
|
||||
@ -2401,7 +2421,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
props += BigInt(Math.pow(2, this.starterPreferences[speciesId]?.form)) * DexAttr.DEFAULT_FORM;
|
||||
} else {
|
||||
// Get the first unlocked form
|
||||
props += globalScene.gameData.getFormAttr(globalScene.gameData.getFormIndex(caughtAttr));
|
||||
props += this.gameData.getFormAttr(this.gameData.getFormIndex(caughtAttr));
|
||||
}
|
||||
|
||||
return props;
|
||||
@ -2426,6 +2446,13 @@ export class PokedexUiHandler extends MessageUiHandler {
|
||||
|
||||
this.starterSelectContainer.setVisible(false);
|
||||
this.blockInput = false;
|
||||
|
||||
// sanitize exit callback so it does not leak into future calls
|
||||
const exitCallback = this.exitCallback;
|
||||
if (exitCallback != null) {
|
||||
this.exitCallback = undefined;
|
||||
exitCallback();
|
||||
}
|
||||
}
|
||||
|
||||
checkIconId(
|
||||
|
||||
@ -2,12 +2,10 @@ import { PokerogueAdminApi } from "#api/pokerogue-admin-api";
|
||||
import { initServerForApiTests } from "#test/test-utils/test-file-initialization";
|
||||
import { getApiBaseUrl } from "#test/test-utils/test-utils";
|
||||
import type {
|
||||
LinkAccountToDiscordIdRequest,
|
||||
LinkAccountToGoogledIdRequest,
|
||||
DiscordRequest,
|
||||
GoogleRequest,
|
||||
SearchAccountRequest,
|
||||
SearchAccountResponse,
|
||||
UnlinkAccountFromDiscordIdRequest,
|
||||
UnlinkAccountFromGoogledIdRequest,
|
||||
} from "#types/api/pokerogue-admin-api";
|
||||
import { HttpResponse, http } from "msw";
|
||||
import type { SetupServerApi } from "msw/node";
|
||||
@ -31,7 +29,7 @@ describe("Pokerogue Admin API", () => {
|
||||
});
|
||||
|
||||
describe("Link Account to Discord", () => {
|
||||
const params: LinkAccountToDiscordIdRequest = {
|
||||
const params: DiscordRequest = {
|
||||
username: "test",
|
||||
discordId: "test-12575756",
|
||||
};
|
||||
@ -39,7 +37,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return null on SUCCESS", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/discordLink`, () => HttpResponse.json(true)));
|
||||
|
||||
const success = await adminApi.linkAccountToDiscord(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Link", "discord", params);
|
||||
|
||||
expect(success).toBeNull();
|
||||
});
|
||||
@ -47,7 +45,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return a ERR_GENERIC and report a warning on FAILURE", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/discordLink`, () => new HttpResponse("", { status: 400 })));
|
||||
|
||||
const success = await adminApi.linkAccountToDiscord(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Link", "discord", params);
|
||||
|
||||
expect(success).toBe(adminApi.ERR_GENERIC);
|
||||
expect(console.warn).toHaveBeenCalledWith("Could not link account with discord!", 400, "Bad Request");
|
||||
@ -56,7 +54,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return a ERR_USERNAME_NOT_FOUND and report a warning on 404", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/discordLink`, () => new HttpResponse("", { status: 404 })));
|
||||
|
||||
const success = await adminApi.linkAccountToDiscord(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Link", "discord", params);
|
||||
|
||||
expect(success).toBe(adminApi.ERR_USERNAME_NOT_FOUND);
|
||||
expect(console.warn).toHaveBeenCalledWith("Could not link account with discord!", 404, "Not Found");
|
||||
@ -65,7 +63,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return a ERR_GENERIC and report a warning on ERROR", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/discordLink`, () => HttpResponse.error()));
|
||||
|
||||
const success = await adminApi.linkAccountToDiscord(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Link", "discord", params);
|
||||
|
||||
expect(success).toBe(adminApi.ERR_GENERIC);
|
||||
expect(console.warn).toHaveBeenCalledWith("Could not link account with discord!", expect.any(Error));
|
||||
@ -73,7 +71,7 @@ describe("Pokerogue Admin API", () => {
|
||||
});
|
||||
|
||||
describe("Unlink Account from Discord", () => {
|
||||
const params: UnlinkAccountFromDiscordIdRequest = {
|
||||
const params: DiscordRequest = {
|
||||
username: "test",
|
||||
discordId: "test-12575756",
|
||||
};
|
||||
@ -81,7 +79,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return null on SUCCESS", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/discordUnlink`, () => HttpResponse.json(true)));
|
||||
|
||||
const success = await adminApi.unlinkAccountFromDiscord(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Unlink", "discord", params);
|
||||
|
||||
expect(success).toBeNull();
|
||||
});
|
||||
@ -89,7 +87,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return a ERR_GENERIC and report a warning on FAILURE", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/discordUnlink`, () => new HttpResponse("", { status: 400 })));
|
||||
|
||||
const success = await adminApi.unlinkAccountFromDiscord(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Unlink", "discord", params);
|
||||
|
||||
expect(success).toBe(adminApi.ERR_GENERIC);
|
||||
expect(console.warn).toHaveBeenCalledWith("Could not unlink account from discord!", 400, "Bad Request");
|
||||
@ -98,7 +96,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return a ERR_USERNAME_NOT_FOUND and report a warning on 404", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/discordUnlink`, () => new HttpResponse("", { status: 404 })));
|
||||
|
||||
const success = await adminApi.unlinkAccountFromDiscord(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Unlink", "discord", params);
|
||||
|
||||
expect(success).toBe(adminApi.ERR_USERNAME_NOT_FOUND);
|
||||
expect(console.warn).toHaveBeenCalledWith("Could not unlink account from discord!", 404, "Not Found");
|
||||
@ -107,7 +105,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return a ERR_GENERIC and report a warning on ERROR", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/discordUnlink`, () => HttpResponse.error()));
|
||||
|
||||
const success = await adminApi.unlinkAccountFromDiscord(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Unlink", "discord", params);
|
||||
|
||||
expect(success).toBe(adminApi.ERR_GENERIC);
|
||||
expect(console.warn).toHaveBeenCalledWith("Could not unlink account from discord!", expect.any(Error));
|
||||
@ -115,7 +113,7 @@ describe("Pokerogue Admin API", () => {
|
||||
});
|
||||
|
||||
describe("Link Account to Google", () => {
|
||||
const params: LinkAccountToGoogledIdRequest = {
|
||||
const params: GoogleRequest = {
|
||||
username: "test",
|
||||
googleId: "test-12575756",
|
||||
};
|
||||
@ -123,7 +121,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return null on SUCCESS", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/googleLink`, () => HttpResponse.json(true)));
|
||||
|
||||
const success = await adminApi.linkAccountToGoogleId(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Link", "google", params);
|
||||
|
||||
expect(success).toBeNull();
|
||||
});
|
||||
@ -131,7 +129,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return a ERR_GENERIC and report a warning on FAILURE", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/googleLink`, () => new HttpResponse("", { status: 400 })));
|
||||
|
||||
const success = await adminApi.linkAccountToGoogleId(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Link", "google", params);
|
||||
|
||||
expect(success).toBe(adminApi.ERR_GENERIC);
|
||||
expect(console.warn).toHaveBeenCalledWith("Could not link account with google!", 400, "Bad Request");
|
||||
@ -140,7 +138,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return a ERR_USERNAME_NOT_FOUND and report a warning on 404", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/googleLink`, () => new HttpResponse("", { status: 404 })));
|
||||
|
||||
const success = await adminApi.linkAccountToGoogleId(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Link", "google", params);
|
||||
|
||||
expect(success).toBe(adminApi.ERR_USERNAME_NOT_FOUND);
|
||||
expect(console.warn).toHaveBeenCalledWith("Could not link account with google!", 404, "Not Found");
|
||||
@ -149,7 +147,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return a ERR_GENERIC and report a warning on ERROR", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/googleLink`, () => HttpResponse.error()));
|
||||
|
||||
const success = await adminApi.linkAccountToGoogleId(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Link", "google", params);
|
||||
|
||||
expect(success).toBe(adminApi.ERR_GENERIC);
|
||||
expect(console.warn).toHaveBeenCalledWith("Could not link account with google!", expect.any(Error));
|
||||
@ -157,7 +155,7 @@ describe("Pokerogue Admin API", () => {
|
||||
});
|
||||
|
||||
describe("Unlink Account from Google", () => {
|
||||
const params: UnlinkAccountFromGoogledIdRequest = {
|
||||
const params: GoogleRequest = {
|
||||
username: "test",
|
||||
googleId: "test-12575756",
|
||||
};
|
||||
@ -165,7 +163,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return null on SUCCESS", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/googleUnlink`, () => HttpResponse.json(true)));
|
||||
|
||||
const success = await adminApi.unlinkAccountFromGoogleId(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Unlink", "google", params);
|
||||
|
||||
expect(success).toBeNull();
|
||||
});
|
||||
@ -173,7 +171,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return a ERR_GENERIC and report a warning on FAILURE", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/googleUnlink`, () => new HttpResponse("", { status: 400 })));
|
||||
|
||||
const success = await adminApi.unlinkAccountFromGoogleId(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Unlink", "google", params);
|
||||
|
||||
expect(success).toBe(adminApi.ERR_GENERIC);
|
||||
expect(console.warn).toHaveBeenCalledWith("Could not unlink account from google!", 400, "Bad Request");
|
||||
@ -182,7 +180,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return a ERR_USERNAME_NOT_FOUND and report a warning on 404", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/googleUnlink`, () => new HttpResponse("", { status: 404 })));
|
||||
|
||||
const success = await adminApi.unlinkAccountFromGoogleId(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Unlink", "google", params);
|
||||
|
||||
expect(success).toBe(adminApi.ERR_USERNAME_NOT_FOUND);
|
||||
expect(console.warn).toHaveBeenCalledWith("Could not unlink account from google!", 404, "Not Found");
|
||||
@ -191,7 +189,7 @@ describe("Pokerogue Admin API", () => {
|
||||
it("should return a ERR_GENERIC and report a warning on ERROR", async () => {
|
||||
server.use(http.post(`${apiBase}/admin/account/googleUnlink`, () => HttpResponse.error()));
|
||||
|
||||
const success = await adminApi.unlinkAccountFromGoogleId(params);
|
||||
const success = await adminApi.linkUnlinkRequest("Unlink", "google", params);
|
||||
|
||||
expect(success).toBe(adminApi.ERR_GENERIC);
|
||||
expect(console.warn).toHaveBeenCalledWith("Could not unlink account from google!", expect.any(Error));
|
||||
|
||||
@ -28,6 +28,7 @@ import type { SelectTargetPhase } from "#phases/select-target-phase";
|
||||
import { TurnEndPhase } from "#phases/turn-end-phase";
|
||||
import { TurnInitPhase } from "#phases/turn-init-phase";
|
||||
import { TurnStartPhase } from "#phases/turn-start-phase";
|
||||
import { GameData } from "#system/game-data";
|
||||
import { ErrorInterceptor } from "#test/test-utils/error-interceptor";
|
||||
import { generateStarters } from "#test/test-utils/game-manager-utils";
|
||||
import { GameWrapper } from "#test/test-utils/game-wrapper";
|
||||
@ -451,7 +452,7 @@ export class GameManager {
|
||||
const dataRaw = fs.readFileSync(path, { encoding: "utf8", flag: "r" });
|
||||
let dataStr = AES.decrypt(dataRaw, saveKey).toString(enc.Utf8);
|
||||
dataStr = this.scene.gameData.convertSystemDataStr(dataStr);
|
||||
const systemData = this.scene.gameData.parseSystemData(dataStr);
|
||||
const systemData = GameData.parseSystemData(dataStr);
|
||||
const valid = !!systemData.dexData && !!systemData.timestamp;
|
||||
if (valid) {
|
||||
await updateUserInfo();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user