mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-12-15 22:35:20 +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 {
|
import type { SystemSaveData } from "#types/save-data";
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SearchAccountRequest {
|
export interface SearchAccountRequest {
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DiscordRequest extends SearchAccountRequest {
|
||||||
|
discordId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GoogleRequest extends SearchAccountRequest {
|
||||||
|
googleId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SearchAccountResponse {
|
export interface SearchAccountResponse {
|
||||||
username: string;
|
username: string;
|
||||||
discordId: string;
|
discordId: string;
|
||||||
googleId: string;
|
googleId: string;
|
||||||
lastLoggedIn: string;
|
lastLoggedIn: string;
|
||||||
registered: 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 { ApiBase } from "#api/api-base";
|
||||||
import type {
|
import type {
|
||||||
LinkAccountToDiscordIdRequest,
|
AdminUiHandlerService,
|
||||||
LinkAccountToGoogledIdRequest,
|
AdminUiHandlerServiceMode,
|
||||||
|
PokerogueAdminApiParams,
|
||||||
SearchAccountRequest,
|
SearchAccountRequest,
|
||||||
SearchAccountResponse,
|
SearchAccountResponse,
|
||||||
UnlinkAccountFromDiscordIdRequest,
|
|
||||||
UnlinkAccountFromGoogledIdRequest,
|
|
||||||
} from "#types/api/pokerogue-admin-api";
|
} from "#types/api/pokerogue-admin-api";
|
||||||
|
|
||||||
export class PokerogueAdminApi extends ApiBase {
|
export class PokerogueAdminApi extends ApiBase {
|
||||||
public readonly ERR_USERNAME_NOT_FOUND: string = "Username not found!";
|
public readonly ERR_USERNAME_NOT_FOUND: string = "Username not found!";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Links an account to a discord id.
|
* Link or unlink a third party service to/from a user account
|
||||||
* @param params The {@linkcode LinkAccountToDiscordIdRequest} to send
|
* @param mode - The mode, either "Link" or "Unlink"
|
||||||
* @returns `null` if successful, error message if not
|
* @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 {
|
try {
|
||||||
const response = await this.doPost("/admin/account/discordLink", params, "form-urlencoded");
|
const response = await this.doPost(endpoint, params, "form-urlencoded");
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return null;
|
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) {
|
if (response.status === 404) {
|
||||||
return this.ERR_USERNAME_NOT_FOUND;
|
return this.ERR_USERNAME_NOT_FOUND;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("Could not link account with discord!", err);
|
console.warn(errMsg, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.ERR_GENERIC;
|
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.
|
* Search an account.
|
||||||
* @param params The {@linkcode SearchAccountRequest} to send
|
* @param params The {@linkcode SearchAccountRequest} to send
|
||||||
|
|||||||
@ -146,12 +146,20 @@ export class GameData {
|
|||||||
public eggPity: number[];
|
public eggPity: number[];
|
||||||
public unlockPity: number[];
|
public unlockPity: number[];
|
||||||
|
|
||||||
constructor() {
|
/**
|
||||||
this.loadSettings();
|
* @param fromRaw - If true, will skip initialization of fields that are normally randomized on new game start. Used for the admin panel; default `false`
|
||||||
this.loadGamepadSettings();
|
*/
|
||||||
this.loadMappingConfigs();
|
constructor(fromRaw = false) {
|
||||||
this.trainerId = randInt(65536);
|
if (fromRaw) {
|
||||||
this.secretId = randInt(65536);
|
this.trainerId = 0;
|
||||||
|
this.secretId = 0;
|
||||||
|
} else {
|
||||||
|
this.loadSettings();
|
||||||
|
this.loadGamepadSettings();
|
||||||
|
this.loadMappingConfigs();
|
||||||
|
this.trainerId = randInt(65536);
|
||||||
|
this.secretId = randInt(65536);
|
||||||
|
}
|
||||||
this.starterData = {};
|
this.starterData = {};
|
||||||
this.gameStats = new GameStats();
|
this.gameStats = new GameStats();
|
||||||
this.runHistory = {};
|
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> {
|
public initSystem(systemDataStr: string, cachedSystemDataStr?: string): Promise<boolean> {
|
||||||
const { promise, resolve } = Promise.withResolvers<boolean>();
|
const { promise, resolve } = Promise.withResolvers<boolean>();
|
||||||
try {
|
try {
|
||||||
let systemData = this.parseSystemData(systemDataStr);
|
let systemData = GameData.parseSystemData(systemDataStr);
|
||||||
|
|
||||||
if (cachedSystemDataStr) {
|
if (cachedSystemDataStr) {
|
||||||
const cachedSystemData = this.parseSystemData(cachedSystemDataStr);
|
const cachedSystemData = GameData.parseSystemData(cachedSystemDataStr);
|
||||||
if (cachedSystemData.timestamp > systemData.timestamp) {
|
if (cachedSystemData.timestamp > systemData.timestamp) {
|
||||||
console.debug("Use cached system");
|
console.debug("Use cached system");
|
||||||
systemData = cachedSystemData;
|
systemData = cachedSystemData;
|
||||||
@ -307,7 +417,9 @@ export class GameData {
|
|||||||
if (isLocal || isBeta) {
|
if (isLocal || isBeta) {
|
||||||
try {
|
try {
|
||||||
console.debug(
|
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) {
|
} catch (err) {
|
||||||
console.debug("Attempt to log system data failed:", err);
|
console.debug("Attempt to log system data failed:", err);
|
||||||
@ -322,90 +434,7 @@ export class GameData {
|
|||||||
localStorage.setItem(lsItemKey, "");
|
localStorage.setItem(lsItemKey, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
applySystemVersionMigration(systemData);
|
this.initParsedSystem(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;
|
|
||||||
|
|
||||||
resolve(true);
|
resolve(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -507,7 +536,7 @@ export class GameData {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseSystemData(dataStr: string): SystemSaveData {
|
static parseSystemData(dataStr: string): SystemSaveData {
|
||||||
return JSON.parse(dataStr, (k: string, v: any) => {
|
return JSON.parse(dataStr, (k: string, v: any) => {
|
||||||
if (k === "gameStats") {
|
if (k === "gameStats") {
|
||||||
return new GameStats(v);
|
return new GameStats(v);
|
||||||
@ -1296,7 +1325,7 @@ export class GameData {
|
|||||||
: this.getSessionSaveData();
|
: this.getSessionSaveData();
|
||||||
const maxIntAttrValue = 0x80000000;
|
const maxIntAttrValue = 0x80000000;
|
||||||
const systemData = useCachedSystem
|
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?
|
: this.getSystemSaveData(); // TODO: is this bang correct?
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
@ -1426,7 +1455,7 @@ export class GameData {
|
|||||||
case GameDataType.SYSTEM: {
|
case GameDataType.SYSTEM: {
|
||||||
dataStr = this.convertSystemDataStr(dataStr);
|
dataStr = this.convertSystemDataStr(dataStr);
|
||||||
dataStr = dataStr.replace(/"playTime":\d+/, `"playTime":${this.gameStats.playTime + 60}`);
|
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;
|
valid = !!systemData.dexData && !!systemData.timestamp;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,41 @@
|
|||||||
import { pokerogueApi } from "#api/pokerogue-api";
|
import { pokerogueApi } from "#api/pokerogue-api";
|
||||||
import { globalScene } from "#app/global-scene";
|
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 { Button } from "#enums/buttons";
|
||||||
import { TextStyle } from "#enums/text-style";
|
import { TextStyle } from "#enums/text-style";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
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 type { InputFieldConfig } from "#ui/form-modal-ui-handler";
|
||||||
import { FormModalUiHandler } from "#ui/form-modal-ui-handler";
|
import { FormModalUiHandler } from "#ui/form-modal-ui-handler";
|
||||||
import type { ModalConfig } from "#ui/modal-ui-handler";
|
import type { ModalConfig } from "#ui/modal-ui-handler";
|
||||||
import { getTextColor } from "#ui/text";
|
import { getTextColor } from "#ui/text";
|
||||||
import { toTitleCase } from "#utils/strings";
|
import { toTitleCase } from "#utils/strings";
|
||||||
|
|
||||||
type AdminUiHandlerService = "discord" | "google";
|
|
||||||
type AdminUiHandlerServiceMode = "Link" | "Unlink";
|
|
||||||
|
|
||||||
export class AdminUiHandler extends FormModalUiHandler {
|
export class AdminUiHandler extends FormModalUiHandler {
|
||||||
private adminMode: AdminMode;
|
private adminMode: AdminMode;
|
||||||
private adminResult: AdminSearchInfo;
|
private adminResult: SearchAccountResponse;
|
||||||
private config: ModalConfig;
|
private config: ModalConfig;
|
||||||
|
|
||||||
|
private tempGameData: GameData | null = null;
|
||||||
|
|
||||||
private readonly buttonGap = 10;
|
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") {
|
if (field === "username") {
|
||||||
return `${toTitleCase(field)} is required`;
|
return `${toTitleCase(field)} is required`;
|
||||||
}
|
}
|
||||||
return `${toTitleCase(field)} Id is required`;
|
return `${toTitleCase(field)} Id is required`;
|
||||||
};
|
}
|
||||||
// returns a string saying whether a username has been successfully linked/unlinked to discord/google
|
/** @returns "Username and [service] successfully [mode]ed" */
|
||||||
private readonly SUCCESS_SERVICE_MODE = (service: string, mode: string) => {
|
private static SUCCESS_SERVICE_MODE(service: string, mode: string) {
|
||||||
return `Username and ${service} successfully ${mode.toLowerCase()}ed`;
|
return `Username and ${service} successfully ${mode.toLowerCase()}ed`;
|
||||||
};
|
}
|
||||||
|
|
||||||
constructor(mode: UiMode | null = null) {
|
constructor(mode: UiMode | null = null) {
|
||||||
super(mode);
|
super(mode);
|
||||||
@ -48,50 +56,41 @@ export class AdminUiHandler extends FormModalUiHandler {
|
|||||||
override getButtonLabels(): string[] {
|
override getButtonLabels(): string[] {
|
||||||
switch (this.adminMode) {
|
switch (this.adminMode) {
|
||||||
case AdminMode.LINK:
|
case AdminMode.LINK:
|
||||||
return ["Link Account", "Cancel"];
|
return ["Link Account", "Cancel", "", ""];
|
||||||
case AdminMode.SEARCH:
|
case AdminMode.SEARCH:
|
||||||
return ["Find account", "Cancel"];
|
return ["Find account", "Cancel", "", ""];
|
||||||
case AdminMode.ADMIN:
|
case AdminMode.ADMIN:
|
||||||
return ["Back to search", "Cancel"];
|
return ["Back to search", "Cancel", "Stats", "Pokedex"];
|
||||||
default:
|
default:
|
||||||
return ["Activate ADMIN", "Cancel"];
|
return ["Activate ADMIN", "Cancel", "Stats", "Pokedex"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override getInputFieldConfigs(): InputFieldConfig[] {
|
override getInputFieldConfigs(): InputFieldConfig[] {
|
||||||
const inputFieldConfigs: InputFieldConfig[] = [];
|
|
||||||
switch (this.adminMode) {
|
switch (this.adminMode) {
|
||||||
case AdminMode.LINK:
|
case AdminMode.LINK:
|
||||||
inputFieldConfigs.push({ label: "Username" });
|
return [{ label: "Username" }, { label: "Discord ID" }];
|
||||||
inputFieldConfigs.push({ label: "Discord ID" });
|
|
||||||
break;
|
|
||||||
case AdminMode.SEARCH:
|
case AdminMode.SEARCH:
|
||||||
inputFieldConfigs.push({ label: "Username" });
|
return [{ label: "Username" }];
|
||||||
break;
|
|
||||||
case AdminMode.ADMIN: {
|
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
|
// Discord and Google ID fields that are not empty get locked, other fields are all locked
|
||||||
inputFieldConfigs.push({ label: "Username", isReadOnly: true });
|
return [
|
||||||
inputFieldConfigs.push({
|
{ label: "Username", isReadOnly: true },
|
||||||
label: "Discord ID",
|
{
|
||||||
isReadOnly: adminResult.discordId !== "",
|
label: "Discord ID",
|
||||||
});
|
isReadOnly: (this.adminResult?.discordId ?? "") !== "",
|
||||||
inputFieldConfigs.push({
|
},
|
||||||
label: "Google ID",
|
{
|
||||||
isReadOnly: adminResult.googleId !== "",
|
label: "Google ID",
|
||||||
});
|
isReadOnly: (this.adminResult?.googleId ?? "") !== "",
|
||||||
inputFieldConfigs.push({ label: "Last played", isReadOnly: true });
|
},
|
||||||
inputFieldConfigs.push({ label: "Registered", isReadOnly: true });
|
{ label: "Last played", isReadOnly: true },
|
||||||
break;
|
{ label: "Registered", isReadOnly: true },
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
return inputFieldConfigs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processInput(button: Button): boolean {
|
processInput(button: Button): boolean {
|
||||||
@ -126,36 +125,41 @@ export class AdminUiHandler extends FormModalUiHandler {
|
|||||||
this.buttonLabels[i].setText(labels[i]); // sets the label text
|
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
|
const msgColor = isMessageError ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_GREEN;
|
||||||
if (isMessageError) {
|
|
||||||
this.errorMessage.setColor(getTextColor(TextStyle.SUMMARY_PINK));
|
this.errorMessage
|
||||||
this.errorMessage.setShadowColor(getTextColor(TextStyle.SUMMARY_PINK, true));
|
.setPosition(10, (hasTitle ? 31 : 5) + 20 * (fields.length - 1) + 16 + this.getButtonTopMargin())
|
||||||
} else {
|
.setColor(getTextColor(msgColor))
|
||||||
this.errorMessage.setColor(getTextColor(TextStyle.SUMMARY_GREEN));
|
.setShadowColor(getTextColor(msgColor, true));
|
||||||
this.errorMessage.setShadowColor(getTextColor(TextStyle.SUMMARY_GREEN, true));
|
|
||||||
|
if (!super.show(args)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (super.show(args)) {
|
this.hideLastButtons(this.adminMode === AdminMode.ADMIN ? 0 : 2);
|
||||||
this.populateFields(this.adminMode, this.adminResult);
|
|
||||||
const originalSubmitAction = this.submitAction;
|
this.populateFields(this.adminMode, this.adminResult);
|
||||||
this.submitAction = _ => {
|
const originalSubmitAction = this.submitAction;
|
||||||
this.submitAction = originalSubmitAction;
|
this.submitAction = () => {
|
||||||
const adminSearchResult: AdminSearchInfo = this.convertInputsToAdmin(); // this converts the input texts into a single object for use later
|
this.submitAction = originalSubmitAction;
|
||||||
const validFields = this.areFieldsValid(this.adminMode);
|
const adminSearchResult: SearchAccountResponse = this.convertInputsToAdmin(); // this converts the input texts into a single object for use later
|
||||||
if (validFields.error) {
|
const validFields = this.areFieldsValid(this.adminMode);
|
||||||
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
|
if (validFields.error) {
|
||||||
return this.showMessage(validFields.errorMessage ?? "", adminSearchResult, true);
|
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) {
|
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] });
|
||||||
|
switch (this.adminMode) {
|
||||||
|
case AdminMode.LINK:
|
||||||
this.adminLinkUnlink(adminSearchResult, "discord", "Link") // calls server to link discord
|
this.adminLinkUnlink(adminSearchResult, "discord", "Link") // calls server to link discord
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
return this.showMessage(response.errorType, adminSearchResult, true); // error or some kind
|
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
|
this.adminSearch(adminSearchResult) // admin search for username
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
@ -163,16 +167,16 @@ export class AdminUiHandler extends FormModalUiHandler {
|
|||||||
}
|
}
|
||||||
this.updateAdminPanelInfo(response.adminSearchResult ?? adminSearchResult); // success
|
this.updateAdminPanelInfo(response.adminSearchResult ?? adminSearchResult); // success
|
||||||
});
|
});
|
||||||
} else if (this.adminMode === AdminMode.ADMIN) {
|
break;
|
||||||
|
case AdminMode.ADMIN:
|
||||||
this.updateAdminPanelInfo(adminSearchResult, AdminMode.SEARCH);
|
this.updateAdminPanelInfo(adminSearchResult, AdminMode.SEARCH);
|
||||||
}
|
break;
|
||||||
};
|
}
|
||||||
return true;
|
};
|
||||||
}
|
return true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showMessage(message: string, adminResult: AdminSearchInfo, isError: boolean) {
|
showMessage(message: string, adminResult: SearchAccountResponse, isError: boolean) {
|
||||||
globalScene.ui.setMode(
|
globalScene.ui.setMode(
|
||||||
UiMode.ADMIN,
|
UiMode.ADMIN,
|
||||||
Object.assign(this.config, { errorMessage: message?.trim() }),
|
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}
|
* 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.
|
* 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
|
* 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
|
* 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) {
|
switch (adminMode) {
|
||||||
case AdminMode.LINK:
|
case AdminMode.LINK:
|
||||||
this.inputs[0].setText(adminResult.username);
|
this.inputs[0].setText(adminResult.username);
|
||||||
@ -203,53 +259,7 @@ export class AdminUiHandler extends FormModalUiHandler {
|
|||||||
this.inputs[0].setText(adminResult.username);
|
this.inputs[0].setText(adminResult.username);
|
||||||
break;
|
break;
|
||||||
case AdminMode.ADMIN:
|
case AdminMode.ADMIN:
|
||||||
Object.keys(adminResult).forEach((aR, i) => {
|
this.populateAdminFields(adminResult);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,23 +271,23 @@ export class AdminUiHandler extends FormModalUiHandler {
|
|||||||
// username missing from link panel
|
// username missing from link panel
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
errorMessage: this.ERR_REQUIRED_FIELD("username"),
|
errorMessage: AdminUiHandler.ERR_REQUIRED_FIELD("username"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!this.inputs[1].text) {
|
if (!this.inputs[1].text) {
|
||||||
// discordId missing from linking panel
|
// discordId missing from linking panel
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
errorMessage: this.ERR_REQUIRED_FIELD("discord"),
|
errorMessage: AdminUiHandler.ERR_REQUIRED_FIELD("discord"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AdminMode.SEARCH:
|
case AdminMode.SEARCH:
|
||||||
if (!this.inputs[0].text) {
|
if (!this.inputs[0].text && !bypassLogin) {
|
||||||
// username missing from search panel
|
// username missing from search panel, skip check for local testing
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
errorMessage: this.ERR_REQUIRED_FIELD("username"),
|
errorMessage: AdminUiHandler.ERR_REQUIRED_FIELD("username"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -286,14 +296,14 @@ export class AdminUiHandler extends FormModalUiHandler {
|
|||||||
// discordId missing from admin panel
|
// discordId missing from admin panel
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
errorMessage: this.ERR_REQUIRED_FIELD(service),
|
errorMessage: AdminUiHandler.ERR_REQUIRED_FIELD(service),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!this.inputs[2].text && service === "google") {
|
if (!this.inputs[2].text && service === "google") {
|
||||||
// googleId missing from admin panel
|
// googleId missing from admin panel
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
errorMessage: this.ERR_REQUIRED_FIELD(service),
|
errorMessage: AdminUiHandler.ERR_REQUIRED_FIELD(service),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -303,17 +313,32 @@ export class AdminUiHandler extends FormModalUiHandler {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertInputsToAdmin(): AdminSearchInfo {
|
private convertInputsToAdmin(): SearchAccountResponse {
|
||||||
|
const inputs = this.inputs;
|
||||||
return {
|
return {
|
||||||
username: this.inputs[0]?.node ? this.inputs[0].text : "",
|
username: inputs[0]?.node ? inputs[0].text : "",
|
||||||
discordId: this.inputs[1]?.node ? this.inputs[1]?.text : "",
|
discordId: inputs[1]?.node ? inputs[1]?.text : "",
|
||||||
googleId: this.inputs[2]?.node ? this.inputs[2]?.text : "",
|
googleId: inputs[2]?.node ? inputs[2]?.text : "",
|
||||||
lastLoggedIn: this.inputs[3]?.node ? this.inputs[3]?.text : "",
|
lastLoggedIn: inputs[3]?.node ? inputs[3]?.text : "",
|
||||||
registered: this.inputs[4]?.node ? this.inputs[4]?.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 {
|
try {
|
||||||
const [adminInfo, errorType] = await pokerogueApi.admin.searchAccount({
|
const [adminInfo, errorType] = await pokerogueApi.admin.searchAccount({
|
||||||
username: adminSearchResult.username,
|
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
|
// error - if adminInfo.status === this.httpUserNotFoundErrorCode that means the username can't be found in the db
|
||||||
return { adminSearchResult, error: true, errorType };
|
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 };
|
return { adminSearchResult: adminInfo, error: false };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -331,58 +363,23 @@ export class AdminUiHandler extends FormModalUiHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async adminLinkUnlink(
|
private async adminLinkUnlink(
|
||||||
adminSearchResult: AdminSearchInfo,
|
adminSearchResult: SearchAccountResponse,
|
||||||
service: AdminUiHandlerService,
|
service: AdminUiHandlerService,
|
||||||
mode: AdminUiHandlerServiceMode,
|
mode: AdminUiHandlerServiceMode,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
let errorType: string | null = null;
|
const error = await pokerogueApi.admin.linkUnlinkRequest(mode, service, adminSearchResult);
|
||||||
|
if (error != null) {
|
||||||
if (service === "discord") {
|
return { error: true, errorType: error };
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return { error: true, errorType: 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;
|
mode = mode ?? AdminMode.ADMIN;
|
||||||
globalScene.ui.setMode(
|
globalScene.ui.setMode(
|
||||||
UiMode.ADMIN,
|
UiMode.ADMIN,
|
||||||
@ -397,6 +394,27 @@ export class AdminUiHandler extends FormModalUiHandler {
|
|||||||
globalScene.ui.revertMode();
|
globalScene.ui.revertMode();
|
||||||
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,
|
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 { globalScene } from "#app/global-scene";
|
||||||
import { Button } from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
import { TextStyle } from "#enums/text-style";
|
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 type { ModalConfig } from "#ui/modal-ui-handler";
|
||||||
import { ModalUiHandler } from "#ui/modal-ui-handler";
|
import { ModalUiHandler } from "#ui/modal-ui-handler";
|
||||||
import { addTextInputObject, addTextObject, getTextColor } from "#ui/text";
|
import { addTextInputObject, addTextObject, getTextColor } from "#ui/text";
|
||||||
@ -14,23 +14,14 @@ export interface FormModalConfig extends ModalConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class FormModalUiHandler extends ModalUiHandler {
|
export abstract class FormModalUiHandler extends ModalUiHandler {
|
||||||
protected editing: boolean;
|
protected editing = false;
|
||||||
protected inputContainers: Phaser.GameObjects.Container[];
|
protected inputContainers: Phaser.GameObjects.Container[] = [];
|
||||||
protected inputs: InputText[];
|
protected inputs: InputText[] = [];
|
||||||
protected errorMessage: Phaser.GameObjects.Text;
|
protected errorMessage: Phaser.GameObjects.Text;
|
||||||
protected submitAction: Function | null;
|
protected submitAction: AnyFn | undefined;
|
||||||
protected cancelAction: (() => void) | null;
|
protected cancelAction: (() => void) | undefined;
|
||||||
protected tween: Phaser.Tweens.Tween;
|
protected tween: Phaser.Tweens.Tween | undefined;
|
||||||
protected formLabels: Phaser.GameObjects.Text[];
|
protected formLabels: Phaser.GameObjects.Text[] = [];
|
||||||
|
|
||||||
constructor(mode: UiMode | null = null) {
|
|
||||||
super(mode);
|
|
||||||
|
|
||||||
this.editing = false;
|
|
||||||
this.inputContainers = [];
|
|
||||||
this.inputs = [];
|
|
||||||
this.formLabels = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get configuration for all fields that should be part of the modal
|
* Get configuration for all fields that should be part of the modal
|
||||||
@ -77,18 +68,18 @@ export abstract class FormModalUiHandler extends ModalUiHandler {
|
|||||||
fontSize: "42px",
|
fontSize: "42px",
|
||||||
wordWrap: { width: 850 },
|
wordWrap: { width: 850 },
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
this.errorMessage.setColor(getTextColor(TextStyle.SUMMARY_PINK));
|
.setColor(getTextColor(TextStyle.SUMMARY_PINK))
|
||||||
this.errorMessage.setShadowColor(getTextColor(TextStyle.SUMMARY_PINK, true));
|
.setShadowColor(getTextColor(TextStyle.SUMMARY_PINK, true))
|
||||||
this.errorMessage.setVisible(false);
|
.setVisible(false);
|
||||||
this.modalContainer.add(this.errorMessage);
|
this.modalContainer.add(this.errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateFields(fieldsConfig: InputFieldConfig[], hasTitle: boolean) {
|
protected updateFields(fieldsConfig: InputFieldConfig[], hasTitle: boolean) {
|
||||||
this.inputContainers = [];
|
const inputContainers = (this.inputContainers = new Array(fieldsConfig.length));
|
||||||
this.inputs = [];
|
const inputs = (this.inputs = new Array(fieldsConfig.length));
|
||||||
this.formLabels = [];
|
const formLabels = (this.formLabels = new Array(fieldsConfig.length));
|
||||||
fieldsConfig.forEach((config, f) => {
|
for (const [f, config] of fieldsConfig.entries()) {
|
||||||
// The Pokédex Scan Window uses width `300` instead of `160` like the other forms
|
// The Pokédex Scan Window uses width `300` instead of `160` like the other forms
|
||||||
// Therefore, the label does not need to be shortened
|
// Therefore, the label does not need to be shortened
|
||||||
const label = addTextObject(
|
const label = addTextObject(
|
||||||
@ -99,12 +90,13 @@ export abstract class FormModalUiHandler extends ModalUiHandler {
|
|||||||
);
|
);
|
||||||
label.name = "formLabel" + f;
|
label.name = "formLabel" + f;
|
||||||
|
|
||||||
this.formLabels.push(label);
|
formLabels[f] = label;
|
||||||
this.modalContainer.add(label);
|
this.modalContainer.add(label);
|
||||||
|
|
||||||
const inputWidth = label.width < 320 ? 80 : 80 - (label.width - 320) / 5.5;
|
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);
|
const inputContainer = globalScene.add
|
||||||
inputContainer.setVisible(false);
|
.container(70 + (80 - inputWidth), (hasTitle ? 28 : 2) + 20 * f)
|
||||||
|
.setVisible(false);
|
||||||
|
|
||||||
const inputBg = addWindow(0, 0, inputWidth, 16, false, false, 0, 0, WindowVariant.XTHIN);
|
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",
|
type: isPassword ? "password" : "text",
|
||||||
maxLength: isPassword ? 64 : 20,
|
maxLength: isPassword ? 64 : 20,
|
||||||
readOnly: isReadOnly,
|
readOnly: isReadOnly,
|
||||||
});
|
}).setOrigin(0);
|
||||||
input.setOrigin(0, 0);
|
|
||||||
|
|
||||||
inputContainer.add(inputBg);
|
inputContainer.add([inputBg, input]);
|
||||||
inputContainer.add(input);
|
|
||||||
|
|
||||||
this.inputContainers.push(inputContainer);
|
inputContainers[f] = inputContainer;
|
||||||
this.modalContainer.add(inputContainer);
|
this.modalContainer.add(inputContainer);
|
||||||
|
|
||||||
this.inputs.push(input);
|
inputs[f] = input;
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override show(args: any[]): boolean {
|
override show(args: any[]): boolean {
|
||||||
if (super.show(args)) {
|
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 config = args[0] as FormModalConfig;
|
||||||
|
const buttonActions = config.buttonActions ?? [];
|
||||||
|
|
||||||
this.submitAction = config.buttonActions.length > 0 ? config.buttonActions[0] : null;
|
[this.submitAction, this.cancelAction] = buttonActions;
|
||||||
this.cancelAction = config.buttonActions[1] ?? null;
|
|
||||||
|
|
||||||
// Auto focus the first input field after a short delay, to prevent accidental inputs
|
// Auto focus the first input field after a short delay, to prevent accidental inputs
|
||||||
setTimeout(() => {
|
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
|
// 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
|
// Some subclasses use this to add behavior to the submit and cancel action
|
||||||
|
|
||||||
this.buttonBgs[0].off("pointerdown");
|
this.buttonBgs[0] // formatting
|
||||||
this.buttonBgs[0].on("pointerdown", () => {
|
.off("pointerdown")
|
||||||
if (this.submitAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) {
|
.on("pointerdown", () => {
|
||||||
this.submitAction();
|
if (this.submitAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) {
|
||||||
}
|
this.submitAction();
|
||||||
});
|
}
|
||||||
const cancelBg = this.buttonBgs[1];
|
});
|
||||||
if (cancelBg) {
|
this.buttonBgs[1] // formatting
|
||||||
cancelBg.off("pointerdown");
|
?.off("pointerdown")
|
||||||
cancelBg.on("pointerdown", () => {
|
.on("pointerdown", () => {
|
||||||
// The seemingly redundant cancelAction check is intentionally left in as a defensive programming measure
|
// The seemingly redundant cancelAction check is intentionally left in as a defensive programming measure
|
||||||
if (this.cancelAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) {
|
if (this.cancelAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) {
|
||||||
this.cancelAction();
|
this.cancelAction();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
//#endregion: Override pointerDown events
|
//#endregion: Override pointerDown events
|
||||||
|
|
||||||
this.modalContainer.y += 24;
|
this.modalContainer.setAlpha(0).y += 24;
|
||||||
this.modalContainer.setAlpha(0);
|
|
||||||
|
|
||||||
this.tween = globalScene.tweens.add({
|
this.tween = globalScene.tweens.add({
|
||||||
targets: this.modalContainer,
|
targets: this.modalContainer,
|
||||||
@ -199,21 +189,37 @@ export abstract class FormModalUiHandler extends ModalUiHandler {
|
|||||||
updateContainer(config?: ModalConfig): void {
|
updateContainer(config?: ModalConfig): void {
|
||||||
super.updateContainer(config);
|
super.updateContainer(config);
|
||||||
|
|
||||||
this.errorMessage.setText(this.getReadableErrorMessage((config as FormModalConfig)?.errorMessage || ""));
|
this.errorMessage
|
||||||
this.errorMessage.setVisible(!!this.errorMessage.text);
|
.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 {
|
clear(): void {
|
||||||
super.clear();
|
super.clear();
|
||||||
this.modalContainer.setVisible(false);
|
this.modalContainer.setVisible(false);
|
||||||
|
|
||||||
this.inputContainers.map(ic => ic.setVisible(false));
|
for (const ic of this.inputContainers) {
|
||||||
|
ic.setVisible(false).setActive(false);
|
||||||
this.submitAction = null;
|
|
||||||
|
|
||||||
if (this.tween) {
|
|
||||||
this.tween.remove();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 { TextStyle } from "#enums/text-style";
|
||||||
import { UiTheme } from "#enums/ui-theme";
|
import { UiTheme } from "#enums/ui-theme";
|
||||||
import type { GameData } from "#system/game-data";
|
import type { GameData } from "#system/game-data";
|
||||||
|
import type { AnyFn } from "#types/type-helpers";
|
||||||
import { addTextObject } from "#ui/text";
|
import { addTextObject } from "#ui/text";
|
||||||
import { UiHandler } from "#ui/ui-handler";
|
import { UiHandler } from "#ui/ui-handler";
|
||||||
import { addWindow } from "#ui/ui-theme";
|
import { addWindow } from "#ui/ui-theme";
|
||||||
@ -239,6 +240,12 @@ export class GameStatsUiHandler extends UiHandler {
|
|||||||
/** Logged in username */
|
/** Logged in username */
|
||||||
private headerText: Phaser.GameObjects.Text;
|
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 */
|
/** Whether the UI is single column mode */
|
||||||
private get singleCol(): boolean {
|
private get singleCol(): boolean {
|
||||||
const resolvedLang = i18next.resolvedLanguage ?? "en";
|
const resolvedLang = i18next.resolvedLanguage ?? "en";
|
||||||
@ -318,9 +325,9 @@ export class GameStatsUiHandler extends UiHandler {
|
|||||||
? i18next.t("trainerNames:playerF")
|
? i18next.t("trainerNames:playerF")
|
||||||
: i18next.t("trainerNames:playerM");
|
: i18next.t("trainerNames:playerM");
|
||||||
|
|
||||||
const displayName = !globalScene.hideUsername
|
const displayName = globalScene.hideUsername
|
||||||
? (loggedInUser?.username ?? i18next.t("common:guest"))
|
? usernameReplacement
|
||||||
: usernameReplacement;
|
: (loggedInUser?.username ?? i18next.t("common:guest"));
|
||||||
|
|
||||||
return i18next.t("gameStatsUiHandler:stats", { username: displayName });
|
return i18next.t("gameStatsUiHandler:stats", { username: displayName });
|
||||||
}
|
}
|
||||||
@ -395,11 +402,19 @@ export class GameStatsUiHandler extends UiHandler {
|
|||||||
this.gameStatsContainer.setVisible(false);
|
this.gameStatsContainer.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
show(args: any[]): boolean {
|
show([username, data, callback]: [] | [username: string, data: GameData, callback?: AnyFn]): boolean {
|
||||||
super.show(args);
|
super.show([]);
|
||||||
|
|
||||||
// show updated username on every render
|
if (username != null && data != null) {
|
||||||
this.headerText.setText(this.getUsername());
|
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);
|
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);
|
const statKeys = Object.keys(displayStats).slice(this.cursor * columns, this.cursor * columns + perPage);
|
||||||
statKeys.forEach((key, s) => {
|
statKeys.forEach((key, s) => {
|
||||||
const stat = displayStats[key] as DisplayStat;
|
const stat = displayStats[key] as DisplayStat;
|
||||||
const value = stat.sourceFunc?.(globalScene.gameData) ?? "-";
|
const value = stat.sourceFunc?.(this.gameData) ?? "-";
|
||||||
const valAsInt = Number.parseInt(value);
|
const valAsInt = Number.parseInt(value);
|
||||||
this.statLabels[s].setText(
|
this.statLabels[s].setText(
|
||||||
!stat.hidden || Number.isNaN(value) || valAsInt ? i18next.t(`gameStatsUiHandler:${stat.label_key}`) : "???",
|
!stat.hidden || Number.isNaN(value) || valAsInt ? i18next.t(`gameStatsUiHandler:${stat.label_key}`) : "???",
|
||||||
@ -512,6 +527,12 @@ export class GameStatsUiHandler extends UiHandler {
|
|||||||
clear() {
|
clear() {
|
||||||
super.clear();
|
super.clear();
|
||||||
this.gameStatsContainer.setVisible(false).setActive(false);
|
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 { globalScene } from "#app/global-scene";
|
||||||
import { bypassLogin } from "#app/global-vars/bypass-login";
|
import { bypassLogin } from "#app/global-vars/bypass-login";
|
||||||
import { handleTutorial, Tutorial } from "#app/tutorial";
|
import { handleTutorial, Tutorial } from "#app/tutorial";
|
||||||
|
import { AdminMode, getAdminModeName } from "#enums/admin-mode";
|
||||||
import { Button } from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
import { GameDataType } from "#enums/game-data-type";
|
import { GameDataType } from "#enums/game-data-type";
|
||||||
import { TextStyle } from "#enums/text-style";
|
import { TextStyle } from "#enums/text-style";
|
||||||
@ -19,7 +20,6 @@ import { getEnumValues } from "#utils/enums";
|
|||||||
import { toCamelCase } from "#utils/strings";
|
import { toCamelCase } from "#utils/strings";
|
||||||
import { isBeta } from "#utils/utility-vars";
|
import { isBeta } from "#utils/utility-vars";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { AdminMode, getAdminModeName } from "./admin-ui-handler";
|
|
||||||
|
|
||||||
enum MenuOptions {
|
enum MenuOptions {
|
||||||
GAME_SETTINGS,
|
GAME_SETTINGS,
|
||||||
@ -452,7 +452,7 @@ export class MenuUiHandler extends MessageUiHandler {
|
|||||||
keepOpen: true,
|
keepOpen: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
if (!bypassLogin && loggedInUser?.hasAdminRole) {
|
if (bypassLogin || loggedInUser?.hasAdminRole) {
|
||||||
communityOptions.push({
|
communityOptions.push({
|
||||||
label: "Admin",
|
label: "Admin",
|
||||||
handler: () => {
|
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 {
|
processInput(_button: Button): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,9 +31,11 @@ import { UiMode } from "#enums/ui-mode";
|
|||||||
import { UiTheme } from "#enums/ui-theme";
|
import { UiTheme } from "#enums/ui-theme";
|
||||||
import type { Variant } from "#sprites/variant";
|
import type { Variant } from "#sprites/variant";
|
||||||
import { getVariantIcon, getVariantTint } from "#sprites/variant";
|
import { getVariantIcon, getVariantTint } from "#sprites/variant";
|
||||||
|
import type { GameData } from "#system/game-data";
|
||||||
import { SettingKeyboard } from "#system/settings-keyboard";
|
import { SettingKeyboard } from "#system/settings-keyboard";
|
||||||
import type { DexEntry } from "#types/dex-data";
|
import type { DexEntry } from "#types/dex-data";
|
||||||
import type { DexAttrProps, StarterAttributes } from "#types/save-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 type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler";
|
||||||
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#ui/dropdown";
|
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#ui/dropdown";
|
||||||
import { FilterBar } from "#ui/filter-bar";
|
import { FilterBar } from "#ui/filter-bar";
|
||||||
@ -237,6 +239,10 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
private canShowFormTray: boolean;
|
private canShowFormTray: boolean;
|
||||||
private filteredIndices: SpeciesId[];
|
private filteredIndices: SpeciesId[];
|
||||||
|
|
||||||
|
private gameData: GameData;
|
||||||
|
private exitCallback?: AnyFn;
|
||||||
|
private blockOpenPage = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(UiMode.POKEDEX);
|
super(UiMode.POKEDEX);
|
||||||
}
|
}
|
||||||
@ -642,8 +648,15 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
this.pokerusSpecies = getPokerusStarters();
|
this.pokerusSpecies = getPokerusStarters();
|
||||||
|
|
||||||
// When calling with "refresh", we do not reset the cursor and filters
|
// When calling with "refresh", we do not reset the cursor and filters
|
||||||
if (args.length > 0 && args[0] === "refresh") {
|
if (args.length > 0) {
|
||||||
return false;
|
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);
|
super.show(args);
|
||||||
@ -685,8 +698,8 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
*/
|
*/
|
||||||
initStarterPrefs(species: PokemonSpecies): StarterAttributes {
|
initStarterPrefs(species: PokemonSpecies): StarterAttributes {
|
||||||
const starterAttributes = this.starterPreferences[species.speciesId];
|
const starterAttributes = this.starterPreferences[species.speciesId];
|
||||||
const dexEntry = globalScene.gameData.dexData[species.speciesId];
|
const dexEntry = this.gameData.dexData[species.speciesId];
|
||||||
const starterData = globalScene.gameData.starterData[species.speciesId];
|
const starterData = this.gameData.starterData[species.speciesId];
|
||||||
|
|
||||||
// no preferences or Pokemon wasn't caught, return empty attribute
|
// no preferences or Pokemon wasn't caught, return empty attribute
|
||||||
if (!starterAttributes || !dexEntry.caughtAttr) {
|
if (!starterAttributes || !dexEntry.caughtAttr) {
|
||||||
@ -753,15 +766,14 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
const selectedForm = starterAttributes.form;
|
const selectedForm = starterAttributes.form;
|
||||||
if (
|
if (
|
||||||
selectedForm !== undefined
|
selectedForm !== undefined
|
||||||
&& (!species.forms[selectedForm]?.isStarterSelectable
|
&& (!species.forms[selectedForm]?.isStarterSelectable || !(caughtAttr & this.gameData.getFormAttr(selectedForm)))
|
||||||
|| !(caughtAttr & globalScene.gameData.getFormAttr(selectedForm)))
|
|
||||||
) {
|
) {
|
||||||
// requested form wasn't unlocked/isn't a starter form, purging setting
|
// requested form wasn't unlocked/isn't a starter form, purging setting
|
||||||
starterAttributes.form = undefined;
|
starterAttributes.form = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (starterAttributes.nature !== 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) {
|
if (unlockedNatures.indexOf(starterAttributes.nature as unknown as Nature) < 0) {
|
||||||
// requested nature wasn't unlocked, purging setting
|
// requested nature wasn't unlocked, purging setting
|
||||||
starterAttributes.nature = undefined;
|
starterAttributes.nature = undefined;
|
||||||
@ -812,7 +824,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!seenFilter) {
|
if (!seenFilter) {
|
||||||
const starterDexEntry = globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)];
|
const starterDexEntry = this.gameData.dexData[this.getStarterSpeciesId(species.speciesId)];
|
||||||
return !!starterDexEntry?.caughtAttr;
|
return !!starterDexEntry?.caughtAttr;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -851,7 +863,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
*/
|
*/
|
||||||
isPassiveAvailable(speciesId: number): boolean {
|
isPassiveAvailable(speciesId: number): boolean {
|
||||||
// Get this species ID's starter data
|
// Get this species ID's starter data
|
||||||
const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)];
|
const starterData = this.gameData.starterData[this.getStarterSpeciesId(speciesId)];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
starterData.candyCount >= getPassiveCandyCount(speciesStarterCosts[this.getStarterSpeciesId(speciesId)])
|
starterData.candyCount >= getPassiveCandyCount(speciesStarterCosts[this.getStarterSpeciesId(speciesId)])
|
||||||
@ -866,7 +878,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
*/
|
*/
|
||||||
isValueReductionAvailable(speciesId: number): boolean {
|
isValueReductionAvailable(speciesId: number): boolean {
|
||||||
// Get this species ID's starter data
|
// Get this species ID's starter data
|
||||||
const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)];
|
const starterData = this.gameData.starterData[this.getStarterSpeciesId(speciesId)];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
starterData.candyCount
|
starterData.candyCount
|
||||||
@ -883,7 +895,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
*/
|
*/
|
||||||
isSameSpeciesEggAvailable(speciesId: number): boolean {
|
isSameSpeciesEggAvailable(speciesId: number): boolean {
|
||||||
// Get this species ID's starter data
|
// Get this species ID's starter data
|
||||||
const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)];
|
const starterData = this.gameData.starterData[this.getStarterSpeciesId(speciesId)];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[this.getStarterSpeciesId(speciesId)])
|
starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[this.getStarterSpeciesId(speciesId)])
|
||||||
@ -1161,8 +1173,13 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
} else if (this.showingTray) {
|
} else if (this.showingTray) {
|
||||||
if (button === Button.ACTION) {
|
if (button === Button.ACTION) {
|
||||||
const formIndex = this.trayForms[this.trayCursor].formIndex;
|
const formIndex = this.trayForms[this.trayCursor].formIndex;
|
||||||
ui.setOverlayMode(UiMode.POKEDEX_PAGE, this.lastSpecies, { form: formIndex }, this.filteredIndices);
|
if (this.blockOpenPage) {
|
||||||
success = true;
|
success = false;
|
||||||
|
error = true;
|
||||||
|
} else {
|
||||||
|
ui.setOverlayMode(UiMode.POKEDEX_PAGE, this.lastSpecies, { form: formIndex }, this.filteredIndices);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const numberOfForms = this.trayContainers.length;
|
const numberOfForms = this.trayContainers.length;
|
||||||
const numOfRows = Math.ceil(numberOfForms / maxColumns);
|
const numOfRows = Math.ceil(numberOfForms / maxColumns);
|
||||||
@ -1209,8 +1226,13 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (button === Button.ACTION) {
|
} else if (button === Button.ACTION) {
|
||||||
ui.setOverlayMode(UiMode.POKEDEX_PAGE, this.lastSpecies, null, this.filteredIndices);
|
if (this.blockOpenPage) {
|
||||||
success = true;
|
success = false;
|
||||||
|
error = true;
|
||||||
|
} else {
|
||||||
|
ui.setOverlayMode(UiMode.POKEDEX_PAGE, this.lastSpecies, null, this.filteredIndices);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (button) {
|
switch (button) {
|
||||||
case Button.UP:
|
case Button.UP:
|
||||||
@ -1372,21 +1394,21 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
const starterId = this.getStarterSpeciesId(species.speciesId);
|
const starterId = this.getStarterSpeciesId(species.speciesId);
|
||||||
|
|
||||||
const currentDexAttr = this.getCurrentDexProps(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 = {
|
const data: ContainerData = {
|
||||||
species,
|
species,
|
||||||
cost: globalScene.gameData.getSpeciesStarterValue(starterId),
|
cost: this.gameData.getSpeciesStarterValue(starterId),
|
||||||
props,
|
props,
|
||||||
};
|
};
|
||||||
|
|
||||||
// First, ensure you have the caught attributes for the species else default to bigint 0
|
// 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
|
// TODO: This might be removed depending on how accessible we want the pokedex function to be
|
||||||
const caughtAttr =
|
const caughtAttr =
|
||||||
(globalScene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0))
|
(this.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0))
|
||||||
& (globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)]?.caughtAttr || BigInt(0))
|
& (this.gameData.dexData[this.getStarterSpeciesId(species.speciesId)]?.caughtAttr || BigInt(0))
|
||||||
& species.getFullUnlocksData();
|
& species.getFullUnlocksData();
|
||||||
const starterData = globalScene.gameData.starterData[starterId];
|
const starterData = this.gameData.starterData[starterId];
|
||||||
const isStarterProgressable = speciesEggMoves.hasOwnProperty(starterId);
|
const isStarterProgressable = speciesEggMoves.hasOwnProperty(starterId);
|
||||||
|
|
||||||
// Name filter
|
// Name filter
|
||||||
@ -1635,7 +1657,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Seen Filter
|
// 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 isItSeen = this.isSeen(species, dexEntry, true) || !!dexEntry.caughtAttr;
|
||||||
const fitsSeen = this.filterBar.getVals(DropDownColumn.MISC).some(misc => {
|
const fitsSeen = this.filterBar.getVals(DropDownColumn.MISC).some(misc => {
|
||||||
if (misc.val === "SEEN_SPECIES" && misc.state === DropDownState.ON) {
|
if (misc.val === "SEEN_SPECIES" && misc.state === DropDownState.ON) {
|
||||||
@ -1725,33 +1747,31 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
case SortCriteria.COST:
|
case SortCriteria.COST:
|
||||||
return (a.cost - b.cost) * -sort.dir;
|
return (a.cost - b.cost) * -sort.dir;
|
||||||
case SortCriteria.CANDY: {
|
case SortCriteria.CANDY: {
|
||||||
const candyCountA =
|
const candyCountA = this.gameData.starterData[this.getStarterSpeciesId(a.species.speciesId)].candyCount;
|
||||||
globalScene.gameData.starterData[this.getStarterSpeciesId(a.species.speciesId)].candyCount;
|
const candyCountB = this.gameData.starterData[this.getStarterSpeciesId(b.species.speciesId)].candyCount;
|
||||||
const candyCountB =
|
|
||||||
globalScene.gameData.starterData[this.getStarterSpeciesId(b.species.speciesId)].candyCount;
|
|
||||||
return (candyCountA - candyCountB) * -sort.dir;
|
return (candyCountA - candyCountB) * -sort.dir;
|
||||||
}
|
}
|
||||||
case SortCriteria.IV: {
|
case SortCriteria.IV: {
|
||||||
const avgIVsA =
|
const avgIVsA =
|
||||||
globalScene.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0)
|
this.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.length;
|
||||||
const avgIVsB =
|
const avgIVsB =
|
||||||
globalScene.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0)
|
this.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.length;
|
||||||
return (avgIVsA - avgIVsB) * -sort.dir;
|
return (avgIVsA - avgIVsB) * -sort.dir;
|
||||||
}
|
}
|
||||||
case SortCriteria.NAME:
|
case SortCriteria.NAME:
|
||||||
return a.species.name.localeCompare(b.species.name) * -sort.dir;
|
return a.species.name.localeCompare(b.species.name) * -sort.dir;
|
||||||
case SortCriteria.CAUGHT:
|
case SortCriteria.CAUGHT:
|
||||||
return (
|
return (
|
||||||
(globalScene.gameData.dexData[a.species.speciesId].caughtCount
|
(this.gameData.dexData[a.species.speciesId].caughtCount
|
||||||
- globalScene.gameData.dexData[b.species.speciesId].caughtCount)
|
- this.gameData.dexData[b.species.speciesId].caughtCount)
|
||||||
* -sort.dir
|
* -sort.dir
|
||||||
);
|
);
|
||||||
case SortCriteria.HATCHED:
|
case SortCriteria.HATCHED:
|
||||||
return (
|
return (
|
||||||
(globalScene.gameData.dexData[this.getStarterSpeciesId(a.species.speciesId)].hatchedCount
|
(this.gameData.dexData[this.getStarterSpeciesId(a.species.speciesId)].hatchedCount
|
||||||
- globalScene.gameData.dexData[this.getStarterSpeciesId(b.species.speciesId)].hatchedCount)
|
- this.gameData.dexData[this.getStarterSpeciesId(b.species.speciesId)].hatchedCount)
|
||||||
* -sort.dir
|
* -sort.dir
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
@ -1795,10 +1815,10 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
container.checkIconId(props.female, props.formIndex, props.shiny, props.variant);
|
container.checkIconId(props.female, props.formIndex, props.shiny, props.variant);
|
||||||
|
|
||||||
const speciesId = data.species.speciesId;
|
const speciesId = data.species.speciesId;
|
||||||
const dexEntry = globalScene.gameData.dexData[speciesId];
|
const dexEntry = this.gameData.dexData[speciesId];
|
||||||
const caughtAttr =
|
const caughtAttr =
|
||||||
dexEntry.caughtAttr
|
dexEntry.caughtAttr
|
||||||
& globalScene.gameData.dexData[this.getStarterSpeciesId(speciesId)].caughtAttr
|
& this.gameData.dexData[this.getStarterSpeciesId(speciesId)].caughtAttr
|
||||||
& data.species.getFullUnlocksData();
|
& data.species.getFullUnlocksData();
|
||||||
|
|
||||||
if (caughtAttr & data.species.getFullUnlocksData() || globalScene.dexForDevs) {
|
if (caughtAttr & data.species.getFullUnlocksData() || globalScene.dexForDevs) {
|
||||||
@ -1857,13 +1877,13 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
container.starterPassiveBgs.setVisible(
|
container.starterPassiveBgs.setVisible(
|
||||||
!!globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].passiveAttr,
|
!!this.gameData.starterData[this.getStarterSpeciesId(speciesId)].passiveAttr,
|
||||||
);
|
);
|
||||||
container.hiddenAbilityIcon.setVisible(
|
container.hiddenAbilityIcon.setVisible(
|
||||||
!!caughtAttr && !!(globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].abilityAttr & 4),
|
!!caughtAttr && !!(this.gameData.starterData[this.getStarterSpeciesId(speciesId)].abilityAttr & 4),
|
||||||
);
|
);
|
||||||
container.classicWinIcon.setVisible(
|
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);
|
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.setX((goLeft ? boxPos.x - 18 * (this.trayColumns - spaceRight) : boxPos.x) - 3);
|
||||||
this.formTrayContainer.setY(goUp ? boxPos.y - this.trayBg.height : boxPos.y + 17);
|
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 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 = [];
|
this.trayContainers = [];
|
||||||
const isFormSeen = this.isSeen(species, dexEntry);
|
const isFormSeen = this.isSeen(species, dexEntry);
|
||||||
this.trayForms.map((f, index) => {
|
this.trayForms.map((f, index) => {
|
||||||
const isFormCaught = dexEntry
|
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;
|
: false;
|
||||||
const formContainer = new PokedexMonContainer(species, {
|
const formContainer = new PokedexMonContainer(species, {
|
||||||
formIndex: f.formIndex,
|
formIndex: f.formIndex,
|
||||||
@ -2066,7 +2086,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFriendship(speciesId: number) {
|
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) {
|
if (!currentFriendship || currentFriendship === undefined) {
|
||||||
currentFriendship = 0;
|
currentFriendship = 0;
|
||||||
}
|
}
|
||||||
@ -2094,7 +2114,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
if (container) {
|
if (container) {
|
||||||
const lastSpeciesIcon = container.icon;
|
const lastSpeciesIcon = container.icon;
|
||||||
const dexAttr = this.getCurrentDexProps(container.species.speciesId);
|
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.checkIconId(lastSpeciesIcon, container.species, props.female, props.formIndex, props.shiny, props.variant);
|
||||||
this.iconAnimHandler.addOrUpdate(lastSpeciesIcon, PokemonIconAnimMode.NONE);
|
this.iconAnimHandler.addOrUpdate(lastSpeciesIcon, PokemonIconAnimMode.NONE);
|
||||||
// Resume the animation for the previously selected species
|
// Resume the animation for the previously selected species
|
||||||
@ -2103,7 +2123,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSpecies(species: PokemonSpecies | null) {
|
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) {
|
if (!species && globalScene.ui.getTooltip().visible) {
|
||||||
globalScene.ui.hideTooltip();
|
globalScene.ui.hideTooltip();
|
||||||
@ -2182,15 +2202,15 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (species) {
|
if (species) {
|
||||||
const dexEntry = globalScene.gameData.dexData[species.speciesId];
|
const dexEntry = this.gameData.dexData[species.speciesId];
|
||||||
const caughtAttr =
|
const caughtAttr =
|
||||||
dexEntry.caughtAttr
|
dexEntry.caughtAttr
|
||||||
& globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)].caughtAttr
|
& this.gameData.dexData[this.getStarterSpeciesId(species.speciesId)].caughtAttr
|
||||||
& species.getFullUnlocksData();
|
& species.getFullUnlocksData();
|
||||||
|
|
||||||
if (caughtAttr) {
|
if (caughtAttr) {
|
||||||
const props = this.getSanitizedProps(
|
const props = this.getSanitizedProps(
|
||||||
globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)),
|
this.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (shiny === undefined) {
|
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 isFormSeen = this.isSeen(species, dexEntry);
|
||||||
|
|
||||||
const assetLoadCancelled = new BooleanHolder(false);
|
const assetLoadCancelled = new BooleanHolder(false);
|
||||||
@ -2291,7 +2311,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
updateStarterValueLabel(starter: PokedexMonContainer): void {
|
updateStarterValueLabel(starter: PokedexMonContainer): void {
|
||||||
const speciesId = starter.species.speciesId;
|
const speciesId = starter.species.speciesId;
|
||||||
const baseStarterValue = speciesStarterCosts[speciesId];
|
const baseStarterValue = speciesStarterCosts[speciesId];
|
||||||
const starterValue = globalScene.gameData.getSpeciesStarterValue(this.getStarterSpeciesId(speciesId));
|
const starterValue = this.gameData.getSpeciesStarterValue(this.getStarterSpeciesId(speciesId));
|
||||||
starter.cost = starterValue;
|
starter.cost = starterValue;
|
||||||
let valueStr = starterValue.toString();
|
let valueStr = starterValue.toString();
|
||||||
if (valueStr.startsWith("0.")) {
|
if (valueStr.startsWith("0.")) {
|
||||||
@ -2356,8 +2376,8 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
let props = 0n;
|
let props = 0n;
|
||||||
const species = allSpecies.find(sp => sp.speciesId === speciesId);
|
const species = allSpecies.find(sp => sp.speciesId === speciesId);
|
||||||
const caughtAttr =
|
const caughtAttr =
|
||||||
globalScene.gameData.dexData[speciesId].caughtAttr
|
this.gameData.dexData[speciesId].caughtAttr
|
||||||
& globalScene.gameData.dexData[this.getStarterSpeciesId(speciesId)].caughtAttr
|
& this.gameData.dexData[this.getStarterSpeciesId(speciesId)].caughtAttr
|
||||||
& (species?.getFullUnlocksData() ?? 0n);
|
& (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
|
/* 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;
|
props += BigInt(Math.pow(2, this.starterPreferences[speciesId]?.form)) * DexAttr.DEFAULT_FORM;
|
||||||
} else {
|
} else {
|
||||||
// Get the first unlocked form
|
// Get the first unlocked form
|
||||||
props += globalScene.gameData.getFormAttr(globalScene.gameData.getFormIndex(caughtAttr));
|
props += this.gameData.getFormAttr(this.gameData.getFormIndex(caughtAttr));
|
||||||
}
|
}
|
||||||
|
|
||||||
return props;
|
return props;
|
||||||
@ -2426,6 +2446,13 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
|
|
||||||
this.starterSelectContainer.setVisible(false);
|
this.starterSelectContainer.setVisible(false);
|
||||||
this.blockInput = 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(
|
checkIconId(
|
||||||
|
|||||||
@ -2,12 +2,10 @@ import { PokerogueAdminApi } from "#api/pokerogue-admin-api";
|
|||||||
import { initServerForApiTests } from "#test/test-utils/test-file-initialization";
|
import { initServerForApiTests } from "#test/test-utils/test-file-initialization";
|
||||||
import { getApiBaseUrl } from "#test/test-utils/test-utils";
|
import { getApiBaseUrl } from "#test/test-utils/test-utils";
|
||||||
import type {
|
import type {
|
||||||
LinkAccountToDiscordIdRequest,
|
DiscordRequest,
|
||||||
LinkAccountToGoogledIdRequest,
|
GoogleRequest,
|
||||||
SearchAccountRequest,
|
SearchAccountRequest,
|
||||||
SearchAccountResponse,
|
SearchAccountResponse,
|
||||||
UnlinkAccountFromDiscordIdRequest,
|
|
||||||
UnlinkAccountFromGoogledIdRequest,
|
|
||||||
} from "#types/api/pokerogue-admin-api";
|
} from "#types/api/pokerogue-admin-api";
|
||||||
import { HttpResponse, http } from "msw";
|
import { HttpResponse, http } from "msw";
|
||||||
import type { SetupServerApi } from "msw/node";
|
import type { SetupServerApi } from "msw/node";
|
||||||
@ -31,7 +29,7 @@ describe("Pokerogue Admin API", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Link Account to Discord", () => {
|
describe("Link Account to Discord", () => {
|
||||||
const params: LinkAccountToDiscordIdRequest = {
|
const params: DiscordRequest = {
|
||||||
username: "test",
|
username: "test",
|
||||||
discordId: "test-12575756",
|
discordId: "test-12575756",
|
||||||
};
|
};
|
||||||
@ -39,7 +37,7 @@ describe("Pokerogue Admin API", () => {
|
|||||||
it("should return null on SUCCESS", async () => {
|
it("should return null on SUCCESS", async () => {
|
||||||
server.use(http.post(`${apiBase}/admin/account/discordLink`, () => HttpResponse.json(true)));
|
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();
|
expect(success).toBeNull();
|
||||||
});
|
});
|
||||||
@ -47,7 +45,7 @@ describe("Pokerogue Admin API", () => {
|
|||||||
it("should return a ERR_GENERIC and report a warning on FAILURE", async () => {
|
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 })));
|
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(success).toBe(adminApi.ERR_GENERIC);
|
||||||
expect(console.warn).toHaveBeenCalledWith("Could not link account with discord!", 400, "Bad Request");
|
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 () => {
|
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 })));
|
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(success).toBe(adminApi.ERR_USERNAME_NOT_FOUND);
|
||||||
expect(console.warn).toHaveBeenCalledWith("Could not link account with discord!", 404, "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 () => {
|
it("should return a ERR_GENERIC and report a warning on ERROR", async () => {
|
||||||
server.use(http.post(`${apiBase}/admin/account/discordLink`, () => HttpResponse.error()));
|
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(success).toBe(adminApi.ERR_GENERIC);
|
||||||
expect(console.warn).toHaveBeenCalledWith("Could not link account with discord!", expect.any(Error));
|
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", () => {
|
describe("Unlink Account from Discord", () => {
|
||||||
const params: UnlinkAccountFromDiscordIdRequest = {
|
const params: DiscordRequest = {
|
||||||
username: "test",
|
username: "test",
|
||||||
discordId: "test-12575756",
|
discordId: "test-12575756",
|
||||||
};
|
};
|
||||||
@ -81,7 +79,7 @@ describe("Pokerogue Admin API", () => {
|
|||||||
it("should return null on SUCCESS", async () => {
|
it("should return null on SUCCESS", async () => {
|
||||||
server.use(http.post(`${apiBase}/admin/account/discordUnlink`, () => HttpResponse.json(true)));
|
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();
|
expect(success).toBeNull();
|
||||||
});
|
});
|
||||||
@ -89,7 +87,7 @@ describe("Pokerogue Admin API", () => {
|
|||||||
it("should return a ERR_GENERIC and report a warning on FAILURE", async () => {
|
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 })));
|
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(success).toBe(adminApi.ERR_GENERIC);
|
||||||
expect(console.warn).toHaveBeenCalledWith("Could not unlink account from discord!", 400, "Bad Request");
|
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 () => {
|
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 })));
|
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(success).toBe(adminApi.ERR_USERNAME_NOT_FOUND);
|
||||||
expect(console.warn).toHaveBeenCalledWith("Could not unlink account from discord!", 404, "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 () => {
|
it("should return a ERR_GENERIC and report a warning on ERROR", async () => {
|
||||||
server.use(http.post(`${apiBase}/admin/account/discordUnlink`, () => HttpResponse.error()));
|
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(success).toBe(adminApi.ERR_GENERIC);
|
||||||
expect(console.warn).toHaveBeenCalledWith("Could not unlink account from discord!", expect.any(Error));
|
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", () => {
|
describe("Link Account to Google", () => {
|
||||||
const params: LinkAccountToGoogledIdRequest = {
|
const params: GoogleRequest = {
|
||||||
username: "test",
|
username: "test",
|
||||||
googleId: "test-12575756",
|
googleId: "test-12575756",
|
||||||
};
|
};
|
||||||
@ -123,7 +121,7 @@ describe("Pokerogue Admin API", () => {
|
|||||||
it("should return null on SUCCESS", async () => {
|
it("should return null on SUCCESS", async () => {
|
||||||
server.use(http.post(`${apiBase}/admin/account/googleLink`, () => HttpResponse.json(true)));
|
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();
|
expect(success).toBeNull();
|
||||||
});
|
});
|
||||||
@ -131,7 +129,7 @@ describe("Pokerogue Admin API", () => {
|
|||||||
it("should return a ERR_GENERIC and report a warning on FAILURE", async () => {
|
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 })));
|
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(success).toBe(adminApi.ERR_GENERIC);
|
||||||
expect(console.warn).toHaveBeenCalledWith("Could not link account with google!", 400, "Bad Request");
|
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 () => {
|
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 })));
|
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(success).toBe(adminApi.ERR_USERNAME_NOT_FOUND);
|
||||||
expect(console.warn).toHaveBeenCalledWith("Could not link account with google!", 404, "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 () => {
|
it("should return a ERR_GENERIC and report a warning on ERROR", async () => {
|
||||||
server.use(http.post(`${apiBase}/admin/account/googleLink`, () => HttpResponse.error()));
|
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(success).toBe(adminApi.ERR_GENERIC);
|
||||||
expect(console.warn).toHaveBeenCalledWith("Could not link account with google!", expect.any(Error));
|
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", () => {
|
describe("Unlink Account from Google", () => {
|
||||||
const params: UnlinkAccountFromGoogledIdRequest = {
|
const params: GoogleRequest = {
|
||||||
username: "test",
|
username: "test",
|
||||||
googleId: "test-12575756",
|
googleId: "test-12575756",
|
||||||
};
|
};
|
||||||
@ -165,7 +163,7 @@ describe("Pokerogue Admin API", () => {
|
|||||||
it("should return null on SUCCESS", async () => {
|
it("should return null on SUCCESS", async () => {
|
||||||
server.use(http.post(`${apiBase}/admin/account/googleUnlink`, () => HttpResponse.json(true)));
|
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();
|
expect(success).toBeNull();
|
||||||
});
|
});
|
||||||
@ -173,7 +171,7 @@ describe("Pokerogue Admin API", () => {
|
|||||||
it("should return a ERR_GENERIC and report a warning on FAILURE", async () => {
|
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 })));
|
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(success).toBe(adminApi.ERR_GENERIC);
|
||||||
expect(console.warn).toHaveBeenCalledWith("Could not unlink account from google!", 400, "Bad Request");
|
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 () => {
|
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 })));
|
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(success).toBe(adminApi.ERR_USERNAME_NOT_FOUND);
|
||||||
expect(console.warn).toHaveBeenCalledWith("Could not unlink account from google!", 404, "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 () => {
|
it("should return a ERR_GENERIC and report a warning on ERROR", async () => {
|
||||||
server.use(http.post(`${apiBase}/admin/account/googleUnlink`, () => HttpResponse.error()));
|
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(success).toBe(adminApi.ERR_GENERIC);
|
||||||
expect(console.warn).toHaveBeenCalledWith("Could not unlink account from google!", expect.any(Error));
|
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 { TurnEndPhase } from "#phases/turn-end-phase";
|
||||||
import { TurnInitPhase } from "#phases/turn-init-phase";
|
import { TurnInitPhase } from "#phases/turn-init-phase";
|
||||||
import { TurnStartPhase } from "#phases/turn-start-phase";
|
import { TurnStartPhase } from "#phases/turn-start-phase";
|
||||||
|
import { GameData } from "#system/game-data";
|
||||||
import { ErrorInterceptor } from "#test/test-utils/error-interceptor";
|
import { ErrorInterceptor } from "#test/test-utils/error-interceptor";
|
||||||
import { generateStarters } from "#test/test-utils/game-manager-utils";
|
import { generateStarters } from "#test/test-utils/game-manager-utils";
|
||||||
import { GameWrapper } from "#test/test-utils/game-wrapper";
|
import { GameWrapper } from "#test/test-utils/game-wrapper";
|
||||||
@ -451,7 +452,7 @@ export class GameManager {
|
|||||||
const dataRaw = fs.readFileSync(path, { encoding: "utf8", flag: "r" });
|
const dataRaw = fs.readFileSync(path, { encoding: "utf8", flag: "r" });
|
||||||
let dataStr = AES.decrypt(dataRaw, saveKey).toString(enc.Utf8);
|
let dataStr = AES.decrypt(dataRaw, saveKey).toString(enc.Utf8);
|
||||||
dataStr = this.scene.gameData.convertSystemDataStr(dataStr);
|
dataStr = this.scene.gameData.convertSystemDataStr(dataStr);
|
||||||
const systemData = this.scene.gameData.parseSystemData(dataStr);
|
const systemData = GameData.parseSystemData(dataStr);
|
||||||
const valid = !!systemData.dexData && !!systemData.timestamp;
|
const valid = !!systemData.dexData && !!systemData.timestamp;
|
||||||
if (valid) {
|
if (valid) {
|
||||||
await updateUserInfo();
|
await updateUserInfo();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user