mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-10 01:19:29 +02:00
Compare commits
6 Commits
c27a7a750f
...
b999cdc279
Author | SHA1 | Date | |
---|---|---|---|
|
b999cdc279 | ||
|
1633df75c4 | ||
|
ca7947c770 | ||
|
6a284b7ab3 | ||
|
c2db0ed397 | ||
|
5f29e4acf2 |
@ -15,3 +15,10 @@ export interface AccountRegisterRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface AccountChangePwRequest {
|
||||
password: string;
|
||||
}
|
||||
export interface AccountChangePwResponse {
|
||||
success: boolean;
|
||||
}
|
||||
|
@ -1,13 +1,24 @@
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type {
|
||||
AttackMove,
|
||||
ChargingAttackMove,
|
||||
ChargingSelfStatusMove,
|
||||
Move,
|
||||
MoveAttr,
|
||||
MoveAttrConstructorMap,
|
||||
SelfStatusMove,
|
||||
StatusMove,
|
||||
} from "#moves/move";
|
||||
|
||||
/**
|
||||
* A generic function producing a message during a Move's execution.
|
||||
* @param user - The {@linkcode Pokemon} using the move
|
||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||
* @param move - The {@linkcode Move} being used
|
||||
* @returns a string
|
||||
*/
|
||||
export type MoveMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => string;
|
||||
|
||||
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
|
||||
|
||||
export type * from "#moves/move";
|
||||
|
@ -1670,6 +1670,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
|
||||
constructor(
|
||||
private newType: PokemonType,
|
||||
private powerMultiplier: number,
|
||||
// TODO: all moves with this attr solely check the move being used...
|
||||
private condition?: PokemonAttackCondition,
|
||||
) {
|
||||
super(false);
|
||||
|
@ -87,7 +87,7 @@ import { PokemonHealPhase } from "#phases/pokemon-heal-phase";
|
||||
import { SwitchSummonPhase } from "#phases/switch-summon-phase";
|
||||
import type { AttackMoveResult } from "#types/attack-move-result";
|
||||
import type { Localizable } from "#types/locales";
|
||||
import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString } from "#types/move-types";
|
||||
import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString, MoveMessageFunc } from "#types/move-types";
|
||||
import type { TurnMove } from "#types/turn-move";
|
||||
import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
@ -1357,20 +1357,20 @@ export class MoveHeaderAttr extends MoveAttr {
|
||||
|
||||
/**
|
||||
* Header attribute to queue a message at the beginning of a turn.
|
||||
* @see {@link MoveHeaderAttr}
|
||||
*/
|
||||
export class MessageHeaderAttr extends MoveHeaderAttr {
|
||||
private message: string | ((user: Pokemon, move: Move) => string);
|
||||
/** The message to display, or a function producing one. */
|
||||
private message: string | MoveMessageFunc;
|
||||
|
||||
constructor(message: string | ((user: Pokemon, move: Move) => string)) {
|
||||
constructor(message: string | MoveMessageFunc) {
|
||||
super();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
|
||||
const message = typeof this.message === "string"
|
||||
? this.message
|
||||
: this.message(user, move);
|
||||
: this.message(user, target, move);
|
||||
|
||||
if (message) {
|
||||
globalScene.phaseManager.queueMessage(message);
|
||||
@ -1418,21 +1418,21 @@ export class BeakBlastHeaderAttr extends AddBattlerTagHeaderAttr {
|
||||
*/
|
||||
export class PreMoveMessageAttr extends MoveAttr {
|
||||
/** The message to display or a function returning one */
|
||||
private message: string | ((user: Pokemon, target: Pokemon, move: Move) => string | undefined);
|
||||
private message: string | MoveMessageFunc;
|
||||
|
||||
/**
|
||||
* Create a new {@linkcode PreMoveMessageAttr} to display a message before move execution.
|
||||
* @param message - The message to display before move use, either as a string or a function producing one.
|
||||
* @param message - The message to display before move use, either` a literal string or a function producing one.
|
||||
* @remarks
|
||||
* If {@linkcode message} evaluates to an empty string (`''`), no message will be displayed
|
||||
* If {@linkcode message} evaluates to an empty string (`""`), no message will be displayed
|
||||
* (though the move will still succeed).
|
||||
*/
|
||||
constructor(message: string | ((user: Pokemon, target: Pokemon, move: Move) => string)) {
|
||||
constructor(message: string | MoveMessageFunc) {
|
||||
super();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, _args: any[]): boolean {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
|
||||
const message = typeof this.message === "function"
|
||||
? this.message(user, target, move)
|
||||
: this.message;
|
||||
@ -1453,18 +1453,17 @@ export class PreMoveMessageAttr extends MoveAttr {
|
||||
* @extends MoveAttr
|
||||
*/
|
||||
export class PreUseInterruptAttr extends MoveAttr {
|
||||
protected message?: string | ((user: Pokemon, target: Pokemon, move: Move) => string);
|
||||
protected overridesFailedMessage: boolean;
|
||||
protected message: string | MoveMessageFunc;
|
||||
protected conditionFunc: MoveConditionFunc;
|
||||
|
||||
/**
|
||||
* Create a new MoveInterruptedMessageAttr.
|
||||
* @param message The message to display when the move is interrupted, or a function that formats the message based on the user, target, and move.
|
||||
*/
|
||||
constructor(message?: string | ((user: Pokemon, target: Pokemon, move: Move) => string), conditionFunc?: MoveConditionFunc) {
|
||||
constructor(message: string | MoveMessageFunc, conditionFunc: MoveConditionFunc) {
|
||||
super();
|
||||
this.message = message;
|
||||
this.conditionFunc = conditionFunc ?? (() => true);
|
||||
this.conditionFunc = conditionFunc;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1485,11 +1484,9 @@ export class PreUseInterruptAttr extends MoveAttr {
|
||||
*/
|
||||
override getFailedText(user: Pokemon, target: Pokemon, move: Move): string | undefined {
|
||||
if (this.message && this.conditionFunc(user, target, move)) {
|
||||
const message =
|
||||
typeof this.message === "string"
|
||||
? (this.message as string)
|
||||
return typeof this.message === "string"
|
||||
? this.message
|
||||
: this.message(user, target, move);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1694,19 +1691,33 @@ export class SurviveDamageAttr extends ModifiedDamageAttr {
|
||||
}
|
||||
}
|
||||
|
||||
export class SplashAttr extends MoveEffectAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:splash"));
|
||||
return true;
|
||||
/**
|
||||
* Move attribute to display arbitrary text during a move's execution.
|
||||
*/
|
||||
export class MessageAttr extends MoveEffectAttr {
|
||||
/** The message to display, either as a string or a function returning one. */
|
||||
private message: string | MoveMessageFunc;
|
||||
|
||||
constructor(message: string | MoveMessageFunc, options?: MoveEffectAttrOptions) {
|
||||
// TODO: Do we need to respect `selfTarget` if we're just displaying text?
|
||||
super(false, options)
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move): boolean {
|
||||
const message = typeof this.message === "function"
|
||||
? this.message(user, target, move)
|
||||
: this.message;
|
||||
|
||||
// TODO: Consider changing if/when MoveAttr `apply` return values become significant
|
||||
if (message) {
|
||||
globalScene.phaseManager.queueMessage(message, 500);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class CelebrateAttr extends MoveEffectAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:celebrate", { playerName: loggedInUser?.username }));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class RecoilAttr extends MoveEffectAttr {
|
||||
private useHp: boolean;
|
||||
@ -5931,38 +5942,6 @@ export class ProtectAttr extends AddBattlerTagAttr {
|
||||
}
|
||||
}
|
||||
|
||||
export class IgnoreAccuracyAttr extends AddBattlerTagAttr {
|
||||
constructor() {
|
||||
super(BattlerTagType.IGNORE_ACCURACY, true, false, 2);
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:tookAimAtTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) }));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class FaintCountdownAttr extends AddBattlerTagAttr {
|
||||
constructor() {
|
||||
super(BattlerTagType.PERISH_SONG, false, true, 4);
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:faintCountdown", { pokemonName: getPokemonNameWithAffix(target), turnCount: this.turnCountMin - 1 }));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute to remove all Substitutes from the field.
|
||||
* @extends MoveEffectAttr
|
||||
@ -6603,8 +6582,10 @@ export class ChillyReceptionAttr extends ForceSwitchOutAttr {
|
||||
return (user, target, move) => globalScene.arena.weather?.weatherType !== WeatherType.SNOW || super.getSwitchOutCondition()(user, target, move);
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveTypeAttr extends MoveEffectAttr {
|
||||
|
||||
// TODO: Remove the message callback
|
||||
private removedType: PokemonType;
|
||||
private messageCallback: ((user: Pokemon) => void) | undefined;
|
||||
|
||||
@ -8293,8 +8274,6 @@ const MoveAttrs = Object.freeze({
|
||||
RandomLevelDamageAttr,
|
||||
ModifiedDamageAttr,
|
||||
SurviveDamageAttr,
|
||||
SplashAttr,
|
||||
CelebrateAttr,
|
||||
RecoilAttr,
|
||||
SacrificialAttr,
|
||||
SacrificialAttrOnHit,
|
||||
@ -8437,8 +8416,7 @@ const MoveAttrs = Object.freeze({
|
||||
RechargeAttr,
|
||||
TrapAttr,
|
||||
ProtectAttr,
|
||||
IgnoreAccuracyAttr,
|
||||
FaintCountdownAttr,
|
||||
MessageAttr,
|
||||
RemoveAllSubstitutesAttr,
|
||||
HitsTagAttr,
|
||||
HitsTagForDoubleDamageAttr,
|
||||
@ -8932,7 +8910,7 @@ export function initMoves() {
|
||||
new AttackMove(MoveId.PSYWAVE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 15, -1, 0, 1)
|
||||
.attr(RandomLevelDamageAttr),
|
||||
new SelfStatusMove(MoveId.SPLASH, PokemonType.NORMAL, -1, 40, -1, 0, 1)
|
||||
.attr(SplashAttr)
|
||||
.attr(MessageAttr, i18next.t("moveTriggers:splash"))
|
||||
.condition(failOnGravityCondition),
|
||||
new SelfStatusMove(MoveId.ACID_ARMOR, PokemonType.POISON, -1, 20, -1, 0, 1)
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
|
||||
@ -8994,7 +8972,10 @@ export function initMoves() {
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1)
|
||||
.reflectable(),
|
||||
new StatusMove(MoveId.MIND_READER, PokemonType.NORMAL, -1, 5, -1, 0, 2)
|
||||
.attr(IgnoreAccuracyAttr),
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_ACCURACY, true, false, 2)
|
||||
.attr(MessageAttr, (user, target) =>
|
||||
i18next.t("moveTriggers:tookAimAtTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })
|
||||
),
|
||||
new StatusMove(MoveId.NIGHTMARE, PokemonType.GHOST, 100, 15, -1, 0, 2)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.NIGHTMARE)
|
||||
.condition(targetSleptOrComatoseCondition),
|
||||
@ -9082,7 +9063,9 @@ export function initMoves() {
|
||||
return lastTurnMove.length === 0 || lastTurnMove[0].move !== move.id || lastTurnMove[0].result !== MoveResult.SUCCESS;
|
||||
}),
|
||||
new StatusMove(MoveId.PERISH_SONG, PokemonType.NORMAL, -1, 5, -1, 0, 2)
|
||||
.attr(FaintCountdownAttr)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.PERISH_SONG, false, true, 4)
|
||||
.attr(MessageAttr, (_user, target) =>
|
||||
i18next.t("moveTriggers:faintCountdown", { pokemonName: getPokemonNameWithAffix(target), turnCount: 3 }))
|
||||
.ignoresProtect()
|
||||
.soundBased()
|
||||
.condition(failOnBossCondition)
|
||||
@ -9098,7 +9081,10 @@ export function initMoves() {
|
||||
.attr(MultiHitAttr)
|
||||
.makesContact(false),
|
||||
new StatusMove(MoveId.LOCK_ON, PokemonType.NORMAL, -1, 5, -1, 0, 2)
|
||||
.attr(IgnoreAccuracyAttr),
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_ACCURACY, true, false, 2)
|
||||
.attr(MessageAttr, (user, target) =>
|
||||
i18next.t("moveTriggers:tookAimAtTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })
|
||||
),
|
||||
new AttackMove(MoveId.OUTRAGE, PokemonType.DRAGON, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 2)
|
||||
.attr(FrenzyAttr)
|
||||
.attr(MissEffectAttr, frenzyMissFunc)
|
||||
@ -9325,8 +9311,8 @@ export function initMoves() {
|
||||
&& (user.status.effect === StatusEffect.BURN || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.PARALYSIS) ? 2 : 1)
|
||||
.attr(BypassBurnDamageReductionAttr),
|
||||
new AttackMove(MoveId.FOCUS_PUNCH, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, -1, -3, 3)
|
||||
.attr(MessageHeaderAttr, (user, move) => i18next.t("moveTriggers:isTighteningFocus", { pokemonName: getPokemonNameWithAffix(user) }))
|
||||
.attr(PreUseInterruptAttr, (user, target, move) => i18next.t("moveTriggers:lostFocus", { pokemonName: getPokemonNameWithAffix(user) }), user => !!user.turnData.attacksReceived.find(r => r.damage))
|
||||
.attr(MessageHeaderAttr, (user) => i18next.t("moveTriggers:isTighteningFocus", { pokemonName: getPokemonNameWithAffix(user) }))
|
||||
.attr(PreUseInterruptAttr, (user) => i18next.t("moveTriggers:lostFocus", { pokemonName: getPokemonNameWithAffix(user) }), user => user.turnData.attacksReceived.some(r => r.damage > 0))
|
||||
.punchingMove(),
|
||||
new AttackMove(MoveId.SMELLING_SALTS, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 3)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1)
|
||||
@ -10427,7 +10413,8 @@ export function initMoves() {
|
||||
new AttackMove(MoveId.DAZZLING_GLEAM, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new SelfStatusMove(MoveId.CELEBRATE, PokemonType.NORMAL, -1, 40, -1, 0, 6)
|
||||
.attr(CelebrateAttr),
|
||||
// NB: This needs a lambda function as the user will not be logged in by the time the moves are initialized
|
||||
.attr(MessageAttr, () => i18next.t("moveTriggers:celebrate", { playerName: loggedInUser?.username })),
|
||||
new StatusMove(MoveId.HOLD_HANDS, PokemonType.NORMAL, -1, 40, -1, 0, 6)
|
||||
.ignoresSubstitute()
|
||||
.target(MoveTarget.NEAR_ALLY),
|
||||
@ -10602,7 +10589,12 @@ export function initMoves() {
|
||||
.attr(StatStageChangeAttr, [ Stat.SPD ], -1)
|
||||
.reflectable(),
|
||||
new SelfStatusMove(MoveId.LASER_FOCUS, PokemonType.NORMAL, -1, 30, -1, 0, 7)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_CRIT, true, false),
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_CRIT, true, false)
|
||||
.attr(MessageAttr, (user) =>
|
||||
i18next.t("battlerTags:laserFocusOnAdd", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(user),
|
||||
}),
|
||||
),
|
||||
new StatusMove(MoveId.GEAR_UP, PokemonType.STEEL, -1, 20, -1, 0, 7)
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, { condition: (user, target, move) => !![ AbilityId.PLUS, AbilityId.MINUS ].find(a => target.hasAbility(a, false)) })
|
||||
.ignoresSubstitute()
|
||||
|
@ -43,5 +43,6 @@ export enum UiMode {
|
||||
TEST_DIALOGUE,
|
||||
AUTO_COMPLETE,
|
||||
ADMIN,
|
||||
MYSTERY_ENCOUNTER
|
||||
MYSTERY_ENCOUNTER,
|
||||
CHANGE_PASSWORD_FORM,
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ApiBase } from "#api/api-base";
|
||||
import { SESSION_ID_COOKIE_NAME } from "#app/constants";
|
||||
import type {
|
||||
AccountChangePwRequest,
|
||||
AccountInfoResponse,
|
||||
AccountLoginRequest,
|
||||
AccountLoginResponse,
|
||||
@ -95,4 +96,19 @@ export class PokerogueAccountApi extends ApiBase {
|
||||
|
||||
removeCookie(SESSION_ID_COOKIE_NAME); // we are always clearing the cookie.
|
||||
}
|
||||
|
||||
public async changePassword(changePwData: AccountChangePwRequest) {
|
||||
try {
|
||||
const response = await this.doPost("/account/changepw", changePwData, "form-urlencoded");
|
||||
if (response.ok) {
|
||||
return null;
|
||||
}
|
||||
console.warn("Change password failed!", response.status, response.statusText);
|
||||
return response.text();
|
||||
} catch (err) {
|
||||
console.warn("Change password failed!", err);
|
||||
}
|
||||
|
||||
return "Unknown error!";
|
||||
}
|
||||
}
|
||||
|
124
src/ui/change-password-form-ui-handler.ts
Normal file
124
src/ui/change-password-form-ui-handler.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import type { InputFieldConfig } from "#ui/form-modal-ui-handler";
|
||||
import { FormModalUiHandler } from "#ui/form-modal-ui-handler";
|
||||
import type { ModalConfig } from "#ui/modal-ui-handler";
|
||||
import i18next from "i18next";
|
||||
|
||||
export class ChangePasswordFormUiHandler extends FormModalUiHandler {
|
||||
private readonly ERR_PASSWORD: string = "invalid password";
|
||||
private readonly ERR_ACCOUNT_EXIST: string = "account doesn't exist";
|
||||
private readonly ERR_PASSWORD_MISMATCH: string = "password doesn't match";
|
||||
|
||||
constructor(mode: UiMode | null = null) {
|
||||
super(mode);
|
||||
}
|
||||
|
||||
setup(): void {
|
||||
super.setup();
|
||||
}
|
||||
|
||||
override getModalTitle(_config?: ModalConfig): string {
|
||||
return i18next.t("menu:changePassword");
|
||||
}
|
||||
|
||||
override getWidth(_config?: ModalConfig): number {
|
||||
return 160;
|
||||
}
|
||||
|
||||
override getMargin(_config?: ModalConfig): [number, number, number, number] {
|
||||
return [0, 0, 48, 0];
|
||||
}
|
||||
|
||||
override getButtonLabels(_config?: ModalConfig): string[] {
|
||||
return [i18next.t("settings:buttonSubmit"), i18next.t("menu:cancel")];
|
||||
}
|
||||
|
||||
override getReadableErrorMessage(error: string): string {
|
||||
const colonIndex = error?.indexOf(":");
|
||||
if (colonIndex > 0) {
|
||||
error = error.slice(0, colonIndex);
|
||||
}
|
||||
switch (error) {
|
||||
case this.ERR_PASSWORD:
|
||||
return i18next.t("menu:invalidRegisterPassword");
|
||||
case this.ERR_ACCOUNT_EXIST:
|
||||
return i18next.t("menu:accountNonExistent");
|
||||
case this.ERR_PASSWORD_MISMATCH:
|
||||
return i18next.t("menu:passwordNotMatchingConfirmPassword");
|
||||
}
|
||||
|
||||
return super.getReadableErrorMessage(error);
|
||||
}
|
||||
|
||||
override getInputFieldConfigs(): InputFieldConfig[] {
|
||||
const inputFieldConfigs: InputFieldConfig[] = [];
|
||||
inputFieldConfigs.push({
|
||||
label: i18next.t("menu:password"),
|
||||
isPassword: true,
|
||||
});
|
||||
inputFieldConfigs.push({
|
||||
label: i18next.t("menu:confirmPassword"),
|
||||
isPassword: true,
|
||||
});
|
||||
return inputFieldConfigs;
|
||||
}
|
||||
|
||||
override show(args: [ModalConfig, ...any]): boolean {
|
||||
if (super.show(args)) {
|
||||
const config = args[0];
|
||||
const originalSubmitAction = this.submitAction;
|
||||
this.submitAction = () => {
|
||||
if (globalScene.tweens.getTweensOf(this.modalContainer).length === 0) {
|
||||
// Prevent overlapping overrides on action modification
|
||||
this.submitAction = originalSubmitAction;
|
||||
this.sanitizeInputs();
|
||||
globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] });
|
||||
const onFail = (error: string | null) => {
|
||||
globalScene.ui.setMode(UiMode.CHANGE_PASSWORD_FORM, Object.assign(config, { errorMessage: error?.trim() }));
|
||||
globalScene.ui.playError();
|
||||
};
|
||||
const [passwordInput, confirmPasswordInput] = this.inputs;
|
||||
if (!passwordInput?.text) {
|
||||
return onFail(this.getReadableErrorMessage("invalid password"));
|
||||
}
|
||||
if (passwordInput.text !== confirmPasswordInput.text) {
|
||||
return onFail(this.ERR_PASSWORD_MISMATCH);
|
||||
}
|
||||
|
||||
pokerogueApi.account.changePassword({ password: passwordInput.text }).then(error => {
|
||||
if (!error && originalSubmitAction) {
|
||||
globalScene.ui.playSelect();
|
||||
originalSubmitAction();
|
||||
// Only clear inputs if the action was successful
|
||||
for (const input of this.inputs) {
|
||||
input.setText("");
|
||||
}
|
||||
} else {
|
||||
onFail(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
// Upon pressing cancel, the inputs should be cleared
|
||||
const originalCancelAction = this.cancelAction;
|
||||
this.cancelAction = () => {
|
||||
globalScene.ui.playSelect();
|
||||
for (const input of this.inputs) {
|
||||
input.setText("");
|
||||
}
|
||||
originalCancelAction?.();
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
override clear() {
|
||||
super.clear();
|
||||
this.setMouseCursorStyle("default"); //reset cursor
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ export abstract class FormModalUiHandler extends ModalUiHandler {
|
||||
protected inputs: InputText[];
|
||||
protected errorMessage: Phaser.GameObjects.Text;
|
||||
protected submitAction: Function | null;
|
||||
protected cancelAction: (() => void) | null;
|
||||
protected tween: Phaser.Tweens.Tween;
|
||||
protected formLabels: Phaser.GameObjects.Text[];
|
||||
|
||||
@ -126,22 +127,37 @@ export abstract class FormModalUiHandler extends ModalUiHandler {
|
||||
});
|
||||
}
|
||||
|
||||
show(args: any[]): boolean {
|
||||
override show(args: any[]): boolean {
|
||||
if (super.show(args)) {
|
||||
this.inputContainers.map(ic => ic.setVisible(true));
|
||||
|
||||
const config = args[0] as FormModalConfig;
|
||||
|
||||
this.submitAction = config.buttonActions.length ? config.buttonActions[0] : null;
|
||||
this.cancelAction = config.buttonActions[1] ?? null;
|
||||
|
||||
if (this.buttonBgs.length) {
|
||||
this.buttonBgs[0].off("pointerdown");
|
||||
this.buttonBgs[0].on("pointerdown", () => {
|
||||
if (this.submitAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) {
|
||||
this.submitAction();
|
||||
// #region: Override button pointerDown
|
||||
// Override the pointerDown event for the buttonBgs to call the `submitAction` and `cancelAction`
|
||||
// properties that we set above, allowing their behavior to change after this method terminates
|
||||
// Some subclasses use this to add behavior to the submit and cancel action
|
||||
|
||||
this.buttonBgs[0].off("pointerdown");
|
||||
this.buttonBgs[0].on("pointerdown", () => {
|
||||
if (this.submitAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) {
|
||||
this.submitAction();
|
||||
}
|
||||
});
|
||||
const cancelBg = this.buttonBgs[1];
|
||||
if (cancelBg) {
|
||||
cancelBg.off("pointerdown");
|
||||
cancelBg.on("pointerdown", () => {
|
||||
// The seemingly redundant cancelAction check is intentionally left in as a defensive programming measure
|
||||
if (this.cancelAction && globalScene.tweens.getTweensOf(this.modalContainer).length === 0) {
|
||||
this.cancelAction();
|
||||
}
|
||||
});
|
||||
}
|
||||
//#endregion: Override pointerDown events
|
||||
|
||||
this.modalContainer.y += 24;
|
||||
this.modalContainer.setAlpha(0);
|
||||
|
@ -311,6 +311,17 @@ export class MenuUiHandler extends MessageUiHandler {
|
||||
},
|
||||
keepOpen: true,
|
||||
},
|
||||
{
|
||||
// Note: i18n key is under `menu`, not `menuUiHandler` to avoid duplication
|
||||
label: i18next.t("menu:changePassword"),
|
||||
handler: () => {
|
||||
ui.setOverlayMode(UiMode.CHANGE_PASSWORD_FORM, {
|
||||
buttonActions: [() => ui.revertMode(), () => ui.revertMode()],
|
||||
});
|
||||
return true;
|
||||
},
|
||||
keepOpen: true,
|
||||
},
|
||||
{
|
||||
label: i18next.t("menuUiHandler:consentPreferences"),
|
||||
handler: () => {
|
||||
|
@ -7,7 +7,7 @@ import { UiHandler } from "#ui/ui-handler";
|
||||
import { addWindow, WindowVariant } from "#ui/ui-theme";
|
||||
|
||||
export interface ModalConfig {
|
||||
buttonActions: Function[];
|
||||
buttonActions: ((...args: any[]) => any)[];
|
||||
}
|
||||
|
||||
export abstract class ModalUiHandler extends UiHandler {
|
||||
|
@ -13,6 +13,7 @@ import { BallUiHandler } from "#ui/ball-ui-handler";
|
||||
import { BattleMessageUiHandler } from "#ui/battle-message-ui-handler";
|
||||
import type { BgmBar } from "#ui/bgm-bar";
|
||||
import { GameChallengesUiHandler } from "#ui/challenges-select-ui-handler";
|
||||
import { ChangePasswordFormUiHandler } from "#ui/change-password-form-ui-handler";
|
||||
import { CommandUiHandler } from "#ui/command-ui-handler";
|
||||
import { ConfirmUiHandler } from "#ui/confirm-ui-handler";
|
||||
import { EggGachaUiHandler } from "#ui/egg-gacha-ui-handler";
|
||||
@ -102,6 +103,7 @@ const noTransitionModes = [
|
||||
UiMode.ADMIN,
|
||||
UiMode.MYSTERY_ENCOUNTER,
|
||||
UiMode.RUN_INFO,
|
||||
UiMode.CHANGE_PASSWORD_FORM,
|
||||
];
|
||||
|
||||
export class UI extends Phaser.GameObjects.Container {
|
||||
@ -172,6 +174,7 @@ export class UI extends Phaser.GameObjects.Container {
|
||||
new AutoCompleteUiHandler(),
|
||||
new AdminUiHandler(),
|
||||
new MysteryEncounterUiHandler(),
|
||||
new ChangePasswordFormUiHandler(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { Status } from "#data/status-effect";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
@ -179,18 +178,13 @@ describe("Moves - Whirlwind", () => {
|
||||
const eligibleEnemy = enemyParty.filter(p => p.hp > 0 && p.isAllowedInBattle());
|
||||
expect(eligibleEnemy.length).toBe(1);
|
||||
|
||||
// Spy on the queueMessage function
|
||||
const queueSpy = vi.spyOn(globalScene.phaseManager, "queueMessage");
|
||||
|
||||
// Player uses Whirlwind; opponent uses Splash
|
||||
game.move.select(MoveId.WHIRLWIND);
|
||||
await game.move.selectEnemyMove(MoveId.SPLASH);
|
||||
await game.toNextTurn();
|
||||
|
||||
// Verify that the failure message is displayed for Whirlwind
|
||||
expect(queueSpy).toHaveBeenCalledWith(expect.stringContaining("But it failed"));
|
||||
// Verify the opponent's Splash message
|
||||
expect(queueSpy).toHaveBeenCalledWith(expect.stringContaining("But nothing happened!"));
|
||||
const player = game.field.getPlayerPokemon();
|
||||
expect(player).toHaveUsedMove({ move: MoveId.WHIRLWIND, result: MoveResult.FAIL });
|
||||
});
|
||||
|
||||
it("should not pull in the other trainer's pokemon in a partner trainer battle", async () => {
|
||||
|
Loading…
Reference in New Issue
Block a user