mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-20 22:39:31 +02:00
Compare commits
9 Commits
0431012b02
...
4caac7a4da
Author | SHA1 | Date | |
---|---|---|---|
|
4caac7a4da | ||
|
076ef81691 | ||
|
23271901cf | ||
|
1517e0512e | ||
|
0da37a0f0c | ||
|
ee80615436 | ||
|
0103b3e20b | ||
|
bd3a037e54 | ||
|
63bc36b39b |
@ -90,9 +90,13 @@ If this feature requires new text, the text should be integrated into the code w
|
|||||||
- For any feature pulled from the mainline Pokémon games (e.g. a Move or Ability implementation), it's best practice to include a source link for any added text.
|
- For any feature pulled from the mainline Pokémon games (e.g. a Move or Ability implementation), it's best practice to include a source link for any added text.
|
||||||
[Poké Corpus](https://abcboy101.github.io/poke-corpus/) is a great resource for finding text from the mainline games; otherwise, a video/picture showing the text being displayed should suffice.
|
[Poké Corpus](https://abcboy101.github.io/poke-corpus/) is a great resource for finding text from the mainline games; otherwise, a video/picture showing the text being displayed should suffice.
|
||||||
- You should also [notify the current Head of Translation](#notifying-translation) to ensure a fast response.
|
- You should also [notify the current Head of Translation](#notifying-translation) to ensure a fast response.
|
||||||
3. At this point, you may begin [testing locales integration in your main PR](#documenting-locales-changes).
|
3. Your locales should use the following format:
|
||||||
4. The Translation Team will approve the locale PR (after corrections, if necessary), then merge it into `pokerogue-locales`.
|
- File names should be in `kebab-case`. Example: `trainer-names.json`
|
||||||
5. The Dev Team will approve your main PR for your feature, then merge it into PokéRogue's beta environment.
|
- Key names should be in `camelCase`. Example: `aceTrainer`
|
||||||
|
- If you make use of i18next's inbuilt [context support](https://www.i18next.com/translation-function/context), you need to use `snake_case` for the context key. Example: `aceTrainer_male`
|
||||||
|
4. At this point, you may begin [testing locales integration in your main PR](#documenting-locales-changes).
|
||||||
|
5. The Translation Team will approve the locale PR (after corrections, if necessary), then merge it into `pokerogue-locales`.
|
||||||
|
6. The Dev Team will approve your main PR for your feature, then merge it into PokéRogue's beta environment.
|
||||||
|
|
||||||
[^2]: For those wondering, the reason for choosing English specifically is due to it being the master language set in Pontoon (the program used by the Translation Team to perform locale updates).
|
[^2]: For those wondering, the reason for choosing English specifically is due to it being the master language set in Pontoon (the program used by the Translation Team to perform locale updates).
|
||||||
If a key is present in any language _except_ the master language, it won't appear anywhere else in the translation tool, rendering missing English keys quite a hassle.
|
If a key is present in any language _except_ the master language, it won't appear anywhere else in the translation tool, rendering missing English keys quite a hassle.
|
||||||
|
@ -1,13 +1,24 @@
|
|||||||
|
import type { Pokemon } from "#field/pokemon";
|
||||||
import type {
|
import type {
|
||||||
AttackMove,
|
AttackMove,
|
||||||
ChargingAttackMove,
|
ChargingAttackMove,
|
||||||
ChargingSelfStatusMove,
|
ChargingSelfStatusMove,
|
||||||
|
Move,
|
||||||
MoveAttr,
|
MoveAttr,
|
||||||
MoveAttrConstructorMap,
|
MoveAttrConstructorMap,
|
||||||
SelfStatusMove,
|
SelfStatusMove,
|
||||||
StatusMove,
|
StatusMove,
|
||||||
} from "#moves/move";
|
} 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 MoveAttrFilter = (attr: MoveAttr) => boolean;
|
||||||
|
|
||||||
export type * from "#moves/move";
|
export type * from "#moves/move";
|
||||||
|
@ -1670,6 +1670,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
|
|||||||
constructor(
|
constructor(
|
||||||
private newType: PokemonType,
|
private newType: PokemonType,
|
||||||
private powerMultiplier: number,
|
private powerMultiplier: number,
|
||||||
|
// TODO: all moves with this attr solely check the move being used...
|
||||||
private condition?: PokemonAttackCondition,
|
private condition?: PokemonAttackCondition,
|
||||||
) {
|
) {
|
||||||
super(false);
|
super(false);
|
||||||
|
69770
src/data/balance/tms.ts
69770
src/data/balance/tms.ts
File diff suppressed because one or more lines are too long
@ -86,7 +86,7 @@ import { PokemonHealPhase } from "#phases/pokemon-heal-phase";
|
|||||||
import { SwitchSummonPhase } from "#phases/switch-summon-phase";
|
import { SwitchSummonPhase } from "#phases/switch-summon-phase";
|
||||||
import type { AttackMoveResult } from "#types/attack-move-result";
|
import type { AttackMoveResult } from "#types/attack-move-result";
|
||||||
import type { Localizable } from "#types/locales";
|
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 type { TurnMove } from "#types/turn-move";
|
||||||
import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
|
import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
|
||||||
import { getEnumValues } from "#utils/enums";
|
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.
|
* Header attribute to queue a message at the beginning of a turn.
|
||||||
* @see {@link MoveHeaderAttr}
|
|
||||||
*/
|
*/
|
||||||
export class MessageHeaderAttr extends 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();
|
super();
|
||||||
this.message = message;
|
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"
|
const message = typeof this.message === "string"
|
||||||
? this.message
|
? this.message
|
||||||
: this.message(user, move);
|
: this.message(user, target, move);
|
||||||
|
|
||||||
if (message) {
|
if (message) {
|
||||||
globalScene.phaseManager.queueMessage(message);
|
globalScene.phaseManager.queueMessage(message);
|
||||||
@ -1418,21 +1418,21 @@ export class BeakBlastHeaderAttr extends AddBattlerTagHeaderAttr {
|
|||||||
*/
|
*/
|
||||||
export class PreMoveMessageAttr extends MoveAttr {
|
export class PreMoveMessageAttr extends MoveAttr {
|
||||||
/** The message to display or a function returning one */
|
/** 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.
|
* 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
|
* @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).
|
* (though the move will still succeed).
|
||||||
*/
|
*/
|
||||||
constructor(message: string | ((user: Pokemon, target: Pokemon, move: Move) => string)) {
|
constructor(message: string | MoveMessageFunc) {
|
||||||
super();
|
super();
|
||||||
this.message = message;
|
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"
|
const message = typeof this.message === "function"
|
||||||
? this.message(user, target, move)
|
? this.message(user, target, move)
|
||||||
: this.message;
|
: this.message;
|
||||||
@ -1453,18 +1453,17 @@ export class PreMoveMessageAttr extends MoveAttr {
|
|||||||
* @extends MoveAttr
|
* @extends MoveAttr
|
||||||
*/
|
*/
|
||||||
export class PreUseInterruptAttr extends MoveAttr {
|
export class PreUseInterruptAttr extends MoveAttr {
|
||||||
protected message?: string | ((user: Pokemon, target: Pokemon, move: Move) => string);
|
protected message: string | MoveMessageFunc;
|
||||||
protected overridesFailedMessage: boolean;
|
|
||||||
protected conditionFunc: MoveConditionFunc;
|
protected conditionFunc: MoveConditionFunc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new MoveInterruptedMessageAttr.
|
* 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.
|
* @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();
|
super();
|
||||||
this.message = message;
|
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 {
|
override getFailedText(user: Pokemon, target: Pokemon, move: Move): string | undefined {
|
||||||
if (this.message && this.conditionFunc(user, target, move)) {
|
if (this.message && this.conditionFunc(user, target, move)) {
|
||||||
const message =
|
return typeof this.message === "string"
|
||||||
typeof this.message === "string"
|
? this.message
|
||||||
? (this.message as string)
|
|
||||||
: this.message(user, target, move);
|
: this.message(user, target, move);
|
||||||
return message;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1694,17 +1691,30 @@ export class SurviveDamageAttr extends ModifiedDamageAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SplashAttr extends MoveEffectAttr {
|
/**
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
* Move attribute to display arbitrary text during a move's execution.
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:splash"));
|
*/
|
||||||
return true;
|
export class MessageAttr extends MoveEffectAttr {
|
||||||
}
|
/** The message to display, either as a string or a function returning one. */
|
||||||
}
|
private message: string | MoveMessageFunc;
|
||||||
|
|
||||||
export class CelebrateAttr extends MoveEffectAttr {
|
constructor(message: string | MoveMessageFunc, options?: MoveEffectAttrOptions) {
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
// TODO: Do we need to respect `selfTarget` if we're just displaying text?
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:celebrate", { playerName: loggedInUser?.username }));
|
super(false, options)
|
||||||
return true;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5931,38 +5941,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.
|
* Attribute to remove all Substitutes from the field.
|
||||||
* @extends MoveEffectAttr
|
* @extends MoveEffectAttr
|
||||||
@ -6603,8 +6581,10 @@ export class ChillyReceptionAttr extends ForceSwitchOutAttr {
|
|||||||
return (user, target, move) => globalScene.arena.weather?.weatherType !== WeatherType.SNOW || super.getSwitchOutCondition()(user, target, move);
|
return (user, target, move) => globalScene.arena.weather?.weatherType !== WeatherType.SNOW || super.getSwitchOutCondition()(user, target, move);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RemoveTypeAttr extends MoveEffectAttr {
|
export class RemoveTypeAttr extends MoveEffectAttr {
|
||||||
|
|
||||||
|
// TODO: Remove the message callback
|
||||||
private removedType: PokemonType;
|
private removedType: PokemonType;
|
||||||
private messageCallback: ((user: Pokemon) => void) | undefined;
|
private messageCallback: ((user: Pokemon) => void) | undefined;
|
||||||
|
|
||||||
@ -8299,8 +8279,6 @@ const MoveAttrs = Object.freeze({
|
|||||||
RandomLevelDamageAttr,
|
RandomLevelDamageAttr,
|
||||||
ModifiedDamageAttr,
|
ModifiedDamageAttr,
|
||||||
SurviveDamageAttr,
|
SurviveDamageAttr,
|
||||||
SplashAttr,
|
|
||||||
CelebrateAttr,
|
|
||||||
RecoilAttr,
|
RecoilAttr,
|
||||||
SacrificialAttr,
|
SacrificialAttr,
|
||||||
SacrificialAttrOnHit,
|
SacrificialAttrOnHit,
|
||||||
@ -8443,8 +8421,7 @@ const MoveAttrs = Object.freeze({
|
|||||||
RechargeAttr,
|
RechargeAttr,
|
||||||
TrapAttr,
|
TrapAttr,
|
||||||
ProtectAttr,
|
ProtectAttr,
|
||||||
IgnoreAccuracyAttr,
|
MessageAttr,
|
||||||
FaintCountdownAttr,
|
|
||||||
RemoveAllSubstitutesAttr,
|
RemoveAllSubstitutesAttr,
|
||||||
HitsTagAttr,
|
HitsTagAttr,
|
||||||
HitsTagForDoubleDamageAttr,
|
HitsTagForDoubleDamageAttr,
|
||||||
@ -8938,7 +8915,7 @@ export function initMoves() {
|
|||||||
new AttackMove(MoveId.PSYWAVE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 15, -1, 0, 1)
|
new AttackMove(MoveId.PSYWAVE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 15, -1, 0, 1)
|
||||||
.attr(RandomLevelDamageAttr),
|
.attr(RandomLevelDamageAttr),
|
||||||
new SelfStatusMove(MoveId.SPLASH, PokemonType.NORMAL, -1, 40, -1, 0, 1)
|
new SelfStatusMove(MoveId.SPLASH, PokemonType.NORMAL, -1, 40, -1, 0, 1)
|
||||||
.attr(SplashAttr)
|
.attr(MessageAttr, i18next.t("moveTriggers:splash"))
|
||||||
.condition(failOnGravityCondition),
|
.condition(failOnGravityCondition),
|
||||||
new SelfStatusMove(MoveId.ACID_ARMOR, PokemonType.POISON, -1, 20, -1, 0, 1)
|
new SelfStatusMove(MoveId.ACID_ARMOR, PokemonType.POISON, -1, 20, -1, 0, 1)
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
|
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
|
||||||
@ -9000,7 +8977,10 @@ export function initMoves() {
|
|||||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1)
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1)
|
||||||
.reflectable(),
|
.reflectable(),
|
||||||
new StatusMove(MoveId.MIND_READER, PokemonType.NORMAL, -1, 5, -1, 0, 2)
|
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)
|
new StatusMove(MoveId.NIGHTMARE, PokemonType.GHOST, 100, 15, -1, 0, 2)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.NIGHTMARE)
|
.attr(AddBattlerTagAttr, BattlerTagType.NIGHTMARE)
|
||||||
.condition(targetSleptOrComatoseCondition),
|
.condition(targetSleptOrComatoseCondition),
|
||||||
@ -9088,7 +9068,9 @@ export function initMoves() {
|
|||||||
return lastTurnMove.length === 0 || lastTurnMove[0].move !== move.id || lastTurnMove[0].result !== MoveResult.SUCCESS;
|
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)
|
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()
|
.ignoresProtect()
|
||||||
.soundBased()
|
.soundBased()
|
||||||
.condition(failOnBossCondition)
|
.condition(failOnBossCondition)
|
||||||
@ -9104,7 +9086,10 @@ export function initMoves() {
|
|||||||
.attr(MultiHitAttr)
|
.attr(MultiHitAttr)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
new StatusMove(MoveId.LOCK_ON, PokemonType.NORMAL, -1, 5, -1, 0, 2)
|
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)
|
new AttackMove(MoveId.OUTRAGE, PokemonType.DRAGON, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 2)
|
||||||
.attr(FrenzyAttr)
|
.attr(FrenzyAttr)
|
||||||
.attr(MissEffectAttr, frenzyMissFunc)
|
.attr(MissEffectAttr, frenzyMissFunc)
|
||||||
@ -9331,8 +9316,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)
|
&& (user.status.effect === StatusEffect.BURN || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.PARALYSIS) ? 2 : 1)
|
||||||
.attr(BypassBurnDamageReductionAttr),
|
.attr(BypassBurnDamageReductionAttr),
|
||||||
new AttackMove(MoveId.FOCUS_PUNCH, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, -1, -3, 3)
|
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(MessageHeaderAttr, (user) => 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(PreUseInterruptAttr, (user) => i18next.t("moveTriggers:lostFocus", { pokemonName: getPokemonNameWithAffix(user) }), user => user.turnData.attacksReceived.some(r => r.damage > 0))
|
||||||
.punchingMove(),
|
.punchingMove(),
|
||||||
new AttackMove(MoveId.SMELLING_SALTS, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 3)
|
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)
|
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1)
|
||||||
@ -10433,7 +10418,8 @@ export function initMoves() {
|
|||||||
new AttackMove(MoveId.DAZZLING_GLEAM, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6)
|
new AttackMove(MoveId.DAZZLING_GLEAM, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6)
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new SelfStatusMove(MoveId.CELEBRATE, PokemonType.NORMAL, -1, 40, -1, 0, 6)
|
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)
|
new StatusMove(MoveId.HOLD_HANDS, PokemonType.NORMAL, -1, 40, -1, 0, 6)
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
.target(MoveTarget.NEAR_ALLY),
|
.target(MoveTarget.NEAR_ALLY),
|
||||||
@ -10608,7 +10594,12 @@ export function initMoves() {
|
|||||||
.attr(StatStageChangeAttr, [ Stat.SPD ], -1)
|
.attr(StatStageChangeAttr, [ Stat.SPD ], -1)
|
||||||
.reflectable(),
|
.reflectable(),
|
||||||
new SelfStatusMove(MoveId.LASER_FOCUS, PokemonType.NORMAL, -1, 30, -1, 0, 7)
|
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)
|
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)) })
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, { condition: (user, target, move) => !![ AbilityId.PLUS, AbilityId.MINUS ].find(a => target.hasAbility(a, false)) })
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
|
@ -620,26 +620,14 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
|
|||||||
|
|
||||||
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||||
if (!this.invertQuery) {
|
if (!this.invertQuery) {
|
||||||
return partyPokemon.filter(
|
return partyPokemon.filter(pokemon => this.requiredMoves.some(m => pokemon.isTmCompatible(m, true)));
|
||||||
pokemon =>
|
|
||||||
this.requiredMoves.filter(learnableMove =>
|
|
||||||
pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m.moveId === tm)).includes(learnableMove),
|
|
||||||
).length > 0,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed learnableMoves
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed learnableMoves
|
||||||
return partyPokemon.filter(
|
return partyPokemon.filter(pokemon => !this.requiredMoves.some(m => pokemon.isTmCompatible(m, true)));
|
||||||
pokemon =>
|
|
||||||
this.requiredMoves.filter(learnableMove =>
|
|
||||||
pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m.moveId === tm)).includes(learnableMove),
|
|
||||||
).length === 0,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
||||||
const includedCompatMoves = this.requiredMoves.filter(reqMove =>
|
const includedCompatMoves = this.requiredMoves.filter(reqMove => pokemon?.isTmCompatible(reqMove, true));
|
||||||
pokemon?.compatibleTms.filter(tm => !pokemon.moveset.find(m => m.moveId === tm)).includes(reqMove),
|
|
||||||
);
|
|
||||||
if (includedCompatMoves.length > 0) {
|
if (includedCompatMoves.length > 0) {
|
||||||
return ["compatibleMove", MoveId[includedCompatMoves[0]]];
|
return ["compatibleMove", MoveId[includedCompatMoves[0]]];
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.excludeTmMoves) {
|
if (!this.excludeTmMoves) {
|
||||||
allPokemonMoves.push(...(pkm.compatibleTms ?? []));
|
allPokemonMoves.push(...(pkm.getCompatibleTms() ?? []));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.excludeEggMoves) {
|
if (!this.excludeEggMoves) {
|
||||||
|
@ -13,10 +13,12 @@ import {
|
|||||||
pokemonSpeciesLevelMoves,
|
pokemonSpeciesLevelMoves,
|
||||||
} from "#balance/pokemon-level-moves";
|
} from "#balance/pokemon-level-moves";
|
||||||
import { speciesStarterCosts } from "#balance/starters";
|
import { speciesStarterCosts } from "#balance/starters";
|
||||||
|
import { speciesFormTmList, speciesTmList } from "#balance/tms";
|
||||||
import type { GrowthRate } from "#data/exp";
|
import type { GrowthRate } from "#data/exp";
|
||||||
import { Gender } from "#data/gender";
|
import { Gender } from "#data/gender";
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { DexAttr } from "#enums/dex-attr";
|
import { DexAttr } from "#enums/dex-attr";
|
||||||
|
import type { MoveId } from "#enums/move-id";
|
||||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||||
import type { PokemonType } from "#enums/pokemon-type";
|
import type { PokemonType } from "#enums/pokemon-type";
|
||||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||||
@ -351,6 +353,41 @@ export abstract class PokemonSpeciesForm {
|
|||||||
return `pokemon_icons_${this.generation}${isVariant ? "v" : ""}`;
|
return `pokemon_icons_${this.generation}${isVariant ? "v" : ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles a list of all TMs compatible with this SpeciesForm
|
||||||
|
* @param formIndex formIndex to check
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getCompatibleTms(formIndex?: number): MoveId[] {
|
||||||
|
const tms: MoveId[] = [];
|
||||||
|
tms.push(...speciesTmList[this.speciesId]);
|
||||||
|
if (speciesFormTmList.hasOwnProperty(this.speciesId)) {
|
||||||
|
formIndex = this.formIndex ?? formIndex ?? -1;
|
||||||
|
const formKey = getPokemonSpecies(this.speciesId).forms[formIndex].formKey;
|
||||||
|
tms.push(...(speciesFormTmList[this.speciesId][formKey] ?? []));
|
||||||
|
}
|
||||||
|
return tms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the actual formKey associated with a given formIndex
|
||||||
|
* @param formIndex The formIndex to check
|
||||||
|
*/
|
||||||
|
abstract getFormKey(formIndex?: number): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a TM is compatible with a SpeciesForm
|
||||||
|
* @param tm The move to check for
|
||||||
|
* @param formIndex If provided, looks specifically for this form
|
||||||
|
* @returns Whether the TM is compatible with this SpeciesForm
|
||||||
|
*/
|
||||||
|
isTmCompatible(tm: MoveId, formIndex?: number): boolean {
|
||||||
|
return (
|
||||||
|
speciesTmList[this.speciesId].includes(tm) ||
|
||||||
|
speciesFormTmList[this.speciesId][this.getFormKey(formIndex)].includes(tm)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getIconId(female: boolean, formIndex?: number, shiny?: boolean, variant?: number): string {
|
getIconId(female: boolean, formIndex?: number, shiny?: boolean, variant?: number): string {
|
||||||
if (formIndex === undefined) {
|
if (formIndex === undefined) {
|
||||||
formIndex = this.formIndex;
|
formIndex = this.formIndex;
|
||||||
@ -878,6 +915,15 @@ export class PokemonSpecies extends PokemonSpeciesForm implements Localizable {
|
|||||||
: ret;
|
: ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the actual formKey associated with the form at the specified index
|
||||||
|
* @param formIndex the formIndex to check
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getFormKey(formIndex?: number): string {
|
||||||
|
return this.forms[formIndex ?? this.formIndex].formKey ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
localize(): void {
|
localize(): void {
|
||||||
this.name = i18next.t(`pokemon:${SpeciesId[this.speciesId].toLowerCase()}`);
|
this.name = i18next.t(`pokemon:${SpeciesId[this.speciesId].toLowerCase()}`);
|
||||||
this.category = i18next.t(`pokemonCategory:${SpeciesId[this.speciesId].toLowerCase()}_category`);
|
this.category = i18next.t(`pokemonCategory:${SpeciesId[this.speciesId].toLowerCase()}_category`);
|
||||||
@ -1317,6 +1363,13 @@ export class PokemonForm extends PokemonSpeciesForm {
|
|||||||
this.isUnobtainable = isUnobtainable;
|
this.isUnobtainable = isUnobtainable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the actual formKey for this PokemonForm
|
||||||
|
*/
|
||||||
|
getFormKey(_formIndex?: number): string {
|
||||||
|
return this.formKey;
|
||||||
|
}
|
||||||
|
|
||||||
getFormSpriteKey(_formIndex?: number) {
|
getFormSpriteKey(_formIndex?: number) {
|
||||||
return this.formSpriteKey !== null ? this.formSpriteKey : this.formKey;
|
return this.formSpriteKey !== null ? this.formSpriteKey : this.formKey;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { timedEventManager } from "#app/global-event-manager";
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions";
|
import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions";
|
||||||
import { signatureSpecies } from "#balance/signature-species";
|
import { signatureSpecies } from "#balance/signature-species";
|
||||||
import { tmSpecies } from "#balance/tms";
|
import { speciesTmList } from "#balance/tms";
|
||||||
import { modifierTypes } from "#data/data-lists";
|
import { modifierTypes } from "#data/data-lists";
|
||||||
import { doubleBattleDialogue } from "#data/double-battle-dialogue";
|
import { doubleBattleDialogue } from "#data/double-battle-dialogue";
|
||||||
import { Gender } from "#data/gender";
|
import { Gender } from "#data/gender";
|
||||||
@ -1445,7 +1445,7 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
.setSpeciesFilter(s => s.isOfType(PokemonType.ELECTRIC)),
|
.setSpeciesFilter(s => s.isOfType(PokemonType.ELECTRIC)),
|
||||||
[TrainerType.HARLEQUIN]: new TrainerConfig(++t)
|
[TrainerType.HARLEQUIN]: new TrainerConfig(++t)
|
||||||
.setEncounterBgm(TrainerType.PSYCHIC)
|
.setEncounterBgm(TrainerType.PSYCHIC)
|
||||||
.setSpeciesFilter(s => tmSpecies[MoveId.TRICK_ROOM].indexOf(s.speciesId) > -1),
|
.setSpeciesFilter(s => speciesTmList[s.speciesId].includes(MoveId.TRICK_ROOM)),
|
||||||
[TrainerType.HIKER]: new TrainerConfig(++t)
|
[TrainerType.HIKER]: new TrainerConfig(++t)
|
||||||
.setEncounterBgm(TrainerType.BACKPACKER)
|
.setEncounterBgm(TrainerType.BACKPACKER)
|
||||||
.setPartyTemplates(
|
.setPartyTemplates(
|
||||||
@ -1589,7 +1589,7 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
trainerPartyTemplates.TWO_AVG,
|
trainerPartyTemplates.TWO_AVG,
|
||||||
trainerPartyTemplates.THREE_AVG,
|
trainerPartyTemplates.THREE_AVG,
|
||||||
)
|
)
|
||||||
.setSpeciesFilter(s => tmSpecies[MoveId.FLY].indexOf(s.speciesId) > -1),
|
.setSpeciesFilter(s => speciesTmList[s.speciesId].includes(MoveId.FLY)),
|
||||||
[TrainerType.POKEFAN]: new TrainerConfig(++t)
|
[TrainerType.POKEFAN]: new TrainerConfig(++t)
|
||||||
.setMoneyMultiplier(1.4)
|
.setMoneyMultiplier(1.4)
|
||||||
.setName("Pokéfan")
|
.setName("Pokéfan")
|
||||||
@ -1605,7 +1605,7 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
trainerPartyTemplates.FIVE_WEAK,
|
trainerPartyTemplates.FIVE_WEAK,
|
||||||
trainerPartyTemplates.SIX_WEAKER_SAME,
|
trainerPartyTemplates.SIX_WEAKER_SAME,
|
||||||
)
|
)
|
||||||
.setSpeciesFilter(s => tmSpecies[MoveId.HELPING_HAND].indexOf(s.speciesId) > -1),
|
.setSpeciesFilter(s => speciesTmList[s.speciesId].includes(MoveId.HELPING_HAND)),
|
||||||
[TrainerType.PRESCHOOLER]: new TrainerConfig(++t)
|
[TrainerType.PRESCHOOLER]: new TrainerConfig(++t)
|
||||||
.setMoneyMultiplier(0.2)
|
.setMoneyMultiplier(0.2)
|
||||||
.setEncounterBgm(TrainerType.YOUNGSTER)
|
.setEncounterBgm(TrainerType.YOUNGSTER)
|
||||||
|
@ -38,6 +38,7 @@ export enum UiMode {
|
|||||||
UNAVAILABLE,
|
UNAVAILABLE,
|
||||||
CHALLENGE_SELECT,
|
CHALLENGE_SELECT,
|
||||||
RENAME_POKEMON,
|
RENAME_POKEMON,
|
||||||
|
RENAME_RUN,
|
||||||
RUN_HISTORY,
|
RUN_HISTORY,
|
||||||
RUN_INFO,
|
RUN_INFO,
|
||||||
TEST_DIALOGUE,
|
TEST_DIALOGUE,
|
||||||
|
@ -18,7 +18,7 @@ import type { LevelMoves } from "#balance/pokemon-level-moves";
|
|||||||
import { EVOLVE_MOVE, RELEARN_MOVE } from "#balance/pokemon-level-moves";
|
import { EVOLVE_MOVE, RELEARN_MOVE } from "#balance/pokemon-level-moves";
|
||||||
import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#balance/rates";
|
import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#balance/rates";
|
||||||
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#balance/starters";
|
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#balance/starters";
|
||||||
import { reverseCompatibleTms, tmPoolTiers, tmSpecies } from "#balance/tms";
|
import { tmPoolTiers } from "#balance/tms";
|
||||||
import type { SuppressAbilitiesTag } from "#data/arena-tag";
|
import type { SuppressAbilitiesTag } from "#data/arena-tag";
|
||||||
import { NoCritTag, WeakenMoveScreenTag } from "#data/arena-tag";
|
import { NoCritTag, WeakenMoveScreenTag } from "#data/arena-tag";
|
||||||
import {
|
import {
|
||||||
@ -3083,27 +3083,13 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasTrainer()) {
|
if (this.hasTrainer()) {
|
||||||
const tms = Object.keys(tmSpecies);
|
const tmset = new Set(this.species.getCompatibleTms(this.formIndex));
|
||||||
for (const tm of tms) {
|
if (this.fusionSpecies) {
|
||||||
const moveId = Number.parseInt(tm) as MoveId;
|
this.fusionSpecies.getCompatibleTms(this.fusionFormIndex).forEach(m => tmset.add(m));
|
||||||
let compatible = false;
|
}
|
||||||
for (const p of tmSpecies[tm]) {
|
const tms = Array.from(tmset);
|
||||||
if (Array.isArray(p)) {
|
for (const moveId of tms) {
|
||||||
if (
|
if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) {
|
||||||
p[0] === this.species.speciesId ||
|
|
||||||
(this.fusionSpecies &&
|
|
||||||
p[0] === this.fusionSpecies.speciesId &&
|
|
||||||
p.slice(1).indexOf(this.species.forms[this.formIndex]) > -1)
|
|
||||||
) {
|
|
||||||
compatible = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (p === this.species.speciesId || (this.fusionSpecies && p === this.fusionSpecies.speciesId)) {
|
|
||||||
compatible = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (compatible && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) {
|
|
||||||
if (tmPoolTiers[moveId] === ModifierTier.COMMON && this.level >= 15) {
|
if (tmPoolTiers[moveId] === ModifierTier.COMMON && this.level >= 15) {
|
||||||
movePool.push([moveId, 4]);
|
movePool.push([moveId, 4]);
|
||||||
} else if (tmPoolTiers[moveId] === ModifierTier.GREAT && this.level >= 30) {
|
} else if (tmPoolTiers[moveId] === ModifierTier.GREAT && this.level >= 30) {
|
||||||
@ -5674,7 +5660,6 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
export class PlayerPokemon extends Pokemon {
|
export class PlayerPokemon extends Pokemon {
|
||||||
protected declare battleInfo: PlayerBattleInfo;
|
protected declare battleInfo: PlayerBattleInfo;
|
||||||
public compatibleTms: MoveId[];
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
species: PokemonSpecies,
|
species: PokemonSpecies,
|
||||||
@ -5712,7 +5697,6 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
this.moveset = [];
|
this.moveset = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.generateCompatibleTms();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initBattleInfo(): void {
|
initBattleInfo(): void {
|
||||||
@ -5744,35 +5728,48 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
return this.getFieldIndex();
|
return this.getFieldIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
generateCompatibleTms(): void {
|
/**
|
||||||
this.compatibleTms = [];
|
* Compiles a list of all TMs compatible with this PlayerPokemon, including its fusion
|
||||||
|
* @param excludeKnown Whether to exclude moves in its current moveset
|
||||||
const tms = Object.keys(tmSpecies);
|
* @param excludeLevelUp Whether to exclude moves learnable at a previous level (incl. relearn-only & evo moves)
|
||||||
for (const tm of tms) {
|
* @param excludeUsed Whether to exclude TMs which were used before on the mon, contained in its "usedTMs" array
|
||||||
const moveId = Number.parseInt(tm) as MoveId;
|
* @returns An array of all compatible MoveId[]
|
||||||
let compatible = false;
|
*/
|
||||||
for (const p of tmSpecies[tm]) {
|
getCompatibleTms(excludeKnown = false, excludeLevelUp = false, excludeUsed = false): MoveId[] {
|
||||||
if (Array.isArray(p)) {
|
const tms = new Set(this.species.getCompatibleTms(this.formIndex));
|
||||||
const [pkm, form] = p;
|
if (this.fusionSpecies) {
|
||||||
if (
|
this.fusionSpecies.getCompatibleTms(this.fusionFormIndex).forEach(m => tms.add(m));
|
||||||
(pkm === this.species.speciesId || (this.fusionSpecies && pkm === this.fusionSpecies.speciesId)) &&
|
}
|
||||||
form === this.getFormKey()
|
if (excludeKnown && excludeLevelUp && excludeUsed) {
|
||||||
) {
|
// All these cases are covered at once by getLearnableLevelMoves
|
||||||
compatible = true;
|
this.getLearnableLevelMoves().forEach(m => tms.delete(m));
|
||||||
break;
|
} else {
|
||||||
}
|
// If any of these are true, but not all three, they need to be individually filtered
|
||||||
} else if (p === this.species.speciesId || (this.fusionSpecies && p === this.fusionSpecies.speciesId)) {
|
if (excludeKnown) {
|
||||||
compatible = true;
|
this.moveset.forEach(pm => tms.delete(pm.moveId));
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (reverseCompatibleTms.indexOf(moveId) > -1) {
|
if (excludeLevelUp) {
|
||||||
compatible = !compatible;
|
this.getLevelMoves(undefined, true, false, true).forEach(lm => tms.delete(lm[1]));
|
||||||
}
|
}
|
||||||
if (compatible) {
|
if (excludeUsed) {
|
||||||
this.compatibleTms.push(moveId);
|
this.usedTMs.forEach(tm => tms.delete(tm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Array.from(tms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a TM is compatible with this PlayerPokemon
|
||||||
|
* @param tm The TM move to check for
|
||||||
|
* @param excludeKnown Whether to exclude moves in its current moveset
|
||||||
|
* @returns Whether it's compatible
|
||||||
|
*/
|
||||||
|
isTmCompatible(tm: MoveId, excludeKnown = false): boolean {
|
||||||
|
return (
|
||||||
|
!(excludeKnown && this.moveset.some(pm => pm.moveId === tm)) &&
|
||||||
|
(this.species.isTmCompatible(tm, this.formIndex) ||
|
||||||
|
(!!this.fusionSpecies && this.fusionSpecies.isTmCompatible(tm, this.fusionFormIndex)))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
tryPopulateMoveset(moveset: StarterMoveset): boolean {
|
tryPopulateMoveset(moveset: StarterMoveset): boolean {
|
||||||
@ -5977,8 +5974,6 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
this.fusionAbilityIndex = 0;
|
this.fusionAbilityIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.compatibleTms.splice(0, this.compatibleTms.length);
|
|
||||||
this.generateCompatibleTms();
|
|
||||||
const updateAndResolve = () => {
|
const updateAndResolve = () => {
|
||||||
this.loadAssets().then(() => {
|
this.loadAssets().then(() => {
|
||||||
this.calculateStats();
|
this.calculateStats();
|
||||||
@ -6090,8 +6085,6 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
this.abilityIndex = abilityCount - 1;
|
this.abilityIndex = abilityCount - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.compatibleTms.splice(0, this.compatibleTms.length);
|
|
||||||
this.generateCompatibleTms();
|
|
||||||
const updateAndResolve = () => {
|
const updateAndResolve = () => {
|
||||||
this.loadAssets().then(() => {
|
this.loadAssets().then(() => {
|
||||||
this.calculateStats();
|
this.calculateStats();
|
||||||
@ -6108,11 +6101,6 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
clearFusionSpecies(): void {
|
|
||||||
super.clearFusionSpecies();
|
|
||||||
this.generateCompatibleTms();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Promise to fuse two PlayerPokemon together
|
* Returns a Promise to fuse two PlayerPokemon together
|
||||||
* @param pokemon The PlayerPokemon to fuse to this one
|
* @param pokemon The PlayerPokemon to fuse to this one
|
||||||
@ -6152,7 +6140,6 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
this.status = pokemon.status; // Inherit the other Pokemon's status
|
this.status = pokemon.status; // Inherit the other Pokemon's status
|
||||||
}
|
}
|
||||||
|
|
||||||
this.generateCompatibleTms();
|
|
||||||
this.updateInfo(true);
|
this.updateInfo(true);
|
||||||
const fusedPartyMemberIndex = globalScene.getPlayerParty().indexOf(pokemon);
|
const fusedPartyMemberIndex = globalScene.getPlayerParty().indexOf(pokemon);
|
||||||
let partyMemberIndex = globalScene.getPlayerParty().indexOf(this);
|
let partyMemberIndex = globalScene.getPlayerParty().indexOf(this);
|
||||||
|
@ -4,7 +4,7 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { EvolutionItem, pokemonEvolutions } from "#balance/pokemon-evolutions";
|
import { EvolutionItem, pokemonEvolutions } from "#balance/pokemon-evolutions";
|
||||||
import { tmPoolTiers, tmSpecies } from "#balance/tms";
|
import { tmPoolTiers } from "#balance/tms";
|
||||||
import { getBerryEffectDescription, getBerryName } from "#data/berry";
|
import { getBerryEffectDescription, getBerryName } from "#data/berry";
|
||||||
import { getDailyEventSeedLuck } from "#data/daily-run";
|
import { getDailyEventSeedLuck } from "#data/daily-run";
|
||||||
import { allMoves, modifierTypes } from "#data/data-lists";
|
import { allMoves, modifierTypes } from "#data/data-lists";
|
||||||
@ -1142,10 +1142,7 @@ export class TmModifierType extends PokemonModifierType {
|
|||||||
`tm_${PokemonType[allMoves[moveId].type].toLowerCase()}`,
|
`tm_${PokemonType[allMoves[moveId].type].toLowerCase()}`,
|
||||||
(_type, args) => new TmModifier(this, (args[0] as PlayerPokemon).id),
|
(_type, args) => new TmModifier(this, (args[0] as PlayerPokemon).id),
|
||||||
(pokemon: PlayerPokemon) => {
|
(pokemon: PlayerPokemon) => {
|
||||||
if (
|
if (!pokemon.isTmCompatible(moveId, true)) {
|
||||||
pokemon.compatibleTms.indexOf(moveId) === -1 ||
|
|
||||||
pokemon.getMoveset().filter(m => m.moveId === moveId).length
|
|
||||||
) {
|
|
||||||
return PartyUiHandler.NoEffectMessage;
|
return PartyUiHandler.NoEffectMessage;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -1158,7 +1155,7 @@ export class TmModifierType extends PokemonModifierType {
|
|||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("modifierType:ModifierType.TmModifierType.name", {
|
return i18next.t("modifierType:ModifierType.TmModifierType.name", {
|
||||||
moveId: padInt(Object.keys(tmSpecies).indexOf(this.moveId.toString()) + 1, 3),
|
moveId: padInt(Object.keys(tmPoolTiers).indexOf(this.moveId.toString()) + 1, 3),
|
||||||
moveName: allMoves[this.moveId].name,
|
moveName: allMoves[this.moveId].name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1519,12 +1516,7 @@ class TmModifierTypeGenerator extends ModifierTypeGenerator {
|
|||||||
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in MoveId) {
|
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in MoveId) {
|
||||||
return new TmModifierType(pregenArgs[0] as MoveId);
|
return new TmModifierType(pregenArgs[0] as MoveId);
|
||||||
}
|
}
|
||||||
const partyMemberCompatibleTms = party.map(p => {
|
const partyMemberCompatibleTms = party.map(p => (p as PlayerPokemon).getCompatibleTms(true, true));
|
||||||
const previousLevelMoves = p.getLearnableLevelMoves();
|
|
||||||
return (p as PlayerPokemon).compatibleTms.filter(
|
|
||||||
tm => !p.moveset.find(m => m.moveId === tm) && !previousLevelMoves.find(lm => lm === tm),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const tierUniqueCompatibleTms = partyMemberCompatibleTms
|
const tierUniqueCompatibleTms = partyMemberCompatibleTms
|
||||||
.flat()
|
.flat()
|
||||||
.filter(tm => tmPoolTiers[tm] === tier)
|
.filter(tm => tmPoolTiers[tm] === tier)
|
||||||
|
@ -127,6 +127,7 @@ export interface SessionSaveData {
|
|||||||
battleType: BattleType;
|
battleType: BattleType;
|
||||||
trainer: TrainerData;
|
trainer: TrainerData;
|
||||||
gameVersion: string;
|
gameVersion: string;
|
||||||
|
runNameText: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
challenges: ChallengeData[];
|
challenges: ChallengeData[];
|
||||||
mysteryEncounterType: MysteryEncounterType | -1; // Only defined when current wave is ME,
|
mysteryEncounterType: MysteryEncounterType | -1; // Only defined when current wave is ME,
|
||||||
@ -979,6 +980,54 @@ export class GameData {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async renameSession(slotId: number, newName: string): Promise<boolean> {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
if (slotId < 0) {
|
||||||
|
return resolve(false);
|
||||||
|
}
|
||||||
|
const sessionData: SessionSaveData | null = await this.getSession(slotId);
|
||||||
|
|
||||||
|
if (!sessionData) {
|
||||||
|
return resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newName === "") {
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionData.runNameText = newName;
|
||||||
|
const updatedDataStr = JSON.stringify(sessionData);
|
||||||
|
const encrypted = encrypt(updatedDataStr, bypassLogin);
|
||||||
|
const secretId = this.secretId;
|
||||||
|
const trainerId = this.trainerId;
|
||||||
|
|
||||||
|
if (bypassLogin) {
|
||||||
|
localStorage.setItem(
|
||||||
|
`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`,
|
||||||
|
encrypt(updatedDataStr, bypassLogin),
|
||||||
|
);
|
||||||
|
resolve(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pokerogueApi.savedata.session
|
||||||
|
.update({ slot: slotId, trainerId, secretId, clientSessionId }, encrypted)
|
||||||
|
.then(error => {
|
||||||
|
if (error) {
|
||||||
|
console.error("Failed to update session name:", error);
|
||||||
|
resolve(false);
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypted);
|
||||||
|
updateUserInfo().then(success => {
|
||||||
|
if (success !== null && !success) {
|
||||||
|
return resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
loadSession(slotId: number, sessionData?: SessionSaveData): Promise<boolean> {
|
loadSession(slotId: number, sessionData?: SessionSaveData): Promise<boolean> {
|
||||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: fix this
|
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: fix this
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
|
@ -2029,9 +2029,9 @@ class PartySlot extends Phaser.GameObjects.Container {
|
|||||||
this.slotHpText.setVisible(false);
|
this.slotHpText.setVisible(false);
|
||||||
let slotTmText: string;
|
let slotTmText: string;
|
||||||
|
|
||||||
if (this.pokemon.getMoveset().filter(m => m.moveId === tmMoveId).length > 0) {
|
if (this.pokemon.getMoveset().some(m => m.moveId === tmMoveId)) {
|
||||||
slotTmText = i18next.t("partyUiHandler:learned");
|
slotTmText = i18next.t("partyUiHandler:learned");
|
||||||
} else if (this.pokemon.compatibleTms.indexOf(tmMoveId) === -1) {
|
} else if (!this.pokemon.isTmCompatible(tmMoveId)) {
|
||||||
slotTmText = i18next.t("partyUiHandler:notAble");
|
slotTmText = i18next.t("partyUiHandler:notAble");
|
||||||
} else {
|
} else {
|
||||||
slotTmText = i18next.t("partyUiHandler:able");
|
slotTmText = i18next.t("partyUiHandler:able");
|
||||||
|
@ -16,7 +16,6 @@ import {
|
|||||||
getValueReductionCandyCounts,
|
getValueReductionCandyCounts,
|
||||||
speciesStarterCosts,
|
speciesStarterCosts,
|
||||||
} from "#balance/starters";
|
} from "#balance/starters";
|
||||||
import { speciesTmMoves } from "#balance/tms";
|
|
||||||
import { allAbilities, allMoves, allSpecies } from "#data/data-lists";
|
import { allAbilities, allMoves, allSpecies } from "#data/data-lists";
|
||||||
import { Egg, getEggTierForSpecies } from "#data/egg";
|
import { Egg, getEggTierForSpecies } from "#data/egg";
|
||||||
import { GrowthRate, getGrowthRateColor } from "#data/exp";
|
import { GrowthRate, getGrowthRateColor } from "#data/exp";
|
||||||
@ -838,10 +837,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.tmMoves =
|
this.tmMoves =
|
||||||
speciesTmMoves[species.speciesId]
|
species.getCompatibleTms(formIndex).sort((a, b) => (allMoves[a].name > allMoves[b].name ? 1 : -1)) ?? [];
|
||||||
?.filter(m => (Array.isArray(m) ? m[0] === formKey : true))
|
|
||||||
.map(m => (Array.isArray(m) ? m[1] : m))
|
|
||||||
.sort((a, b) => (allMoves[a].name > allMoves[b].name ? 1 : -1)) ?? [];
|
|
||||||
|
|
||||||
const passiveId = starterPassiveAbilities.hasOwnProperty(species.speciesId)
|
const passiveId = starterPassiveAbilities.hasOwnProperty(species.speciesId)
|
||||||
? species.speciesId
|
? species.speciesId
|
||||||
|
@ -12,7 +12,6 @@ import {
|
|||||||
POKERUS_STARTER_COUNT,
|
POKERUS_STARTER_COUNT,
|
||||||
speciesStarterCosts,
|
speciesStarterCosts,
|
||||||
} from "#balance/starters";
|
} from "#balance/starters";
|
||||||
import { speciesTmMoves } from "#balance/tms";
|
|
||||||
import { allAbilities, allMoves, allSpecies } from "#data/data-lists";
|
import { allAbilities, allMoves, allSpecies } from "#data/data-lists";
|
||||||
import type { PokemonForm, PokemonSpecies } from "#data/pokemon-species";
|
import type { PokemonForm, PokemonSpecies } from "#data/pokemon-species";
|
||||||
import { normalForm } from "#data/pokemon-species";
|
import { normalForm } from "#data/pokemon-species";
|
||||||
@ -1380,7 +1379,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
const levelMoves = pokemonSpeciesLevelMoves[species.speciesId].map(m => allMoves[m[1]].name);
|
const levelMoves = pokemonSpeciesLevelMoves[species.speciesId].map(m => allMoves[m[1]].name);
|
||||||
// This always gets egg moves from the starter
|
// This always gets egg moves from the starter
|
||||||
const eggMoves = speciesEggMoves[starterId]?.map(m => allMoves[m].name) ?? [];
|
const eggMoves = speciesEggMoves[starterId]?.map(m => allMoves[m].name) ?? [];
|
||||||
const tmMoves = speciesTmMoves[species.speciesId]?.map(m => allMoves[Array.isArray(m) ? m[1] : m].name) ?? [];
|
const tmMoves = species.getCompatibleTms().map(m => allMoves[m].name) ?? [];
|
||||||
const selectedMove1 = this.filterText.getValue(FilterTextRow.MOVE_1);
|
const selectedMove1 = this.filterText.getValue(FilterTextRow.MOVE_1);
|
||||||
const selectedMove2 = this.filterText.getValue(FilterTextRow.MOVE_2);
|
const selectedMove2 = this.filterText.getValue(FilterTextRow.MOVE_2);
|
||||||
|
|
||||||
|
54
src/ui/rename-run-ui-handler.ts
Normal file
54
src/ui/rename-run-ui-handler.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import i18next from "i18next";
|
||||||
|
import type { InputFieldConfig } from "./form-modal-ui-handler";
|
||||||
|
import { FormModalUiHandler } from "./form-modal-ui-handler";
|
||||||
|
import type { ModalConfig } from "./modal-ui-handler";
|
||||||
|
|
||||||
|
export class RenameRunFormUiHandler extends FormModalUiHandler {
|
||||||
|
getModalTitle(_config?: ModalConfig): string {
|
||||||
|
return i18next.t("menu:renamerun");
|
||||||
|
}
|
||||||
|
|
||||||
|
getWidth(_config?: ModalConfig): number {
|
||||||
|
return 160;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMargin(_config?: ModalConfig): [number, number, number, number] {
|
||||||
|
return [0, 0, 48, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
getButtonLabels(_config?: ModalConfig): string[] {
|
||||||
|
return [i18next.t("menu:rename"), i18next.t("menu:cancel")];
|
||||||
|
}
|
||||||
|
|
||||||
|
getReadableErrorMessage(error: string): string {
|
||||||
|
const colonIndex = error?.indexOf(":");
|
||||||
|
if (colonIndex > 0) {
|
||||||
|
error = error.slice(0, colonIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getReadableErrorMessage(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
override getInputFieldConfigs(): InputFieldConfig[] {
|
||||||
|
return [{ label: i18next.t("menu:runName") }];
|
||||||
|
}
|
||||||
|
|
||||||
|
show(args: any[]): boolean {
|
||||||
|
if (!super.show(args)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.inputs?.length) {
|
||||||
|
this.inputs.forEach(input => {
|
||||||
|
input.text = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const config = args[0] as ModalConfig;
|
||||||
|
this.submitAction = _ => {
|
||||||
|
this.sanitizeInputs();
|
||||||
|
const sanitizedName = btoa(encodeURIComponent(this.inputs[0].text));
|
||||||
|
config.buttonActions[0](sanitizedName);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -207,6 +207,10 @@ export class RunInfoUiHandler extends UiHandler {
|
|||||||
headerText.setOrigin(0, 0);
|
headerText.setOrigin(0, 0);
|
||||||
headerText.setPositionRelative(headerBg, 8, 4);
|
headerText.setPositionRelative(headerBg, 8, 4);
|
||||||
this.runContainer.add(headerText);
|
this.runContainer.add(headerText);
|
||||||
|
const runName = addTextObject(0, 0, this.runInfo.runNameText, TextStyle.WINDOW);
|
||||||
|
runName.setOrigin(0, 0);
|
||||||
|
runName.setPositionRelative(headerBg, 60, 4);
|
||||||
|
this.runContainer.add(runName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { GameMode } from "#app/game-mode";
|
import { GameMode } from "#app/game-mode";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { Button } from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
|
import { GameModes } from "#enums/game-modes";
|
||||||
import { TextStyle } from "#enums/text-style";
|
import { TextStyle } from "#enums/text-style";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
import { UiMode } from "#enums/ui-mode";
|
||||||
// biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts`
|
// biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts`
|
||||||
import * as Modifier from "#modifiers/modifier";
|
import * as Modifier from "#modifiers/modifier";
|
||||||
import type { SessionSaveData } from "#system/game-data";
|
import type { SessionSaveData } from "#system/game-data";
|
||||||
import type { PokemonData } from "#system/pokemon-data";
|
import type { PokemonData } from "#system/pokemon-data";
|
||||||
|
import type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler";
|
||||||
import { MessageUiHandler } from "#ui/message-ui-handler";
|
import { MessageUiHandler } from "#ui/message-ui-handler";
|
||||||
import { RunDisplayMode } from "#ui/run-info-ui-handler";
|
import { RunDisplayMode } from "#ui/run-info-ui-handler";
|
||||||
import { addTextObject } from "#ui/text";
|
import { addTextObject } from "#ui/text";
|
||||||
@ -15,7 +17,7 @@ import { fixedInt, formatLargeNumber, getPlayTimeString, isNullOrUndefined } fro
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
||||||
const SESSION_SLOTS_COUNT = 5;
|
const SESSION_SLOTS_COUNT = 5;
|
||||||
const SLOTS_ON_SCREEN = 3;
|
const SLOTS_ON_SCREEN = 2;
|
||||||
|
|
||||||
export enum SaveSlotUiMode {
|
export enum SaveSlotUiMode {
|
||||||
LOAD,
|
LOAD,
|
||||||
@ -33,6 +35,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
|
|||||||
|
|
||||||
private uiMode: SaveSlotUiMode;
|
private uiMode: SaveSlotUiMode;
|
||||||
private saveSlotSelectCallback: SaveSlotSelectCallback | null;
|
private saveSlotSelectCallback: SaveSlotSelectCallback | null;
|
||||||
|
protected manageDataConfig: OptionSelectConfig;
|
||||||
|
|
||||||
private scrollCursor = 0;
|
private scrollCursor = 0;
|
||||||
|
|
||||||
@ -101,6 +104,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
|
|||||||
|
|
||||||
processInput(button: Button): boolean {
|
processInput(button: Button): boolean {
|
||||||
const ui = this.getUi();
|
const ui = this.getUi();
|
||||||
|
const manageDataOptions: any[] = [];
|
||||||
|
|
||||||
let success = false;
|
let success = false;
|
||||||
let error = false;
|
let error = false;
|
||||||
@ -109,14 +113,115 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
|
|||||||
const originalCallback = this.saveSlotSelectCallback;
|
const originalCallback = this.saveSlotSelectCallback;
|
||||||
if (button === Button.ACTION) {
|
if (button === Button.ACTION) {
|
||||||
const cursor = this.cursor + this.scrollCursor;
|
const cursor = this.cursor + this.scrollCursor;
|
||||||
if (this.uiMode === SaveSlotUiMode.LOAD && !this.sessionSlots[cursor].hasData) {
|
const sessionSlot = this.sessionSlots[cursor];
|
||||||
|
if (this.uiMode === SaveSlotUiMode.LOAD && !sessionSlot.hasData) {
|
||||||
error = true;
|
error = true;
|
||||||
} else {
|
} else {
|
||||||
switch (this.uiMode) {
|
switch (this.uiMode) {
|
||||||
case SaveSlotUiMode.LOAD:
|
case SaveSlotUiMode.LOAD:
|
||||||
this.saveSlotSelectCallback = null;
|
if (!sessionSlot.malformed) {
|
||||||
originalCallback?.(cursor);
|
manageDataOptions.push({
|
||||||
|
label: i18next.t("menu:loadGame"),
|
||||||
|
handler: () => {
|
||||||
|
globalScene.ui.revertMode();
|
||||||
|
originalCallback?.(cursor);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
keepOpen: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
manageDataOptions.push({
|
||||||
|
label: i18next.t("saveSlotSelectUiHandler:renameRun"),
|
||||||
|
handler: () => {
|
||||||
|
globalScene.ui.revertMode();
|
||||||
|
ui.setOverlayMode(
|
||||||
|
UiMode.RENAME_RUN,
|
||||||
|
{
|
||||||
|
buttonActions: [
|
||||||
|
(sanitizedName: string) => {
|
||||||
|
const name = decodeURIComponent(atob(sanitizedName));
|
||||||
|
globalScene.gameData.renameSession(cursor, name).then(response => {
|
||||||
|
if (response[0] === false) {
|
||||||
|
globalScene.reset(true);
|
||||||
|
} else {
|
||||||
|
this.clearSessionSlots();
|
||||||
|
this.cursorObj = null;
|
||||||
|
this.populateSessionSlots();
|
||||||
|
this.setScrollCursor(0);
|
||||||
|
this.setCursor(0);
|
||||||
|
ui.revertMode();
|
||||||
|
ui.showText("", 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
ui.revertMode();
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.manageDataConfig = {
|
||||||
|
xOffset: 0,
|
||||||
|
yOffset: 48,
|
||||||
|
options: manageDataOptions,
|
||||||
|
maxOptions: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
manageDataOptions.push({
|
||||||
|
label: i18next.t("saveSlotSelectUiHandler:deleteRun"),
|
||||||
|
handler: () => {
|
||||||
|
globalScene.ui.revertMode();
|
||||||
|
ui.showText(i18next.t("saveSlotSelectUiHandler:deleteData"), null, () => {
|
||||||
|
ui.setOverlayMode(
|
||||||
|
UiMode.CONFIRM,
|
||||||
|
() => {
|
||||||
|
globalScene.gameData.tryClearSession(cursor).then(response => {
|
||||||
|
if (response[0] === false) {
|
||||||
|
globalScene.reset(true);
|
||||||
|
} else {
|
||||||
|
this.clearSessionSlots();
|
||||||
|
this.cursorObj = null;
|
||||||
|
this.populateSessionSlots();
|
||||||
|
this.setScrollCursor(0);
|
||||||
|
this.setCursor(0);
|
||||||
|
ui.revertMode();
|
||||||
|
ui.showText("", 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
ui.revertMode();
|
||||||
|
ui.showText("", 0);
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
19,
|
||||||
|
import.meta.env.DEV ? 300 : 2000,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
keepOpen: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
manageDataOptions.push({
|
||||||
|
label: i18next.t("menuUiHandler:cancel"),
|
||||||
|
handler: () => {
|
||||||
|
globalScene.ui.revertMode();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
keepOpen: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.setOverlayMode(UiMode.MENU_OPTION_SELECT, this.manageDataConfig);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SaveSlotUiMode.SAVE: {
|
case SaveSlotUiMode.SAVE: {
|
||||||
const saveAndCallback = () => {
|
const saveAndCallback = () => {
|
||||||
const originalCallback = this.saveSlotSelectCallback;
|
const originalCallback = this.saveSlotSelectCallback;
|
||||||
@ -161,6 +266,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.saveSlotSelectCallback = null;
|
this.saveSlotSelectCallback = null;
|
||||||
|
ui.showText("", 0);
|
||||||
originalCallback?.(-1);
|
originalCallback?.(-1);
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
@ -267,33 +373,34 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
|
|||||||
this.cursorObj = globalScene.add.container(0, 0);
|
this.cursorObj = globalScene.add.container(0, 0);
|
||||||
const cursorBox = globalScene.add.nineslice(
|
const cursorBox = globalScene.add.nineslice(
|
||||||
0,
|
0,
|
||||||
0,
|
15,
|
||||||
"select_cursor_highlight_thick",
|
"select_cursor_highlight_thick",
|
||||||
undefined,
|
undefined,
|
||||||
296,
|
294,
|
||||||
44,
|
this.sessionSlots[prevSlotIndex ?? 0]?.saveData?.runNameText ? 50 : 60,
|
||||||
6,
|
6,
|
||||||
6,
|
6,
|
||||||
6,
|
6,
|
||||||
6,
|
6,
|
||||||
);
|
);
|
||||||
const rightArrow = globalScene.add.image(0, 0, "cursor");
|
const rightArrow = globalScene.add.image(0, 0, "cursor");
|
||||||
rightArrow.setPosition(160, 0);
|
rightArrow.setPosition(160, 15);
|
||||||
rightArrow.setName("rightArrow");
|
rightArrow.setName("rightArrow");
|
||||||
this.cursorObj.add([cursorBox, rightArrow]);
|
this.cursorObj.add([cursorBox, rightArrow]);
|
||||||
this.sessionSlotsContainer.add(this.cursorObj);
|
this.sessionSlotsContainer.add(this.cursorObj);
|
||||||
}
|
}
|
||||||
const cursorPosition = cursor + this.scrollCursor;
|
const cursorPosition = cursor + this.scrollCursor;
|
||||||
const cursorIncrement = cursorPosition * 56;
|
const cursorIncrement = cursorPosition * 76;
|
||||||
if (this.sessionSlots[cursorPosition] && this.cursorObj) {
|
if (this.sessionSlots[cursorPosition] && this.cursorObj) {
|
||||||
const hasData = this.sessionSlots[cursorPosition].hasData;
|
const session = this.sessionSlots[cursorPosition];
|
||||||
|
const hasData = session.hasData && !session.malformed;
|
||||||
// If the session slot lacks session data, it does not move from its default, central position.
|
// If the session slot lacks session data, it does not move from its default, central position.
|
||||||
// Only session slots with session data will move leftwards and have a visible arrow.
|
// Only session slots with session data will move leftwards and have a visible arrow.
|
||||||
if (!hasData) {
|
if (!hasData) {
|
||||||
this.cursorObj.setPosition(151, 26 + cursorIncrement);
|
this.cursorObj.setPosition(151, 20 + cursorIncrement);
|
||||||
this.sessionSlots[cursorPosition].setPosition(0, cursorIncrement);
|
this.sessionSlots[cursorPosition].setPosition(0, cursorIncrement);
|
||||||
} else {
|
} else {
|
||||||
this.cursorObj.setPosition(145, 26 + cursorIncrement);
|
this.cursorObj.setPosition(145, 20 + cursorIncrement);
|
||||||
this.sessionSlots[cursorPosition].setPosition(-6, cursorIncrement);
|
this.sessionSlots[cursorPosition].setPosition(-6, cursorIncrement);
|
||||||
}
|
}
|
||||||
this.setArrowVisibility(hasData);
|
this.setArrowVisibility(hasData);
|
||||||
@ -311,7 +418,8 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
|
|||||||
revertSessionSlot(slotIndex: number): void {
|
revertSessionSlot(slotIndex: number): void {
|
||||||
const sessionSlot = this.sessionSlots[slotIndex];
|
const sessionSlot = this.sessionSlots[slotIndex];
|
||||||
if (sessionSlot) {
|
if (sessionSlot) {
|
||||||
sessionSlot.setPosition(0, slotIndex * 56);
|
const valueHeight = 76;
|
||||||
|
sessionSlot.setPosition(0, slotIndex * valueHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,7 +448,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
|
|||||||
this.setCursor(this.cursor, prevSlotIndex);
|
this.setCursor(this.cursor, prevSlotIndex);
|
||||||
globalScene.tweens.add({
|
globalScene.tweens.add({
|
||||||
targets: this.sessionSlotsContainer,
|
targets: this.sessionSlotsContainer,
|
||||||
y: this.sessionSlotsContainerInitialY - 56 * scrollCursor,
|
y: this.sessionSlotsContainerInitialY - 76 * scrollCursor,
|
||||||
duration: fixedInt(325),
|
duration: fixedInt(325),
|
||||||
ease: "Sine.easeInOut",
|
ease: "Sine.easeInOut",
|
||||||
});
|
});
|
||||||
@ -374,12 +482,14 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
|
|||||||
class SessionSlot extends Phaser.GameObjects.Container {
|
class SessionSlot extends Phaser.GameObjects.Container {
|
||||||
public slotId: number;
|
public slotId: number;
|
||||||
public hasData: boolean;
|
public hasData: boolean;
|
||||||
|
/** Indicates the save slot ran into an error while being loaded */
|
||||||
|
public malformed: boolean;
|
||||||
|
private slotWindow: Phaser.GameObjects.NineSlice;
|
||||||
private loadingLabel: Phaser.GameObjects.Text;
|
private loadingLabel: Phaser.GameObjects.Text;
|
||||||
|
|
||||||
public saveData: SessionSaveData;
|
public saveData: SessionSaveData;
|
||||||
|
|
||||||
constructor(slotId: number) {
|
constructor(slotId: number) {
|
||||||
super(globalScene, 0, slotId * 56);
|
super(globalScene, 0, slotId * 76);
|
||||||
|
|
||||||
this.slotId = slotId;
|
this.slotId = slotId;
|
||||||
|
|
||||||
@ -387,32 +497,89 @@ class SessionSlot extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const slotWindow = addWindow(0, 0, 304, 52);
|
this.slotWindow = addWindow(0, 0, 304, 70);
|
||||||
this.add(slotWindow);
|
this.add(this.slotWindow);
|
||||||
|
|
||||||
this.loadingLabel = addTextObject(152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW);
|
this.loadingLabel = addTextObject(152, 33, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW);
|
||||||
this.loadingLabel.setOrigin(0.5, 0.5);
|
this.loadingLabel.setOrigin(0.5, 0.5);
|
||||||
this.add(this.loadingLabel);
|
this.add(this.loadingLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a name for sessions that don't have a name yet.
|
||||||
|
* @param data - The {@linkcode SessionSaveData} being checked
|
||||||
|
* @returns The default name for the given data.
|
||||||
|
*/
|
||||||
|
decideFallback(data: SessionSaveData): string {
|
||||||
|
let fallbackName = `${GameMode.getModeName(data.gameMode)}`;
|
||||||
|
switch (data.gameMode) {
|
||||||
|
case GameModes.CLASSIC:
|
||||||
|
fallbackName += ` (${globalScene.gameData.gameStats.classicSessionsPlayed + 1})`;
|
||||||
|
break;
|
||||||
|
case GameModes.ENDLESS:
|
||||||
|
case GameModes.SPLICED_ENDLESS:
|
||||||
|
fallbackName += ` (${globalScene.gameData.gameStats.endlessSessionsPlayed + 1})`;
|
||||||
|
break;
|
||||||
|
case GameModes.DAILY: {
|
||||||
|
const runDay = new Date(data.timestamp).toLocaleDateString();
|
||||||
|
fallbackName += ` (${runDay})`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GameModes.CHALLENGE: {
|
||||||
|
const activeChallenges = data.challenges.filter(c => c.value !== 0);
|
||||||
|
if (activeChallenges.length === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fallbackName = "";
|
||||||
|
for (const challenge of activeChallenges.slice(0, 3)) {
|
||||||
|
if (fallbackName !== "") {
|
||||||
|
fallbackName += ", ";
|
||||||
|
}
|
||||||
|
fallbackName += challenge.toChallenge().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeChallenges.length > 3) {
|
||||||
|
fallbackName += ", ...";
|
||||||
|
} else if (fallbackName === "") {
|
||||||
|
// Something went wrong when retrieving the names of the active challenges,
|
||||||
|
// so fall back to just naming the run "Challenge"
|
||||||
|
fallbackName = `${GameMode.getModeName(data.gameMode)}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallbackName;
|
||||||
|
}
|
||||||
|
|
||||||
async setupWithData(data: SessionSaveData) {
|
async setupWithData(data: SessionSaveData) {
|
||||||
|
const hasName = data?.runNameText;
|
||||||
this.remove(this.loadingLabel, true);
|
this.remove(this.loadingLabel, true);
|
||||||
|
if (hasName) {
|
||||||
|
const nameLabel = addTextObject(8, 5, data.runNameText, TextStyle.WINDOW);
|
||||||
|
this.add(nameLabel);
|
||||||
|
} else {
|
||||||
|
const fallbackName = this.decideFallback(data);
|
||||||
|
await globalScene.gameData.renameSession(this.slotId, fallbackName);
|
||||||
|
const nameLabel = addTextObject(8, 5, fallbackName, TextStyle.WINDOW);
|
||||||
|
this.add(nameLabel);
|
||||||
|
}
|
||||||
|
|
||||||
const gameModeLabel = addTextObject(
|
const gameModeLabel = addTextObject(
|
||||||
8,
|
8,
|
||||||
5,
|
19,
|
||||||
`${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unknown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}`,
|
`${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unknown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}`,
|
||||||
TextStyle.WINDOW,
|
TextStyle.WINDOW,
|
||||||
);
|
);
|
||||||
this.add(gameModeLabel);
|
this.add(gameModeLabel);
|
||||||
|
|
||||||
const timestampLabel = addTextObject(8, 19, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW);
|
const timestampLabel = addTextObject(8, 33, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW);
|
||||||
this.add(timestampLabel);
|
this.add(timestampLabel);
|
||||||
|
|
||||||
const playTimeLabel = addTextObject(8, 33, getPlayTimeString(data.playTime), TextStyle.WINDOW);
|
const playTimeLabel = addTextObject(8, 47, getPlayTimeString(data.playTime), TextStyle.WINDOW);
|
||||||
this.add(playTimeLabel);
|
this.add(playTimeLabel);
|
||||||
|
|
||||||
const pokemonIconsContainer = globalScene.add.container(144, 4);
|
const pokemonIconsContainer = globalScene.add.container(144, 16);
|
||||||
data.party.forEach((p: PokemonData, i: number) => {
|
data.party.forEach((p: PokemonData, i: number) => {
|
||||||
const iconContainer = globalScene.add.container(26 * i, 0);
|
const iconContainer = globalScene.add.container(26 * i, 0);
|
||||||
iconContainer.setScale(0.75);
|
iconContainer.setScale(0.75);
|
||||||
@ -427,13 +594,9 @@ class SessionSlot extends Phaser.GameObjects.Container {
|
|||||||
TextStyle.PARTY,
|
TextStyle.PARTY,
|
||||||
{ fontSize: "54px", color: "#f8f8f8" },
|
{ fontSize: "54px", color: "#f8f8f8" },
|
||||||
);
|
);
|
||||||
text.setShadow(0, 0, undefined);
|
text.setShadow(0, 0, undefined).setStroke("#424242", 14).setOrigin(1, 0);
|
||||||
text.setStroke("#424242", 14);
|
|
||||||
text.setOrigin(1, 0);
|
|
||||||
|
|
||||||
iconContainer.add(icon);
|
|
||||||
iconContainer.add(text);
|
|
||||||
|
|
||||||
|
iconContainer.add([icon, text]);
|
||||||
pokemonIconsContainer.add(iconContainer);
|
pokemonIconsContainer.add(iconContainer);
|
||||||
|
|
||||||
pokemon.destroy();
|
pokemon.destroy();
|
||||||
@ -441,7 +604,7 @@ class SessionSlot extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
this.add(pokemonIconsContainer);
|
this.add(pokemonIconsContainer);
|
||||||
|
|
||||||
const modifierIconsContainer = globalScene.add.container(148, 30);
|
const modifierIconsContainer = globalScene.add.container(148, 38);
|
||||||
modifierIconsContainer.setScale(0.5);
|
modifierIconsContainer.setScale(0.5);
|
||||||
let visibleModifierIndex = 0;
|
let visibleModifierIndex = 0;
|
||||||
for (const m of data.modifiers) {
|
for (const m of data.modifiers) {
|
||||||
@ -464,22 +627,33 @@ class SessionSlot extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
load(): Promise<boolean> {
|
load(): Promise<boolean> {
|
||||||
return new Promise<boolean>(resolve => {
|
return new Promise<boolean>(resolve => {
|
||||||
globalScene.gameData.getSession(this.slotId).then(async sessionData => {
|
globalScene.gameData
|
||||||
// Ignore the results if the view was exited
|
.getSession(this.slotId)
|
||||||
if (!this.active) {
|
.then(async sessionData => {
|
||||||
return;
|
// Ignore the results if the view was exited
|
||||||
}
|
if (!this.active) {
|
||||||
if (!sessionData) {
|
return;
|
||||||
this.hasData = false;
|
}
|
||||||
this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty"));
|
this.hasData = !!sessionData;
|
||||||
resolve(false);
|
if (!sessionData) {
|
||||||
return;
|
this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty"));
|
||||||
}
|
resolve(false);
|
||||||
this.hasData = true;
|
return;
|
||||||
this.saveData = sessionData;
|
}
|
||||||
await this.setupWithData(sessionData);
|
this.saveData = sessionData;
|
||||||
resolve(true);
|
this.setupWithData(sessionData);
|
||||||
});
|
resolve(true);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
if (!this.active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.warn(`Failed to load session slot #${this.slotId}:`, e);
|
||||||
|
this.loadingLabel.setText(i18next.t("menu:failedToLoadSession"));
|
||||||
|
this.hasData = true;
|
||||||
|
this.malformed = true;
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,7 @@ import { addWindow } from "#ui/ui-theme";
|
|||||||
import { UnavailableModalUiHandler } from "#ui/unavailable-modal-ui-handler";
|
import { UnavailableModalUiHandler } from "#ui/unavailable-modal-ui-handler";
|
||||||
import { executeIf } from "#utils/common";
|
import { executeIf } from "#utils/common";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { RenameRunFormUiHandler } from "./rename-run-ui-handler";
|
||||||
|
|
||||||
const transitionModes = [
|
const transitionModes = [
|
||||||
UiMode.SAVE_SLOT,
|
UiMode.SAVE_SLOT,
|
||||||
@ -98,6 +99,7 @@ const noTransitionModes = [
|
|||||||
UiMode.SESSION_RELOAD,
|
UiMode.SESSION_RELOAD,
|
||||||
UiMode.UNAVAILABLE,
|
UiMode.UNAVAILABLE,
|
||||||
UiMode.RENAME_POKEMON,
|
UiMode.RENAME_POKEMON,
|
||||||
|
UiMode.RENAME_RUN,
|
||||||
UiMode.TEST_DIALOGUE,
|
UiMode.TEST_DIALOGUE,
|
||||||
UiMode.AUTO_COMPLETE,
|
UiMode.AUTO_COMPLETE,
|
||||||
UiMode.ADMIN,
|
UiMode.ADMIN,
|
||||||
@ -168,6 +170,7 @@ export class UI extends Phaser.GameObjects.Container {
|
|||||||
new UnavailableModalUiHandler(),
|
new UnavailableModalUiHandler(),
|
||||||
new GameChallengesUiHandler(),
|
new GameChallengesUiHandler(),
|
||||||
new RenameFormUiHandler(),
|
new RenameFormUiHandler(),
|
||||||
|
new RenameRunFormUiHandler(),
|
||||||
new RunHistoryUiHandler(),
|
new RunHistoryUiHandler(),
|
||||||
new RunInfoUiHandler(),
|
new RunInfoUiHandler(),
|
||||||
new TestDialogueUiHandler(UiMode.TEST_DIALOGUE),
|
new TestDialogueUiHandler(UiMode.TEST_DIALOGUE),
|
||||||
|
@ -80,8 +80,8 @@ describe("Spec - Pokemon", () => {
|
|||||||
|
|
||||||
const fanRotom = game.field.getPlayerPokemon();
|
const fanRotom = game.field.getPlayerPokemon();
|
||||||
|
|
||||||
expect(fanRotom.compatibleTms).not.toContain(MoveId.BLIZZARD);
|
expect(fanRotom.isTmCompatible(MoveId.BLIZZARD)).toBe(false);
|
||||||
expect(fanRotom.compatibleTms).toContain(MoveId.AIR_SLASH);
|
expect(fanRotom.isTmCompatible(MoveId.AIR_SLASH)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Get correct fusion type", () => {
|
describe("Get correct fusion type", () => {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
|
||||||
import { Status } from "#data/status-effect";
|
import { Status } from "#data/status-effect";
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { BattleType } from "#enums/battle-type";
|
import { BattleType } from "#enums/battle-type";
|
||||||
@ -179,18 +178,13 @@ describe("Moves - Whirlwind", () => {
|
|||||||
const eligibleEnemy = enemyParty.filter(p => p.hp > 0 && p.isAllowedInBattle());
|
const eligibleEnemy = enemyParty.filter(p => p.hp > 0 && p.isAllowedInBattle());
|
||||||
expect(eligibleEnemy.length).toBe(1);
|
expect(eligibleEnemy.length).toBe(1);
|
||||||
|
|
||||||
// Spy on the queueMessage function
|
|
||||||
const queueSpy = vi.spyOn(globalScene.phaseManager, "queueMessage");
|
|
||||||
|
|
||||||
// Player uses Whirlwind; opponent uses Splash
|
// Player uses Whirlwind; opponent uses Splash
|
||||||
game.move.select(MoveId.WHIRLWIND);
|
game.move.select(MoveId.WHIRLWIND);
|
||||||
await game.move.selectEnemyMove(MoveId.SPLASH);
|
await game.move.selectEnemyMove(MoveId.SPLASH);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
// Verify that the failure message is displayed for Whirlwind
|
const player = game.field.getPlayerPokemon();
|
||||||
expect(queueSpy).toHaveBeenCalledWith(expect.stringContaining("But it failed"));
|
expect(player).toHaveUsedMove({ move: MoveId.WHIRLWIND, result: MoveResult.FAIL });
|
||||||
// Verify the opponent's Splash message
|
|
||||||
expect(queueSpy).toHaveBeenCalledWith(expect.stringContaining("But nothing happened!"));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not pull in the other trainer's pokemon in a partner trainer battle", async () => {
|
it("should not pull in the other trainer's pokemon in a partner trainer battle", async () => {
|
||||||
|
82
test/system/rename-run.test.ts
Normal file
82
test/system/rename-run.test.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import * as account from "#app/account";
|
||||||
|
import * as bypassLoginModule from "#app/global-vars/bypass-login";
|
||||||
|
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
|
||||||
|
import type { SessionSaveData } from "#app/system/game-data";
|
||||||
|
import { AbilityId } from "#enums/ability-id";
|
||||||
|
import { MoveId } from "#enums/move-id";
|
||||||
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("System - Rename Run", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.moveset([MoveId.SPLASH])
|
||||||
|
.battleStyle("single")
|
||||||
|
.enemyAbility(AbilityId.BALL_FETCH)
|
||||||
|
.enemyMoveset(MoveId.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("renameSession", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.spyOn(bypassLoginModule, "bypassLogin", "get").mockReturnValue(false);
|
||||||
|
vi.spyOn(account, "updateUserInfo").mockImplementation(async () => [true, 1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if slotId < 0", async () => {
|
||||||
|
const result = await game.scene.gameData.renameSession(-1, "Named Run");
|
||||||
|
|
||||||
|
expect(result).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if getSession returns null", async () => {
|
||||||
|
vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue(null as unknown as SessionSaveData);
|
||||||
|
|
||||||
|
const result = await game.scene.gameData.renameSession(-1, "Named Run");
|
||||||
|
|
||||||
|
expect(result).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true if bypassLogin is true", async () => {
|
||||||
|
vi.spyOn(bypassLoginModule, "bypassLogin", "get").mockReturnValue(true);
|
||||||
|
vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue({} as SessionSaveData);
|
||||||
|
|
||||||
|
const result = await game.scene.gameData.renameSession(0, "Named Run");
|
||||||
|
|
||||||
|
expect(result).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if api returns error", async () => {
|
||||||
|
vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue({} as SessionSaveData);
|
||||||
|
vi.spyOn(pokerogueApi.savedata.session, "update").mockResolvedValue("Unknown Error!");
|
||||||
|
|
||||||
|
const result = await game.scene.gameData.renameSession(0, "Named Run");
|
||||||
|
|
||||||
|
expect(result).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true if api is succesfull", async () => {
|
||||||
|
vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue({} as SessionSaveData);
|
||||||
|
vi.spyOn(pokerogueApi.savedata.session, "update").mockResolvedValue("");
|
||||||
|
|
||||||
|
const result = await game.scene.gameData.renameSession(0, "Named Run");
|
||||||
|
|
||||||
|
expect(result).toEqual(true);
|
||||||
|
expect(account.updateUserInfo).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user