Hotfix 1.11.5 to main

Hotfix 1.11.5 to main
This commit is contained in:
damocleas 2025-12-24 19:12:16 -05:00 committed by GitHub
commit 235991e51d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 159 additions and 75 deletions

2
assets

@ -1 +1 @@
Subproject commit a36741a2112217eaf067248d7d1917266339a56d
Subproject commit 9398703b8f0e437ef6d5212373e2ae8465b3bfda

@ -1 +1 @@
Subproject commit 6b5e2130256dd521908f15a485d045fb36baca41
Subproject commit fe7025942cf5ad1391380cc4e52695535c56c986

View File

@ -79,51 +79,14 @@ export class LoginPhase extends Phase {
}
private showLoginRegister(): void {
const { gameData, phaseManager, ui } = globalScene;
const backButton = () => {
phaseManager.unshiftNew("LoginPhase", false);
this.end();
};
const checkUserInfo = async (): Promise<boolean> => {
ui.playSelect();
const success = await updateUserInfo();
if (!success[0]) {
removeCookie(sessionIdKey);
globalScene.reset(true, true);
return false;
}
return true;
};
const loginButton = async () => {
const success = await checkUserInfo();
if (!success) {
return;
}
await gameData.loadSystem();
this.end();
};
const registerButton = async () => {
const success = await checkUserInfo();
if (!success) {
return;
}
this.end();
};
const { ui } = globalScene;
const goToLoginButton = () => {
globalScene.playSound("ui/menu_open");
ui.setMode(UiMode.LOGIN_FORM, { buttonActions: [loginButton, backButton] });
this.goToLogin();
};
const goToRegistrationButton = () => {
globalScene.playSound("ui/menu_open");
ui.setMode(UiMode.REGISTRATION_FORM, { buttonActions: [registerButton, backButton] });
this.goToRegister();
};
if (this.showText) {
@ -134,4 +97,56 @@ export class LoginPhase extends Phase {
ui.setMode(UiMode.LOGIN_OR_REGISTER, { buttonActions: [goToLoginButton, goToRegistrationButton] });
}
private async checkUserInfo(): Promise<boolean> {
globalScene.ui.playSelect();
const success = await updateUserInfo();
if (!success[0]) {
removeCookie(sessionIdKey);
globalScene.reset(true, true);
return false;
}
return true;
}
public goToLogin(): void {
const { gameData, ui, phaseManager } = globalScene;
const backButton = () => {
phaseManager.unshiftNew("LoginPhase", false);
this.end();
};
const loginButton = async () => {
const success = await this.checkUserInfo();
if (!success) {
return;
}
await gameData.loadSystem();
this.end();
};
globalScene.playSound("ui/menu_open");
ui.setMode(UiMode.LOGIN_FORM, { buttonActions: [loginButton, backButton] });
}
public goToRegister(): void {
const { phaseManager, ui } = globalScene;
const backButton = () => {
phaseManager.unshiftNew("LoginPhase", false);
this.end();
};
const registerButton = async () => {
const success = await this.checkUserInfo();
if (!success) {
return;
}
this.end();
};
globalScene.playSound("ui/menu_open");
ui.setMode(UiMode.REGISTRATION_FORM, { buttonActions: [registerButton, backButton] });
}
}

View File

@ -74,6 +74,7 @@ export abstract class ApiBase {
console.log(`Sending ${config.method ?? "GET"} request to: `, this.base + path, config);
}
// TODO: need some sort of error handling here?
return await fetch(this.base + path, config);
}

View File

