mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-19 22:09:27 +02:00
Compare commits
13 Commits
f027967a6e
...
fbefde83c4
Author | SHA1 | Date | |
---|---|---|---|
|
fbefde83c4 | ||
|
b2990aaa15 | ||
|
ee4950633e | ||
|
30058ed70e | ||
|
140e4ab142 | ||
|
76d8357d0b | ||
|
f42237d415 | ||
|
b44f0a4176 | ||
|
5a445e3633 | ||
|
c18c2e0dce | ||
|
554b154e8b | ||
|
028bc1ed1a | ||
|
c657e61312 |
@ -81,7 +81,7 @@ For example, here is how you could test a scenario where the player Pokemon has
|
||||
```typescript
|
||||
const overrides = {
|
||||
ABILITY_OVERRIDE: AbilityId.DROUGHT,
|
||||
OPP_MOVESET_OVERRIDE: MoveId.WATER_GUN,
|
||||
ENEMY_MOVESET_OVERRIDE: MoveId.WATER_GUN,
|
||||
} satisfies Partial<InstanceType<typeof DefaultOverrides>>;
|
||||
```
|
||||
|
||||
|
@ -29,6 +29,7 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.0",
|
||||
"@ls-lint/ls-lint": "2.3.1",
|
||||
"@types/crypto-js": "^4.2.0",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^22.16.5",
|
||||
"@vitest/coverage-istanbul": "^3.2.4",
|
||||
|
@ -48,6 +48,9 @@ importers:
|
||||
'@ls-lint/ls-lint':
|
||||
specifier: 2.3.1
|
||||
version: 2.3.1
|
||||
'@types/crypto-js':
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.2
|
||||
'@types/jsdom':
|
||||
specifier: ^21.1.7
|
||||
version: 21.1.7
|
||||
@ -718,6 +721,9 @@ packages:
|
||||
'@types/cookie@0.6.0':
|
||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||
|
||||
'@types/crypto-js@4.2.2':
|
||||
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
|
||||
|
||||
'@types/deep-eql@4.0.2':
|
||||
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||
|
||||
@ -2525,6 +2531,8 @@ snapshots:
|
||||
|
||||
'@types/cookie@0.6.0': {}
|
||||
|
||||
'@types/crypto-js@4.2.2': {}
|
||||
|
||||
'@types/deep-eql@4.0.2': {}
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
BIN
public/images/ui/champion_ribbon_emerald.png
Normal file
BIN
public/images/ui/champion_ribbon_emerald.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 225 B |
BIN
public/images/ui/legacy/champion_ribbon_emerald.png
Normal file
BIN
public/images/ui/legacy/champion_ribbon_emerald.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 225 B |
Binary file not shown.
Before Width: | Height: | Size: 837 B After Width: | Height: | Size: 799 B |
146
public/images/ui/party_slot_main_short.json
Normal file
146
public/images/ui/party_slot_main_short.json
Normal file
@ -0,0 +1,146 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "party_slot_main_short.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 110,
|
||||
"h": 294
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "party_slot_main_short",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "party_slot_main_short_sel",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 41,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "party_slot_main_short_fnt",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 82,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "party_slot_main_short_fnt_sel",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 123,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "party_slot_main_short_swap",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 164,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "party_slot_main_short_swap_sel",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 205,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:29685f2f538901cf5bf7f0ed2ea867c3:a080ea6c8cccd1e03244214053e79796:565f7afc5ca419b6ba8dbce51ea30818$"
|
||||
}
|
||||
}
|
BIN
public/images/ui/party_slot_main_short.png
Normal file
BIN
public/images/ui/party_slot_main_short.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
@ -1,3 +1,5 @@
|
||||
import type { RibbonData } from "#system/ribbons/ribbon-data";
|
||||
|
||||
export interface DexData {
|
||||
[key: number]: DexEntry;
|
||||
}
|
||||
@ -10,4 +12,5 @@ export interface DexEntry {
|
||||
caughtCount: number;
|
||||
hatchedCount: number;
|
||||
ivs: number[];
|
||||
ribbons: RibbonData;
|
||||
}
|
||||
|
@ -103,3 +103,12 @@ export type CoerceNullPropertiesToUndefined<T extends object> = {
|
||||
* @typeParam T - The type to render partial
|
||||
*/
|
||||
export type AtLeastOne<T extends object> = Partial<T> & ObjectValues<{ [K in keyof T]: Pick<Required<T>, K> }>;
|
||||
|
||||
/** Type helper that adds a brand to a type, used for nominal typing.
|
||||
*
|
||||
* @remarks
|
||||
* Brands should be either a string or unique symbol. This prevents overlap with other types.
|
||||
*/
|
||||
export declare class Brander<B> {
|
||||
private __brand: B;
|
||||
}
|
||||
|
@ -17,45 +17,42 @@ export function initLoggedInUser(): void {
|
||||
};
|
||||
}
|
||||
|
||||
export function updateUserInfo(): Promise<[boolean, number]> {
|
||||
return new Promise<[boolean, number]>(resolve => {
|
||||
if (bypassLogin) {
|
||||
loggedInUser = {
|
||||
username: "Guest",
|
||||
lastSessionSlot: -1,
|
||||
discordId: "",
|
||||
googleId: "",
|
||||
hasAdminRole: false,
|
||||
};
|
||||
let lastSessionSlot = -1;
|
||||
for (let s = 0; s < 5; s++) {
|
||||
if (localStorage.getItem(`sessionData${s ? s : ""}_${loggedInUser.username}`)) {
|
||||
lastSessionSlot = s;
|
||||
break;
|
||||
}
|
||||
export async function updateUserInfo(): Promise<[boolean, number]> {
|
||||
if (bypassLogin) {
|
||||
loggedInUser = {
|
||||
username: "Guest",
|
||||
lastSessionSlot: -1,
|
||||
discordId: "",
|
||||
googleId: "",
|
||||
hasAdminRole: false,
|
||||
};
|
||||
let lastSessionSlot = -1;
|
||||
for (let s = 0; s < 5; s++) {
|
||||
if (localStorage.getItem(`sessionData${s ? s : ""}_${loggedInUser.username}`)) {
|
||||
lastSessionSlot = s;
|
||||
break;
|
||||
}
|
||||
loggedInUser.lastSessionSlot = lastSessionSlot;
|
||||
// Migrate old data from before the username was appended
|
||||
["data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4"].map(d => {
|
||||
const lsItem = localStorage.getItem(d);
|
||||
if (lsItem && !!loggedInUser?.username) {
|
||||
const lsUserItem = localStorage.getItem(`${d}_${loggedInUser.username}`);
|
||||
if (lsUserItem) {
|
||||
localStorage.setItem(`${d}_${loggedInUser.username}_bak`, lsUserItem);
|
||||
}
|
||||
localStorage.setItem(`${d}_${loggedInUser.username}`, lsItem);
|
||||
localStorage.removeItem(d);
|
||||
}
|
||||
});
|
||||
return resolve([true, 200]);
|
||||
}
|
||||
pokerogueApi.account.getInfo().then(([accountInfo, status]) => {
|
||||
if (!accountInfo) {
|
||||
resolve([false, status]);
|
||||
return;
|
||||
loggedInUser.lastSessionSlot = lastSessionSlot;
|
||||
// Migrate old data from before the username was appended
|
||||
["data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4"].forEach(d => {
|
||||
const lsItem = localStorage.getItem(d);
|
||||
if (lsItem && !!loggedInUser?.username) {
|
||||
const lsUserItem = localStorage.getItem(`${d}_${loggedInUser.username}`);
|
||||
if (lsUserItem) {
|
||||
localStorage.setItem(`${d}_${loggedInUser.username}_bak`, lsUserItem);
|
||||
}
|
||||
localStorage.setItem(`${d}_${loggedInUser.username}`, lsItem);
|
||||
localStorage.removeItem(d);
|
||||
}
|
||||
loggedInUser = accountInfo;
|
||||
resolve([true, 200]);
|
||||
});
|
||||
});
|
||||
return [true, 200];
|
||||
}
|
||||
|
||||
const [accountInfo, status] = await pokerogueApi.account.getInfo();
|
||||
if (!accountInfo) {
|
||||
return [false, status];
|
||||
}
|
||||
loggedInUser = accountInfo;
|
||||
return [true, 200];
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ import {
|
||||
getLuckString,
|
||||
getLuckTextTint,
|
||||
getPartyLuckValue,
|
||||
type ModifierType,
|
||||
PokemonHeldItemModifierType,
|
||||
} from "#modifiers/modifier-type";
|
||||
import { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
@ -943,17 +944,17 @@ export class BattleScene extends SceneBase {
|
||||
dataSource?: PokemonData,
|
||||
postProcess?: (enemyPokemon: EnemyPokemon) => void,
|
||||
): EnemyPokemon {
|
||||
if (Overrides.OPP_LEVEL_OVERRIDE > 0) {
|
||||
level = Overrides.OPP_LEVEL_OVERRIDE;
|
||||
if (Overrides.ENEMY_LEVEL_OVERRIDE > 0) {
|
||||
level = Overrides.ENEMY_LEVEL_OVERRIDE;
|
||||
}
|
||||
if (Overrides.OPP_SPECIES_OVERRIDE) {
|
||||
species = getPokemonSpecies(Overrides.OPP_SPECIES_OVERRIDE);
|
||||
if (Overrides.ENEMY_SPECIES_OVERRIDE) {
|
||||
species = getPokemonSpecies(Overrides.ENEMY_SPECIES_OVERRIDE);
|
||||
// The fact that a Pokemon is a boss or not can change based on its Species and level
|
||||
boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1;
|
||||
}
|
||||
|
||||
const pokemon = new EnemyPokemon(species, level, trainerSlot, boss, shinyLock, dataSource);
|
||||
if (Overrides.OPP_FUSION_OVERRIDE) {
|
||||
if (Overrides.ENEMY_FUSION_OVERRIDE) {
|
||||
pokemon.generateFusionSpecies();
|
||||
}
|
||||
|
||||
@ -1203,7 +1204,9 @@ export class BattleScene extends SceneBase {
|
||||
this.updateScoreText();
|
||||
this.scoreText.setVisible(false);
|
||||
|
||||
[this.luckLabelText, this.luckText].map(t => t.setVisible(false));
|
||||
[this.luckLabelText, this.luckText].forEach(t => {
|
||||
t.setVisible(false);
|
||||
});
|
||||
|
||||
this.newArena(Overrides.STARTING_BIOME_OVERRIDE || BiomeId.TOWN);
|
||||
|
||||
@ -1237,8 +1240,7 @@ export class BattleScene extends SceneBase {
|
||||
Object.values(mp)
|
||||
.flat()
|
||||
.map(mt => mt.modifierType)
|
||||
.filter(mt => "localize" in mt)
|
||||
.map(lpb => lpb as unknown as Localizable),
|
||||
.filter((mt): mt is ModifierType & Localizable => "localize" in mt && typeof mt.localize === "function"),
|
||||
),
|
||||
];
|
||||
for (const item of localizable) {
|
||||
@ -1513,8 +1515,8 @@ export class BattleScene extends SceneBase {
|
||||
return this.currentBattle;
|
||||
}
|
||||
|
||||
newArena(biome: BiomeId, playerFaints?: number): Arena {
|
||||
this.arena = new Arena(biome, BiomeId[biome].toLowerCase(), playerFaints);
|
||||
newArena(biome: BiomeId, playerFaints = 0): Arena {
|
||||
this.arena = new Arena(biome, playerFaints);
|
||||
this.eventTarget.dispatchEvent(new NewArenaEvent());
|
||||
|
||||
this.arenaBg.pipelineData = {
|
||||
@ -1764,10 +1766,10 @@ export class BattleScene extends SceneBase {
|
||||
}
|
||||
|
||||
getEncounterBossSegments(waveIndex: number, level: number, species?: PokemonSpecies, forceBoss = false): number {
|
||||
if (Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE > 1) {
|
||||
return Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE;
|
||||
if (Overrides.ENEMY_HEALTH_SEGMENTS_OVERRIDE > 1) {
|
||||
return Overrides.ENEMY_HEALTH_SEGMENTS_OVERRIDE;
|
||||
}
|
||||
if (Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE === 1) {
|
||||
if (Overrides.ENEMY_HEALTH_SEGMENTS_OVERRIDE === 1) {
|
||||
// The rest of the code expects to be returned 0 and not 1 if the enemy is not a boss
|
||||
return 0;
|
||||
}
|
||||
@ -2711,7 +2713,9 @@ export class BattleScene extends SceneBase {
|
||||
}
|
||||
}
|
||||
|
||||
this.party.map(p => p.updateInfo(instant));
|
||||
this.party.forEach(p => {
|
||||
p.updateInfo(instant);
|
||||
});
|
||||
} else {
|
||||
const args = [this];
|
||||
if (modifier.shouldApply(...args)) {
|
||||
|
@ -101,3 +101,9 @@ export const ANTI_VARIANCE_WEIGHT_MODIFIER = 15;
|
||||
* Default: `10000` (0.01%)
|
||||
*/
|
||||
export const FAKE_TITLE_LOGO_CHANCE = 10000;
|
||||
|
||||
/**
|
||||
* The ceiling on friendship amount that can be reached through the use of rare candies.
|
||||
* Using rare candies will never increase friendship beyond this value.
|
||||
*/
|
||||
export const RARE_CANDY_FRIENDSHIP_CAP = 200;
|
||||
|
@ -74,6 +74,7 @@ import {
|
||||
randSeedItem,
|
||||
toDmgValue,
|
||||
} from "#utils/common";
|
||||
import { toCamelCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
export class Ability implements Localizable {
|
||||
@ -109,13 +110,9 @@ export class Ability implements Localizable {
|
||||
}
|
||||
|
||||
localize(): void {
|
||||
const i18nKey = AbilityId[this.id]
|
||||
.split("_")
|
||||
.filter(f => f)
|
||||
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
|
||||
.join("") as string;
|
||||
const i18nKey = toCamelCase(AbilityId[this.id]);
|
||||
|
||||
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`) as string}${this.nameAppend}` : "";
|
||||
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`)}${this.nameAppend}` : "";
|
||||
this.description = this.id ? (i18next.t(`ability:${i18nKey}.description`) as string) : "";
|
||||
}
|
||||
|
||||
|
@ -1866,17 +1866,16 @@ interface PokemonPrevolutions {
|
||||
export const pokemonPrevolutions: PokemonPrevolutions = {};
|
||||
|
||||
export function initPokemonPrevolutions(): void {
|
||||
const megaFormKeys = [ SpeciesFormKey.MEGA, "", SpeciesFormKey.MEGA_X, "", SpeciesFormKey.MEGA_Y ].map(sfk => sfk as string);
|
||||
const prevolutionKeys = Object.keys(pokemonEvolutions);
|
||||
prevolutionKeys.forEach(pk => {
|
||||
const evolutions = pokemonEvolutions[pk];
|
||||
// TODO: Why do we have empty strings in our array?
|
||||
const megaFormKeys = [ SpeciesFormKey.MEGA, "", SpeciesFormKey.MEGA_X, "", SpeciesFormKey.MEGA_Y ];
|
||||
for (const [pk, evolutions] of Object.entries(pokemonEvolutions)) {
|
||||
for (const ev of evolutions) {
|
||||
if (ev.evoFormKey && megaFormKeys.indexOf(ev.evoFormKey) > -1) {
|
||||
continue;
|
||||
}
|
||||
pokemonPrevolutions[ev.speciesId] = Number.parseInt(pk) as SpeciesId;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@ import { Trainer } from "#field/trainer";
|
||||
import type { ModifierTypeOption } from "#modifiers/modifier-type";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import type { DexAttrProps, GameData } from "#system/game-data";
|
||||
import { RibbonData, type RibbonFlag } from "#system/ribbons/ribbon-data";
|
||||
import { type BooleanHolder, isBetween, type NumberHolder, randSeedItem } from "#utils/common";
|
||||
import { deepCopy } from "#utils/data";
|
||||
import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils";
|
||||
@ -42,6 +43,15 @@ export abstract class Challenge {
|
||||
|
||||
public conditions: ChallengeCondition[];
|
||||
|
||||
/**
|
||||
* The Ribbon awarded on challenge completion, or 0 if the challenge has no ribbon or is not enabled
|
||||
*
|
||||
* @defaultValue 0
|
||||
*/
|
||||
public get ribbonAwarded(): RibbonFlag {
|
||||
return 0 as RibbonFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id {@link Challenges} The enum value for the challenge
|
||||
*/
|
||||
@ -423,6 +433,12 @@ type ChallengeCondition = (data: GameData) => boolean;
|
||||
* Implements a mono generation challenge.
|
||||
*/
|
||||
export class SingleGenerationChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
// NOTE: This logic will not work for the eventual mono gen 10 ribbon, as
|
||||
// as its flag will not be in sequence with the other mono gen ribbons.
|
||||
return this.value ? ((RibbonData.MONO_GEN_1 << (this.value - 1)) as RibbonFlag) : 0;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(Challenges.SINGLE_GENERATION, 9);
|
||||
}
|
||||
@ -686,6 +702,12 @@ interface monotypeOverride {
|
||||
* Implements a mono type challenge.
|
||||
*/
|
||||
export class SingleTypeChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
// `this.value` represents the 1-based index of pokemon type
|
||||
// `RibbonData.MONO_NORMAL` starts the flag position for the types,
|
||||
// and we shift it by 1 for the specific type.
|
||||
return this.value ? ((RibbonData.MONO_NORMAL << (this.value - 1)) as RibbonFlag) : 0;
|
||||
}
|
||||
private static TYPE_OVERRIDES: monotypeOverride[] = [
|
||||
{ species: SpeciesId.CASTFORM, type: PokemonType.NORMAL, fusion: false },
|
||||
];
|
||||
@ -755,6 +777,9 @@ export class SingleTypeChallenge extends Challenge {
|
||||
* Implements a fresh start challenge.
|
||||
*/
|
||||
export class FreshStartChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
return this.value ? RibbonData.FRESH_START : 0;
|
||||
}
|
||||
constructor() {
|
||||
super(Challenges.FRESH_START, 2);
|
||||
}
|
||||
@ -828,6 +853,9 @@ export class FreshStartChallenge extends Challenge {
|
||||
* Implements an inverse battle challenge.
|
||||
*/
|
||||
export class InverseBattleChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
return this.value ? RibbonData.INVERSE : 0;
|
||||
}
|
||||
constructor() {
|
||||
super(Challenges.INVERSE_BATTLE, 1);
|
||||
}
|
||||
@ -861,6 +889,9 @@ export class InverseBattleChallenge extends Challenge {
|
||||
* Implements a flip stat challenge.
|
||||
*/
|
||||
export class FlipStatChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
return this.value ? RibbonData.FLIP_STATS : 0;
|
||||
}
|
||||
constructor() {
|
||||
super(Challenges.FLIP_STAT, 1);
|
||||
}
|
||||
@ -941,6 +972,9 @@ export class LowerStarterPointsChallenge extends Challenge {
|
||||
* Implements a No Support challenge
|
||||
*/
|
||||
export class LimitedSupportChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
return this.value ? ((RibbonData.NO_HEAL << (this.value - 1)) as RibbonFlag) : 0;
|
||||
}
|
||||
constructor() {
|
||||
super(Challenges.LIMITED_SUPPORT, 3);
|
||||
}
|
||||
@ -973,6 +1007,9 @@ export class LimitedSupportChallenge extends Challenge {
|
||||
* Implements a Limited Catch challenge
|
||||
*/
|
||||
export class LimitedCatchChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
return this.value ? RibbonData.LIMITED_CATCH : 0;
|
||||
}
|
||||
constructor() {
|
||||
super(Challenges.LIMITED_CATCH, 1);
|
||||
}
|
||||
@ -997,6 +1034,9 @@ export class LimitedCatchChallenge extends Challenge {
|
||||
* Implements a Permanent Faint challenge
|
||||
*/
|
||||
export class HardcoreChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
return this.value ? RibbonData.HARDCORE : 0;
|
||||
}
|
||||
constructor() {
|
||||
super(Challenges.HARDCORE, 1);
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ export class EggHatchData {
|
||||
caughtCount: currDexEntry.caughtCount,
|
||||
hatchedCount: currDexEntry.hatchedCount,
|
||||
ivs: [...currDexEntry.ivs],
|
||||
ribbons: currDexEntry.ribbons,
|
||||
};
|
||||
this.starterDataEntryBeforeUpdate = {
|
||||
moveset: currStarterDataEntry.moveset,
|
||||
|
@ -90,7 +90,7 @@ import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindS
|
||||
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";
|
||||
import { toTitleCase } from "#utils/strings";
|
||||
import { toCamelCase, toTitleCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
import { applyChallenges } from "#utils/challenge-utils";
|
||||
|
||||
@ -162,10 +162,16 @@ export abstract class Move implements Localizable {
|
||||
}
|
||||
|
||||
localize(): void {
|
||||
const i18nKey = MoveId[this.id].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("") as unknown as string;
|
||||
const i18nKey = toCamelCase(MoveId[this.id])
|
||||
|
||||
this.name = this.id ? `${i18next.t(`move:${i18nKey}.name`)}${this.nameAppend}` : "";
|
||||
this.effect = this.id ? `${i18next.t(`move:${i18nKey}.effect`)}${this.nameAppend}` : "";
|
||||
if (this.id === MoveId.NONE) {
|
||||
this.name = "";
|
||||
this.effect = ""
|
||||
return;
|
||||
}
|
||||
|
||||
this.name = `${i18next.t(`move:${i18nKey}.name`)}${this.nameAppend}`;
|
||||
this.effect = `${i18next.t(`move:${i18nKey}.effect`)}${this.nameAppend}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -5926,8 +5932,8 @@ export class ProtectAttr extends AddBattlerTagAttr {
|
||||
for (const turnMove of user.getLastXMoves(-1).slice()) {
|
||||
if (
|
||||
// Quick & Wide guard increment the Protect counter without using it for fail chance
|
||||
!(allMoves[turnMove.move].hasAttr("ProtectAttr") ||
|
||||
[MoveId.QUICK_GUARD, MoveId.WIDE_GUARD].includes(turnMove.move)) ||
|
||||
!(allMoves[turnMove.move].hasAttr("ProtectAttr") ||
|
||||
[MoveId.QUICK_GUARD, MoveId.WIDE_GUARD].includes(turnMove.move)) ||
|
||||
turnMove.result !== MoveResult.SUCCESS
|
||||
) {
|
||||
break;
|
||||
|
@ -11,7 +11,7 @@ import { BooleanHolder, toDmgValue } from "#utils/common";
|
||||
* These are the moves assigned to a {@linkcode Pokemon} object.
|
||||
* It links to {@linkcode Move} class via the move ID.
|
||||
* Compared to {@linkcode Move}, this class also tracks things like
|
||||
* PP Ups recieved, PP used, etc.
|
||||
* PP Ups received, PP used, etc.
|
||||
* @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented.
|
||||
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
|
||||
* @see {@linkcode usePp} - removes a point of PP from the move.
|
||||
|
@ -82,7 +82,7 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
encounter.dialogue.intro = [
|
||||
{
|
||||
speaker: `trainerNames:${trainerNameKey}`,
|
||||
text: `${namespace}:${trainerNameKey}.intro_dialogue`,
|
||||
text: `${namespace}:${trainerNameKey}.introDialogue`,
|
||||
},
|
||||
];
|
||||
encounter.options[0].dialogue!.selected = [
|
||||
|
@ -237,7 +237,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
modifierConfigs: bossModifierConfigs,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
|
||||
queueEncounterMessage(`${namespace}:option.1.bossEnraged`);
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"StatStageChangePhase",
|
||||
pokemon.getBattlerIndex(),
|
||||
@ -300,7 +300,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
globalScene.addModifier(seedModifier, false, false, false, true);
|
||||
}
|
||||
});
|
||||
queueEncounterMessage(`${namespace}:option.1.food_stash`);
|
||||
queueEncounterMessage(`${namespace}:option.1.foodStash`);
|
||||
};
|
||||
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, givePartyPokemonReviverSeeds);
|
||||
|
@ -71,7 +71,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
@ -152,7 +152,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.tooltip_disabled`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.tooltipDisabled`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
|
@ -254,7 +254,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
undefined,
|
||||
doBerryRewards,
|
||||
);
|
||||
await showEncounterText(`${namespace}:option.2.selected_bad`);
|
||||
await showEncounterText(`${namespace}:option.2.selectedBad`);
|
||||
await initBattleWithEnemyConfig(config);
|
||||
return;
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
@ -312,7 +312,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabledTooltip`,
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Player shows off their bug types
|
||||
@ -333,7 +333,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.2.selected_0_to_1`,
|
||||
text: `${namespace}:option.2.selected0To1`,
|
||||
},
|
||||
];
|
||||
} else if (numBugTypes < 4) {
|
||||
@ -344,7 +344,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.2.selected_2_to_3`,
|
||||
text: `${namespace}:option.2.selected2To3`,
|
||||
},
|
||||
];
|
||||
} else if (numBugTypes < 6) {
|
||||
@ -355,7 +355,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.2.selected_4_to_5`,
|
||||
text: `${namespace}:option.2.selected4To5`,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
@ -398,7 +398,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.2.selected_6`,
|
||||
text: `${namespace}:option.2.selected6`,
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -421,17 +421,17 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.3.selected_dialogue`,
|
||||
text: `${namespace}:option.3.selectedDialogue`,
|
||||
},
|
||||
],
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
secondOptionPrompt: `${namespace}:option.3.selectPrompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
@ -476,7 +476,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
);
|
||||
});
|
||||
if (!hasValidItem) {
|
||||
return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null;
|
||||
return getEncounterText(`${namespace}:option.3.invalidSelection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -713,7 +713,7 @@ function doBugTypeMoveTutor(): Promise<void> {
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO explain
|
||||
return new Promise<void>(async resolve => {
|
||||
const moveOptions = globalScene.currentBattle.mysteryEncounter!.misc.moveTutorOptions;
|
||||
await showEncounterDialogue(`${namespace}:battle_won`, `${namespace}:speaker`);
|
||||
await showEncounterDialogue(`${namespace}:battleWon`, `${namespace}:speaker`);
|
||||
|
||||
const moveInfoOverlay = new MoveInfoOverlay({
|
||||
delayVisibility: false,
|
||||
@ -748,7 +748,7 @@ function doBugTypeMoveTutor(): Promise<void> {
|
||||
|
||||
const result = await selectOptionThenPokemon(
|
||||
optionSelectItems,
|
||||
`${namespace}:teach_move_prompt`,
|
||||
`${namespace}:teachMovePrompt`,
|
||||
undefined,
|
||||
onHoverOverCancel,
|
||||
);
|
||||
|
@ -119,7 +119,7 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
@ -233,7 +233,7 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
// After the battle, offer the player the opportunity to permanently swap ability
|
||||
const abilityWasSwapped = await handleSwapAbility();
|
||||
if (abilityWasSwapped) {
|
||||
await showEncounterText(`${namespace}:option.1.ability_gained`);
|
||||
await showEncounterText(`${namespace}:option.1.abilityGained`);
|
||||
}
|
||||
|
||||
// Play animations once ability swap is complete
|
||||
@ -267,10 +267,10 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.2.selected_2`,
|
||||
text: `${namespace}:option.2.selected2`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.2.selected_3`,
|
||||
text: `${namespace}:option.2.selected3`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
],
|
||||
@ -359,10 +359,10 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.3.selected_2`,
|
||||
text: `${namespace}:option.3.selected2`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.3.selected_3`,
|
||||
text: `${namespace}:option.3.selected3`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
],
|
||||
@ -432,8 +432,8 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
async function handleSwapAbility() {
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
|
||||
return new Promise<boolean>(async resolve => {
|
||||
await showEncounterDialogue(`${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`);
|
||||
await showEncounterText(`${namespace}:option.1.apply_ability_message`);
|
||||
await showEncounterDialogue(`${namespace}:option.1.applyAbilityDialogue`, `${namespace}:speaker`);
|
||||
await showEncounterText(`${namespace}:option.1.applyAbilityMessage`);
|
||||
|
||||
globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
|
||||
displayYesNoOptions(resolve);
|
||||
@ -442,7 +442,7 @@ async function handleSwapAbility() {
|
||||
}
|
||||
|
||||
function displayYesNoOptions(resolve) {
|
||||
showEncounterText(`${namespace}:option.1.ability_prompt`, null, 500, false);
|
||||
showEncounterText(`${namespace}:option.1.abilityPrompt`, null, 500, false);
|
||||
const fullOptions = [
|
||||
{
|
||||
label: i18next.t("menu:yes"),
|
||||
|
@ -174,7 +174,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
// Gets +1 to all stats except SPD on battle start
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
|
||||
queueEncounterMessage(`${namespace}:option.1.bossEnraged`);
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"StatStageChangePhase",
|
||||
pokemon.getBattlerIndex(),
|
||||
@ -273,8 +273,8 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabledTooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.selectPrompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
@ -316,7 +316,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
}
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
||||
return getEncounterText(`${namespace}:invalidSelection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -118,7 +118,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
])
|
||||
.withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
@ -136,10 +136,10 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.1.selected_dialogue`,
|
||||
text: `${namespace}:option.1.selectedDialogue`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.1.selected_message`,
|
||||
text: `${namespace}:option.1.selectedMessage`,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
@ -193,7 +193,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.2.select_prompt`,
|
||||
secondOptionPrompt: `${namespace}:option.2.selectPrompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
@ -229,7 +229,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
// If pokemon has valid item, it can be selected
|
||||
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
||||
return getEncounterText(`${namespace}:invalidSelection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -303,7 +303,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
secondOptionPrompt: `${namespace}:option.3.selectPrompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
@ -341,7 +341,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
// If pokemon has valid item, it can be selected
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
||||
return getEncounterText(`${namespace}:invalidSelection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -43,7 +43,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
|
@ -56,7 +56,7 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
@ -70,7 +70,7 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:second_option_prompt`,
|
||||
secondOptionPrompt: `${namespace}:secondOptionPrompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
@ -118,7 +118,7 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:second_option_prompt`,
|
||||
secondOptionPrompt: `${namespace}:secondOptionPrompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
@ -166,7 +166,7 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:second_option_prompt`,
|
||||
secondOptionPrompt: `${namespace}:secondOptionPrompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
@ -226,7 +226,7 @@ function pokemonAndMoveChosen(pokemon: PlayerPokemon, move: PokemonMove, correct
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:incorrect_exp`,
|
||||
text: `${namespace}:incorrectExp`,
|
||||
},
|
||||
];
|
||||
setEncounterExp(
|
||||
@ -243,7 +243,7 @@ function pokemonAndMoveChosen(pokemon: PlayerPokemon, move: PokemonMove, correct
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:correct_exp`,
|
||||
text: `${namespace}:correctExp`,
|
||||
},
|
||||
];
|
||||
setEncounterExp([pokemon.id], 100);
|
||||
|
@ -247,7 +247,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
|
||||
// Burn applied
|
||||
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
|
||||
encounter.setDialogueToken("abilityName", allAbilities[AbilityId.HEATPROOF].name);
|
||||
queueEncounterMessage(`${namespace}:option.2.target_burned`);
|
||||
queueEncounterMessage(`${namespace}:option.2.targetBurned`);
|
||||
|
||||
// Also permanently change the burned Pokemon's ability to Heatproof
|
||||
applyAbilityOverrideToPokemon(chosenPokemon, AbilityId.HEATPROOF);
|
||||
@ -269,7 +269,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
@ -313,6 +313,6 @@ function giveLeadPokemonAttackTypeBoostItem() {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
encounter.setDialogueToken("itemName", boosterModifierType.name);
|
||||
encounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
|
||||
queueEncounterMessage(`${namespace}:found_item`);
|
||||
queueEncounterMessage(`${namespace}:foundItem`);
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
isBoss: true,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.1.stat_boost`);
|
||||
queueEncounterMessage(`${namespace}:option.1.statBoost`);
|
||||
// Randomly boost 1 stat 2 stages
|
||||
// Cannot boost Spd, Acc, or Evasion
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
@ -165,7 +165,7 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
|
@ -78,7 +78,7 @@ export const FunAndGamesEncounter: MysteryEncounter = MysteryEncounterBuilder.wi
|
||||
.withIntroDialogue([
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
@ -118,7 +118,7 @@ export const FunAndGamesEncounter: MysteryEncounter = MysteryEncounterBuilder.wi
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalidSelection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
@ -284,25 +284,25 @@ function handleNextTurn() {
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.MULTI_LENS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
resultMessageKey = `${namespace}:best_result`;
|
||||
resultMessageKey = `${namespace}:bestResult`;
|
||||
} else if (healthRatio < 0.15) {
|
||||
// 2nd prize
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SCOPE_LENS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
resultMessageKey = `${namespace}:great_result`;
|
||||
resultMessageKey = `${namespace}:greatResult`;
|
||||
} else if (healthRatio < 0.33) {
|
||||
// 3rd prize
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.WIDE_LENS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
resultMessageKey = `${namespace}:good_result`;
|
||||
resultMessageKey = `${namespace}:goodResult`;
|
||||
} else {
|
||||
// No prize
|
||||
isHealPhase = true;
|
||||
resultMessageKey = `${namespace}:bad_result`;
|
||||
resultMessageKey = `${namespace}:badResult`;
|
||||
}
|
||||
|
||||
// End the battle
|
||||
@ -312,7 +312,7 @@ function handleNextTurn() {
|
||||
globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined;
|
||||
leaveEncounterWithoutBattle(isHealPhase);
|
||||
// Must end the TurnInit phase prematurely so battle phases aren't added to queue
|
||||
queueEncounterMessage(`${namespace}:end_game`);
|
||||
queueEncounterMessage(`${namespace}:endGame`);
|
||||
queueEncounterMessage(resultMessageKey);
|
||||
|
||||
// Skip remainder of TurnInitPhase
|
||||
@ -320,9 +320,9 @@ function handleNextTurn() {
|
||||
}
|
||||
if (encounter.misc.turnsRemaining < 3) {
|
||||
// Display charging messages on turns that aren't the initial turn
|
||||
queueEncounterMessage(`${namespace}:charging_continue`);
|
||||
queueEncounterMessage(`${namespace}:chargingContinue`);
|
||||
}
|
||||
queueEncounterMessage(`${namespace}:turn_remaining_${encounter.misc.turnsRemaining}`);
|
||||
queueEncounterMessage(`${namespace}:turnRemaining${encounter.misc.turnsRemaining}`);
|
||||
encounter.misc.turnsRemaining--;
|
||||
|
||||
// Don't skip remainder of TurnInitPhase
|
||||
|
@ -158,7 +158,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.1.trade_options_prompt`,
|
||||
secondOptionPrompt: `${namespace}:option.1.tradeOptionsPrompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
@ -248,7 +248,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
// Show the trade animation
|
||||
await showTradeBackground();
|
||||
await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon);
|
||||
await showEncounterText(`${namespace}:trade_received`, null, 0, true, 4000);
|
||||
await showEncounterText(`${namespace}:tradeReceived`, null, 0, true, 4000);
|
||||
globalScene.playBgm(encounter.misc.bgmKey);
|
||||
await addPokemonDataToDexAndValidateAchievements(newPlayerPokemon);
|
||||
await hideTradeBackground();
|
||||
@ -369,7 +369,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
// Show the trade animation
|
||||
await showTradeBackground();
|
||||
await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon);
|
||||
await showEncounterText(`${namespace}:trade_received`, null, 0, true, 4000);
|
||||
await showEncounterText(`${namespace}:tradeReceived`, null, 0, true, 4000);
|
||||
globalScene.playBgm(encounter.misc.bgmKey);
|
||||
await addPokemonDataToDexAndValidateAchievements(newPlayerPokemon);
|
||||
await hideTradeBackground();
|
||||
@ -384,7 +384,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.trade_options_prompt`,
|
||||
secondOptionPrompt: `${namespace}:option.3.tradeOptionsPrompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
@ -416,7 +416,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
return it.isTransferable;
|
||||
}).length > 0;
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null;
|
||||
return getEncounterText(`${namespace}:option.3.invalidSelection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -468,7 +468,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
// Generate a trainer name
|
||||
const traderName = generateRandomTraderName();
|
||||
encounter.setDialogueToken("tradeTrainerName", traderName.trim());
|
||||
await showEncounterText(`${namespace}:item_trade_selected`);
|
||||
await showEncounterText(`${namespace}:itemTradeSelected`);
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build(),
|
||||
@ -740,10 +740,10 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
|
||||
duration: 500,
|
||||
onComplete: async () => {
|
||||
globalScene.fadeOutBgm(1000, false);
|
||||
await showEncounterText(`${namespace}:pokemon_trade_selected`);
|
||||
await showEncounterText(`${namespace}:pokemonTradeSelected`);
|
||||
tradedPokemon.cry();
|
||||
globalScene.playBgm("evolution");
|
||||
await showEncounterText(`${namespace}:pokemon_trade_goodbye`);
|
||||
await showEncounterText(`${namespace}:pokemonTradeGoodbye`);
|
||||
|
||||
tradedPokeball.setAlpha(0);
|
||||
tradedPokeball.setVisible(true);
|
||||
|
@ -63,9 +63,9 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
disabledButtonLabel: `${namespace}:option.1.label_disabled`,
|
||||
disabledButtonLabel: `${namespace}:option.1.labelDisabled`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.1.tooltip_disabled`,
|
||||
disabledButtonTooltip: `${namespace}:option.1.tooltipDisabled`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
@ -81,9 +81,9 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
disabledButtonLabel: `${namespace}:option.2.label_disabled`,
|
||||
disabledButtonLabel: `${namespace}:option.2.labelDisabled`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.tooltip_disabled`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.tooltipDisabled`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
|
@ -58,7 +58,7 @@ export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
@ -128,7 +128,7 @@ export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
|
||||
// Only Pokemon non-KOd pokemon can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalidSelection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
@ -142,9 +142,9 @@ export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
|
||||
// Give money and do dialogue
|
||||
if (moneyMultiplier > 2.5) {
|
||||
await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`);
|
||||
await showEncounterDialogue(`${namespace}:jobCompleteGood`, `${namespace}:speaker`);
|
||||
} else {
|
||||
await showEncounterDialogue(`${namespace}:job_complete_bad`, `${namespace}:speaker`);
|
||||
await showEncounterDialogue(`${namespace}:jobCompleteBad`, `${namespace}:speaker`);
|
||||
}
|
||||
const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier);
|
||||
updatePlayerMoney(moneyChange, true, false);
|
||||
@ -153,7 +153,7 @@ export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
amount: moneyChange,
|
||||
}),
|
||||
);
|
||||
await showEncounterText(`${namespace}:pokemon_tired`);
|
||||
await showEncounterText(`${namespace}:pokemonTired`);
|
||||
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
leaveEncounterWithoutBattle();
|
||||
@ -210,7 +210,7 @@ export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
|
||||
// Only Pokemon non-KOd pokemon can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalidSelection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
@ -224,9 +224,9 @@ export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
|
||||
// Give money and do dialogue
|
||||
if (moneyMultiplier > 2.5) {
|
||||
await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`);
|
||||
await showEncounterDialogue(`${namespace}:jobCompleteGood`, `${namespace}:speaker`);
|
||||
} else {
|
||||
await showEncounterDialogue(`${namespace}:job_complete_bad`, `${namespace}:speaker`);
|
||||
await showEncounterDialogue(`${namespace}:jobCompleteBad`, `${namespace}:speaker`);
|
||||
}
|
||||
const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier);
|
||||
updatePlayerMoney(moneyChange, true, false);
|
||||
@ -235,7 +235,7 @@ export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
amount: moneyChange,
|
||||
}),
|
||||
);
|
||||
await showEncounterText(`${namespace}:pokemon_tired`);
|
||||
await showEncounterText(`${namespace}:pokemonTired`);
|
||||
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
leaveEncounterWithoutBattle();
|
||||
@ -248,7 +248,7 @@ export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
@ -282,7 +282,7 @@ export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
await transitionMysteryEncounterIntroVisuals(false, false);
|
||||
|
||||
// Give money and do dialogue
|
||||
await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`);
|
||||
await showEncounterDialogue(`${namespace}:jobCompleteGood`, `${namespace}:speaker`);
|
||||
const moneyChange = globalScene.getWaveMoneyAmount(2.5);
|
||||
updatePlayerMoney(moneyChange, true, false);
|
||||
await showEncounterText(
|
||||
@ -290,7 +290,7 @@ export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
amount: moneyChange,
|
||||
}),
|
||||
);
|
||||
await showEncounterText(`${namespace}:pokemon_tired`);
|
||||
await showEncounterText(`${namespace}:pokemonTired`);
|
||||
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
leaveEncounterWithoutBattle();
|
||||
|
@ -205,7 +205,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
|
||||
// 80% chance to increase flee stage +1
|
||||
const fleeChangeResult = tryChangeFleeStage(1, 8);
|
||||
if (!fleeChangeResult) {
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.busy_eating`) ?? "", null, 1000, false);
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.busyEating`) ?? "", null, 1000, false);
|
||||
} else {
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.eating`) ?? "", null, 1000, false);
|
||||
}
|
||||
@ -233,7 +233,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
|
||||
// 80% chance to decrease catch stage -1
|
||||
const catchChangeResult = tryChangeCatchStage(-1, 8);
|
||||
if (!catchChangeResult) {
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.beside_itself_angry`) ?? "", null, 1000, false);
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.besideItselfAngry`) ?? "", null, 1000, false);
|
||||
} else {
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.angry`) ?? "", null, 1000, false);
|
||||
}
|
||||
@ -274,7 +274,7 @@ async function summonSafariPokemon() {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Message pokemon remaining
|
||||
encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining);
|
||||
globalScene.phaseManager.queueMessage(getEncounterText(`${namespace}:safari.remaining_count`) ?? "", null, true);
|
||||
globalScene.phaseManager.queueMessage(getEncounterText(`${namespace}:safari.remainingCount`) ?? "", null, true);
|
||||
|
||||
// Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken
|
||||
// Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
|
||||
|
@ -70,7 +70,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
@ -119,7 +119,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
);
|
||||
}
|
||||
if (!encounter.pokemonMeetsPrimaryRequirements(pokemon)) {
|
||||
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
||||
return getEncounterText(`${namespace}:invalidSelection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -155,7 +155,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
|
||||
chosenPokemon.setCustomNature(newNature);
|
||||
encounter.setDialogueToken("newNature", getNatureName(newNature));
|
||||
queueEncounterMessage(`${namespace}:cheap_side_effects`);
|
||||
queueEncounterMessage(`${namespace}:cheapSideEffects`);
|
||||
setEncounterExp([chosenPokemon.id], 100);
|
||||
await chosenPokemon.updateInfo();
|
||||
})
|
||||
@ -193,7 +193,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
|
||||
// Only Pokemon that can gain benefits are unfainted
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalidSelection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
@ -215,7 +215,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
queueEncounterMessage(`${namespace}:no_bad_effects`);
|
||||
queueEncounterMessage(`${namespace}:noBadEffects`);
|
||||
setEncounterExp([chosenPokemon.id], 100);
|
||||
|
||||
await chosenPokemon.updateInfo();
|
||||
|
@ -157,7 +157,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
// Fall asleep waiting for Snorlax
|
||||
// Full heal party
|
||||
globalScene.phaseManager.unshiftNew("PartyHealPhase", true);
|
||||
queueEncounterMessage(`${namespace}:option.2.rest_result`);
|
||||
queueEncounterMessage(`${namespace}:option.2.restResult`);
|
||||
leaveEncounterWithoutBattle();
|
||||
},
|
||||
)
|
||||
@ -167,7 +167,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
|
@ -122,7 +122,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
@ -227,7 +227,7 @@ async function doBiomeTransitionDialogueAndBattleInit() {
|
||||
isBoss: true,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:boss_enraged`);
|
||||
queueEncounterMessage(`${namespace}:bossEnraged`);
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"StatStageChangePhase",
|
||||
pokemon.getBattlerIndex(),
|
||||
|
@ -139,7 +139,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
},
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
@ -189,13 +189,13 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
|
||||
// Dialogue and egg calcs for Pokemon 1
|
||||
const [pokemon1CommonEggs, pokemon1RareEggs] = calculateEggRewardsForPokemon(pokemon1);
|
||||
let pokemon1Tooltip = getEncounterText(`${namespace}:option.1.tooltip_base`)!;
|
||||
let pokemon1Tooltip = getEncounterText(`${namespace}:option.1.tooltipBase`)!;
|
||||
if (pokemon1RareEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, {
|
||||
count: pokemon1RareEggs,
|
||||
rarity: i18next.t("egg:greatTier"),
|
||||
});
|
||||
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
|
||||
pokemon1Tooltip += i18next.t(`${namespace}:eggsTooltip`, {
|
||||
eggs: eggsText,
|
||||
});
|
||||
encounter.setDialogueToken("pokemon1RareEggs", eggsText);
|
||||
@ -205,7 +205,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
count: pokemon1CommonEggs,
|
||||
rarity: i18next.t("egg:defaultTier"),
|
||||
});
|
||||
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
|
||||
pokemon1Tooltip += i18next.t(`${namespace}:eggsTooltip`, {
|
||||
eggs: eggsText,
|
||||
});
|
||||
encounter.setDialogueToken("pokemon1CommonEggs", eggsText);
|
||||
@ -214,13 +214,13 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
|
||||
// Dialogue and egg calcs for Pokemon 2
|
||||
const [pokemon2CommonEggs, pokemon2RareEggs] = calculateEggRewardsForPokemon(pokemon2);
|
||||
let pokemon2Tooltip = getEncounterText(`${namespace}:option.2.tooltip_base`)!;
|
||||
let pokemon2Tooltip = getEncounterText(`${namespace}:option.2.tooltipBase`)!;
|
||||
if (pokemon2RareEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, {
|
||||
count: pokemon2RareEggs,
|
||||
rarity: i18next.t("egg:greatTier"),
|
||||
});
|
||||
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
|
||||
pokemon2Tooltip += i18next.t(`${namespace}:eggsTooltip`, {
|
||||
eggs: eggsText,
|
||||
});
|
||||
encounter.setDialogueToken("pokemon2RareEggs", eggsText);
|
||||
@ -230,7 +230,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
count: pokemon2CommonEggs,
|
||||
rarity: i18next.t("egg:defaultTier"),
|
||||
});
|
||||
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
|
||||
pokemon2Tooltip += i18next.t(`${namespace}:eggsTooltip`, {
|
||||
eggs: eggsText,
|
||||
});
|
||||
encounter.setDialogueToken("pokemon2CommonEggs", eggsText);
|
||||
@ -239,13 +239,13 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
|
||||
// Dialogue and egg calcs for Pokemon 3
|
||||
const [pokemon3CommonEggs, pokemon3RareEggs] = calculateEggRewardsForPokemon(pokemon3);
|
||||
let pokemon3Tooltip = getEncounterText(`${namespace}:option.3.tooltip_base`)!;
|
||||
let pokemon3Tooltip = getEncounterText(`${namespace}:option.3.tooltipBase`)!;
|
||||
if (pokemon3RareEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, {
|
||||
count: pokemon3RareEggs,
|
||||
rarity: i18next.t("egg:greatTier"),
|
||||
});
|
||||
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
|
||||
pokemon3Tooltip += i18next.t(`${namespace}:eggsTooltip`, {
|
||||
eggs: eggsText,
|
||||
});
|
||||
encounter.setDialogueToken("pokemon3RareEggs", eggsText);
|
||||
@ -255,7 +255,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
count: pokemon3CommonEggs,
|
||||
rarity: i18next.t("egg:defaultTier"),
|
||||
});
|
||||
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
|
||||
pokemon3Tooltip += i18next.t(`${namespace}:eggsTooltip`, {
|
||||
eggs: eggsText,
|
||||
});
|
||||
encounter.setDialogueToken("pokemon3CommonEggs", eggsText);
|
||||
@ -321,14 +321,14 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
];
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon1CommonEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, {
|
||||
text: i18next.t(`${namespace}:gainedEggs`, {
|
||||
numEggs: encounter.dialogueTokens["pokemon1CommonEggs"],
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon1RareEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, {
|
||||
text: i18next.t(`${namespace}:gainedEggs`, {
|
||||
numEggs: encounter.dialogueTokens["pokemon1RareEggs"],
|
||||
}),
|
||||
});
|
||||
@ -380,14 +380,14 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
];
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon2CommonEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, {
|
||||
text: i18next.t(`${namespace}:gainedEggs`, {
|
||||
numEggs: encounter.dialogueTokens["pokemon2CommonEggs"],
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon2RareEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, {
|
||||
text: i18next.t(`${namespace}:gainedEggs`, {
|
||||
numEggs: encounter.dialogueTokens["pokemon2RareEggs"],
|
||||
}),
|
||||
});
|
||||
@ -439,14 +439,14 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
];
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon3CommonEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, {
|
||||
text: i18next.t(`${namespace}:gainedEggs`, {
|
||||
numEggs: encounter.dialogueTokens["pokemon3CommonEggs"],
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon3RareEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, {
|
||||
text: i18next.t(`${namespace}:gainedEggs`, {
|
||||
numEggs: encounter.dialogueTokens["pokemon3RareEggs"],
|
||||
}),
|
||||
});
|
||||
@ -482,7 +482,7 @@ function getPartyConfig(): EnemyPartyConfig {
|
||||
trainerType: TrainerType.EXPERT_POKEMON_BREEDER,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
nickname: i18next.t(`${namespace}:cleffa_1_nickname`, {
|
||||
nickname: i18next.t(`${namespace}:cleffa1Nickname`, {
|
||||
speciesName: getPokemonSpecies(cleffaSpecies).getName(),
|
||||
}),
|
||||
species: getPokemonSpecies(cleffaSpecies),
|
||||
@ -501,7 +501,7 @@ function getPartyConfig(): EnemyPartyConfig {
|
||||
// All 3 members always Cleffa line, but different configs
|
||||
baseConfig.pokemonConfigs!.push(
|
||||
{
|
||||
nickname: i18next.t(`${namespace}:cleffa_2_nickname`, {
|
||||
nickname: i18next.t(`${namespace}:cleffa2Nickname`, {
|
||||
speciesName: getPokemonSpecies(cleffaSpecies).getName(),
|
||||
}),
|
||||
species: getPokemonSpecies(cleffaSpecies),
|
||||
@ -514,7 +514,7 @@ function getPartyConfig(): EnemyPartyConfig {
|
||||
ivs: [31, 31, 31, 31, 31, 31],
|
||||
},
|
||||
{
|
||||
nickname: i18next.t(`${namespace}:cleffa_3_nickname`, {
|
||||
nickname: i18next.t(`${namespace}:cleffa3Nickname`, {
|
||||
speciesName: getPokemonSpecies(cleffaSpecies).getName(),
|
||||
}),
|
||||
species: getPokemonSpecies(cleffaSpecies),
|
||||
@ -647,7 +647,7 @@ function onGameOver() {
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:outro_failed`,
|
||||
text: `${namespace}:outroFailed`,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -66,7 +66,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
@ -178,8 +178,8 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
// Always max price for shiny (flip HA back to normal), and add special messaging
|
||||
priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER;
|
||||
pokemon.abilityIndex = 0;
|
||||
encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}:description_shiny`;
|
||||
encounter.options[0].dialogue!.buttonTooltip = `${namespace}:option.1.tooltip_shiny`;
|
||||
encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}:descriptionShiny`;
|
||||
encounter.options[0].dialogue!.buttonTooltip = `${namespace}:option.1.tooltipShiny`;
|
||||
}
|
||||
const price = globalScene.getWaveMoneyAmount(priceMultiplier);
|
||||
encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
|
||||
@ -202,7 +202,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected_message`,
|
||||
text: `${namespace}:option.1.selectedMessage`,
|
||||
},
|
||||
],
|
||||
})
|
||||
@ -215,7 +215,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
updatePlayerMoney(-price, true, false);
|
||||
|
||||
// Show dialogue
|
||||
await showEncounterDialogue(`${namespace}:option.1.selected_dialogue`, `${namespace}:speaker`);
|
||||
await showEncounterDialogue(`${namespace}:option.1.selectedDialogue`, `${namespace}:speaker`);
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
|
||||
// "Catch" purchased pokemon
|
||||
|
@ -115,7 +115,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
],
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.2.stat_boost`);
|
||||
queueEncounterMessage(`${namespace}:option.2.statBoost`);
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"StatStageChangePhase",
|
||||
pokemon.getBattlerIndex(),
|
||||
@ -181,7 +181,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
|
||||
encounter.setDialogueToken("reductionValue", HIGH_BST_REDUCTION_VALUE.toString());
|
||||
encounter.setDialogueToken("increaseValue", BST_INCREASE_VALUE.toString());
|
||||
await showEncounterText(`${namespace}:option.1.selected_2`, null, undefined, true);
|
||||
await showEncounterText(`${namespace}:option.1.selected2`, null, undefined, true);
|
||||
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
|
@ -87,7 +87,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounter
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
@ -163,7 +163,7 @@ async function spawnNextTrainerOrEndEncounter() {
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }));
|
||||
|
||||
await showEncounterDialogue(`${namespace}:victory_2`, `${namespace}:speaker`);
|
||||
await showEncounterDialogue(`${namespace}:victory2`, `${namespace}:speaker`);
|
||||
globalScene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in
|
||||
const machoBrace = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!;
|
||||
machoBrace.type.tier = ModifierTier.MASTER;
|
||||
|
@ -90,7 +90,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalidSelection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
@ -174,7 +174,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.2.select_prompt`,
|
||||
secondOptionPrompt: `${namespace}:option.2.selectPrompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
@ -205,7 +205,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalidSelection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
@ -248,7 +248,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
secondOptionPrompt: `${namespace}:option.3.selectPrompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
@ -295,7 +295,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalidSelection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
|
@ -194,7 +194,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
.withOptionPhase(async () => {
|
||||
// Investigate garbage, battle Gmax Garbodor
|
||||
globalScene.setFieldScale(0.75);
|
||||
await showEncounterText(`${namespace}:option.2.selected_2`);
|
||||
await showEncounterText(`${namespace}:option.2.selected2`);
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
@ -97,7 +97,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
isBoss: false,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.1.stat_boost`);
|
||||
queueEncounterMessage(`${namespace}:option.1.statBoost`);
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"StatStageChangePhase",
|
||||
pokemon.getBattlerIndex(),
|
||||
@ -191,7 +191,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
@ -236,7 +236,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
|
@ -143,7 +143,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
@ -216,7 +216,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
await cutsceneDialoguePromise;
|
||||
|
||||
doHideDreamBackground();
|
||||
await showEncounterText(`${namespace}:option.1.dream_complete`);
|
||||
await showEncounterText(`${namespace}:option.1.dreamComplete`);
|
||||
|
||||
await doNewTeamPostProcess(transformations);
|
||||
setEncounterRewards({
|
||||
@ -329,7 +329,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
onBeforeRewards,
|
||||
);
|
||||
|
||||
await showEncounterText(`${namespace}:option.2.selected_2`, null, undefined, true);
|
||||
await showEncounterText(`${namespace}:option.2.selected2`, null, undefined, true);
|
||||
await initBattleWithEnemyConfig(enemyPartyConfig);
|
||||
},
|
||||
)
|
||||
|
@ -12,6 +12,7 @@ import { WeatherType } from "#enums/weather-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { PokemonFormChangeItemModifier } from "#modifiers/modifier";
|
||||
import { type Constructor, coerceArray } from "#utils/common";
|
||||
import { toCamelCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
export abstract class SpeciesFormChangeTrigger {
|
||||
@ -143,11 +144,7 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge
|
||||
super();
|
||||
this.move = move;
|
||||
this.known = known;
|
||||
const moveKey = MoveId[this.move]
|
||||
.split("_")
|
||||
.filter(f => f)
|
||||
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
|
||||
.join("") as unknown as string;
|
||||
const moveKey = toCamelCase(MoveId[this.move]);
|
||||
this.description = known
|
||||
? i18next.t("pokemonEvolutions:Forms.moveLearned", {
|
||||
move: i18next.t(`move:${moveKey}.name`),
|
||||
|
@ -54,7 +54,7 @@ export class Arena {
|
||||
public bgm: string;
|
||||
public ignoreAbilities: boolean;
|
||||
public ignoringEffectSource: BattlerIndex | null;
|
||||
public playerTerasUsed: number;
|
||||
public playerTerasUsed = 0;
|
||||
/**
|
||||
* Saves the number of times a party pokemon faints during a arena encounter.
|
||||
* {@linkcode globalScene.currentBattle.enemyFaints} is the corresponding faint counter for the enemy (this resets every wave).
|
||||
@ -68,12 +68,11 @@ export class Arena {
|
||||
|
||||
public readonly eventTarget: EventTarget = new EventTarget();
|
||||
|
||||
constructor(biome: BiomeId, bgm: string, playerFaints = 0) {
|
||||
constructor(biome: BiomeId, playerFaints = 0) {
|
||||
this.biomeType = biome;
|
||||
this.bgm = bgm;
|
||||
this.bgm = BiomeId[biome].toLowerCase();
|
||||
this.trainerPool = biomeTrainerPools[biome];
|
||||
this.updatePoolsForTimeOfDay();
|
||||
this.playerTerasUsed = 0;
|
||||
this.playerFaints = playerFaints;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { Ability, PreAttackModifyDamageAbAttrParams } from "#abilities/ability";
|
||||
import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#abilities/apply-ab-attrs";
|
||||
import type { AnySound, BattleScene } from "#app/battle-scene";
|
||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||
import { PLAYER_PARTY_MAX_SIZE, RARE_CANDY_FRIENDSHIP_CAP } from "#app/constants";
|
||||
import { timedEventManager } from "#app/global-event-manager";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
@ -139,6 +139,8 @@ import { populateVariantColors, variantColorCache, variantData } from "#sprites/
|
||||
import { achvs } from "#system/achv";
|
||||
import type { StarterDataEntry, StarterMoveset } from "#system/game-data";
|
||||
import type { PokemonData } from "#system/pokemon-data";
|
||||
import { RibbonData } from "#system/ribbons/ribbon-data";
|
||||
import { awardRibbonsToSpeciesLine } from "#system/ribbons/ribbon-methods";
|
||||
import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#types/ability-types";
|
||||
import type { DamageCalculationResult, DamageResult } from "#types/damage-result";
|
||||
import type { IllusionData } from "#types/illusion-data";
|
||||
@ -1825,7 +1827,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
// Overrides moveset based on arrays specified in overrides.ts
|
||||
let overrideArray: MoveId | Array<MoveId> = this.isPlayer()
|
||||
? Overrides.MOVESET_OVERRIDE
|
||||
: Overrides.OPP_MOVESET_OVERRIDE;
|
||||
: Overrides.ENEMY_MOVESET_OVERRIDE;
|
||||
overrideArray = coerceArray(overrideArray);
|
||||
if (overrideArray.length > 0) {
|
||||
if (!this.isPlayer()) {
|
||||
@ -2030,8 +2032,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (Overrides.ABILITY_OVERRIDE && this.isPlayer()) {
|
||||
return allAbilities[Overrides.ABILITY_OVERRIDE];
|
||||
}
|
||||
if (Overrides.OPP_ABILITY_OVERRIDE && this.isEnemy()) {
|
||||
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
|
||||
if (Overrides.ENEMY_ABILITY_OVERRIDE && this.isEnemy()) {
|
||||
return allAbilities[Overrides.ENEMY_ABILITY_OVERRIDE];
|
||||
}
|
||||
if (this.isFusion()) {
|
||||
if (!isNullOrUndefined(this.fusionCustomPokemonData?.ability) && this.fusionCustomPokemonData.ability !== -1) {
|
||||
@ -2060,8 +2062,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (Overrides.PASSIVE_ABILITY_OVERRIDE && this.isPlayer()) {
|
||||
return allAbilities[Overrides.PASSIVE_ABILITY_OVERRIDE];
|
||||
}
|
||||
if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && this.isEnemy()) {
|
||||
return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE];
|
||||
if (Overrides.ENEMY_PASSIVE_ABILITY_OVERRIDE && this.isEnemy()) {
|
||||
return allAbilities[Overrides.ENEMY_PASSIVE_ABILITY_OVERRIDE];
|
||||
}
|
||||
if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) {
|
||||
return allAbilities[this.customPokemonData.passive];
|
||||
@ -2128,14 +2130,14 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
// returns override if valid for current case
|
||||
if (
|
||||
(Overrides.HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isPlayer()) ||
|
||||
(Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isEnemy())
|
||||
(Overrides.ENEMY_HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isEnemy())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
((Overrides.PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) &&
|
||||
this.isPlayer()) ||
|
||||
((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) &&
|
||||
((Overrides.ENEMY_PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.ENEMY_HAS_PASSIVE_ABILITY_OVERRIDE) &&
|
||||
this.isEnemy())
|
||||
) {
|
||||
return true;
|
||||
@ -3001,8 +3003,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
if (forStarter && this.isPlayer() && Overrides.STARTER_FUSION_SPECIES_OVERRIDE) {
|
||||
fusionOverride = getPokemonSpecies(Overrides.STARTER_FUSION_SPECIES_OVERRIDE);
|
||||
} else if (this.isEnemy() && Overrides.OPP_FUSION_SPECIES_OVERRIDE) {
|
||||
fusionOverride = getPokemonSpecies(Overrides.OPP_FUSION_SPECIES_OVERRIDE);
|
||||
} else if (this.isEnemy() && Overrides.ENEMY_FUSION_SPECIES_OVERRIDE) {
|
||||
fusionOverride = getPokemonSpecies(Overrides.ENEMY_FUSION_SPECIES_OVERRIDE);
|
||||
}
|
||||
|
||||
this.fusionSpecies =
|
||||
@ -5822,45 +5824,59 @@ export class PlayerPokemon extends Pokemon {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
addFriendship(friendship: number): void {
|
||||
if (friendship > 0) {
|
||||
const starterSpeciesId = this.species.getRootSpeciesId();
|
||||
const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0;
|
||||
const starterData = [
|
||||
globalScene.gameData.starterData[starterSpeciesId],
|
||||
fusionStarterSpeciesId ? globalScene.gameData.starterData[fusionStarterSpeciesId] : null,
|
||||
].filter(d => !!d);
|
||||
const amount = new NumberHolder(friendship);
|
||||
globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
|
||||
const candyFriendshipMultiplier = globalScene.gameMode.isClassic
|
||||
? timedEventManager.getClassicFriendshipMultiplier()
|
||||
: 1;
|
||||
const fusionReduction = fusionStarterSpeciesId
|
||||
? timedEventManager.areFusionsBoosted()
|
||||
? 1.5 // Divide candy gain for fusions by 1.5 during events
|
||||
: 2 // 2 for fusions outside events
|
||||
: 1; // 1 for non-fused mons
|
||||
const starterAmount = new NumberHolder(Math.floor((amount.value * candyFriendshipMultiplier) / fusionReduction));
|
||||
|
||||
// Add friendship to this PlayerPokemon
|
||||
this.friendship = Math.min(this.friendship + amount.value, 255);
|
||||
if (this.friendship === 255) {
|
||||
globalScene.validateAchv(achvs.MAX_FRIENDSHIP);
|
||||
}
|
||||
// Add to candy progress for this mon's starter species and its fused species (if it has one)
|
||||
starterData.forEach((sd: StarterDataEntry, i: number) => {
|
||||
const speciesId = !i ? starterSpeciesId : (fusionStarterSpeciesId as SpeciesId);
|
||||
sd.friendship = (sd.friendship || 0) + starterAmount.value;
|
||||
if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[speciesId])) {
|
||||
globalScene.gameData.addStarterCandy(getPokemonSpecies(speciesId), 1);
|
||||
sd.friendship = 0;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Lose friendship upon fainting
|
||||
/**
|
||||
* Add friendship to this Pokemon
|
||||
*
|
||||
* @remarks
|
||||
* This adds friendship to the pokemon's friendship stat (used for evolution, return, etc.) and candy progress.
|
||||
* For fusions, candy progress for each species in the fusion is halved.
|
||||
*
|
||||
* @param friendship - The amount of friendship to add. Negative values will reduce friendship, though not below 0.
|
||||
* @param capped - If true, don't allow the friendship gain to exceed 200. Used to cap friendship gains from rare candies.
|
||||
*/
|
||||
addFriendship(friendship: number, capped = false): void {
|
||||
// Short-circuit friendship loss, which doesn't impact candy friendship
|
||||
if (friendship <= 0) {
|
||||
this.friendship = Math.max(this.friendship + friendship, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const starterSpeciesId = this.species.getRootSpeciesId();
|
||||
const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0;
|
||||
const starterGameData = globalScene.gameData.starterData;
|
||||
const starterData: [StarterDataEntry, SpeciesId][] = [[starterGameData[starterSpeciesId], starterSpeciesId]];
|
||||
if (fusionStarterSpeciesId) {
|
||||
starterData.push([starterGameData[fusionStarterSpeciesId], fusionStarterSpeciesId]);
|
||||
}
|
||||
const amount = new NumberHolder(friendship);
|
||||
globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
|
||||
friendship = amount.value;
|
||||
|
||||
const newFriendship = this.friendship + friendship;
|
||||
// If capped is true, only adjust friendship if the new friendship is less than or equal to 200.
|
||||
if (!capped || newFriendship <= RARE_CANDY_FRIENDSHIP_CAP) {
|
||||
this.friendship = Math.min(newFriendship, 255);
|
||||
if (newFriendship >= 255) {
|
||||
globalScene.validateAchv(achvs.MAX_FRIENDSHIP);
|
||||
awardRibbonsToSpeciesLine(this.species.speciesId, RibbonData.FRIENDSHIP);
|
||||
}
|
||||
}
|
||||
|
||||
let candyFriendshipMultiplier = globalScene.gameMode.isClassic
|
||||
? timedEventManager.getClassicFriendshipMultiplier()
|
||||
: 1;
|
||||
if (fusionStarterSpeciesId) {
|
||||
candyFriendshipMultiplier /= timedEventManager.areFusionsBoosted() ? 1.5 : 2;
|
||||
}
|
||||
const candyFriendshipAmount = Math.floor(friendship * candyFriendshipMultiplier);
|
||||
// Add to candy progress for this mon's starter species and its fused species (if it has one)
|
||||
starterData.forEach(([sd, id]: [StarterDataEntry, SpeciesId]) => {
|
||||
sd.friendship = (sd.friendship || 0) + candyFriendshipAmount;
|
||||
if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[id])) {
|
||||
globalScene.gameData.addStarterCandy(getPokemonSpecies(id), 1);
|
||||
sd.friendship = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getPossibleEvolution(evolution: SpeciesFormEvolution | null): Promise<Pokemon> {
|
||||
@ -6241,22 +6257,22 @@ export class EnemyPokemon extends Pokemon {
|
||||
this.setBoss(boss, dataSource?.bossSegments);
|
||||
}
|
||||
|
||||
if (Overrides.OPP_STATUS_OVERRIDE) {
|
||||
this.status = new Status(Overrides.OPP_STATUS_OVERRIDE, 0, 4);
|
||||
if (Overrides.ENEMY_STATUS_OVERRIDE) {
|
||||
this.status = new Status(Overrides.ENEMY_STATUS_OVERRIDE, 0, 4);
|
||||
}
|
||||
|
||||
if (Overrides.OPP_GENDER_OVERRIDE !== null) {
|
||||
this.gender = Overrides.OPP_GENDER_OVERRIDE;
|
||||
if (Overrides.ENEMY_GENDER_OVERRIDE !== null) {
|
||||
this.gender = Overrides.ENEMY_GENDER_OVERRIDE;
|
||||
}
|
||||
|
||||
const speciesId = this.species.speciesId;
|
||||
|
||||
if (
|
||||
speciesId in Overrides.OPP_FORM_OVERRIDES &&
|
||||
!isNullOrUndefined(Overrides.OPP_FORM_OVERRIDES[speciesId]) &&
|
||||
this.species.forms[Overrides.OPP_FORM_OVERRIDES[speciesId]]
|
||||
speciesId in Overrides.ENEMY_FORM_OVERRIDES &&
|
||||
!isNullOrUndefined(Overrides.ENEMY_FORM_OVERRIDES[speciesId]) &&
|
||||
this.species.forms[Overrides.ENEMY_FORM_OVERRIDES[speciesId]]
|
||||
) {
|
||||
this.formIndex = Overrides.OPP_FORM_OVERRIDES[speciesId];
|
||||
this.formIndex = Overrides.ENEMY_FORM_OVERRIDES[speciesId];
|
||||
} else if (globalScene.gameMode.isDaily && globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) {
|
||||
const eventBoss = getDailyEventSeedBoss(globalScene.seed);
|
||||
if (!isNullOrUndefined(eventBoss)) {
|
||||
@ -6266,21 +6282,21 @@ export class EnemyPokemon extends Pokemon {
|
||||
|
||||
if (!dataSource) {
|
||||
this.generateAndPopulateMoveset();
|
||||
if (shinyLock || Overrides.OPP_SHINY_OVERRIDE === false) {
|
||||
if (shinyLock || Overrides.ENEMY_SHINY_OVERRIDE === false) {
|
||||
this.shiny = false;
|
||||
} else {
|
||||
this.trySetShiny();
|
||||
}
|
||||
|
||||
if (!this.shiny && Overrides.OPP_SHINY_OVERRIDE) {
|
||||
if (!this.shiny && Overrides.ENEMY_SHINY_OVERRIDE) {
|
||||
this.shiny = true;
|
||||
this.initShinySparkle();
|
||||
}
|
||||
|
||||
if (this.shiny) {
|
||||
this.variant = this.generateShinyVariant();
|
||||
if (Overrides.OPP_VARIANT_OVERRIDE !== null) {
|
||||
this.variant = Overrides.OPP_VARIANT_OVERRIDE;
|
||||
if (Overrides.ENEMY_VARIANT_OVERRIDE !== null) {
|
||||
this.variant = Overrides.ENEMY_VARIANT_OVERRIDE;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,6 +90,7 @@ export class LoadingScene extends SceneBase {
|
||||
this.loadAtlas("shiny_icons", "ui");
|
||||
this.loadImage("ha_capsule", "ui", "ha_capsule.png");
|
||||
this.loadImage("champion_ribbon", "ui", "champion_ribbon.png");
|
||||
this.loadImage("champion_ribbon_emerald", "ui", "champion_ribbon_emerald.png");
|
||||
this.loadImage("icon_spliced", "ui");
|
||||
this.loadImage("icon_lock", "ui", "icon_lock.png");
|
||||
this.loadImage("icon_stop", "ui", "icon_stop.png");
|
||||
@ -122,6 +123,7 @@ export class LoadingScene extends SceneBase {
|
||||
this.loadImage("party_bg_double", "ui");
|
||||
this.loadImage("party_bg_double_manage", "ui");
|
||||
this.loadAtlas("party_slot_main", "ui");
|
||||
this.loadAtlas("party_slot_main_short", "ui");
|
||||
this.loadAtlas("party_slot", "ui");
|
||||
this.loadImage("party_slot_overlay_lv", "ui");
|
||||
this.loadImage("party_slot_hp_bar", "ui");
|
||||
@ -447,7 +449,9 @@ export class LoadingScene extends SceneBase {
|
||||
);
|
||||
|
||||
if (!mobile) {
|
||||
loadingGraphics.map(g => g.setVisible(false));
|
||||
loadingGraphics.forEach(g => {
|
||||
g.setVisible(false);
|
||||
});
|
||||
}
|
||||
|
||||
const intro = this.add.video(0, 0);
|
||||
|
@ -121,8 +121,8 @@ export class ModifierBar extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
updateModifierOverflowVisibility(ignoreLimit: boolean) {
|
||||
const modifierIcons = this.getAll().reverse();
|
||||
for (const modifier of modifierIcons.map(m => m as Phaser.GameObjects.Container).slice(iconOverflowIndex)) {
|
||||
const modifierIcons = this.getAll().reverse() as Phaser.GameObjects.Container[];
|
||||
for (const modifier of modifierIcons.slice(iconOverflowIndex)) {
|
||||
modifier.setVisible(ignoreLimit);
|
||||
}
|
||||
}
|
||||
@ -2304,7 +2304,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier {
|
||||
playerPokemon.levelExp = 0;
|
||||
}
|
||||
|
||||
playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY);
|
||||
playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY, true);
|
||||
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"LevelUpPhase",
|
||||
@ -3755,7 +3755,7 @@ export class EnemyFusionChanceModifier extends EnemyPersistentModifier {
|
||||
export function overrideModifiers(isPlayer = true): void {
|
||||
const modifiersOverride: ModifierOverride[] = isPlayer
|
||||
? Overrides.STARTING_MODIFIER_OVERRIDE
|
||||
: Overrides.OPP_MODIFIER_OVERRIDE;
|
||||
: Overrides.ENEMY_MODIFIER_OVERRIDE;
|
||||
if (!modifiersOverride || modifiersOverride.length === 0 || !globalScene) {
|
||||
return;
|
||||
}
|
||||
@ -3797,7 +3797,7 @@ export function overrideModifiers(isPlayer = true): void {
|
||||
export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void {
|
||||
const heldItemsOverride: ModifierOverride[] = isPlayer
|
||||
? Overrides.STARTING_HELD_ITEMS_OVERRIDE
|
||||
: Overrides.OPP_HELD_ITEMS_OVERRIDE;
|
||||
: Overrides.ENEMY_HELD_ITEMS_OVERRIDE;
|
||||
if (!heldItemsOverride || heldItemsOverride.length === 0 || !globalScene) {
|
||||
return;
|
||||
}
|
||||
|
@ -179,25 +179,24 @@ class DefaultOverrides {
|
||||
// --------------------------
|
||||
// OPPONENT / ENEMY OVERRIDES
|
||||
// --------------------------
|
||||
// TODO: rename `OPP_` to `ENEMY_`
|
||||
readonly OPP_SPECIES_OVERRIDE: SpeciesId | number = 0;
|
||||
readonly ENEMY_SPECIES_OVERRIDE: SpeciesId | number = 0;
|
||||
/**
|
||||
* This will make all opponents fused Pokemon
|
||||
*/
|
||||
readonly OPP_FUSION_OVERRIDE: boolean = false;
|
||||
readonly ENEMY_FUSION_OVERRIDE: boolean = false;
|
||||
/**
|
||||
* This will override the species of the fusion only when the opponent is already a fusion
|
||||
*/
|
||||
readonly OPP_FUSION_SPECIES_OVERRIDE: SpeciesId | number = 0;
|
||||
readonly OPP_LEVEL_OVERRIDE: number = 0;
|
||||
readonly OPP_ABILITY_OVERRIDE: AbilityId = AbilityId.NONE;
|
||||
readonly OPP_PASSIVE_ABILITY_OVERRIDE: AbilityId = AbilityId.NONE;
|
||||
readonly OPP_HAS_PASSIVE_ABILITY_OVERRIDE: boolean | null = null;
|
||||
readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
|
||||
readonly OPP_GENDER_OVERRIDE: Gender | null = null;
|
||||
readonly OPP_MOVESET_OVERRIDE: MoveId | Array<MoveId> = [];
|
||||
readonly OPP_SHINY_OVERRIDE: boolean | null = null;
|
||||
readonly OPP_VARIANT_OVERRIDE: Variant | null = null;
|
||||
readonly ENEMY_FUSION_SPECIES_OVERRIDE: SpeciesId | number = 0;
|
||||
readonly ENEMY_LEVEL_OVERRIDE: number = 0;
|
||||
readonly ENEMY_ABILITY_OVERRIDE: AbilityId = AbilityId.NONE;
|
||||
readonly ENEMY_PASSIVE_ABILITY_OVERRIDE: AbilityId = AbilityId.NONE;
|
||||
readonly ENEMY_HAS_PASSIVE_ABILITY_OVERRIDE: boolean | null = null;
|
||||
readonly ENEMY_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
|
||||
readonly ENEMY_GENDER_OVERRIDE: Gender | null = null;
|
||||
readonly ENEMY_MOVESET_OVERRIDE: MoveId | Array<MoveId> = [];
|
||||
readonly ENEMY_SHINY_OVERRIDE: boolean | null = null;
|
||||
readonly ENEMY_VARIANT_OVERRIDE: Variant | null = null;
|
||||
/**
|
||||
* Overrides the IVs of enemy pokemon. Values must never be outside the range `0` to `31`!
|
||||
* - If set to a number between `0` and `31`, set all IVs of all enemy pokemon to that number.
|
||||
@ -207,7 +206,7 @@ class DefaultOverrides {
|
||||
readonly ENEMY_IVS_OVERRIDE: number | number[] | null = null;
|
||||
/** Override the nature of all enemy pokemon to the specified nature. Disabled if `null`. */
|
||||
readonly ENEMY_NATURE_OVERRIDE: Nature | null = null;
|
||||
readonly OPP_FORM_OVERRIDES: Partial<Record<SpeciesId, number>> = {};
|
||||
readonly ENEMY_FORM_OVERRIDES: Partial<Record<SpeciesId, number>> = {};
|
||||
/**
|
||||
* Override to give the enemy Pokemon a given amount of health segments
|
||||
*
|
||||
@ -215,7 +214,7 @@ class DefaultOverrides {
|
||||
* 1: the Pokemon will have a single health segment and therefore will not be a boss
|
||||
* 2+: the Pokemon will be a boss with the given number of health segments
|
||||
*/
|
||||
readonly OPP_HEALTH_SEGMENTS_OVERRIDE: number = 0;
|
||||
readonly ENEMY_HEALTH_SEGMENTS_OVERRIDE: number = 0;
|
||||
|
||||
// -------------
|
||||
// EGG OVERRIDES
|
||||
@ -277,12 +276,12 @@ class DefaultOverrides {
|
||||
*
|
||||
* Note that any previous modifiers are cleared.
|
||||
*/
|
||||
readonly OPP_MODIFIER_OVERRIDE: ModifierOverride[] = [];
|
||||
readonly ENEMY_MODIFIER_OVERRIDE: ModifierOverride[] = [];
|
||||
|
||||
/** Override array of {@linkcode ModifierOverride}s used to provide held items to first party member when starting a new game. */
|
||||
readonly STARTING_HELD_ITEMS_OVERRIDE: ModifierOverride[] = [];
|
||||
/** Override array of {@linkcode ModifierOverride}s used to provide held items to enemies on spawn. */
|
||||
readonly OPP_HELD_ITEMS_OVERRIDE: ModifierOverride[] = [];
|
||||
readonly ENEMY_HELD_ITEMS_OVERRIDE: ModifierOverride[] = [];
|
||||
|
||||
/**
|
||||
* Override array of {@linkcode ModifierOverride}s used to replace the generated item rolls after a wave.
|
||||
|
@ -229,7 +229,7 @@ export class EncounterPhase extends BattlePhase {
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const overridedBossSegments = Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE > 1;
|
||||
const overridedBossSegments = Overrides.ENEMY_HEALTH_SEGMENTS_OVERRIDE > 1;
|
||||
// for double battles, reduce the health segments for boss Pokemon unless there is an override
|
||||
if (!overridedBossSegments && battle.enemyParty.filter(p => p.isBoss()).length > 1) {
|
||||
for (const enemyPokemon of battle.enemyParty) {
|
||||
|
@ -19,8 +19,11 @@ import { ChallengeData } from "#system/challenge-data";
|
||||
import type { SessionSaveData } from "#system/game-data";
|
||||
import { ModifierData as PersistentModifierData } from "#system/modifier-data";
|
||||
import { PokemonData } from "#system/pokemon-data";
|
||||
import { RibbonData, type RibbonFlag } from "#system/ribbons/ribbon-data";
|
||||
import { awardRibbonsToSpeciesLine } from "#system/ribbons/ribbon-methods";
|
||||
import { TrainerData } from "#system/trainer-data";
|
||||
import { trainerConfigs } from "#trainers/trainer-config";
|
||||
import { checkSpeciesValidForChallenge, isNuzlockeChallenge } from "#utils/challenge-utils";
|
||||
import { isLocal, isLocalServerConnected } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import i18next from "i18next";
|
||||
@ -111,6 +114,40 @@ export class GameOverPhase extends BattlePhase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submethod of {@linkcode handleGameOver} that awards ribbons to Pokémon in the player's party based on the current
|
||||
* game mode and challenges.
|
||||
*/
|
||||
private awardRibbons(): void {
|
||||
let ribbonFlags = 0;
|
||||
if (globalScene.gameMode.isClassic) {
|
||||
ribbonFlags |= RibbonData.CLASSIC;
|
||||
}
|
||||
if (isNuzlockeChallenge()) {
|
||||
ribbonFlags |= RibbonData.NUZLOCKE;
|
||||
}
|
||||
for (const challenge of globalScene.gameMode.challenges) {
|
||||
const ribbon = challenge.ribbonAwarded;
|
||||
if (challenge.value && ribbon) {
|
||||
ribbonFlags |= ribbon;
|
||||
}
|
||||
}
|
||||
// Award ribbons to all Pokémon in the player's party that are considered valid
|
||||
// for the current game mode and challenges.
|
||||
for (const pokemon of globalScene.getPlayerParty()) {
|
||||
const species = pokemon.species;
|
||||
if (
|
||||
checkSpeciesValidForChallenge(
|
||||
species,
|
||||
globalScene.gameData.getSpeciesDexAttrProps(species, pokemon.getDexAttr()),
|
||||
false,
|
||||
)
|
||||
) {
|
||||
awardRibbonsToSpeciesLine(species.speciesId, ribbonFlags as RibbonFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleGameOver(): void {
|
||||
const doGameOver = (newClear: boolean) => {
|
||||
globalScene.disableMenu = true;
|
||||
@ -122,12 +159,12 @@ export class GameOverPhase extends BattlePhase {
|
||||
globalScene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY);
|
||||
globalScene.gameData.gameStats.sessionsWon++;
|
||||
for (const pokemon of globalScene.getPlayerParty()) {
|
||||
this.awardRibbon(pokemon);
|
||||
|
||||
this.awardFirstClassicCompletion(pokemon);
|
||||
if (pokemon.species.getRootSpeciesId() !== pokemon.species.getRootSpeciesId(true)) {
|
||||
this.awardRibbon(pokemon, true);
|
||||
this.awardFirstClassicCompletion(pokemon, true);
|
||||
}
|
||||
}
|
||||
this.awardRibbons();
|
||||
} else if (globalScene.gameMode.isDaily && newClear) {
|
||||
globalScene.gameData.gameStats.dailyRunSessionsWon++;
|
||||
}
|
||||
@ -263,7 +300,7 @@ export class GameOverPhase extends BattlePhase {
|
||||
}
|
||||
}
|
||||
|
||||
awardRibbon(pokemon: Pokemon, forStarter = false): void {
|
||||
awardFirstClassicCompletion(pokemon: Pokemon, forStarter = false): void {
|
||||
const speciesId = getPokemonSpecies(pokemon.species.speciesId);
|
||||
const speciesRibbonCount = globalScene.gameData.incrementRibbonCount(speciesId, forStarter);
|
||||
// first time classic win, award voucher
|
||||
|
@ -56,15 +56,15 @@ export class PokerogueSessionSavedataApi extends ApiBase {
|
||||
|
||||
/**
|
||||
* Update a session savedata.
|
||||
* @param params The {@linkcode UpdateSessionSavedataRequest} to send
|
||||
* @param rawSavedata The raw savedata (as `string`)
|
||||
* @param params - The request to send
|
||||
* @param rawSavedata - The raw, unencrypted savedata
|
||||
* @returns An error message if something went wrong
|
||||
*/
|
||||
public async update(params: UpdateSessionSavedataRequest, rawSavedata: string) {
|
||||
public async update(params: UpdateSessionSavedataRequest, rawSavedata: string): Promise<string> {
|
||||
try {
|
||||
const urlSearchParams = this.toUrlSearchParams(params);
|
||||
const response = await this.doPost(`/savedata/session/update?${urlSearchParams}`, rawSavedata);
|
||||
|
||||
const response = await this.doPost(`/savedata/session/update?${urlSearchParams}`, rawSavedata);
|
||||
return await response.text();
|
||||
} catch (err) {
|
||||
console.warn("Could not update session savedata!", err);
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
FlipStatChallenge,
|
||||
FreshStartChallenge,
|
||||
InverseBattleChallenge,
|
||||
LimitedCatchChallenge,
|
||||
SingleGenerationChallenge,
|
||||
SingleTypeChallenge,
|
||||
} from "#data/challenge";
|
||||
@ -14,6 +13,7 @@ import { PlayerGender } from "#enums/player-gender";
|
||||
import { getShortenedStatKey, Stat } from "#enums/stat";
|
||||
import { TurnHeldItemTransferModifier } from "#modifiers/modifier";
|
||||
import type { ConditionFn } from "#types/common";
|
||||
import { isNuzlockeChallenge } from "#utils/challenge-utils";
|
||||
import { NumberHolder } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
import type { Modifier } from "typescript";
|
||||
@ -924,18 +924,7 @@ export const achvs = {
|
||||
globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0),
|
||||
).setSecret(),
|
||||
// TODO: Decide on icon
|
||||
NUZLOCKE: new ChallengeAchv(
|
||||
"NUZLOCKE",
|
||||
"",
|
||||
"NUZLOCKE.description",
|
||||
"leaf_stone",
|
||||
100,
|
||||
c =>
|
||||
c instanceof LimitedCatchChallenge &&
|
||||
c.value > 0 &&
|
||||
globalScene.gameMode.challenges.some(c => c.id === Challenges.HARDCORE && c.value > 0) &&
|
||||
globalScene.gameMode.challenges.some(c => c.id === Challenges.FRESH_START && c.value > 0),
|
||||
),
|
||||
NUZLOCKE: new ChallengeAchv("NUZLOCKE", "", "NUZLOCKE.description", "leaf_stone", 100, isNuzlockeChallenge),
|
||||
BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 50).setSecret(),
|
||||
};
|
||||
|
||||
|
@ -48,6 +48,7 @@ import { EggData } from "#system/egg-data";
|
||||
import { GameStats } from "#system/game-stats";
|
||||
import { ModifierData as PersistentModifierData } from "#system/modifier-data";
|
||||
import { PokemonData } from "#system/pokemon-data";
|
||||
import { RibbonData } from "#system/ribbons/ribbon-data";
|
||||
import { resetSettings, SettingKeys, setSetting } from "#system/settings";
|
||||
import { SettingGamepad, setSettingGamepad, settingGamepadDefaults } from "#system/settings-gamepad";
|
||||
import type { SettingKeyboard } from "#system/settings-keyboard";
|
||||
@ -127,7 +128,8 @@ export interface SessionSaveData {
|
||||
battleType: BattleType;
|
||||
trainer: TrainerData;
|
||||
gameVersion: string;
|
||||
runNameText: string;
|
||||
/** The player-chosen name of the run */
|
||||
name: string;
|
||||
timestamp: number;
|
||||
challenges: ChallengeData[];
|
||||
mysteryEncounterType: MysteryEncounterType | -1; // Only defined when current wave is ME,
|
||||
@ -207,10 +209,12 @@ export interface StarterData {
|
||||
[key: number]: StarterDataEntry;
|
||||
}
|
||||
|
||||
export interface TutorialFlags {
|
||||
[key: string]: boolean;
|
||||
}
|
||||
// TODO: Rework into a bitmask
|
||||
export type TutorialFlags = {
|
||||
[key in Tutorial]: boolean;
|
||||
};
|
||||
|
||||
// TODO: Rework into a bitmask
|
||||
export interface SeenDialogues {
|
||||
[key: string]: boolean;
|
||||
}
|
||||
@ -400,121 +404,121 @@ export class GameData {
|
||||
}
|
||||
|
||||
public initSystem(systemDataStr: string, cachedSystemDataStr?: string): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
try {
|
||||
let systemData = this.parseSystemData(systemDataStr);
|
||||
const { promise, resolve } = Promise.withResolvers<boolean>();
|
||||
try {
|
||||
let systemData = this.parseSystemData(systemDataStr);
|
||||
|
||||
if (cachedSystemDataStr) {
|
||||
const cachedSystemData = this.parseSystemData(cachedSystemDataStr);
|
||||
if (cachedSystemData.timestamp > systemData.timestamp) {
|
||||
console.debug("Use cached system");
|
||||
systemData = cachedSystemData;
|
||||
systemDataStr = cachedSystemDataStr;
|
||||
} else {
|
||||
this.clearLocalData();
|
||||
}
|
||||
}
|
||||
|
||||
console.debug(systemData);
|
||||
|
||||
localStorage.setItem(`data_${loggedInUser?.username}`, encrypt(systemDataStr, bypassLogin));
|
||||
|
||||
const lsItemKey = `runHistoryData_${loggedInUser?.username}`;
|
||||
const lsItem = localStorage.getItem(lsItemKey);
|
||||
if (!lsItem) {
|
||||
localStorage.setItem(lsItemKey, "");
|
||||
}
|
||||
|
||||
applySystemVersionMigration(systemData);
|
||||
|
||||
this.trainerId = systemData.trainerId;
|
||||
this.secretId = systemData.secretId;
|
||||
|
||||
this.gender = systemData.gender;
|
||||
|
||||
this.saveSetting(SettingKeys.Player_Gender, systemData.gender === PlayerGender.FEMALE ? 1 : 0);
|
||||
|
||||
if (!systemData.starterData) {
|
||||
this.initStarterData();
|
||||
|
||||
if (systemData["starterMoveData"]) {
|
||||
const starterMoveData = systemData["starterMoveData"];
|
||||
for (const s of Object.keys(starterMoveData)) {
|
||||
this.starterData[s].moveset = starterMoveData[s];
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData["starterEggMoveData"]) {
|
||||
const starterEggMoveData = systemData["starterEggMoveData"];
|
||||
for (const s of Object.keys(starterEggMoveData)) {
|
||||
this.starterData[s].eggMoves = starterEggMoveData[s];
|
||||
}
|
||||
}
|
||||
|
||||
this.migrateStarterAbilities(systemData, this.starterData);
|
||||
|
||||
const starterIds = Object.keys(this.starterData).map(s => Number.parseInt(s) as SpeciesId);
|
||||
for (const s of starterIds) {
|
||||
this.starterData[s].candyCount += systemData.dexData[s].caughtCount;
|
||||
this.starterData[s].candyCount += systemData.dexData[s].hatchedCount * 2;
|
||||
if (systemData.dexData[s].caughtAttr & DexAttr.SHINY) {
|
||||
this.starterData[s].candyCount += 4;
|
||||
}
|
||||
}
|
||||
if (cachedSystemDataStr) {
|
||||
const cachedSystemData = this.parseSystemData(cachedSystemDataStr);
|
||||
if (cachedSystemData.timestamp > systemData.timestamp) {
|
||||
console.debug("Use cached system");
|
||||
systemData = cachedSystemData;
|
||||
systemDataStr = cachedSystemDataStr;
|
||||
} else {
|
||||
this.starterData = systemData.starterData;
|
||||
this.clearLocalData();
|
||||
}
|
||||
|
||||
if (systemData.gameStats) {
|
||||
this.gameStats = systemData.gameStats;
|
||||
}
|
||||
|
||||
if (systemData.unlocks) {
|
||||
for (const key of Object.keys(systemData.unlocks)) {
|
||||
if (this.unlocks.hasOwnProperty(key)) {
|
||||
this.unlocks[key] = systemData.unlocks[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.achvUnlocks) {
|
||||
for (const a of Object.keys(systemData.achvUnlocks)) {
|
||||
if (achvs.hasOwnProperty(a)) {
|
||||
this.achvUnlocks[a] = systemData.achvUnlocks[a];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.voucherUnlocks) {
|
||||
for (const v of Object.keys(systemData.voucherUnlocks)) {
|
||||
if (vouchers.hasOwnProperty(v)) {
|
||||
this.voucherUnlocks[v] = systemData.voucherUnlocks[v];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.voucherCounts) {
|
||||
getEnumKeys(VoucherType).forEach(key => {
|
||||
const index = VoucherType[key];
|
||||
this.voucherCounts[index] = systemData.voucherCounts[index] || 0;
|
||||
});
|
||||
}
|
||||
|
||||
this.eggs = systemData.eggs ? systemData.eggs.map(e => e.toEgg()) : [];
|
||||
|
||||
this.eggPity = systemData.eggPity ? systemData.eggPity.slice(0) : [0, 0, 0, 0];
|
||||
this.unlockPity = systemData.unlockPity ? systemData.unlockPity.slice(0) : [0, 0, 0, 0];
|
||||
|
||||
this.dexData = Object.assign(this.dexData, systemData.dexData);
|
||||
this.consolidateDexData(this.dexData);
|
||||
this.defaultDexData = null;
|
||||
|
||||
resolve(true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
|
||||
console.debug(systemData);
|
||||
|
||||
localStorage.setItem(`data_${loggedInUser?.username}`, encrypt(systemDataStr, bypassLogin));
|
||||
|
||||
const lsItemKey = `runHistoryData_${loggedInUser?.username}`;
|
||||
const lsItem = localStorage.getItem(lsItemKey);
|
||||
if (!lsItem) {
|
||||
localStorage.setItem(lsItemKey, "");
|
||||
}
|
||||
|
||||
applySystemVersionMigration(systemData);
|
||||
|
||||
this.trainerId = systemData.trainerId;
|
||||
this.secretId = systemData.secretId;
|
||||
|
||||
this.gender = systemData.gender;
|
||||
|
||||
this.saveSetting(SettingKeys.Player_Gender, systemData.gender === PlayerGender.FEMALE ? 1 : 0);
|
||||
|
||||
if (!systemData.starterData) {
|
||||
this.initStarterData();
|
||||
|
||||
if (systemData["starterMoveData"]) {
|
||||
const starterMoveData = systemData["starterMoveData"];
|
||||
for (const s of Object.keys(starterMoveData)) {
|
||||
this.starterData[s].moveset = starterMoveData[s];
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData["starterEggMoveData"]) {
|
||||
const starterEggMoveData = systemData["starterEggMoveData"];
|
||||
for (const s of Object.keys(starterEggMoveData)) {
|
||||
this.starterData[s].eggMoves = starterEggMoveData[s];
|
||||
}
|
||||
}
|
||||
|
||||
this.migrateStarterAbilities(systemData, this.starterData);
|
||||
|
||||
const starterIds = Object.keys(this.starterData).map(s => Number.parseInt(s) as SpeciesId);
|
||||
for (const s of starterIds) {
|
||||
this.starterData[s].candyCount += systemData.dexData[s].caughtCount;
|
||||
this.starterData[s].candyCount += systemData.dexData[s].hatchedCount * 2;
|
||||
if (systemData.dexData[s].caughtAttr & DexAttr.SHINY) {
|
||||
this.starterData[s].candyCount += 4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.starterData = systemData.starterData;
|
||||
}
|
||||
|
||||
if (systemData.gameStats) {
|
||||
this.gameStats = systemData.gameStats;
|
||||
}
|
||||
|
||||
if (systemData.unlocks) {
|
||||
for (const key of Object.keys(systemData.unlocks)) {
|
||||
if (this.unlocks.hasOwnProperty(key)) {
|
||||
this.unlocks[key] = systemData.unlocks[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.achvUnlocks) {
|
||||
for (const a of Object.keys(systemData.achvUnlocks)) {
|
||||
if (achvs.hasOwnProperty(a)) {
|
||||
this.achvUnlocks[a] = systemData.achvUnlocks[a];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.voucherUnlocks) {
|
||||
for (const v of Object.keys(systemData.voucherUnlocks)) {
|
||||
if (vouchers.hasOwnProperty(v)) {
|
||||
this.voucherUnlocks[v] = systemData.voucherUnlocks[v];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (systemData.voucherCounts) {
|
||||
getEnumKeys(VoucherType).forEach(key => {
|
||||
const index = VoucherType[key];
|
||||
this.voucherCounts[index] = systemData.voucherCounts[index] || 0;
|
||||
});
|
||||
}
|
||||
|
||||
this.eggs = systemData.eggs ? systemData.eggs.map(e => e.toEgg()) : [];
|
||||
|
||||
this.eggPity = systemData.eggPity ? systemData.eggPity.slice(0) : [0, 0, 0, 0];
|
||||
this.unlockPity = systemData.unlockPity ? systemData.unlockPity.slice(0) : [0, 0, 0, 0];
|
||||
|
||||
this.dexData = Object.assign(this.dexData, systemData.dexData);
|
||||
this.consolidateDexData(this.dexData);
|
||||
this.defaultDexData = null;
|
||||
|
||||
resolve(true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
resolve(false);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -625,6 +629,9 @@ export class GameData {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
if (k === "ribbons") {
|
||||
return RibbonData.fromJSON(v);
|
||||
}
|
||||
|
||||
return k.endsWith("Attr") && !["natureAttr", "abilityAttr", "passiveAttr"].includes(k) ? BigInt(v ?? 0) : v;
|
||||
}) as SystemSaveData;
|
||||
@ -823,52 +830,51 @@ export class GameData {
|
||||
return true; // TODO: is `true` the correct return value?
|
||||
}
|
||||
|
||||
private loadGamepadSettings(): boolean {
|
||||
Object.values(SettingGamepad)
|
||||
.map(setting => setting as SettingGamepad)
|
||||
.forEach(setting => setSettingGamepad(setting, settingGamepadDefaults[setting]));
|
||||
private loadGamepadSettings(): void {
|
||||
Object.values(SettingGamepad).forEach(setting => {
|
||||
setSettingGamepad(setting, settingGamepadDefaults[setting]);
|
||||
});
|
||||
|
||||
if (!localStorage.hasOwnProperty("settingsGamepad")) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
const settingsGamepad = JSON.parse(localStorage.getItem("settingsGamepad")!); // TODO: is this bang correct?
|
||||
|
||||
for (const setting of Object.keys(settingsGamepad)) {
|
||||
setSettingGamepad(setting as SettingGamepad, settingsGamepad[setting]);
|
||||
}
|
||||
|
||||
return true; // TODO: is `true` the correct return value?
|
||||
}
|
||||
|
||||
public saveTutorialFlag(tutorial: Tutorial, flag: boolean): boolean {
|
||||
const key = getDataTypeKey(GameDataType.TUTORIALS);
|
||||
let tutorials: object = {};
|
||||
if (localStorage.hasOwnProperty(key)) {
|
||||
tutorials = JSON.parse(localStorage.getItem(key)!); // TODO: is this bang correct?
|
||||
/**
|
||||
* Save the specified tutorial as having the specified completion status.
|
||||
* @param tutorial - The {@linkcode Tutorial} whose completion status is being saved
|
||||
* @param status - The completion status to set
|
||||
*/
|
||||
public saveTutorialFlag(tutorial: Tutorial, status: boolean): void {
|
||||
// Grab the prior save data tutorial
|
||||
const saveDataKey = getDataTypeKey(GameDataType.TUTORIALS);
|
||||
const tutorials: TutorialFlags = localStorage.hasOwnProperty(saveDataKey)
|
||||
? JSON.parse(localStorage.getItem(saveDataKey)!)
|
||||
: {};
|
||||
|
||||
// TODO: We shouldn't be storing this like that
|
||||
for (const key of Object.values(Tutorial)) {
|
||||
if (key === tutorial) {
|
||||
tutorials[key] = status;
|
||||
} else {
|
||||
tutorials[key] ??= false;
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(Tutorial)
|
||||
.map(t => t as Tutorial)
|
||||
.forEach(t => {
|
||||
const key = Tutorial[t];
|
||||
if (key === tutorial) {
|
||||
tutorials[key] = flag;
|
||||
} else {
|
||||
tutorials[key] ??= false;
|
||||
}
|
||||
});
|
||||
|
||||
localStorage.setItem(key, JSON.stringify(tutorials));
|
||||
|
||||
return true;
|
||||
localStorage.setItem(saveDataKey, JSON.stringify(tutorials));
|
||||
}
|
||||
|
||||
public getTutorialFlags(): TutorialFlags {
|
||||
const key = getDataTypeKey(GameDataType.TUTORIALS);
|
||||
const ret: TutorialFlags = {};
|
||||
Object.values(Tutorial)
|
||||
.map(tutorial => tutorial as Tutorial)
|
||||
.forEach(tutorial => (ret[Tutorial[tutorial]] = false));
|
||||
const ret: TutorialFlags = Object.values(Tutorial).reduce((acc, tutorial) => {
|
||||
acc[Tutorial[tutorial]] = false;
|
||||
return acc;
|
||||
}, {} as TutorialFlags);
|
||||
|
||||
if (!localStorage.hasOwnProperty(key)) {
|
||||
return ret;
|
||||
@ -981,51 +987,45 @@ 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 (slotId < 0) {
|
||||
return false;
|
||||
}
|
||||
if (newName === "") {
|
||||
return true;
|
||||
}
|
||||
const sessionData: SessionSaveData | null = await this.getSession(slotId);
|
||||
|
||||
if (!sessionData) {
|
||||
return resolve(false);
|
||||
}
|
||||
if (!sessionData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newName === "") {
|
||||
return resolve(true);
|
||||
}
|
||||
sessionData.name = newName;
|
||||
// update timestamp by 1 to ensure the session is saved
|
||||
sessionData.timestamp += 1;
|
||||
const updatedDataStr = JSON.stringify(sessionData);
|
||||
const encrypted = encrypt(updatedDataStr, bypassLogin);
|
||||
const secretId = this.secretId;
|
||||
const trainerId = this.trainerId;
|
||||
|
||||
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),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
const response = await pokerogueApi.savedata.session.update(
|
||||
{ slot: slotId, trainerId, secretId, clientSessionId },
|
||||
updatedDataStr,
|
||||
);
|
||||
|
||||
if (response) {
|
||||
return false;
|
||||
}
|
||||
localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypted);
|
||||
const success = await updateUserInfo();
|
||||
return !(success !== null && !success);
|
||||
}
|
||||
|
||||
loadSession(slotId: number, sessionData?: SessionSaveData): Promise<boolean> {
|
||||
@ -1633,6 +1633,7 @@ export class GameData {
|
||||
caughtCount: 0,
|
||||
hatchedCount: 0,
|
||||
ivs: [0, 0, 0, 0, 0, 0],
|
||||
ribbons: new RibbonData(0),
|
||||
};
|
||||
}
|
||||
|
||||
@ -1877,6 +1878,12 @@ export class GameData {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the number of classic ribbons won with this species.
|
||||
* @param species - The species to increment the ribbon count for
|
||||
* @param forStarter - If true, will increment the ribbon count for the root species of the given species
|
||||
* @returns The number of classic wins after incrementing.
|
||||
*/
|
||||
incrementRibbonCount(species: PokemonSpecies, forStarter = false): number {
|
||||
const speciesIdToIncrement: SpeciesId = species.getRootSpeciesId(forStarter);
|
||||
|
||||
@ -2176,6 +2183,9 @@ export class GameData {
|
||||
if (!entry.hasOwnProperty("natureAttr") || (entry.caughtAttr && !entry.natureAttr)) {
|
||||
entry.natureAttr = this.defaultDexData?.[k].natureAttr || 1 << randInt(25, 1);
|
||||
}
|
||||
if (!entry.hasOwnProperty("ribbons")) {
|
||||
entry.ribbons = new RibbonData(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
148
src/system/ribbons/ribbon-data.ts
Normal file
148
src/system/ribbons/ribbon-data.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import type { Brander } from "#types/type-helpers";
|
||||
|
||||
export type RibbonFlag = (number & Brander<"RibbonFlag">) | 0;
|
||||
|
||||
/**
|
||||
* Class for ribbon data management. Usually constructed via the {@linkcode fromJSON} method.
|
||||
*
|
||||
* @remarks
|
||||
* Stores information about the ribbons earned by a species using a bitfield.
|
||||
*/
|
||||
export class RibbonData {
|
||||
/** Internal bitfield storing the unlock state for each ribbon */
|
||||
private payload: number;
|
||||
|
||||
//#region Ribbons
|
||||
//#region Monotype challenge ribbons
|
||||
/** Ribbon for winning the normal monotype challenge */
|
||||
public static readonly MONO_NORMAL = 0x1 as RibbonFlag;
|
||||
/** Ribbon for winning the fighting monotype challenge */
|
||||
public static readonly MONO_FIGHTING = 0x2 as RibbonFlag;
|
||||
/** Ribbon for winning the flying monotype challenge */
|
||||
public static readonly MONO_FLYING = 0x4 as RibbonFlag;
|
||||
/** Ribbon for winning the poision monotype challenge */
|
||||
public static readonly MONO_POISON = 0x8 as RibbonFlag;
|
||||
/** Ribbon for winning the ground monotype challenge */
|
||||
public static readonly MONO_GROUND = 0x10 as RibbonFlag;
|
||||
/** Ribbon for winning the rock monotype challenge */
|
||||
public static readonly MONO_ROCK = 0x20 as RibbonFlag;
|
||||
/** Ribbon for winning the bug monotype challenge */
|
||||
public static readonly MONO_BUG = 0x40 as RibbonFlag;
|
||||
/** Ribbon for winning the ghost monotype challenge */
|
||||
public static readonly MONO_GHOST = 0x80 as RibbonFlag;
|
||||
/** Ribbon for winning the steel monotype challenge */
|
||||
public static readonly MONO_STEEL = 0x100 as RibbonFlag;
|
||||
/** Ribbon for winning the fire monotype challenge */
|
||||
public static readonly MONO_FIRE = 0x200 as RibbonFlag;
|
||||
/** Ribbon for winning the water monotype challenge */
|
||||
public static readonly MONO_WATER = 0x400 as RibbonFlag;
|
||||
/** Ribbon for winning the grass monotype challenge */
|
||||
public static readonly MONO_GRASS = 0x800 as RibbonFlag;
|
||||
/** Ribbon for winning the electric monotype challenge */
|
||||
public static readonly MONO_ELECTRIC = 0x1000 as RibbonFlag;
|
||||
/** Ribbon for winning the psychic monotype challenge */
|
||||
public static readonly MONO_PSYCHIC = 0x2000 as RibbonFlag;
|
||||
/** Ribbon for winning the ice monotype challenge */
|
||||
public static readonly MONO_ICE = 0x4000 as RibbonFlag;
|
||||
/** Ribbon for winning the dragon monotype challenge */
|
||||
public static readonly MONO_DRAGON = 0x8000 as RibbonFlag;
|
||||
/** Ribbon for winning the dark monotype challenge */
|
||||
public static readonly MONO_DARK = 0x10000 as RibbonFlag;
|
||||
/** Ribbon for winning the fairy monotype challenge */
|
||||
public static readonly MONO_FAIRY = 0x20000 as RibbonFlag;
|
||||
//#endregion Monotype ribbons
|
||||
|
||||
//#region Monogen ribbons
|
||||
/** Ribbon for winning the the mono gen 1 challenge */
|
||||
public static readonly MONO_GEN_1 = 0x40000 as RibbonFlag;
|
||||
/** Ribbon for winning the the mono gen 2 challenge */
|
||||
public static readonly MONO_GEN_2 = 0x80000 as RibbonFlag;
|
||||
/** Ribbon for winning the mono gen 3 challenge */
|
||||
public static readonly MONO_GEN_3 = 0x100000 as RibbonFlag;
|
||||
/** Ribbon for winning the mono gen 4 challenge */
|
||||
public static readonly MONO_GEN_4 = 0x200000 as RibbonFlag;
|
||||
/** Ribbon for winning the mono gen 5 challenge */
|
||||
public static readonly MONO_GEN_5 = 0x400000 as RibbonFlag;
|
||||
/** Ribbon for winning the mono gen 6 challenge */
|
||||
public static readonly MONO_GEN_6 = 0x800000 as RibbonFlag;
|
||||
/** Ribbon for winning the mono gen 7 challenge */
|
||||
public static readonly MONO_GEN_7 = 0x1000000 as RibbonFlag;
|
||||
/** Ribbon for winning the mono gen 8 challenge */
|
||||
public static readonly MONO_GEN_8 = 0x2000000 as RibbonFlag;
|
||||
/** Ribbon for winning the mono gen 9 challenge */
|
||||
public static readonly MONO_GEN_9 = 0x4000000 as RibbonFlag;
|
||||
//#endregion Monogen ribbons
|
||||
|
||||
/** Ribbon for winning classic */
|
||||
public static readonly CLASSIC = 0x8000000 as RibbonFlag;
|
||||
/** Ribbon for winning the nuzzlocke challenge */
|
||||
public static readonly NUZLOCKE = 0x10000000 as RibbonFlag;
|
||||
/** Ribbon for reaching max friendship */
|
||||
public static readonly FRIENDSHIP = 0x20000000 as RibbonFlag;
|
||||
/** Ribbon for winning the flip stats challenge */
|
||||
public static readonly FLIP_STATS = 0x40000000 as RibbonFlag;
|
||||
/** Ribbon for winning the inverse challenge */
|
||||
public static readonly INVERSE = 0x80000000 as RibbonFlag;
|
||||
/** Ribbon for winning the fresh start challenge */
|
||||
public static readonly FRESH_START = 0x100000000 as RibbonFlag;
|
||||
/** Ribbon for winning the hardcore challenge */
|
||||
public static readonly HARDCORE = 0x200000000 as RibbonFlag;
|
||||
/** Ribbon for winning the limited catch challenge */
|
||||
public static readonly LIMITED_CATCH = 0x400000000 as RibbonFlag;
|
||||
/** Ribbon for winning the limited support challenge set to no heal */
|
||||
public static readonly NO_HEAL = 0x800000000 as RibbonFlag;
|
||||
/** Ribbon for winning the limited uspport challenge set to no shop */
|
||||
public static readonly NO_SHOP = 0x1000000000 as RibbonFlag;
|
||||
/** Ribbon for winning the limited support challenge set to both*/
|
||||
public static readonly NO_SUPPORT = 0x2000000000 as RibbonFlag;
|
||||
|
||||
// NOTE: max possible ribbon flag is 0x20000000000000 (53 total ribbons)
|
||||
// Once this is exceeded, bitfield needs to be changed to a bigint or even a uint array
|
||||
// Note that this has no impact on serialization as it is stored in hex.
|
||||
|
||||
//#endregion Ribbons
|
||||
|
||||
/** Create a new instance of RibbonData. Generally, {@linkcode fromJSON} is used instead. */
|
||||
constructor(value: number) {
|
||||
this.payload = value;
|
||||
}
|
||||
|
||||
/** Serialize the bitfield payload as a hex encoded string */
|
||||
public toJSON(): string {
|
||||
return this.payload.toString(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a hexadecimal string representation of the bitfield into a `RibbonData` instance
|
||||
*
|
||||
* @param value - Hexadecimal string representation of the bitfield (without the leading 0x)
|
||||
* @returns A new instance of `RibbonData` initialized with the provided bitfield.
|
||||
*/
|
||||
public static fromJSON(value: string): RibbonData {
|
||||
try {
|
||||
return new RibbonData(Number.parseInt(value, 16));
|
||||
} catch {
|
||||
return new RibbonData(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Award one or more ribbons to the ribbon data by setting the corresponding flags in the bitfield.
|
||||
*
|
||||
* @param flags - The flags to set. Can be a single flag or multiple flags.
|
||||
*/
|
||||
public award(...flags: [RibbonFlag, ...RibbonFlag[]]): void {
|
||||
for (const f of flags) {
|
||||
this.payload |= f;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific ribbon has been awarded
|
||||
* @param flag - The ribbon to check
|
||||
* @returns Whether the specified flag has been awarded
|
||||
*/
|
||||
public has(flag: RibbonFlag): boolean {
|
||||
return !!(this.payload & flag);
|
||||
}
|
||||
}
|
20
src/system/ribbons/ribbon-methods.ts
Normal file
20
src/system/ribbons/ribbon-methods.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { pokemonPrevolutions } from "#balance/pokemon-evolutions";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
import type { RibbonFlag } from "#system/ribbons/ribbon-data";
|
||||
import { isNullOrUndefined } from "#utils/common";
|
||||
|
||||
/**
|
||||
* Award one or more ribbons to a species and its pre-evolutions
|
||||
*
|
||||
* @param id - The ID of the species to award ribbons to
|
||||
* @param ribbons - The ribbon(s) to award (use bitwise OR to combine multiple)
|
||||
*/
|
||||
export function awardRibbonsToSpeciesLine(id: SpeciesId, ribbons: RibbonFlag): void {
|
||||
const dexData = globalScene.gameData.dexData;
|
||||
dexData[id].ribbons.award(ribbons);
|
||||
// Mark all pre-evolutions of the Pokémon with the same ribbon flags.
|
||||
for (let prevoId = pokemonPrevolutions[id]; !isNullOrUndefined(prevoId); prevoId = pokemonPrevolutions[prevoId]) {
|
||||
dexData[id].ribbons.award(ribbons);
|
||||
}
|
||||
}
|
@ -31,6 +31,11 @@ import { toTitleCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
|
||||
|
||||
const DISCARD_BUTTON_X = 60;
|
||||
const DISCARD_BUTTON_X_DOUBLES = 64;
|
||||
const DISCARD_BUTTON_Y = -73;
|
||||
const DISCARD_BUTTON_Y_DOUBLES = -58;
|
||||
|
||||
const defaultMessage = i18next.t("partyUiHandler:choosePokemon");
|
||||
|
||||
/**
|
||||
@ -301,7 +306,7 @@ export class PartyUiHandler extends MessageUiHandler {
|
||||
const partyMessageText = addTextObject(10, 8, defaultMessage, TextStyle.WINDOW, { maxLines: 2 });
|
||||
partyMessageText.setName("text-party-msg");
|
||||
|
||||
partyMessageText.setOrigin(0, 0);
|
||||
partyMessageText.setOrigin(0);
|
||||
partyMessageBoxContainer.add(partyMessageText);
|
||||
|
||||
this.message = partyMessageText;
|
||||
@ -317,10 +322,8 @@ export class PartyUiHandler extends MessageUiHandler {
|
||||
this.iconAnimHandler = new PokemonIconAnimHandler();
|
||||
this.iconAnimHandler.setup();
|
||||
|
||||
const partyDiscardModeButton = new PartyDiscardModeButton(60, -globalScene.game.canvas.height / 15 - 1, this);
|
||||
|
||||
const partyDiscardModeButton = new PartyDiscardModeButton(DISCARD_BUTTON_X, DISCARD_BUTTON_Y, this);
|
||||
partyContainer.add(partyDiscardModeButton);
|
||||
|
||||
this.partyDiscardModeButton = partyDiscardModeButton;
|
||||
|
||||
// prepare move overlay
|
||||
@ -1233,7 +1236,7 @@ export class PartyUiHandler extends MessageUiHandler {
|
||||
}
|
||||
if (!this.optionsCursorObj) {
|
||||
this.optionsCursorObj = globalScene.add.image(0, 0, "cursor");
|
||||
this.optionsCursorObj.setOrigin(0, 0);
|
||||
this.optionsCursorObj.setOrigin(0);
|
||||
this.optionsContainer.add(this.optionsCursorObj);
|
||||
}
|
||||
this.optionsCursorObj.setPosition(
|
||||
@ -1605,7 +1608,7 @@ export class PartyUiHandler extends MessageUiHandler {
|
||||
optionText.setColor("#40c8f8");
|
||||
optionText.setShadowColor("#006090");
|
||||
}
|
||||
optionText.setOrigin(0, 0);
|
||||
optionText.setOrigin(0);
|
||||
|
||||
/** For every item that has stack bigger than 1, display the current quantity selection */
|
||||
const itemModifiers = this.getItemModifiers(pokemon);
|
||||
@ -1802,6 +1805,7 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
private selected: boolean;
|
||||
private transfer: boolean;
|
||||
private slotIndex: number;
|
||||
private isBenched: boolean;
|
||||
private pokemon: PlayerPokemon;
|
||||
|
||||
private slotBg: Phaser.GameObjects.Image;
|
||||
@ -1812,6 +1816,7 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
public slotHpText: Phaser.GameObjects.Text;
|
||||
public slotDescriptionLabel: Phaser.GameObjects.Text; // this is used to show text instead of the HP bar i.e. for showing "Able"/"Not Able" for TMs when you try to learn them
|
||||
|
||||
private slotBgKey: string;
|
||||
private pokemonIcon: Phaser.GameObjects.Container;
|
||||
private iconAnimHandler: PokemonIconAnimHandler;
|
||||
|
||||
@ -1822,19 +1827,34 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
partyUiMode: PartyUiMode,
|
||||
tmMoveId: MoveId,
|
||||
) {
|
||||
super(
|
||||
globalScene,
|
||||
slotIndex >= globalScene.currentBattle.getBattlerCount() ? 230.5 : 64,
|
||||
slotIndex >= globalScene.currentBattle.getBattlerCount()
|
||||
? -184 +
|
||||
(globalScene.currentBattle.double ? -40 : 0) +
|
||||
(28 + (globalScene.currentBattle.double ? 8 : 0)) * slotIndex
|
||||
: partyUiMode === PartyUiMode.MODIFIER_TRANSFER
|
||||
? -124 + (globalScene.currentBattle.double ? -20 : 0) + slotIndex * 55
|
||||
: -124 + (globalScene.currentBattle.double ? -8 : 0) + slotIndex * 64,
|
||||
);
|
||||
const isBenched = slotIndex >= globalScene.currentBattle.getBattlerCount();
|
||||
const isDoubleBattle = globalScene.currentBattle.double;
|
||||
const isItemManageMode = partyUiMode === PartyUiMode.MODIFIER_TRANSFER || partyUiMode === PartyUiMode.DISCARD;
|
||||
|
||||
/*
|
||||
* Here we determine the position of the slot.
|
||||
* The x coordinate depends on whether the pokemon is on the field or in the bench.
|
||||
* The y coordinate depends on various factors, such as the number of pokémon on the field,
|
||||
* and whether the transfer/discard button is also on the screen.
|
||||
*/
|
||||
const slotPositionX = isBenched ? 143 : 9;
|
||||
|
||||
let slotPositionY: number;
|
||||
if (isBenched) {
|
||||
slotPositionY = -196 + (isDoubleBattle ? -40 : 0);
|
||||
slotPositionY += (28 + (isDoubleBattle ? 8 : 0)) * slotIndex;
|
||||
} else {
|
||||
slotPositionY = -148.5;
|
||||
if (isDoubleBattle) {
|
||||
slotPositionY += isItemManageMode ? -20 : -8;
|
||||
}
|
||||
slotPositionY += (isItemManageMode ? (isDoubleBattle ? 47 : 55) : 64) * slotIndex;
|
||||
}
|
||||
|
||||
super(globalScene, slotPositionX, slotPositionY);
|
||||
|
||||
this.slotIndex = slotIndex;
|
||||
this.isBenched = isBenched;
|
||||
this.pokemon = pokemon;
|
||||
this.iconAnimHandler = iconAnimHandler;
|
||||
|
||||
@ -1848,27 +1868,75 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
setup(partyUiMode: PartyUiMode, tmMoveId: MoveId) {
|
||||
const currentLanguage = i18next.resolvedLanguage ?? "en";
|
||||
const offsetJa = currentLanguage === "ja";
|
||||
const isItemManageMode = partyUiMode === PartyUiMode.MODIFIER_TRANSFER || partyUiMode === PartyUiMode.DISCARD;
|
||||
|
||||
const battlerCount = globalScene.currentBattle.getBattlerCount();
|
||||
this.slotBgKey = this.isBenched
|
||||
? "party_slot"
|
||||
: isItemManageMode && globalScene.currentBattle.double
|
||||
? "party_slot_main_short"
|
||||
: "party_slot_main";
|
||||
const fullSlotBgKey = this.pokemon.hp ? this.slotBgKey : `${this.slotBgKey}${"_fnt"}`;
|
||||
this.slotBg = globalScene.add.sprite(0, 0, this.slotBgKey, fullSlotBgKey);
|
||||
this.slotBg.setOrigin(0);
|
||||
this.add(this.slotBg);
|
||||
|
||||
const slotKey = `party_slot${this.slotIndex >= battlerCount ? "" : "_main"}`;
|
||||
const genderSymbol = getGenderSymbol(this.pokemon.getGender(true));
|
||||
const isFusion = this.pokemon.isFusion();
|
||||
|
||||
const slotBg = globalScene.add.sprite(0, 0, slotKey, `${slotKey}${this.pokemon.hp ? "" : "_fnt"}`);
|
||||
this.slotBg = slotBg;
|
||||
// Here we define positions and offsets
|
||||
// Base values are for the active pokemon; they are changed for benched pokemon,
|
||||
// or for active pokemon if in a double battle in item management mode.
|
||||
|
||||
this.add(slotBg);
|
||||
// icon position relative to slot background
|
||||
let slotPb = { x: 4, y: 4 };
|
||||
// name position relative to slot background
|
||||
let namePosition = { x: 24, y: 10 + (offsetJa ? 2 : 0) };
|
||||
// maximum allowed length of name; must accomodate fusion symbol
|
||||
let maxNameTextWidth = 76 - (isFusion ? 8 : 0);
|
||||
// "Lv." label position relative to slot background
|
||||
let levelLabelPosition = { x: 24 + 8, y: 10 + 12 };
|
||||
// offset from "Lv." to the level number; should not be changed.
|
||||
const levelTextToLevelLabelOffset = { x: 9, y: offsetJa ? 1.5 : 0 };
|
||||
// offests from "Lv." to gender, spliced and status icons, these depend on the type of slot.
|
||||
let genderTextToLevelLabelOffset = { x: 68 - (isFusion ? 8 : 0), y: -9 };
|
||||
let splicedIconToLevelLabelOffset = { x: 68, y: 3.5 - 12 };
|
||||
let statusIconToLevelLabelOffset = { x: 55, y: 0 };
|
||||
// offset from the name to the shiny icon (on the left); should not be changed.
|
||||
const shinyIconToNameOffset = { x: -9, y: 3 };
|
||||
// hp bar position relative to slot background
|
||||
let hpBarPosition = { x: 8, y: 31 };
|
||||
// offsets of hp bar overlay (showing the remaining hp) and number; should not be changed.
|
||||
const hpOverlayToBarOffset = { x: 16, y: 2 };
|
||||
const hpTextToBarOffset = { x: -3, y: -2 + (offsetJa ? 2 : 0) };
|
||||
// description position relative to slot background
|
||||
let descriptionLabelPosition = { x: 32, y: 46 };
|
||||
|
||||
const slotPb = globalScene.add.sprite(
|
||||
this.slotIndex >= battlerCount ? -85.5 : -51,
|
||||
this.slotIndex >= battlerCount ? 0 : -20.5,
|
||||
"party_pb",
|
||||
);
|
||||
this.slotPb = slotPb;
|
||||
// If in item management mode, the active slots are shorter
|
||||
if (isItemManageMode && globalScene.currentBattle.double && !this.isBenched) {
|
||||
namePosition.y -= 8;
|
||||
levelLabelPosition.y -= 8;
|
||||
hpBarPosition.y -= 8;
|
||||
descriptionLabelPosition.y -= 8;
|
||||
}
|
||||
|
||||
this.add(slotPb);
|
||||
// Benched slots have significantly different parameters
|
||||
if (this.isBenched) {
|
||||
slotPb = { x: 2, y: 12 };
|
||||
namePosition = { x: 21, y: 2 + (offsetJa ? 2 : 0) };
|
||||
maxNameTextWidth = 52;
|
||||
levelLabelPosition = { x: 21 + 8, y: 2 + 12 };
|
||||
genderTextToLevelLabelOffset = { x: 36, y: 0 };
|
||||
splicedIconToLevelLabelOffset = { x: 36 + (genderSymbol ? 8 : 0), y: 0.5 };
|
||||
statusIconToLevelLabelOffset = { x: 43, y: 0 };
|
||||
hpBarPosition = { x: 72, y: 6 };
|
||||
descriptionLabelPosition = { x: 94, y: 16 };
|
||||
}
|
||||
|
||||
this.pokemonIcon = globalScene.addPokemonIcon(this.pokemon, slotPb.x, slotPb.y, 0.5, 0.5, true);
|
||||
this.slotPb = globalScene.add.sprite(0, 0, "party_pb");
|
||||
this.slotPb.setPosition(slotPb.x, slotPb.y);
|
||||
this.add(this.slotPb);
|
||||
|
||||
this.pokemonIcon = globalScene.addPokemonIcon(this.pokemon, this.slotPb.x, this.slotPb.y, 0.5, 0.5, true);
|
||||
this.add(this.pokemonIcon);
|
||||
|
||||
this.iconAnimHandler.addOrUpdate(this.pokemonIcon, PokemonIconAnimMode.PASSIVE);
|
||||
@ -1882,7 +1950,7 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.PARTY);
|
||||
nameTextWidth = nameSizeTest.displayWidth;
|
||||
|
||||
while (nameTextWidth > (this.slotIndex >= battlerCount ? 52 : 76 - (this.pokemon.fusionSpecies ? 8 : 0))) {
|
||||
while (nameTextWidth > maxNameTextWidth) {
|
||||
displayName = `${displayName.slice(0, displayName.endsWith(".") ? -2 : -1).trimEnd()}.`;
|
||||
nameSizeTest.setText(displayName);
|
||||
nameTextWidth = nameSizeTest.displayWidth;
|
||||
@ -1891,78 +1959,59 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
nameSizeTest.destroy();
|
||||
|
||||
this.slotName = addTextObject(0, 0, displayName, TextStyle.PARTY);
|
||||
this.slotName.setPositionRelative(
|
||||
slotBg,
|
||||
this.slotIndex >= battlerCount ? 21 : 24,
|
||||
(this.slotIndex >= battlerCount ? 2 : 10) + (offsetJa ? 2 : 0),
|
||||
);
|
||||
this.slotName.setOrigin(0, 0);
|
||||
this.slotName.setPositionRelative(this.slotBg, namePosition.x, namePosition.y);
|
||||
this.slotName.setOrigin(0);
|
||||
|
||||
const slotLevelLabel = globalScene.add.image(0, 0, "party_slot_overlay_lv");
|
||||
slotLevelLabel.setPositionRelative(
|
||||
slotBg,
|
||||
(this.slotIndex >= battlerCount ? 21 : 24) + 8,
|
||||
(this.slotIndex >= battlerCount ? 2 : 10) + 12,
|
||||
);
|
||||
slotLevelLabel.setOrigin(0, 0);
|
||||
const slotLevelLabel = globalScene.add
|
||||
.image(0, 0, "party_slot_overlay_lv")
|
||||
.setPositionRelative(this.slotBg, levelLabelPosition.x, levelLabelPosition.y)
|
||||
.setOrigin(0);
|
||||
|
||||
const slotLevelText = addTextObject(
|
||||
0,
|
||||
0,
|
||||
this.pokemon.level.toString(),
|
||||
this.pokemon.level < globalScene.getMaxExpLevel() ? TextStyle.PARTY : TextStyle.PARTY_RED,
|
||||
);
|
||||
slotLevelText.setPositionRelative(slotLevelLabel, 9, offsetJa ? 1.5 : 0);
|
||||
slotLevelText.setOrigin(0, 0.25);
|
||||
|
||||
)
|
||||
.setPositionRelative(slotLevelLabel, levelTextToLevelLabelOffset.x, levelTextToLevelLabelOffset.y)
|
||||
.setOrigin(0, 0.25);
|
||||
slotInfoContainer.add([this.slotName, slotLevelLabel, slotLevelText]);
|
||||
|
||||
const genderSymbol = getGenderSymbol(this.pokemon.getGender(true));
|
||||
|
||||
if (genderSymbol) {
|
||||
const slotGenderText = addTextObject(0, 0, genderSymbol, TextStyle.PARTY);
|
||||
slotGenderText.setColor(getGenderColor(this.pokemon.getGender(true)));
|
||||
slotGenderText.setShadowColor(getGenderColor(this.pokemon.getGender(true), true));
|
||||
if (this.slotIndex >= battlerCount) {
|
||||
slotGenderText.setPositionRelative(slotLevelLabel, 36, 0);
|
||||
} else {
|
||||
slotGenderText.setPositionRelative(this.slotName, 76 - (this.pokemon.fusionSpecies ? 8 : 0), 3);
|
||||
}
|
||||
slotGenderText.setOrigin(0, 0.25);
|
||||
|
||||
const slotGenderText = addTextObject(0, 0, genderSymbol, TextStyle.PARTY)
|
||||
.setColor(getGenderColor(this.pokemon.getGender(true)))
|
||||
.setShadowColor(getGenderColor(this.pokemon.getGender(true), true))
|
||||
.setPositionRelative(slotLevelLabel, genderTextToLevelLabelOffset.x, genderTextToLevelLabelOffset.y)
|
||||
.setOrigin(0, 0.25);
|
||||
slotInfoContainer.add(slotGenderText);
|
||||
}
|
||||
|
||||
if (this.pokemon.fusionSpecies) {
|
||||
const splicedIcon = globalScene.add.image(0, 0, "icon_spliced");
|
||||
splicedIcon.setScale(0.5);
|
||||
splicedIcon.setOrigin(0, 0);
|
||||
if (this.slotIndex >= battlerCount) {
|
||||
splicedIcon.setPositionRelative(slotLevelLabel, 36 + (genderSymbol ? 8 : 0), 0.5);
|
||||
} else {
|
||||
splicedIcon.setPositionRelative(this.slotName, 76, 3.5);
|
||||
}
|
||||
|
||||
if (isFusion) {
|
||||
const splicedIcon = globalScene.add
|
||||
.image(0, 0, "icon_spliced")
|
||||
.setScale(0.5)
|
||||
.setOrigin(0)
|
||||
.setPositionRelative(slotLevelLabel, splicedIconToLevelLabelOffset.x, splicedIconToLevelLabelOffset.y);
|
||||
slotInfoContainer.add(splicedIcon);
|
||||
}
|
||||
|
||||
if (this.pokemon.status) {
|
||||
const statusIndicator = globalScene.add.sprite(0, 0, getLocalizedSpriteKey("statuses"));
|
||||
statusIndicator.setFrame(StatusEffect[this.pokemon.status?.effect].toLowerCase());
|
||||
statusIndicator.setOrigin(0, 0);
|
||||
statusIndicator.setPositionRelative(slotLevelLabel, this.slotIndex >= battlerCount ? 43 : 55, 0);
|
||||
|
||||
const statusIndicator = globalScene.add
|
||||
.sprite(0, 0, getLocalizedSpriteKey("statuses"))
|
||||
.setFrame(StatusEffect[this.pokemon.status?.effect].toLowerCase())
|
||||
.setOrigin(0)
|
||||
.setPositionRelative(slotLevelLabel, statusIconToLevelLabelOffset.x, statusIconToLevelLabelOffset.y);
|
||||
slotInfoContainer.add(statusIndicator);
|
||||
}
|
||||
|
||||
if (this.pokemon.isShiny()) {
|
||||
const doubleShiny = this.pokemon.isDoubleShiny(false);
|
||||
|
||||
const shinyStar = globalScene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`);
|
||||
shinyStar.setOrigin(0, 0);
|
||||
shinyStar.setPositionRelative(this.slotName, -9, 3);
|
||||
shinyStar.setTint(getVariantTint(this.pokemon.getBaseVariant()));
|
||||
|
||||
const shinyStar = globalScene.add
|
||||
.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`)
|
||||
.setOrigin(0)
|
||||
.setPositionRelative(this.slotName, shinyIconToNameOffset.x, shinyIconToNameOffset.y)
|
||||
.setTint(getVariantTint(this.pokemon.getBaseVariant()));
|
||||
slotInfoContainer.add(shinyStar);
|
||||
|
||||
if (doubleShiny) {
|
||||
@ -1971,50 +2020,38 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
.setOrigin(0)
|
||||
.setPosition(shinyStar.x, shinyStar.y)
|
||||
.setTint(getVariantTint(this.pokemon.fusionVariant));
|
||||
|
||||
slotInfoContainer.add(fusionShinyStar);
|
||||
}
|
||||
}
|
||||
|
||||
this.slotHpBar = globalScene.add.image(0, 0, "party_slot_hp_bar");
|
||||
this.slotHpBar.setPositionRelative(
|
||||
slotBg,
|
||||
this.slotIndex >= battlerCount ? 72 : 8,
|
||||
this.slotIndex >= battlerCount ? 6 : 31,
|
||||
);
|
||||
this.slotHpBar.setOrigin(0, 0);
|
||||
this.slotHpBar.setVisible(false);
|
||||
this.slotHpBar = globalScene.add
|
||||
.image(0, 0, "party_slot_hp_bar")
|
||||
.setOrigin(0)
|
||||
.setVisible(false)
|
||||
.setPositionRelative(this.slotBg, hpBarPosition.x, hpBarPosition.y);
|
||||
|
||||
const hpRatio = this.pokemon.getHpRatio();
|
||||
|
||||
this.slotHpOverlay = globalScene.add.sprite(
|
||||
0,
|
||||
0,
|
||||
"party_slot_hp_overlay",
|
||||
hpRatio > 0.5 ? "high" : hpRatio > 0.25 ? "medium" : "low",
|
||||
);
|
||||
this.slotHpOverlay.setPositionRelative(this.slotHpBar, 16, 2);
|
||||
this.slotHpOverlay.setOrigin(0, 0);
|
||||
this.slotHpOverlay.setScale(hpRatio, 1);
|
||||
this.slotHpOverlay.setVisible(false);
|
||||
this.slotHpOverlay = globalScene.add
|
||||
.sprite(0, 0, "party_slot_hp_overlay", hpRatio > 0.5 ? "high" : hpRatio > 0.25 ? "medium" : "low")
|
||||
.setOrigin(0)
|
||||
.setPositionRelative(this.slotHpBar, hpOverlayToBarOffset.x, hpOverlayToBarOffset.y)
|
||||
.setScale(hpRatio, 1)
|
||||
.setVisible(false);
|
||||
|
||||
this.slotHpText = addTextObject(0, 0, `${this.pokemon.hp}/${this.pokemon.getMaxHp()}`, TextStyle.PARTY);
|
||||
this.slotHpText.setPositionRelative(
|
||||
this.slotHpBar,
|
||||
this.slotHpBar.width - 3,
|
||||
this.slotHpBar.height - 2 + (offsetJa ? 2 : 0),
|
||||
);
|
||||
this.slotHpText.setOrigin(1, 0);
|
||||
this.slotHpText.setVisible(false);
|
||||
this.slotHpText = addTextObject(0, 0, `${this.pokemon.hp}/${this.pokemon.getMaxHp()}`, TextStyle.PARTY)
|
||||
.setOrigin(1, 0)
|
||||
.setPositionRelative(
|
||||
this.slotHpBar,
|
||||
this.slotHpBar.width + hpTextToBarOffset.x,
|
||||
this.slotHpBar.height + hpTextToBarOffset.y,
|
||||
) // TODO: annoying because it contains the width
|
||||
.setVisible(false);
|
||||
|
||||
this.slotDescriptionLabel = addTextObject(0, 0, "", TextStyle.MESSAGE);
|
||||
this.slotDescriptionLabel.setPositionRelative(
|
||||
slotBg,
|
||||
this.slotIndex >= battlerCount ? 94 : 32,
|
||||
this.slotIndex >= battlerCount ? 16 : 46,
|
||||
);
|
||||
this.slotDescriptionLabel.setOrigin(0, 1);
|
||||
this.slotDescriptionLabel.setVisible(false);
|
||||
this.slotDescriptionLabel = addTextObject(0, 0, "", TextStyle.MESSAGE)
|
||||
.setOrigin(0, 1)
|
||||
.setVisible(false)
|
||||
.setPositionRelative(this.slotBg, descriptionLabelPosition.x, descriptionLabelPosition.y);
|
||||
|
||||
slotInfoContainer.add([this.slotHpBar, this.slotHpOverlay, this.slotHpText, this.slotDescriptionLabel]);
|
||||
|
||||
@ -2076,10 +2113,9 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
private updateSlotTexture(): void {
|
||||
const battlerCount = globalScene.currentBattle.getBattlerCount();
|
||||
this.slotBg.setTexture(
|
||||
`party_slot${this.slotIndex >= battlerCount ? "" : "_main"}`,
|
||||
`party_slot${this.slotIndex >= battlerCount ? "" : "_main"}${this.transfer ? "_swap" : this.pokemon.hp ? "" : "_fnt"}${this.selected ? "_sel" : ""}`,
|
||||
this.slotBgKey,
|
||||
`${this.slotBgKey}${this.transfer ? "_swap" : this.pokemon.hp ? "" : "_fnt"}${this.selected ? "_sel" : ""}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -2198,10 +2234,6 @@ class PartyDiscardModeButton extends Phaser.GameObjects.Container {
|
||||
this.discardIcon.setVisible(false);
|
||||
this.textBox.setVisible(true);
|
||||
this.textBox.setText(i18next.t("partyUiHandler:TRANSFER"));
|
||||
this.setPosition(
|
||||
globalScene.currentBattle.double ? 64 : 60,
|
||||
globalScene.currentBattle.double ? -48 : -globalScene.game.canvas.height / 15 - 1,
|
||||
);
|
||||
this.transferIcon.displayWidth = this.textBox.text.length * 9 + 3;
|
||||
break;
|
||||
case PartyUiMode.DISCARD:
|
||||
@ -2209,13 +2241,13 @@ class PartyDiscardModeButton extends Phaser.GameObjects.Container {
|
||||
this.discardIcon.setVisible(true);
|
||||
this.textBox.setVisible(true);
|
||||
this.textBox.setText(i18next.t("partyUiHandler:DISCARD"));
|
||||
this.setPosition(
|
||||
globalScene.currentBattle.double ? 64 : 60,
|
||||
globalScene.currentBattle.double ? -48 : -globalScene.game.canvas.height / 15 - 1,
|
||||
);
|
||||
this.discardIcon.displayWidth = this.textBox.text.length * 9 + 3;
|
||||
break;
|
||||
}
|
||||
this.setPosition(
|
||||
globalScene.currentBattle.double ? DISCARD_BUTTON_X_DOUBLES : DISCARD_BUTTON_X,
|
||||
globalScene.currentBattle.double ? DISCARD_BUTTON_Y_DOUBLES : DISCARD_BUTTON_Y,
|
||||
);
|
||||
}
|
||||
|
||||
clear() {
|
||||
|
@ -26,6 +26,7 @@ import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text";
|
||||
import { UiHandler } from "#ui/ui-handler";
|
||||
import { addWindow } from "#ui/ui-theme";
|
||||
import { formatFancyLargeNumber, formatLargeNumber, formatMoney, getPlayTimeString } from "#utils/common";
|
||||
import { toCamelCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
import RoundRectangle from "phaser3-rex-plugins/plugins/roundrectangle";
|
||||
|
||||
@ -207,7 +208,7 @@ export class RunInfoUiHandler extends UiHandler {
|
||||
headerText.setOrigin(0, 0);
|
||||
headerText.setPositionRelative(headerBg, 8, 4);
|
||||
this.runContainer.add(headerText);
|
||||
const runName = addTextObject(0, 0, this.runInfo.runNameText, TextStyle.WINDOW);
|
||||
const runName = addTextObject(0, 0, this.runInfo.name, TextStyle.WINDOW);
|
||||
runName.setOrigin(0, 0);
|
||||
runName.setPositionRelative(headerBg, 60, 4);
|
||||
this.runContainer.add(runName);
|
||||
@ -706,10 +707,7 @@ export class RunInfoUiHandler extends UiHandler {
|
||||
rules.push(i18next.t("challenges:inverseBattle.shortName"));
|
||||
break;
|
||||
default: {
|
||||
const localizationKey = Challenges[this.runInfo.challenges[i].id]
|
||||
.split("_")
|
||||
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
|
||||
.join("");
|
||||
const localizationKey = toCamelCase(Challenges[this.runInfo.challenges[i].id]);
|
||||
rules.push(i18next.t(`challenges:${localizationKey}.name`));
|
||||
break;
|
||||
}
|
||||
|
@ -377,7 +377,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
|
||||
"select_cursor_highlight_thick",
|
||||
undefined,
|
||||
294,
|
||||
this.sessionSlots[prevSlotIndex ?? 0]?.saveData?.runNameText ? 50 : 60,
|
||||
this.sessionSlots[prevSlotIndex ?? 0]?.saveData?.name ? 50 : 60,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
@ -553,10 +553,10 @@ class SessionSlot extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
async setupWithData(data: SessionSaveData) {
|
||||
const hasName = data?.runNameText;
|
||||
const hasName = data?.name;
|
||||
this.remove(this.loadingLabel, true);
|
||||
if (hasName) {
|
||||
const nameLabel = addTextObject(8, 5, data.runNameText, TextStyle.WINDOW);
|
||||
const nameLabel = addTextObject(8, 5, data.name, TextStyle.WINDOW);
|
||||
this.add(nameLabel);
|
||||
} else {
|
||||
const fallbackName = this.decideFallback(data);
|
||||
|
@ -45,6 +45,7 @@ import type { Variant } from "#sprites/variant";
|
||||
import { getVariantIcon, getVariantTint } from "#sprites/variant";
|
||||
import { achvs } from "#system/achv";
|
||||
import type { DexAttrProps, StarterAttributes, StarterMoveset } from "#system/game-data";
|
||||
import { RibbonData } from "#system/ribbons/ribbon-data";
|
||||
import { SettingKeyboard } from "#system/settings-keyboard";
|
||||
import type { DexEntry } from "#types/dex-data";
|
||||
import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler";
|
||||
@ -3226,6 +3227,8 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
||||
onScreenFirstIndex + maxRows * maxColumns - 1,
|
||||
);
|
||||
|
||||
const gameData = globalScene.gameData;
|
||||
|
||||
this.starterSelectScrollBar.setScrollCursor(this.scrollCursor);
|
||||
|
||||
let pokerusCursorIndex = 0;
|
||||
@ -3265,9 +3268,9 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
||||
|
||||
container.label.setVisible(true);
|
||||
const speciesVariants =
|
||||
speciesId && globalScene.gameData.dexData[speciesId].caughtAttr & DexAttr.SHINY
|
||||
speciesId && gameData.dexData[speciesId].caughtAttr & DexAttr.SHINY
|
||||
? [DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3].filter(
|
||||
v => !!(globalScene.gameData.dexData[speciesId].caughtAttr & v),
|
||||
v => !!(gameData.dexData[speciesId].caughtAttr & v),
|
||||
)
|
||||
: [];
|
||||
for (let v = 0; v < 3; v++) {
|
||||
@ -3282,12 +3285,15 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
container.starterPassiveBgs.setVisible(!!globalScene.gameData.starterData[speciesId].passiveAttr);
|
||||
container.starterPassiveBgs.setVisible(!!gameData.starterData[speciesId].passiveAttr);
|
||||
container.hiddenAbilityIcon.setVisible(
|
||||
!!globalScene.gameData.dexData[speciesId].caughtAttr &&
|
||||
!!(globalScene.gameData.starterData[speciesId].abilityAttr & 4),
|
||||
!!gameData.dexData[speciesId].caughtAttr && !!(gameData.starterData[speciesId].abilityAttr & 4),
|
||||
);
|
||||
container.classicWinIcon.setVisible(globalScene.gameData.starterData[speciesId].classicWinCount > 0);
|
||||
container.classicWinIcon
|
||||
.setVisible(gameData.starterData[speciesId].classicWinCount > 0)
|
||||
.setTexture(
|
||||
gameData.dexData[speciesId].ribbons.has(RibbonData.NUZLOCKE) ? "champion_ribbon_emerald" : "champion_ribbon",
|
||||
);
|
||||
container.favoriteIcon.setVisible(this.starterPreferences[speciesId]?.favorite ?? false);
|
||||
|
||||
// 'Candy Icon' mode
|
||||
|
@ -31,7 +31,7 @@ export class TestDialogueUiHandler extends FormModalUiHandler {
|
||||
// we check for null or undefined here as per above - the typeof is still an object but the value is null so we need to exit out of this and pass the null key
|
||||
|
||||
// Return in the format expected by i18next
|
||||
return middleKey ? `${topKey}:${middleKey.map(m => m).join(".")}.${t}` : `${topKey}:${t}`;
|
||||
return middleKey ? `${topKey}:${middleKey.join(".")}.${t}` : `${topKey}:${t}`;
|
||||
}
|
||||
})
|
||||
.filter(t => t);
|
||||
|
@ -4,6 +4,7 @@ import { pokemonEvolutions } from "#balance/pokemon-evolutions";
|
||||
import { pokemonFormChanges } from "#data/pokemon-forms";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { ChallengeType } from "#enums/challenge-type";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { MoveSourceType } from "#enums/move-source-type";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
@ -378,7 +379,7 @@ export function checkStarterValidForChallenge(species: PokemonSpecies, props: De
|
||||
* @param soft - If `true`, allow it if it could become valid through a form change.
|
||||
* @returns `true` if the species is considered valid.
|
||||
*/
|
||||
function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) {
|
||||
export function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) {
|
||||
const isValidForChallenge = new BooleanHolder(true);
|
||||
applyChallenges(ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props);
|
||||
if (!soft || !pokemonFormChanges.hasOwnProperty(species.speciesId)) {
|
||||
@ -407,3 +408,28 @@ function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrPr
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @returns Whether the current game mode meets the criteria to be considered a Nuzlocke challenge */
|
||||
export function isNuzlockeChallenge(): boolean {
|
||||
let isFreshStart = false;
|
||||
let isLimitedCatch = false;
|
||||
let isHardcore = false;
|
||||
for (const challenge of globalScene.gameMode.challenges) {
|
||||
// value is 0 if challenge is not active
|
||||
if (!challenge.value) {
|
||||
continue;
|
||||
}
|
||||
switch (challenge.id) {
|
||||
case Challenges.FRESH_START:
|
||||
isFreshStart = true;
|
||||
break;
|
||||
case Challenges.LIMITED_CATCH:
|
||||
isLimitedCatch = true;
|
||||
break;
|
||||
case Challenges.HARDCORE:
|
||||
isHardcore = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isFreshStart && isLimitedCatch && isHardcore;
|
||||
}
|
||||
|
@ -45,17 +45,17 @@ export function deepMergeSpriteData(dest: object, source: object) {
|
||||
}
|
||||
|
||||
export function encrypt(data: string, bypassLogin: boolean): string {
|
||||
return (bypassLogin
|
||||
? (data: string) => btoa(encodeURIComponent(data))
|
||||
: (data: string) => AES.encrypt(data, saveKey))(data) as unknown as string; // TODO: is this correct?
|
||||
if (bypassLogin) {
|
||||
return btoa(encodeURIComponent(data));
|
||||
}
|
||||
return AES.encrypt(data, saveKey).toString();
|
||||
}
|
||||
|
||||
export function decrypt(data: string, bypassLogin: boolean): string {
|
||||
return (
|
||||
bypassLogin
|
||||
? (data: string) => decodeURIComponent(atob(data))
|
||||
: (data: string) => AES.decrypt(data, saveKey).toString(enc.Utf8)
|
||||
)(data);
|
||||
if (bypassLogin) {
|
||||
return decodeURIComponent(atob(data));
|
||||
}
|
||||
return AES.decrypt(data, saveKey).toString(enc.Utf8);
|
||||
}
|
||||
|
||||
// the latest data saved/loaded for the Starter Preferences. Required to reduce read/writes. Initialize as "{}", since this is the default value and no data needs to be stored if present.
|
||||
|
27
test/@types/test-helpers.ts
Normal file
27
test/@types/test-helpers.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type { AtLeastOne, NonFunctionPropertiesRecursive as nonFunc } from "#types/type-helpers";
|
||||
|
||||
/**
|
||||
* Helper type to admit an object containing the given properties
|
||||
* _and_ at least 1 other non-function property.
|
||||
* @example
|
||||
* ```ts
|
||||
* type foo = {
|
||||
* qux: 1 | 2 | 3,
|
||||
* bar: number,
|
||||
* baz: string
|
||||
* quux: () => void; // ignored!
|
||||
* }
|
||||
*
|
||||
* type quxAndSomethingElse = OneOther<foo, "qux">
|
||||
*
|
||||
* const good1: quxAndSomethingElse = {qux: 1, bar: 3} // OK!
|
||||
* const good2: quxAndSomethingElse = {qux: 2, baz: "4", bar: 12} // OK!
|
||||
* const bad1: quxAndSomethingElse = {baz: "4", bar: 12} // Errors because `qux` is required
|
||||
* const bad2: quxAndSomethingElse = {qux: 1} // Errors because at least 1 thing _other_ than `qux` is required
|
||||
* ```
|
||||
* @typeParam O - The object to source keys from
|
||||
* @typeParam K - One or more of O's keys to render mandatory
|
||||
*/
|
||||
export type OneOther<O extends object, K extends keyof O> = AtLeastOne<Omit<nonFunc<O>, K>> & {
|
||||
[key in K]: O[K];
|
||||
};
|
140
test/@types/vitest.d.ts
vendored
140
test/@types/vitest.d.ts
vendored
@ -1,23 +1,32 @@
|
||||
import type { TerrainType } from "#app/data/terrain";
|
||||
import type Overrides from "#app/overrides";
|
||||
import type { ArenaTag } from "#data/arena-tag";
|
||||
import type { PositionalTag } from "#data/positional-tags/positional-tag";
|
||||
import type { AbilityId } from "#enums/ability-id";
|
||||
import type { ArenaTagSide } from "#enums/arena-tag-side";
|
||||
import type { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import type { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import type { PositionalTagType } from "#enums/positional-tag-type";
|
||||
import type { BattleStat, EffectiveStat, Stat } from "#enums/stat";
|
||||
import type { StatusEffect } from "#enums/status-effect";
|
||||
import type { WeatherType } from "#enums/weather-type";
|
||||
import type { Arena } from "#field/arena";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { ToHaveEffectiveStatMatcherOptions } from "#test/test-utils/matchers/to-have-effective-stat";
|
||||
import type { PokemonMove } from "#moves/pokemon-move";
|
||||
import type { toHaveArenaTagOptions } from "#test/test-utils/matchers/to-have-arena-tag";
|
||||
import type { toHaveEffectiveStatOptions } from "#test/test-utils/matchers/to-have-effective-stat";
|
||||
import type { toHavePositionalTagOptions } from "#test/test-utils/matchers/to-have-positional-tag";
|
||||
import type { expectedStatusType } from "#test/test-utils/matchers/to-have-status-effect";
|
||||
import type { toHaveTypesOptions } from "#test/test-utils/matchers/to-have-types";
|
||||
import type { TurnMove } from "#types/turn-move";
|
||||
import type { AtLeastOne } from "#types/type-helpers";
|
||||
import type { toDmgValue } from "utils/common";
|
||||
import type { expect } from "vitest";
|
||||
import type Overrides from "#app/overrides";
|
||||
import type { PokemonMove } from "#moves/pokemon-move";
|
||||
|
||||
declare module "vitest" {
|
||||
interface Assertion {
|
||||
interface Assertion<T> {
|
||||
/**
|
||||
* Check whether an array contains EXACTLY the given items (in any order).
|
||||
*
|
||||
@ -27,45 +36,9 @@ declare module "vitest" {
|
||||
* @param expected - The expected contents of the array, in any order
|
||||
* @see {@linkcode expect.arrayContaining}
|
||||
*/
|
||||
toEqualArrayUnsorted<E>(expected: E[]): void;
|
||||
toEqualArrayUnsorted(expected: T[]): void;
|
||||
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon}'s current typing includes the given types.
|
||||
*
|
||||
* @param expected - The expected types (in any order)
|
||||
* @param options - The options passed to the matcher
|
||||
*/
|
||||
toHaveTypes(expected: [PokemonType, ...PokemonType[]], options?: toHaveTypesOptions): void;
|
||||
|
||||
/**
|
||||
* Matcher to check the contents of a {@linkcode Pokemon}'s move history.
|
||||
*
|
||||
* @param expectedValue - The expected value; can be a {@linkcode MoveId} or a partially filled {@linkcode TurnMove}
|
||||
* containing the desired properties to check
|
||||
* @param index - The index of the move history entry to check, in order from most recent to least recent.
|
||||
* Default `0` (last used move)
|
||||
* @see {@linkcode Pokemon.getLastXMoves}
|
||||
*/
|
||||
toHaveUsedMove(expected: MoveId | AtLeastOne<TurnMove>, index?: number): void;
|
||||
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon}'s effective stat is as expected
|
||||
* (checked after all stat value modifications).
|
||||
*
|
||||
* @param stat - The {@linkcode EffectiveStat} to check
|
||||
* @param expectedValue - The expected value of {@linkcode stat}
|
||||
* @param options - (Optional) The {@linkcode ToHaveEffectiveStatMatcherOptions}
|
||||
* @remarks
|
||||
* If you want to check the stat **before** modifiers are applied, use {@linkcode Pokemon.getStat} instead.
|
||||
*/
|
||||
toHaveEffectiveStat(stat: EffectiveStat, expectedValue: number, options?: ToHaveEffectiveStatMatcherOptions): void;
|
||||
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon} has taken a specific amount of damage.
|
||||
* @param expectedDamageTaken - The expected amount of damage taken
|
||||
* @param roundDown - Whether to round down {@linkcode expectedDamageTaken} with {@linkcode toDmgValue}; default `true`
|
||||
*/
|
||||
toHaveTakenDamage(expectedDamageTaken: number, roundDown?: boolean): void;
|
||||
// #region Arena Matchers
|
||||
|
||||
/**
|
||||
* Check whether the current {@linkcode WeatherType} is as expected.
|
||||
@ -80,9 +53,60 @@ declare module "vitest" {
|
||||
toHaveTerrain(expectedTerrainType: TerrainType): void;
|
||||
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon} is at full HP.
|
||||
* Check whether the current {@linkcode Arena} contains the given {@linkcode ArenaTag}.
|
||||
* @param expectedTag - A partially-filled {@linkcode ArenaTag} containing the desired properties
|
||||
*/
|
||||
toHaveFullHp(): void;
|
||||
toHaveArenaTag<A extends ArenaTagType>(expectedTag: toHaveArenaTagOptions<A>): void;
|
||||
/**
|
||||
* Check whether the current {@linkcode Arena} contains the given {@linkcode ArenaTag}.
|
||||
* @param expectedType - The {@linkcode ArenaTagType} of the desired tag
|
||||
* @param side - The {@linkcode ArenaTagSide | side(s) of the field} the tag should affect; default {@linkcode ArenaTagSide.BOTH}
|
||||
*/
|
||||
toHaveArenaTag(expectedType: ArenaTagType, side?: ArenaTagSide): void;
|
||||
|
||||
/**
|
||||
* Check whether the current {@linkcode Arena} contains the given {@linkcode PositionalTag}.
|
||||
* @param expectedTag - A partially-filled `PositionalTag` containing the desired properties
|
||||
*/
|
||||
toHavePositionalTag<P extends PositionalTagType>(expectedTag: toHavePositionalTagOptions<P>): void;
|
||||
/**
|
||||
* Check whether the current {@linkcode Arena} contains the given number of {@linkcode PositionalTag}s.
|
||||
* @param expectedType - The {@linkcode PositionalTagType} of the desired tag
|
||||
* @param count - The number of instances of {@linkcode expectedType} that should be active;
|
||||
* defaults to `1` and must be within the range `[0, 4]`
|
||||
*/
|
||||
toHavePositionalTag(expectedType: PositionalTagType, count?: number): void;
|
||||
|
||||
// #endregion Arena Matchers
|
||||
|
||||
// #region Pokemon Matchers
|
||||
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon}'s current typing includes the given types.
|
||||
* @param expectedTypes - The expected {@linkcode PokemonType}s to check against; must have length `>0`
|
||||
* @param options - The {@linkcode toHaveTypesOptions | options} passed to the matcher
|
||||
*/
|
||||
toHaveTypes(expectedTypes: PokemonType[], options?: toHaveTypesOptions): void;
|
||||
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon} has used a move matching the given criteria.
|
||||
* @param expectedMove - The {@linkcode MoveId} the Pokemon is expected to have used,
|
||||
* or a partially filled {@linkcode TurnMove} containing the desired properties to check
|
||||
* @param index - The index of the move history entry to check, in order from most recent to least recent; default `0`
|
||||
* @see {@linkcode Pokemon.getLastXMoves}
|
||||
*/
|
||||
toHaveUsedMove(expectedMove: MoveId | AtLeastOne<TurnMove>, index?: number): void;
|
||||
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon}'s effective stat is as expected
|
||||
* (checked after all stat value modifications).
|
||||
* @param stat - The {@linkcode EffectiveStat} to check
|
||||
* @param expectedValue - The expected value of {@linkcode stat}
|
||||
* @param options - The {@linkcode toHaveEffectiveStatOptions | options} passed to the matcher
|
||||
* @remarks
|
||||
* If you want to check the stat **before** modifiers are applied, use {@linkcode Pokemon.getStat} instead.
|
||||
*/
|
||||
toHaveEffectiveStat(stat: EffectiveStat, expectedValue: number, options?: toHaveEffectiveStatOptions): void;
|
||||
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon} has a specific {@linkcode StatusEffect | non-volatile status effect}.
|
||||
@ -106,7 +130,7 @@ declare module "vitest" {
|
||||
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon} has applied a specific {@linkcode AbilityId}.
|
||||
* @param expectedAbilityId - The expected {@linkcode AbilityId}
|
||||
* @param expectedAbilityId - The `AbilityId` to check for
|
||||
*/
|
||||
toHaveAbilityApplied(expectedAbilityId: AbilityId): void;
|
||||
|
||||
@ -116,24 +140,36 @@ declare module "vitest" {
|
||||
*/
|
||||
toHaveHp(expectedHp: number): void;
|
||||
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon} has taken a specific amount of damage.
|
||||
* @param expectedDamageTaken - The expected amount of damage taken
|
||||
* @param roundDown - Whether to round down `expectedDamageTaken` with {@linkcode toDmgValue}; default `true`
|
||||
*/
|
||||
toHaveTakenDamage(expectedDamageTaken: number, roundDown?: boolean): void;
|
||||
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon} is currently fainted (as determined by {@linkcode Pokemon.isFainted}).
|
||||
* @remarks
|
||||
* When checking whether an enemy wild Pokemon is fainted, one must reference it in a variable _before_ the fainting effect occurs
|
||||
* as otherwise the Pokemon will be GC'ed and rendered `undefined`.
|
||||
* When checking whether an enemy wild Pokemon is fainted, one must store a reference to it in a variable _before_ the fainting effect occurs.
|
||||
* Otherwise, the Pokemon will be removed from the field and garbage collected.
|
||||
*/
|
||||
toHaveFainted(): void;
|
||||
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon} is at full HP.
|
||||
*/
|
||||
toHaveFullHp(): void;
|
||||
/**
|
||||
* Check whether a {@linkcode Pokemon} has consumed the given amount of PP for one of its moves.
|
||||
* @param expectedValue - The {@linkcode MoveId} of the {@linkcode PokemonMove} that should have consumed PP
|
||||
* @param moveId - The {@linkcode MoveId} corresponding to the {@linkcode PokemonMove} that should have consumed PP
|
||||
* @param ppUsed - The numerical amount of PP that should have been consumed,
|
||||
* or `all` to indicate the move should be _out_ of PP
|
||||
* @remarks
|
||||
* If the Pokemon's moveset has been set via {@linkcode Overrides.MOVESET_OVERRIDE}/{@linkcode Overrides.OPP_MOVESET_OVERRIDE},
|
||||
* does not contain {@linkcode expectedMove}
|
||||
* or contains the desired move more than once, this will fail the test.
|
||||
* If the Pokemon's moveset has been set via {@linkcode Overrides.MOVESET_OVERRIDE}/{@linkcode Overrides.ENEMY_MOVESET_OVERRIDE}
|
||||
* or does not contain exactly one copy of `moveId`, this will fail the test.
|
||||
*/
|
||||
toHaveUsedPP(expectedMove: MoveId, ppUsed: number | "all"): void;
|
||||
toHaveUsedPP(moveId: MoveId, ppUsed: number | "all"): void;
|
||||
|
||||
// #endregion Pokemon Matchers
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { toEqualArrayUnsorted } from "#test/test-utils/matchers/to-equal-array-unsorted";
|
||||
import { toHaveAbilityApplied } from "#test/test-utils/matchers/to-have-ability-applied";
|
||||
import { toHaveArenaTag } from "#test/test-utils/matchers/to-have-arena-tag";
|
||||
import { toHaveBattlerTag } from "#test/test-utils/matchers/to-have-battler-tag";
|
||||
import { toHaveEffectiveStat } from "#test/test-utils/matchers/to-have-effective-stat";
|
||||
import { toHaveFainted } from "#test/test-utils/matchers/to-have-fainted";
|
||||
import { toHaveFullHp } from "#test/test-utils/matchers/to-have-full-hp";
|
||||
import { toHaveHp } from "#test/test-utils/matchers/to-have-hp";
|
||||
import { toHavePositionalTag } from "#test/test-utils/matchers/to-have-positional-tag";
|
||||
import { toHaveStatStage } from "#test/test-utils/matchers/to-have-stat-stage";
|
||||
import { toHaveStatusEffect } from "#test/test-utils/matchers/to-have-status-effect";
|
||||
import { toHaveTakenDamage } from "#test/test-utils/matchers/to-have-taken-damage";
|
||||
@ -22,18 +24,20 @@ import { expect } from "vitest";
|
||||
|
||||
expect.extend({
|
||||
toEqualArrayUnsorted,
|
||||
toHaveWeather,
|
||||
toHaveTerrain,
|
||||
toHaveArenaTag,
|
||||
toHavePositionalTag,
|
||||
toHaveTypes,
|
||||
toHaveUsedMove,
|
||||
toHaveEffectiveStat,
|
||||
toHaveTakenDamage,
|
||||
toHaveWeather,
|
||||
toHaveTerrain,
|
||||
toHaveFullHp,
|
||||
toHaveStatusEffect,
|
||||
toHaveStatStage,
|
||||
toHaveBattlerTag,
|
||||
toHaveAbilityApplied,
|
||||
toHaveHp,
|
||||
toHaveTakenDamage,
|
||||
toHaveFullHp,
|
||||
toHaveFainted,
|
||||
toHaveUsedPP,
|
||||
});
|
||||
|
@ -39,15 +39,6 @@ describe("Move - Wish", () => {
|
||||
.enemyLevel(100);
|
||||
});
|
||||
|
||||
/**
|
||||
* Expect that wish is active with the specified number of attacks.
|
||||
* @param numAttacks - The number of wish instances that should be queued; default `1`
|
||||
*/
|
||||
function expectWishActive(numAttacks = 1) {
|
||||
const wishes = game.scene.arena.positionalTagManager["tags"].filter(t => t.tagType === PositionalTagType.WISH);
|
||||
expect(wishes).toHaveLength(numAttacks);
|
||||
}
|
||||
|
||||
it("should heal the Pokemon in the current slot for 50% of the user's maximum HP", async () => {
|
||||
await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]);
|
||||
|
||||
@ -58,19 +49,19 @@ describe("Move - Wish", () => {
|
||||
game.move.use(MoveId.WISH);
|
||||
await game.toNextTurn();
|
||||
|
||||
expectWishActive();
|
||||
expect(game).toHavePositionalTag(PositionalTagType.WISH);
|
||||
|
||||
game.doSwitchPokemon(1);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
expectWishActive(0);
|
||||
expect(game).toHavePositionalTag(PositionalTagType.WISH, 0);
|
||||
expect(game.textInterceptor.logs).toContain(
|
||||
i18next.t("arenaTag:wishTagOnAdd", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(alomomola),
|
||||
}),
|
||||
);
|
||||
expect(alomomola.hp).toBe(1);
|
||||
expect(blissey.hp).toBe(toDmgValue(alomomola.getMaxHp() / 2) + 1);
|
||||
expect(alomomola).toHaveHp(1);
|
||||
expect(blissey).toHaveHp(toDmgValue(alomomola.getMaxHp() / 2) + 1);
|
||||
});
|
||||
|
||||
it("should work if the user has full HP, but not if it already has an active Wish", async () => {
|
||||
@ -82,13 +73,13 @@ describe("Move - Wish", () => {
|
||||
game.move.use(MoveId.WISH);
|
||||
await game.toNextTurn();
|
||||
|
||||
expectWishActive();
|
||||
expect(game).toHavePositionalTag(PositionalTagType.WISH);
|
||||
|
||||
game.move.use(MoveId.WISH);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
expect(alomomola.hp).toBe(toDmgValue(alomomola.getMaxHp() / 2) + 1);
|
||||
expect(alomomola.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
expect(alomomola).toHaveUsedMove({ result: MoveResult.FAIL });
|
||||
});
|
||||
|
||||
it("should function independently of Future Sight", async () => {
|
||||
@ -103,7 +94,8 @@ describe("Move - Wish", () => {
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expectWishActive(1);
|
||||
expect(game).toHavePositionalTag(PositionalTagType.WISH);
|
||||
expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK);
|
||||
});
|
||||
|
||||
it("should work in double battles and trigger in order of creation", async () => {
|
||||
@ -127,7 +119,7 @@ describe("Move - Wish", () => {
|
||||
await game.setTurnOrder(oldOrder.map(p => p.getBattlerIndex()));
|
||||
await game.toNextTurn();
|
||||
|
||||
expectWishActive(4);
|
||||
expect(game).toHavePositionalTag(PositionalTagType.WISH, 4);
|
||||
|
||||
// Lower speed to change turn order
|
||||
alomomola.setStatStage(Stat.SPD, 6);
|
||||
@ -141,7 +133,7 @@ describe("Move - Wish", () => {
|
||||
await game.phaseInterceptor.to("PositionalTagPhase");
|
||||
|
||||
// all wishes have activated and added healing phases
|
||||
expectWishActive(0);
|
||||
expect(game).toHavePositionalTag(PositionalTagType.WISH, 0);
|
||||
|
||||
const healPhases = game.scene.phaseManager.phaseQueue.filter(p => p.is("PokemonHealPhase"));
|
||||
expect(healPhases).toHaveLength(4);
|
||||
@ -165,14 +157,14 @@ describe("Move - Wish", () => {
|
||||
game.move.use(MoveId.WISH, BattlerIndex.PLAYER_2);
|
||||
await game.toNextTurn();
|
||||
|
||||
expectWishActive();
|
||||
expect(game).toHavePositionalTag(PositionalTagType.WISH);
|
||||
|
||||
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
|
||||
game.move.use(MoveId.MEMENTO, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
// Wish went away without doing anything
|
||||
expectWishActive(0);
|
||||
expect(game).toHavePositionalTag(PositionalTagType.WISH, 0);
|
||||
expect(game.textInterceptor.logs).not.toContain(
|
||||
i18next.t("arenaTag:wishTagOnAdd", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(blissey),
|
||||
|
@ -66,7 +66,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
|
||||
expect(AnOfferYouCantRefuseEncounter.dialogue).toBeDefined();
|
||||
expect(AnOfferYouCantRefuseEncounter.dialogue.intro).toStrictEqual([
|
||||
{ text: `${namespace}:intro` },
|
||||
{ speaker: `${namespace}:speaker`, text: `${namespace}:intro_dialogue` },
|
||||
{ speaker: `${namespace}:speaker`, text: `${namespace}:introDialogue` },
|
||||
]);
|
||||
expect(AnOfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
|
||||
expect(AnOfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue?.description).toBe(
|
||||
@ -180,7 +180,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.tooltip_disabled`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.tooltipDisabled`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
|
@ -194,7 +194,7 @@ describe("Berries Abound - Mystery Encounter", () => {
|
||||
|
||||
// Should be enraged
|
||||
expect(enemyField[0].summonData.statStages).toEqual([0, 1, 0, 1, 1, 0, 0]);
|
||||
expect(encounterTextSpy).toHaveBeenCalledWith(`${namespace}:option.2.selected_bad`);
|
||||
expect(encounterTextSpy).toHaveBeenCalledWith(`${namespace}:option.2.selectedBad`);
|
||||
});
|
||||
|
||||
it("should start battle if fastest pokemon is slower than boss above wave 50", async () => {
|
||||
@ -218,7 +218,7 @@ describe("Berries Abound - Mystery Encounter", () => {
|
||||
|
||||
// Should be enraged
|
||||
expect(enemyField[0].summonData.statStages).toEqual([1, 1, 1, 1, 1, 0, 0]);
|
||||
expect(encounterTextSpy).toHaveBeenCalledWith(`${namespace}:option.2.selected_bad`);
|
||||
expect(encounterTextSpy).toHaveBeenCalledWith(`${namespace}:option.2.selectedBad`);
|
||||
});
|
||||
|
||||
it("Should skip battle when fastest pokemon is faster than boss", async () => {
|
||||
|
@ -181,7 +181,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
]);
|
||||
expect(BugTypeSuperfanEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
|
||||
@ -389,7 +389,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabledTooltip`,
|
||||
});
|
||||
});
|
||||
|
||||
@ -513,17 +513,17 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.3.selected_dialogue`,
|
||||
text: `${namespace}:option.3.selectedDialogue`,
|
||||
},
|
||||
],
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
secondOptionPrompt: `${namespace}:option.3.selectPrompt`,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -80,7 +80,7 @@ describe("Clowning Around - Mystery Encounter", () => {
|
||||
{ text: `${namespace}:intro` },
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
]);
|
||||
expect(ClowningAroundEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
|
||||
@ -249,11 +249,11 @@ describe("Clowning Around - Mystery Encounter", () => {
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.2.selected_2`,
|
||||
text: `${namespace}:option.2.selected2`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.2.selected_3`,
|
||||
text: `${namespace}:option.2.selected3`,
|
||||
},
|
||||
],
|
||||
});
|
||||
@ -334,11 +334,11 @@ describe("Clowning Around - Mystery Encounter", () => {
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.3.selected_2`,
|
||||
text: `${namespace}:option.3.selected2`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.3.selected_3`,
|
||||
text: `${namespace}:option.3.selected3`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -186,8 +186,8 @@ describe("Dancing Lessons - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabledTooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.selectPrompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
|
@ -186,7 +186,7 @@ describe("Delibird-y - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.2.select_prompt`,
|
||||
secondOptionPrompt: `${namespace}:option.2.selectPrompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
@ -348,7 +348,7 @@ describe("Delibird-y - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
secondOptionPrompt: `${namespace}:option.3.selectPrompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
|
@ -61,7 +61,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
|
||||
{ text: `${namespace}:intro` },
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
]);
|
||||
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
|
||||
|
@ -61,7 +61,7 @@ describe("Field Trip - Mystery Encounter", () => {
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
]);
|
||||
expect(FieldTripEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
|
||||
@ -78,7 +78,7 @@ describe("Field Trip - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:second_option_prompt`,
|
||||
secondOptionPrompt: `${namespace}:secondOptionPrompt`,
|
||||
});
|
||||
});
|
||||
|
||||
@ -139,7 +139,7 @@ describe("Field Trip - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:second_option_prompt`,
|
||||
secondOptionPrompt: `${namespace}:secondOptionPrompt`,
|
||||
});
|
||||
});
|
||||
|
||||
@ -200,7 +200,7 @@ describe("Field Trip - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:second_option_prompt`,
|
||||
secondOptionPrompt: `${namespace}:secondOptionPrompt`,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -253,7 +253,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
||||
expect(option1.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
|
@ -143,7 +143,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
|
@ -71,7 +71,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
|
||||
expect(FunAndGamesEncounter.dialogue.intro).toStrictEqual([
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
]);
|
||||
expect(FunAndGamesEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
|
||||
|
@ -98,7 +98,7 @@ describe("Global Trade System - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.1.trade_options_prompt`,
|
||||
secondOptionPrompt: `${namespace}:option.1.tradeOptionsPrompt`,
|
||||
});
|
||||
});
|
||||
|
||||
@ -210,7 +210,7 @@ describe("Global Trade System - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.trade_options_prompt`,
|
||||
secondOptionPrompt: `${namespace}:option.3.tradeOptionsPrompt`,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -99,9 +99,9 @@ describe("Lost at Sea - Mystery Encounter", () => {
|
||||
expect(option1.dialogue).toBeDefined();
|
||||
expect(option1.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
disabledButtonLabel: `${namespace}:option.1.label_disabled`,
|
||||
disabledButtonLabel: `${namespace}:option.1.labelDisabled`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.1.tooltip_disabled`,
|
||||
disabledButtonTooltip: `${namespace}:option.1.tooltipDisabled`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
@ -162,9 +162,9 @@ describe("Lost at Sea - Mystery Encounter", () => {
|
||||
expect(option2.dialogue).toBeDefined();
|
||||
expect(option2.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
disabledButtonLabel: `${namespace}:option.2.label_disabled`,
|
||||
disabledButtonLabel: `${namespace}:option.2.labelDisabled`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.tooltip_disabled`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.tooltipDisabled`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
|
@ -65,7 +65,7 @@ describe("Part-Timer - Mystery Encounter", () => {
|
||||
{ text: `${namespace}:intro` },
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
]);
|
||||
expect(PartTimerEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
|
||||
@ -219,7 +219,7 @@ describe("Part-Timer - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
|
@ -207,7 +207,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
|
@ -71,7 +71,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
|
||||
},
|
||||
{
|
||||
speaker: "trainerNames:expert_pokemon_breeder",
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
]);
|
||||
expect(TheExpertPokemonBreederEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
|
||||
|
@ -67,7 +67,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
||||
expect(dialogue).toBeDefined();
|
||||
expect(dialogue.intro).toStrictEqual([
|
||||
{ text: `${namespace}:intro` },
|
||||
{ speaker: `${namespace}:speaker`, text: `${namespace}:intro_dialogue` },
|
||||
{ speaker: `${namespace}:speaker`, text: `${namespace}:introDialogue` },
|
||||
]);
|
||||
const { title, description, query } = dialogue.encounterOptionsDialogue!;
|
||||
expect(title).toBe(`${namespace}:title`);
|
||||
@ -120,7 +120,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
||||
buttonTooltip: expect.stringMatching(new RegExp(`^${namespace}\\:option\\.1\\.tooltip(_shiny)?$`)),
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected_message`,
|
||||
text: `${namespace}:option.1.selectedMessage`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
|
||||
{ text: `${namespace}:intro` },
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
]);
|
||||
expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
|
||||
|
@ -172,7 +172,7 @@ describe("Uncommon Breed - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
@ -237,7 +237,7 @@ describe("Uncommon Breed - Mystery Encounter", () => {
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabledTooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
|
@ -68,7 +68,7 @@ describe("Weird Dream - Mystery Encounter", () => {
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
text: `${namespace}:introDialogue`,
|
||||
},
|
||||
]);
|
||||
expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
|
||||
@ -112,7 +112,7 @@ describe("Weird Dream - Mystery Encounter", () => {
|
||||
it("should transform the new party into new species, 2 at +90/+110, the rest at +40/50 BST", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty);
|
||||
|
||||
const pokemonPrior = scene.getPlayerParty().map(pokemon => pokemon);
|
||||
const pokemonPrior = scene.getPlayerParty().slice();
|
||||
const bstsPrior = pokemonPrior.map(species => species.getSpeciesForm().getBaseStatTotal());
|
||||
|
||||
await runMysteryEncounterToEnd(game, 1);
|
||||
|
@ -224,7 +224,7 @@ export class GameManager {
|
||||
// This will consider all battle entry dialog as seens and skip them
|
||||
vi.spyOn(this.scene.ui, "shouldSkipDialogue").mockReturnValue(true);
|
||||
|
||||
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) {
|
||||
if (overrides.ENEMY_HELD_ITEMS_OVERRIDE.length === 0) {
|
||||
this.removeEnemyHeldItems();
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ export class ChallengeModeHelper extends GameManagerHelper {
|
||||
});
|
||||
|
||||
await this.game.phaseInterceptor.run(EncounterPhase);
|
||||
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) {
|
||||
if (overrides.ENEMY_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) {
|
||||
this.game.removeEnemyHeldItems();
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ export class ClassicModeHelper extends GameManagerHelper {
|
||||
});
|
||||
|
||||
await this.game.phaseInterceptor.to(EncounterPhase);
|
||||
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) {
|
||||
if (overrides.ENEMY_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) {
|
||||
this.game.removeEnemyHeldItems();
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ export class DailyModeHelper extends GameManagerHelper {
|
||||
|
||||
await this.game.phaseInterceptor.to(EncounterPhase);
|
||||
|
||||
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) {
|
||||
if (overrides.ENEMY_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) {
|
||||
this.game.removeEnemyHeldItems();
|
||||
}
|
||||
}
|
||||
|
@ -228,8 +228,8 @@ export class MoveHelper extends GameManagerHelper {
|
||||
console.warn("Player moveset override disabled due to use of `game.move.changeMoveset`!");
|
||||
}
|
||||
} else {
|
||||
if (coerceArray(Overrides.OPP_MOVESET_OVERRIDE).length > 0) {
|
||||
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([]);
|
||||
if (coerceArray(Overrides.ENEMY_MOVESET_OVERRIDE).length > 0) {
|
||||
vi.spyOn(Overrides, "ENEMY_MOVESET_OVERRIDE", "get").mockReturnValue([]);
|
||||
console.warn("Enemy moveset override disabled due to use of `game.move.changeMoveset`!");
|
||||
}
|
||||
}
|
||||
@ -302,8 +302,8 @@ export class MoveHelper extends GameManagerHelper {
|
||||
(this.game.scene.phaseManager.getCurrentPhase() as EnemyCommandPhase).getFieldIndex()
|
||||
];
|
||||
|
||||
if ([Overrides.OPP_MOVESET_OVERRIDE].flat().length > 0) {
|
||||
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([]);
|
||||
if ([Overrides.ENEMY_MOVESET_OVERRIDE].flat().length > 0) {
|
||||
vi.spyOn(Overrides, "ENEMY_MOVESET_OVERRIDE", "get").mockReturnValue([]);
|
||||
console.warn(
|
||||
"Warning: `forceEnemyMove` overwrites the Pokemon's moveset and disables the enemy moveset override!",
|
||||
);
|
||||
|
@ -406,7 +406,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns `this`
|
||||
*/
|
||||
public enemySpecies(species: SpeciesId | number): this {
|
||||
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(species);
|
||||
vi.spyOn(Overrides, "ENEMY_SPECIES_OVERRIDE", "get").mockReturnValue(species);
|
||||
this.log(`Enemy Pokemon species set to ${SpeciesId[species]} (=${species})!`);
|
||||
return this;
|
||||
}
|
||||
@ -416,7 +416,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns `this`
|
||||
*/
|
||||
public enableEnemyFusion(): this {
|
||||
vi.spyOn(Overrides, "OPP_FUSION_OVERRIDE", "get").mockReturnValue(true);
|
||||
vi.spyOn(Overrides, "ENEMY_FUSION_OVERRIDE", "get").mockReturnValue(true);
|
||||
this.log("Enemy Pokemon is a random fusion!");
|
||||
return this;
|
||||
}
|
||||
@ -427,7 +427,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns `this`
|
||||
*/
|
||||
public enemyFusionSpecies(species: SpeciesId | number): this {
|
||||
vi.spyOn(Overrides, "OPP_FUSION_SPECIES_OVERRIDE", "get").mockReturnValue(species);
|
||||
vi.spyOn(Overrides, "ENEMY_FUSION_SPECIES_OVERRIDE", "get").mockReturnValue(species);
|
||||
this.log(`Enemy Pokemon fusion species set to ${SpeciesId[species]} (=${species})!`);
|
||||
return this;
|
||||
}
|
||||
@ -438,7 +438,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns `this`
|
||||
*/
|
||||
public enemyAbility(ability: AbilityId): this {
|
||||
vi.spyOn(Overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(ability);
|
||||
vi.spyOn(Overrides, "ENEMY_ABILITY_OVERRIDE", "get").mockReturnValue(ability);
|
||||
this.log(`Enemy Pokemon ability set to ${AbilityId[ability]} (=${ability})!`);
|
||||
return this;
|
||||
}
|
||||
@ -449,7 +449,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns `this`
|
||||
*/
|
||||
public enemyPassiveAbility(passiveAbility: AbilityId): this {
|
||||
vi.spyOn(Overrides, "OPP_PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(passiveAbility);
|
||||
vi.spyOn(Overrides, "ENEMY_PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(passiveAbility);
|
||||
this.log(`Enemy Pokemon PASSIVE ability set to ${AbilityId[passiveAbility]} (=${passiveAbility})!`);
|
||||
return this;
|
||||
}
|
||||
@ -460,7 +460,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns `this`
|
||||
*/
|
||||
public enemyHasPassiveAbility(hasPassiveAbility: boolean | null): this {
|
||||
vi.spyOn(Overrides, "OPP_HAS_PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(hasPassiveAbility);
|
||||
vi.spyOn(Overrides, "ENEMY_HAS_PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(hasPassiveAbility);
|
||||
if (hasPassiveAbility === null) {
|
||||
this.log("Enemy Pokemon PASSIVE ability no longer force enabled or disabled!");
|
||||
} else {
|
||||
@ -475,7 +475,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns `this`
|
||||
*/
|
||||
public enemyMoveset(moveset: MoveId | MoveId[]): this {
|
||||
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue(moveset);
|
||||
vi.spyOn(Overrides, "ENEMY_MOVESET_OVERRIDE", "get").mockReturnValue(moveset);
|
||||
moveset = coerceArray(moveset);
|
||||
const movesetStr = moveset.map(moveId => MoveId[moveId]).join(", ");
|
||||
this.log(`Enemy Pokemon moveset set to ${movesetStr} (=[${moveset.join(", ")}])!`);
|
||||
@ -488,7 +488,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns `this`
|
||||
*/
|
||||
public enemyLevel(level: number): this {
|
||||
vi.spyOn(Overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(level);
|
||||
vi.spyOn(Overrides, "ENEMY_LEVEL_OVERRIDE", "get").mockReturnValue(level);
|
||||
this.log(`Enemy Pokemon level set to ${level}!`);
|
||||
return this;
|
||||
}
|
||||
@ -499,7 +499,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns `this`
|
||||
*/
|
||||
public enemyStatusEffect(statusEffect: StatusEffect): this {
|
||||
vi.spyOn(Overrides, "OPP_STATUS_OVERRIDE", "get").mockReturnValue(statusEffect);
|
||||
vi.spyOn(Overrides, "ENEMY_STATUS_OVERRIDE", "get").mockReturnValue(statusEffect);
|
||||
this.log(`Enemy Pokemon status-effect set to ${StatusEffect[statusEffect]} (=${statusEffect})!`);
|
||||
return this;
|
||||
}
|
||||
@ -510,7 +510,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns `this`
|
||||
*/
|
||||
public enemyHeldItems(items: ModifierOverride[]): this {
|
||||
vi.spyOn(Overrides, "OPP_HELD_ITEMS_OVERRIDE", "get").mockReturnValue(items);
|
||||
vi.spyOn(Overrides, "ENEMY_HELD_ITEMS_OVERRIDE", "get").mockReturnValue(items);
|
||||
this.log("Enemy Pokemon held items set to:", items);
|
||||
return this;
|
||||
}
|
||||
@ -571,7 +571,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @param variant - (Optional) The enemy's shiny {@linkcode Variant}.
|
||||
*/
|
||||
enemyShiny(shininess: boolean | null, variant?: Variant): this {
|
||||
vi.spyOn(Overrides, "OPP_SHINY_OVERRIDE", "get").mockReturnValue(shininess);
|
||||
vi.spyOn(Overrides, "ENEMY_SHINY_OVERRIDE", "get").mockReturnValue(shininess);
|
||||
if (shininess === null) {
|
||||
this.log("Disabled enemy Pokemon shiny override!");
|
||||
} else {
|
||||
@ -579,7 +579,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
}
|
||||
|
||||
if (variant !== undefined) {
|
||||
vi.spyOn(Overrides, "OPP_VARIANT_OVERRIDE", "get").mockReturnValue(variant);
|
||||
vi.spyOn(Overrides, "ENEMY_VARIANT_OVERRIDE", "get").mockReturnValue(variant);
|
||||
this.log(`Set enemy shiny variant to be ${variant}!`);
|
||||
}
|
||||
return this;
|
||||
@ -594,7 +594,7 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns `this`
|
||||
*/
|
||||
public enemyHealthSegments(healthSegments: number): this {
|
||||
vi.spyOn(Overrides, "OPP_HEALTH_SEGMENTS_OVERRIDE", "get").mockReturnValue(healthSegments);
|
||||
vi.spyOn(Overrides, "ENEMY_HEALTH_SEGMENTS_OVERRIDE", "get").mockReturnValue(healthSegments);
|
||||
this.log("Enemy Pokemon health segments set to:", healthSegments);
|
||||
return this;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { getOnelineDiffStr } from "#test/test-utils/string-utils";
|
||||
import { receivedStr } from "#test/test-utils/test-utils";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
|
||||
/**
|
||||
@ -14,22 +15,22 @@ export function toEqualArrayUnsorted(
|
||||
): SyncExpectationResult {
|
||||
if (!Array.isArray(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected an array, but got ${this.utils.stringify(received)}!`,
|
||||
pass: this.isNot,
|
||||
message: () => `Expected to receive an array, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
if (received.length !== expected.length) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected to receive array of length ${received.length}, but got ${expected.length} instead!`,
|
||||
actual: received,
|
||||
message: () => `Expected to receive an array of length ${received.length}, but got ${expected.length} instead!`,
|
||||
expected,
|
||||
actual: received,
|
||||
};
|
||||
}
|
||||
|
||||
const actualSorted = received.slice().sort();
|
||||
const expectedSorted = expected.slice().sort();
|
||||
const actualSorted = received.toSorted();
|
||||
const expectedSorted = expected.toSorted();
|
||||
const pass = this.equals(actualSorted, expectedSorted, [...this.customTesters, this.utils.iterableEquality]);
|
||||
|
||||
const actualStr = getOnelineDiffStr.call(this, actualSorted);
|
||||
|
@ -21,8 +21,8 @@ export function toHaveAbilityApplied(
|
||||
): SyncExpectationResult {
|
||||
if (!isPokemonInstance(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected to recieve a Pokemon, but got ${receivedStr(received)}!`,
|
||||
pass: this.isNot,
|
||||
message: () => `Expected to receive a Pokemon, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user