@ -52,7 +52,7 @@ export class PokerogueAccountApi extends ApiBase {
console.warn("Register failed!", err);
}
return "Unknown error!";
return "Unknown registration error!";
}
/**
@ -76,7 +76,7 @@ export class PokerogueAccountApi extends ApiBase {
console.warn("Login failed!", err);
}
return "Unknown error!";
return "Unknown login error!";
}
/**

View File

@ -1,4 +1,5 @@
import { globalScene } from "#app/global-scene";
import type { LoginRegisterInfoContainerUiHandler } from "#ui/login-register-info-container-ui-handler";
import type { SettingsDisplayUiHandler } from "#ui/settings-display-ui-handler";
import i18next from "i18next";
@ -8,6 +9,8 @@ const cancelHandler = () => {
// Reset the cursor to the current language, if in the settings menu
if (handler && typeof (handler as SettingsDisplayUiHandler).setOptionCursor === "function") {
(handler as SettingsDisplayUiHandler).setOptionCursor(-1, 0, true);
} else if (handler && typeof (handler as LoginRegisterInfoContainerUiHandler).setInteractive === "function") {
(handler as LoginRegisterInfoContainerUiHandler).setInteractive(true);
}
};

View File

@ -708,7 +708,7 @@ export class TimedEventManager {
}
export class TimedEventDisplay extends Phaser.GameObjects.Container {
private event: TimedEvent | nil;
private readonly event: TimedEvent | nil;
private eventTimerText: Phaser.GameObjects.Text;
private banner: Phaser.GameObjects.Image;
private availableWidth: number;
@ -725,7 +725,7 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
* Set the width that can be used to display the event timer and banner. By default
* these elements get centered horizontally in that space, in the bottom left of the screen
*/
setWidth(width: number) {
public setWidth(width: number): void {
if (width !== this.availableWidth) {
this.availableWidth = width;
const xPosition = this.availableWidth / 2 + (this.event?.xOffset ?? 0);
@ -738,7 +738,7 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
}
}
setup() {
public setup(): void {
const lang = i18next.resolvedLanguage;
if (this.event?.bannerKey) {
let key = this.event.bannerKey;
@ -775,7 +775,7 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
}
}
show() {
public show(): void {
this.setVisible(true);
this.updateCountdown();
@ -784,13 +784,13 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
}, 1000);
}
clear() {
public clear(): void {
this.setVisible(false);
this.eventTimer && clearInterval(this.eventTimer);
this.eventTimer = null;
}
private timeToGo(date: Date) {
private timeToGo(date: Date): string {
// Utility to add leading zero
function z(n) {
return (n < 10 ? "0" : "") + n;
@ -816,8 +816,8 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
});
}
updateCountdown() {
if (this.event && this.event.eventType !== EventType.NO_TIMER_DISPLAY) {
private updateCountdown(): void {
if (this.event && this.event.eventType !== EventType.NO_TIMER_DISPLAY && this.eventTimerText.visible) {
this.eventTimerText.setText(this.timeToGo(this.event.endDate));
}
}

View File

@ -29,7 +29,6 @@ export class LoginOrRegisterUiHandler extends LoginRegisterInfoContainerUiHandle
return [i18next.t("menu:login"), i18next.t("menu:register")];
}
// TODO: use mixins so it's not necessary to inherit from `FormModalUiHandler`
public override getInputFieldConfigs(): InputFieldConfig[] {
return [];
}

View File

@ -7,6 +7,7 @@ import type { ModalConfig } from "#ui/modal-ui-handler";
import { fixedInt } from "#utils/common";
import i18next from "i18next";
import JSZip from "jszip";
import type InputText from "phaser3-rex-plugins/plugins/inputtext";
interface BuildInteractableImageOpts {
scale?: number;
@ -30,12 +31,12 @@ const MAX_SAVES_FOR_USERNAME_PANEL = 7;
const ERR_NO_SAVES: string = "No save files found";
const ERR_TOO_MANY_SAVES: string = "Too many save files found";
// TODO: use mixins
export abstract class LoginRegisterInfoContainerUiHandler extends FormModalUiHandler {
private usernameInfoImage: Phaser.GameObjects.Image;
private saveDownloadImage: Phaser.GameObjects.Image;
private changeLanguageImage: Phaser.GameObjects.Image;
private infoContainer: Phaser.GameObjects.Container;
private lastFocusedInput: InputText | null = null;
public override getReadableErrorMessage(error: string): string {
if (!error) {
@ -109,6 +110,7 @@ export abstract class LoginRegisterInfoContainerUiHandler extends FormModalUiHan
this.changeLanguageImage //
.setPositionRelative(this.infoContainer, 40, 0)
.on("pointerdown", () => {
this.setInteractive(false);
globalScene.ui.setOverlayMode(UiMode.OPTION_SELECT, {
options: languageOptions,
maxOptions: 7,
@ -154,6 +156,7 @@ export abstract class LoginRegisterInfoContainerUiHandler extends FormModalUiHan
const handler = () => {
globalScene.ui.revertMode();
this.infoContainer.disableInteractive();
this.setInteractive(true);
return true;
};
@ -162,6 +165,7 @@ export abstract class LoginRegisterInfoContainerUiHandler extends FormModalUiHan
}
globalScene.ui.setOverlayMode(UiMode.OPTION_SELECT, { options, delay: 1000 });
this.setInteractive(false);
this.infoContainer.setInteractive(
new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width, globalScene.game.canvas.height),
Phaser.Geom.Rectangle.Contains,
@ -230,4 +234,45 @@ export abstract class LoginRegisterInfoContainerUiHandler extends FormModalUiHan
return img;
}
/**
* Enable or disable interactivity on all interactive objects.
* @param active - Whether to enable or disable interactivity
*/
public setInteractive(active: boolean): void {
const objects = [...this.buttonBgs, this.usernameInfoImage, this.saveDownloadImage, this.changeLanguageImage];
for (const obj of objects) {
if (active) {
obj.setInteractive();
} else {
obj.disableInteractive();
if (obj instanceof Phaser.GameObjects.Image) {
obj.clearTint();
}
}
}
this.setInteractiveInputs(active);
this.setMouseCursorStyle("default");
}
/**
* Enable or disable interactivity on all input fields.
* @param active - Whether to enable or disable interactivity
*/
private setInteractiveInputs(active: boolean): void {
if (active) {
// `setFocus` doesn't focus without a timeout
setTimeout(() => {
this.lastFocusedInput?.setFocus();
this.lastFocusedInput = null;
}, 50);
} else {
this.lastFocusedInput = this.inputs.find(input => input.isFocused) ?? null;
}
for (const input of this.inputs) {
(input.node as HTMLInputElement).disabled = !active;
}
}
}

View File

@ -6,7 +6,6 @@ import { addWindow } from "#ui/ui-theme";
import { fixedInt } from "#utils/common";
import i18next from "i18next";
// TODO: use mixins
export abstract class OAuthProvidersUiHandler extends LoginRegisterInfoContainerUiHandler {
private discordImage: Phaser.GameObjects.Image;
private googleImage: Phaser.GameObjects.Image;
@ -56,6 +55,18 @@ export abstract class OAuthProvidersUiHandler extends LoginRegisterInfoContainer
this.getUi().add(this.externalPartyContainer);
}
public override setInteractive(active: boolean): void {
super.setInteractive(active);
const externalPartyIcons = this.externalPartyContainer.list.filter(obj => obj instanceof Phaser.GameObjects.Image);
for (const obj of externalPartyIcons) {
if (active) {
obj.setInteractive();
} else {
obj.disableInteractive();
}
}
}
protected processExternalProvider(): void {
const titleX = 22;
this.externalPartyTitle

View File

@ -1515,9 +1515,6 @@ export class PartyUiHandler extends MessageUiHandler {
);
}
this.addCommonOptions(pokemon);
if (this.partyUiMode === PartyUiMode.SWITCH && pokemon.isFusion()) {
this.options.push(PartyOption.UNSPLICE);
}
break;
case PartyUiMode.REVIVAL_BLESSING:
this.options.push(PartyOption.REVIVE);
@ -1551,6 +1548,9 @@ export class PartyUiHandler extends MessageUiHandler {
case PartyUiMode.CHECK:
this.addCommonOptions(pokemon);
if (globalScene.phaseManager.getCurrentPhase().is("SelectModifierPhase")) {
if (pokemon.isFusion()) {
this.options.push(PartyOption.UNSPLICE);
}
this.options.push(PartyOption.RELEASE);
const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon);
for (let i = 0; i < formChangeItemModifiers.length; i++) {

View File

@ -2,9 +2,11 @@ import { pokerogueApi } from "#api/pokerogue-api";
import { globalScene } from "#app/global-scene";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { LoginPhase } from "#phases/login-phase";
import type { InputFieldConfig } from "#ui/form-modal-ui-handler";
import type { ModalConfig } from "#ui/modal-ui-handler";
import { addTextObject } from "#ui/text";
import { fixedInt } from "#utils/common";
import i18next from "i18next";
import { LoginRegisterInfoContainerUiHandler } from "./login-register-info-container-ui-handler";
@ -86,7 +88,7 @@ export class RegistrationFormUiHandler extends LoginRegisterInfoContainerUiHandl
this.submitAction = originalRegistrationAction;
this.sanitizeInputs();
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] });
const onFail = error => {
const onFail = (error: string) => {
globalScene.ui.setMode(UiMode.REGISTRATION_FORM, Object.assign(config, { errorMessage: error?.trim() }));
globalScene.ui.playError();
};
@ -109,18 +111,25 @@ export class RegistrationFormUiHandler extends LoginRegisterInfoContainerUiHandl
if (registerError) {
onFail(registerError);
} else {
pokerogueApi.account
.login({
username: usernameInput.text,
password: passwordInput.text,
})
.then(loginError => {
if (loginError) {
onFail(loginError);
} else {
originalRegistrationAction?.();
}
});
const username = usernameInput.text;
const password = passwordInput.text;
pokerogueApi.account.login({ username, password }).then(loginError => {
if (loginError) {
// retry once if the first attempt fails
const retryLogin = () => {
pokerogueApi.account.login({ username, password }).then(error => {
if (error) {
(globalScene.phaseManager.getCurrentPhase() as LoginPhase).goToLogin();
} else {
originalRegistrationAction?.();
}
});
};
globalScene.time.delayedCall(fixedInt(2000), retryLogin);
} else {
originalRegistrationAction?.();
}
});
}
});
}

View File

@ -261,6 +261,7 @@ export class TitleUiHandler extends OptionSelectUiHandler {
private getSnow(): void {
const width = globalScene.scaledCanvas.width;
const height = globalScene.scaledCanvas.height;
this.snow?.destroy(); // Ensures no duplicate snow layers
this.snow = globalScene.add.tileSprite(width, height, width, height, "snow");
this.snow.setOrigin(1, 1);

View File

@ -85,12 +85,12 @@ describe("Pokerogue Account API", () => {
expect(error).toBe("Username is already taken");
});
it('should return "Unknown error" and report a warning on ERROR', async () => {
it('should return "Unknown registration error!" and report a warning on ERROR', async () => {
server.use(http.post(`${apiBase}/account/register`, () => HttpResponse.error()));
const error = await accountApi.register(registerParams);
expect(error).toBe("Unknown error!");
expect(error).toBe("Unknown registration error!");
expect(console.warn).toHaveBeenCalledWith("Register failed!", expect.any(Error));
});
});
@ -119,12 +119,12 @@ describe("Pokerogue Account API", () => {
expect(console.warn).toHaveBeenCalledWith("Login failed!", 401, "Unauthorized");
});
it('should return "Unknown error" and report a warning on ERROR', async () => {
it('should return "Unknown login error!" and report a warning on ERROR', async () => {
server.use(http.post(`${apiBase}/account/login`, () => HttpResponse.error()));
const error = await accountApi.login(loginParams);
expect(error).toBe("Unknown error!");
expect(error).toBe("Unknown login error!");
expect(console.warn).toHaveBeenCalledWith("Login failed!", expect.any(Error));
});
});