Compare commits

..

No commits in common. "d06eb1bcc7ec0e3476745018cc53f332d8ec0a3c" and "061859fb374539dfc4f1ad301874c093c0bd996a" have entirely different histories.

72 changed files with 898 additions and 776 deletions

View File

@ -3,7 +3,7 @@
# top-most EditorConfig file # top-most EditorConfig file
root = true root = true
[**/*.{js,ts,json,jsonc}] [src/*.{js,ts}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
end_of_line = lf end_of_line = lf

View File

@ -1,5 +1,4 @@
{ {
// ! Just ignore the errors for now guys.
"$schema": "https://biomejs.dev/schemas/2.1.4/schema.json", "$schema": "https://biomejs.dev/schemas/2.1.4/schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
@ -67,7 +66,6 @@
}, },
"style": { "style": {
"useCollapsedIf": "error", "useCollapsedIf": "error",
"useCollapsedElseIf": "error",
"noDoneCallback": "error", "noDoneCallback": "error",
"noSubstr": "error", "noSubstr": "error",
"noYodaExpression": "error", "noYodaExpression": "error",
@ -88,10 +86,7 @@
"level": "warn", "level": "warn",
"fix": "none" "fix": "none"
}, },
"useSingleVarDeclarator": { "useSingleVarDeclarator": "error",
"level": "error",
"fix": "safe"
},
"useNodejsImportProtocol": "off", "useNodejsImportProtocol": "off",
"useTemplate": "off", // string concatenation is faster: https://stackoverflow.com/questions/29055518/are-es6-template-literals-faster-than-string-concatenation "useTemplate": "off", // string concatenation is faster: https://stackoverflow.com/questions/29055518/are-es6-template-literals-faster-than-string-concatenation
"useAsConstAssertion": "error", "useAsConstAssertion": "error",

View File

@ -19,22 +19,23 @@ Generally speaking, most users shouldn't need to run Biome directly; in addition
> ![WARNING] > ![WARNING]
> You will **not** be able to commit code if any staged files contain `error`-level linting problems. \ > You will **not** be able to commit code if any staged files contain `error`-level linting problems. \
> If you, for whatever reason, _absolutely need_ to bypass Lefthook for a given commit, > If you, for whatever reason, _absolutely need_ to bypass Lefthook while committing,
> `LEFTHOOK=0 git commit` will skip running all pre-commit hooks during the commit process. > `LEFTHOOK=0 git commit` will skip all pre-commit hooks for the given operation.
We also have a [Github Action](../.github/workflows/linting.yml) to verify code quality each time a PR is updated, preventing bad code from inadvertently making its way upstream. \ We also have a [Github Action](../.github/workflows/quality.yml) to verify code quality each time a PR is updated, preventing bad code from inadvertently making its way upstream. \
These are effectively the same commands as run by Lefthook, merely on a project-wide scale. These are effectively the same commands as run by Lefthook, merely on a project-wide scale.
## Running Biome via CLI ## Running Biome via CLI
To run you Biome on your files manually, you have 2 main options: If you want Biome to check your files manually, you have 2 options:
1. Run the scripts included in `package.json` (`pnpm biome` and `pnpm biome:all`). \ 1. Run the `biome` script included in `package.json` (`pnpm biome`).
These have sensible defaults for command-line options, but do not allow altering certain flags (as some cannot be specified twice in the same command)
This has sensible defaults for command-line options, but does not allow altering certain flags (as some cannot be specified twice in the same command)
2. Execute the Biome executable manually from the command line like so: 2. Execute the Biome executable manually from the command line like so:
```sh ```sh
pnpm exec biome check --[flags] pnpm exec biome check --[flags]
``` ```
This allows customizing non-overridable flags like `--diagnostic-level` on a more granular level, but requires slightly more verbosity and specifying more options. This allows customizing flags non-overridable flags like `--diagnostic-level` on a more granular level, but requires slightly more verbosity.
A full list of flags and options can be found on [their website](https://biomejs.dev/reference/cli/), but here's a few useful ones to keep in mind: A full list of flags and options can be found on [their website](https://biomejs.dev/reference/cli/), but here's a few useful ones to keep in mind:

View File

@ -18,7 +18,6 @@
"eslint": "eslint --fix .", "eslint": "eslint --fix .",
"eslint-ci": "eslint .", "eslint-ci": "eslint .",
"biome": "biome check --write --changed --no-errors-on-unmatched --diagnostic-level=error", "biome": "biome check --write --changed --no-errors-on-unmatched --diagnostic-level=error",
"biome:all": "biome check --write --no-errors-on-unmatched --diagnostic-level=error",
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched", "biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched",
"docs": "typedoc", "docs": "typedoc",
"depcruise": "depcruise src test", "depcruise": "depcruise src test",

View File

@ -1463,6 +1463,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
private chance: number; private chance: number;
private attacker: Pokemon; private attacker: Pokemon;
private move: Move;
constructor(chance: number) { constructor(chance: number) {
super(); super();
@ -1478,9 +1479,11 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
); );
} }
override apply({ simulated, opponent: attacker, pokemon }: PostMoveInteractionAbAttrParams): void { override apply({ simulated, opponent: attacker, move, pokemon }: PostMoveInteractionAbAttrParams): void {
// TODO: investigate why this is setting properties
if (!simulated) { if (!simulated) {
this.attacker = attacker; this.attacker = attacker;
this.move = move;
this.attacker.addTag(BattlerTagType.DISABLED, 4, 0, pokemon.id); this.attacker.addTag(BattlerTagType.DISABLED, 4, 0, pokemon.id);
} }
} }
@ -6342,13 +6345,10 @@ class ForceSwitchOutHelper {
return !blockedByAbility.value; return !blockedByAbility.value;
} }
if ( if (!player && globalScene.currentBattle.battleType === BattleType.WILD) {
!player && if (!globalScene.currentBattle.waveIndex && globalScene.currentBattle.waveIndex % 10 === 0) {
globalScene.currentBattle.battleType === BattleType.WILD && return false;
!globalScene.currentBattle.waveIndex && }
globalScene.currentBattle.waveIndex % 10 === 0
) {
return false;
} }
if ( if (

View File

@ -616,7 +616,7 @@ export const speciesStarterCosts = {
[SpeciesId.PALDEA_TAUROS]: 5, [SpeciesId.PALDEA_TAUROS]: 5,
[SpeciesId.PALDEA_WOOPER]: 3, [SpeciesId.PALDEA_WOOPER]: 3,
[SpeciesId.BLOODMOON_URSALUNA]: 5, [SpeciesId.BLOODMOON_URSALUNA]: 5,
} as const; };
const starterCandyCosts: { passive: number; costReduction: [number, number]; egg: number; }[] = [ const starterCandyCosts: { passive: number; costReduction: [number, number]; egg: number; }[] = [
{ passive: 40, costReduction: [ 25, 60 ], egg: 30 }, // 1 Cost { passive: 40, costReduction: [ 25, 60 ], egg: 30 }, // 1 Cost
@ -657,3 +657,4 @@ export function getValueReductionCandyCounts(starterCost: number): [number, numb
export function getSameSpeciesEggCandyCounts(starterCost: number): number { export function getSameSpeciesEggCandyCounts(starterCost: number): number {
return starterCandyCosts[starterCost - 1].egg; return starterCandyCosts[starterCost - 1].egg;
} }

View File

@ -1397,7 +1397,6 @@ export class EncounterBattleAnim extends BattleAnim {
} }
} }
// biome-ignore-start lint/style/useForOf: This is being removed
export async function populateAnims() { export async function populateAnims() {
const commonAnimNames = getEnumKeys(CommonAnim).map(k => k.toLowerCase()); const commonAnimNames = getEnumKeys(CommonAnim).map(k => k.toLowerCase());
const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/_/g, "")); const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/_/g, ""));
@ -1673,5 +1672,3 @@ export async function populateAnims() {
})(); })();
}*/ }*/
} }
// biome-ignore-end lint/style/useForOf: This is being removed

View File

@ -2537,10 +2537,12 @@ export class RoostedTag extends BattlerTag {
let modifiedTypes: PokemonType[]; let modifiedTypes: PokemonType[];
if (this.isBasePureFlying && !isCurrentlyDualType) { if (this.isBasePureFlying && !isCurrentlyDualType) {
modifiedTypes = [PokemonType.NORMAL]; modifiedTypes = [PokemonType.NORMAL];
} else if (!!pokemon.getTag(RemovedTypeTag) && isOriginallyDualType && !isCurrentlyDualType) {
modifiedTypes = [PokemonType.UNKNOWN];
} else { } else {
modifiedTypes = currentTypes.filter(type => type !== PokemonType.FLYING); if (!!pokemon.getTag(RemovedTypeTag) && isOriginallyDualType && !isCurrentlyDualType) {
modifiedTypes = [PokemonType.UNKNOWN];
} else {
modifiedTypes = currentTypes.filter(type => type !== PokemonType.FLYING);
}
} }
pokemon.summonData.types = modifiedTypes; pokemon.summonData.types = modifiedTypes;
pokemon.updateInfo(); pokemon.updateInfo();

View File

@ -42,9 +42,10 @@ export function getDailyRunStarters(seed: string): Starter[] {
starterCosts.push(randSeedInt(9 - starterCosts[0], 1)); starterCosts.push(randSeedInt(9 - starterCosts[0], 1));
starterCosts.push(10 - (starterCosts[0] + starterCosts[1])); starterCosts.push(10 - (starterCosts[0] + starterCosts[1]));
for (const cost of starterCosts) { for (let c = 0; c < starterCosts.length; c++) {
const cost = starterCosts[c];
const costSpecies = Object.keys(speciesStarterCosts) const costSpecies = Object.keys(speciesStarterCosts)
.map(s => Number.parseInt(s) as SpeciesId) // TODO: Remove .map(s => Number.parseInt(s) as SpeciesId)
.filter(s => speciesStarterCosts[s] === cost); .filter(s => speciesStarterCosts[s] === cost);
const randPkmSpecies = getPokemonSpecies(randSeedItem(costSpecies)); const randPkmSpecies = getPokemonSpecies(randSeedItem(costSpecies));
const starterSpecies = getPokemonSpecies( const starterSpecies = getPokemonSpecies(

View File

@ -419,8 +419,10 @@ export class Egg {
const rand = randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1; const rand = randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1;
return rand ? SpeciesId.PHIONE : SpeciesId.MANAPHY; return rand ? SpeciesId.PHIONE : SpeciesId.MANAPHY;
} }
if (this.tier === EggTier.LEGENDARY && this._sourceType === EggSourceType.GACHA_LEGENDARY && !randSeedInt(2)) { if (this.tier === EggTier.LEGENDARY && this._sourceType === EggSourceType.GACHA_LEGENDARY) {
return getLegendaryGachaSpeciesForTimestamp(this.timestamp); if (!randSeedInt(2)) {
return getLegendaryGachaSpeciesForTimestamp(this.timestamp);
}
} }
let minStarterValue: number; let minStarterValue: number;

View File

@ -527,8 +527,7 @@ function doBerrySpritePile(isEat = false) {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
animationOrder.forEach((berry, i) => { animationOrder.forEach((berry, i) => {
const introVisualsIndex = encounter.spriteConfigs.findIndex(config => config.spriteKey?.includes(berry)); const introVisualsIndex = encounter.spriteConfigs.findIndex(config => config.spriteKey?.includes(berry));
let sprite: Phaser.GameObjects.Sprite; let sprite: Phaser.GameObjects.Sprite, tintSprite: Phaser.GameObjects.Sprite;
let tintSprite: Phaser.GameObjects.Sprite;
const sprites = encounter.introVisuals?.getSpriteAtIndex(introVisualsIndex); const sprites = encounter.introVisuals?.getSpriteAtIndex(introVisualsIndex);
if (sprites) { if (sprites) {
sprite = sprites[0]; sprite = sprites[0];

View File

@ -213,8 +213,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
female: true, female: true,
}); });
let beedrillKeys: { spriteKey: string; fileRoot: string }; let beedrillKeys: { spriteKey: string; fileRoot: string }, butterfreeKeys: { spriteKey: string; fileRoot: string };
let butterfreeKeys: { spriteKey: string; fileRoot: string };
if (globalScene.currentBattle.waveIndex < WAVE_LEVEL_BREAKPOINTS[3]) { if (globalScene.currentBattle.waveIndex < WAVE_LEVEL_BREAKPOINTS[3]) {
beedrillKeys = getSpriteKeysFromSpecies(SpeciesId.BEEDRILL, false); beedrillKeys = getSpriteKeysFromSpecies(SpeciesId.BEEDRILL, false);
butterfreeKeys = getSpriteKeysFromSpecies(SpeciesId.BUTTERFREE, false); butterfreeKeys = getSpriteKeysFromSpecies(SpeciesId.BUTTERFREE, false);

View File

@ -289,14 +289,16 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
// Extra HA roll at base 1/64 odds (boosted by events and charms) // Extra HA roll at base 1/64 odds (boosted by events and charms)
const hiddenIndex = tradePokemon.species.ability2 ? 2 : 1; const hiddenIndex = tradePokemon.species.ability2 ? 2 : 1;
if (tradePokemon.species.abilityHidden && tradePokemon.abilityIndex < hiddenIndex) { if (tradePokemon.species.abilityHidden) {
const hiddenAbilityChance = new NumberHolder(64); if (tradePokemon.abilityIndex < hiddenIndex) {
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); const hiddenAbilityChance = new NumberHolder(64);
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
if (hasHiddenAbility) { if (hasHiddenAbility) {
tradePokemon.abilityIndex = hiddenIndex; tradePokemon.abilityIndex = hiddenIndex;
}
} }
} }

View File

@ -1107,10 +1107,12 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
} }
} else if (biomeLinks.hasOwnProperty(currentBiome)) { } else if (biomeLinks.hasOwnProperty(currentBiome)) {
currentBiome = biomeLinks[currentBiome] as BiomeId; currentBiome = biomeLinks[currentBiome] as BiomeId;
} else if (!(i % 50)) {
currentBiome = BiomeId.END;
} else { } else {
currentBiome = globalScene.generateRandomBiome(i); if (!(i % 50)) {
currentBiome = BiomeId.END;
} else {
currentBiome = globalScene.generateRandomBiome(i);
}
} }
currentArena = globalScene.newArena(currentBiome); currentArena = globalScene.newArena(currentBiome);

View File

@ -986,39 +986,41 @@ export class PokemonSpecies extends PokemonSpeciesForm implements Localizable {
if (!forTrainer && isRegionalEvolution) { if (!forTrainer && isRegionalEvolution) {
evolutionChance = 0; evolutionChance = 0;
} else if (ev.wildDelay === SpeciesWildEvolutionDelay.NONE) { } else {
if (strength === PartyMemberStrength.STRONGER) { if (ev.wildDelay === SpeciesWildEvolutionDelay.NONE) {
evolutionChance = 1; if (strength === PartyMemberStrength.STRONGER) {
evolutionChance = 1;
} else {
const maxLevelDiff = this.getStrengthLevelDiff(strength); //The maximum distance from the evolution level tolerated for the mon to not evolve
const minChance: number = 0.875 - 0.125 * strength;
evolutionChance = Math.min(
minChance + easeInFunc(Math.min(level - ev.level, maxLevelDiff) / maxLevelDiff) * (1 - minChance),
1,
);
}
} else { } else {
const maxLevelDiff = this.getStrengthLevelDiff(strength); //The maximum distance from the evolution level tolerated for the mon to not evolve const preferredMinLevel = Math.max(ev.level - 1 + ev.wildDelay! * this.getStrengthLevelDiff(strength), 1); // TODO: is the bang correct?
const minChance: number = 0.875 - 0.125 * strength; let evolutionLevel = Math.max(ev.level > 1 ? ev.level : Math.floor(preferredMinLevel / 2), 1);
if (ev.level <= 1 && pokemonPrevolutions.hasOwnProperty(this.speciesId)) {
const prevolutionLevel = pokemonEvolutions[pokemonPrevolutions[this.speciesId]].find(
ev => ev.speciesId === this.speciesId,
)!.level; // TODO: is the bang correct?
if (prevolutionLevel > 1) {
evolutionLevel = prevolutionLevel;
}
}
evolutionChance = Math.min( evolutionChance = Math.min(
minChance + easeInFunc(Math.min(level - ev.level, maxLevelDiff) / maxLevelDiff) * (1 - minChance), 0.65 * easeInFunc(Math.min(Math.max(level - evolutionLevel, 0), preferredMinLevel) / preferredMinLevel) +
0.35 *
easeOutFunc(
Math.min(Math.max(level - evolutionLevel, 0), preferredMinLevel * 2.5) / (preferredMinLevel * 2.5),
),
1, 1,
); );
} }
} else {
const preferredMinLevel = Math.max(ev.level - 1 + ev.wildDelay! * this.getStrengthLevelDiff(strength), 1); // TODO: is the bang correct?
let evolutionLevel = Math.max(ev.level > 1 ? ev.level : Math.floor(preferredMinLevel / 2), 1);
if (ev.level <= 1 && pokemonPrevolutions.hasOwnProperty(this.speciesId)) {
const prevolutionLevel = pokemonEvolutions[pokemonPrevolutions[this.speciesId]].find(
ev => ev.speciesId === this.speciesId,
)!.level; // TODO: is the bang correct?
if (prevolutionLevel > 1) {
evolutionLevel = prevolutionLevel;
}
}
evolutionChance = Math.min(
0.65 * easeInFunc(Math.min(Math.max(level - evolutionLevel, 0), preferredMinLevel) / preferredMinLevel) +
0.35 *
easeOutFunc(
Math.min(Math.max(level - evolutionLevel, 0), preferredMinLevel * 2.5) / (preferredMinLevel * 2.5),
),
1,
);
} }
//TODO: Adjust templates and delays so we don't have to hardcode it //TODO: Adjust templates and delays so we don't have to hardcode it
@ -1208,9 +1210,9 @@ export class PokemonSpecies extends PokemonSpeciesForm implements Localizable {
} }
/** /**
* Generates a {@linkcode BigInt} corresponding to the maximum unlocks possible for this species, * Generates a {@linkcode bigint} corresponding to the maximum unlocks possible for this species,
* taking into account if the species has a male/female gender, and which variants are implemented. * taking into account if the species has a male/female gender, and which variants are implemented.
* @returns The maximum unlocks for the species as a `BigInt`; can be compared with {@linkcode DexEntry.caughtAttr}. * @returns {@linkcode bigint} Maximum unlocks, can be compared with {@linkcode DexEntry.caughtAttr}.
*/ */
getFullUnlocksData(): bigint { getFullUnlocksData(): bigint {
let caughtAttr = 0n; let caughtAttr = 0n;

View File

@ -6,7 +6,6 @@ import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
import { loadPokemonVariantAssets } from "#sprites/pokemon-sprite"; import { loadPokemonVariantAssets } from "#sprites/pokemon-sprite";
import type { Variant } from "#sprites/variant"; import type { Variant } from "#sprites/variant";
import { isNullOrUndefined } from "#utils/common"; import { isNullOrUndefined } from "#utils/common";
import console from "node:console";
import type { GameObjects } from "phaser"; import type { GameObjects } from "phaser";
type PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig; type PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig;
@ -88,7 +87,6 @@ export class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Container {
variant: Variant; variant: Variant;
}[]; }[];
// TODO: Refactor
constructor(encounter: MysteryEncounter) { constructor(encounter: MysteryEncounter) {
super(globalScene, -72, 76); super(globalScene, -72, 76);
this.encounter = encounter; this.encounter = encounter;
@ -195,15 +193,17 @@ export class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Container {
sprite.setPosition(sprite.x, sprite.y + y); sprite.setPosition(sprite.x, sprite.y + y);
tintSprite.setPosition(tintSprite.x, tintSprite.y + y); tintSprite.setPosition(tintSprite.x, tintSprite.y + y);
} }
// Single sprite
} else if (this.spriteConfigs.length === 1) {
sprite.x = origin;
tintSprite.x = origin;
} else { } else {
// Do standard sprite spacing (not including offset sprites) // Single sprite
sprite.x = minX + (n + 0.5) * spacingValue + origin; if (this.spriteConfigs.length === 1) {
tintSprite.x = minX + (n + 0.5) * spacingValue + origin; sprite.x = origin;
n++; tintSprite.x = origin;
} else {
// Do standard sprite spacing (not including offset sprites)
sprite.x = minX + (n + 0.5) * spacingValue + origin;
tintSprite.x = minX + (n + 0.5) * spacingValue + origin;
n++;
}
} }
if (!isNullOrUndefined(pokemonShinySparkle)) { if (!isNullOrUndefined(pokemonShinySparkle)) {

View File

@ -2511,13 +2511,17 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
defenderType, defenderType,
}); });
} }
if (ignoreImmunity.value && multiplier.value === 0) { if (ignoreImmunity.value) {
return 1; if (multiplier.value === 0) {
return 1;
}
} }
const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[]; const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[];
if (exposedTags.some(t => t.ignoreImmunity(defenderType, moveType)) && multiplier.value === 0) { if (exposedTags.some(t => t.ignoreImmunity(defenderType, moveType))) {
return 1; if (multiplier.value === 0) {
return 1;
}
} }
} }
return multiplier.value; return multiplier.value;
@ -2956,8 +2960,10 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
return false; return false;
} }
const haThreshold = new NumberHolder(thresholdOverride ?? BASE_HIDDEN_ABILITY_CHANCE); const haThreshold = new NumberHolder(thresholdOverride ?? BASE_HIDDEN_ABILITY_CHANCE);
if (applyModifiersToOverride && !this.hasTrainer()) { if (applyModifiersToOverride) {
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold); if (!this.hasTrainer()) {
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold);
}
} }
if (randSeedInt(65536) < haThreshold.value) { if (randSeedInt(65536) < haThreshold.value) {
@ -3057,7 +3063,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
return; return;
} }
for (const levelMove of allLevelMoves) { for (let m = 0; m < allLevelMoves.length; m++) {
const levelMove = allLevelMoves[m];
if (this.level < levelMove[0]) { if (this.level < levelMove[0]) {
break; break;
} }
@ -4822,9 +4829,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
return true; return true;
}); });
if ((this.isOfType(PokemonType.POISON) || this.isOfType(PokemonType.STEEL)) && poisonImmunity.includes(true)) { if (this.isOfType(PokemonType.POISON) || this.isOfType(PokemonType.STEEL)) {
this.queueStatusImmuneMessage(quiet); if (poisonImmunity.includes(true)) {
return false; this.queueStatusImmuneMessage(quiet);
return false;
}
} }
break; break;
} }
@ -5004,8 +5013,10 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
this.lapseTag(BattlerTagType.NIGHTMARE); this.lapseTag(BattlerTagType.NIGHTMARE);
} }
} }
if (confusion && this.getTag(BattlerTagType.CONFUSED)) { if (confusion) {
this.lapseTag(BattlerTagType.CONFUSED); if (this.getTag(BattlerTagType.CONFUSED)) {
this.lapseTag(BattlerTagType.CONFUSED);
}
} }
if (reloadAssets) { if (reloadAssets) {
this.loadAssets(false).then(() => this.playAnim()); this.loadAssets(false).then(() => this.playAnim());
@ -5489,8 +5500,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
spriteColors.forEach((sc: number[], i: number) => { spriteColors.forEach((sc: number[], i: number) => {
paletteDeltas.push([]); paletteDeltas.push([]);
for (const p of palette) { for (let p = 0; p < palette.length; p++) {
paletteDeltas[i].push(deltaRgb(sc, p)); paletteDeltas[i].push(deltaRgb(sc, palette[p]));
} }
}); });
@ -6763,12 +6774,10 @@ export class EnemyPokemon extends Pokemon {
} }
canBypassBossSegments(segmentCount = 1): boolean { canBypassBossSegments(segmentCount = 1): boolean {
if ( if (globalScene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
globalScene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && if (!this.formIndex && this.bossSegmentIndex - segmentCount < 1) {
!this.formIndex && return false;
this.bossSegmentIndex - segmentCount < 1 }
) {
return false;
} }
return true; return true;

View File

@ -403,14 +403,16 @@ export class Trainer extends Phaser.GameObjects.Container {
} else { } else {
newSpeciesPool = speciesPoolFiltered; newSpeciesPool = speciesPoolFiltered;
} }
} else {
// If the index is odd, use the species pool for the partner trainer (that way he only uses his own pokemon in battle) // If the index is odd, use the species pool for the partner trainer (that way he only uses his own pokemon in battle)
// Since the only currently allowed double battle with named trainers is Tate & Liza, we need to make sure that Solrock is the first pokemon in the party for Tate and Lunatone for Liza // Since the only currently allowed double battle with named trainers is Tate & Liza, we need to make sure that Solrock is the first pokemon in the party for Tate and Lunatone for Liza
} else if (index === 1 && TrainerType[this.config.trainerTypeDouble] === TrainerType[TrainerType.TATE]) { if (index === 1 && TrainerType[this.config.trainerTypeDouble] === TrainerType[TrainerType.TATE]) {
newSpeciesPool = [SpeciesId.SOLROCK]; newSpeciesPool = [SpeciesId.SOLROCK];
} else if (index === 1 && TrainerType[this.config.trainerTypeDouble] === TrainerType[TrainerType.LIZA]) { } else if (index === 1 && TrainerType[this.config.trainerTypeDouble] === TrainerType[TrainerType.LIZA]) {
newSpeciesPool = [SpeciesId.LUNATONE]; newSpeciesPool = [SpeciesId.LUNATONE];
} else { } else {
newSpeciesPool = speciesPoolPartnerFiltered; newSpeciesPool = speciesPoolPartnerFiltered;
}
} }
// Fallback for when the species pool is empty // Fallback for when the species pool is empty
if (newSpeciesPool.length === 0) { if (newSpeciesPool.length === 0) {
@ -792,12 +794,10 @@ export class Trainer extends Phaser.GameObjects.Container {
* @returns boolean Whether the EnemyPokemon should Terastalize this turn * @returns boolean Whether the EnemyPokemon should Terastalize this turn
*/ */
shouldTera(pokemon: EnemyPokemon): boolean { shouldTera(pokemon: EnemyPokemon): boolean {
if ( if (this.config.trainerAI.teraMode === TeraAIMode.INSTANT_TERA) {
this.config.trainerAI.teraMode === TeraAIMode.INSTANT_TERA && if (!pokemon.isTerastallized && this.config.trainerAI.instantTeras.includes(pokemon.initialTeamIndex)) {
!pokemon.isTerastallized && return true;
this.config.trainerAI.instantTeras.includes(pokemon.initialTeamIndex) }
) {
return true;
} }
return false; return false;
} }

View File

@ -1634,9 +1634,9 @@ export class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
if (p.species.speciesId === SpeciesId.NECROZMA) { if (p.species.speciesId === SpeciesId.NECROZMA) {
// technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break... // technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break...
let foundULTRA_Z = false; let foundULTRA_Z = false,
let foundN_LUNA = false; foundN_LUNA = false,
let foundN_SOLAR = false; foundN_SOLAR = false;
formChangeItemTriggers.forEach((fc, _i) => { formChangeItemTriggers.forEach((fc, _i) => {
console.log("Checking ", fc.item); console.log("Checking ", fc.item);
switch (fc.item) { switch (fc.item) {

View File

@ -102,13 +102,10 @@ import { WeatherEffectPhase } from "#phases/weather-effect-phase";
import type { PhaseMap, PhaseString } from "#types/phase-types"; import type { PhaseMap, PhaseString } from "#types/phase-types";
import { type Constructor, coerceArray } from "#utils/common"; import { type Constructor, coerceArray } from "#utils/common";
/** /*
* @module
* Manager for phases used by battle scene. * Manager for phases used by battle scene.
* *
* @remarks * *This file must not be imported or used directly. The manager is exclusively used by the battle scene and is not intended for external use.*
* **This file must not be imported or used directly.**
* The manager is exclusively used by the Battle Scene and is NOT intended for external use.
*/ */
/** /**

View File

@ -604,8 +604,9 @@ export class CommandPhase extends FieldPhase {
* @returns Whether the command was successful * @returns Whether the command was successful
*/ */
handleCommand(command: Command.FIGHT | Command.TERA, cursor: number, useMode?: MoveUseMode, move?: TurnMove): boolean; handleCommand(command: Command.FIGHT | Command.TERA, cursor: number, useMode?: MoveUseMode, move?: TurnMove): boolean;
handleCommand(command: Command.BALL, cursor: number): boolean;
handleCommand(command: Command.POKEMON, cursor: number, useBaton: boolean): boolean; handleCommand(command: Command.POKEMON, cursor: number, useBaton: boolean): boolean;
handleCommand(command: Command.BALL | Command.RUN, cursor: number): boolean; handleCommand(command: Command.RUN, cursor: number): boolean;
handleCommand(command: Command, cursor: number, useMode?: boolean | MoveUseMode, move?: TurnMove): boolean; handleCommand(command: Command, cursor: number, useMode?: boolean | MoveUseMode, move?: TurnMove): boolean;
public handleCommand( public handleCommand(

View File

@ -10,12 +10,19 @@ export class CommonAnimPhase extends PokemonPhase {
public readonly phaseName: "CommonAnimPhase" | "PokemonHealPhase" | "WeatherEffectPhase" = "CommonAnimPhase"; public readonly phaseName: "CommonAnimPhase" | "PokemonHealPhase" | "WeatherEffectPhase" = "CommonAnimPhase";
private anim: CommonAnim | null; private anim: CommonAnim | null;
private targetIndex?: BattlerIndex; private targetIndex?: BattlerIndex;
private playOnEmptyField: boolean;
constructor(battlerIndex?: BattlerIndex, targetIndex?: BattlerIndex, anim: CommonAnim | null = null) { constructor(
battlerIndex?: BattlerIndex,
targetIndex?: BattlerIndex,
anim: CommonAnim | null = null,
playOnEmptyField = false,
) {
super(battlerIndex); super(battlerIndex);
this.anim = anim; this.anim = anim;
this.targetIndex = targetIndex; this.targetIndex = targetIndex;
this.playOnEmptyField = playOnEmptyField;
} }
setAnimation(anim: CommonAnim) { setAnimation(anim: CommonAnim) {

View File

@ -559,11 +559,10 @@ export class MoveEffectPhase extends PokemonPhase {
// Strikes after the first in a multi-strike move are guaranteed to hit, // Strikes after the first in a multi-strike move are guaranteed to hit,
// unless the move is flagged to check all hits and the user does not have Skill Link. // unless the move is flagged to check all hits and the user does not have Skill Link.
if ( if (user.turnData.hitsLeft < user.turnData.hitCount) {
user.turnData.hitsLeft < user.turnData.hitCount && if (!move.hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr("MaxMultiHitAbAttr")) {
(!move.hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr("MaxMultiHitAbAttr")) return [HitCheckResult.HIT, effectiveness];
) { }
return [HitCheckResult.HIT, effectiveness];
} }
const bypassAccuracy = const bypassAccuracy =

View File

@ -23,24 +23,25 @@ export class ScanIvsPhase extends PokemonPhase {
let enemyIvs: number[] = []; let enemyIvs: number[] = [];
let statsContainer: Phaser.GameObjects.Sprite[] = []; let statsContainer: Phaser.GameObjects.Sprite[] = [];
let statsContainerLabels: Phaser.GameObjects.Sprite[] = []; let statsContainerLabels: Phaser.GameObjects.Sprite[] = [];
const enemyField = globalScene.getEnemyField();
const uiTheme = globalScene.uiTheme; // Assuming uiTheme is accessible const uiTheme = globalScene.uiTheme; // Assuming uiTheme is accessible
for (const enemy of globalScene.getEnemyField()) { for (let e = 0; e < enemyField.length; e++) {
enemyIvs = enemy.ivs; enemyIvs = enemyField[e].ivs;
// we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists
const currentIvs = globalScene.gameData.dexData[enemy.species.getRootSpeciesId()].ivs; const currentIvs = globalScene.gameData.dexData[enemyField[e].species.getRootSpeciesId()].ivs;
statsContainer = enemy.getBattleInfo().getStatsValueContainer().list as Phaser.GameObjects.Sprite[]; statsContainer = enemyField[e].getBattleInfo().getStatsValueContainer().list as Phaser.GameObjects.Sprite[];
statsContainerLabels = statsContainer.filter(m => m.name.indexOf("icon_stat_label") >= 0); statsContainerLabels = statsContainer.filter(m => m.name.indexOf("icon_stat_label") >= 0);
for (const statContainer of statsContainerLabels) { for (let s = 0; s < statsContainerLabels.length; s++) {
const ivStat = Stat[statContainer.frame.name]; const ivStat = Stat[statsContainerLabels[s].frame.name];
if (enemyIvs[ivStat] > currentIvs[ivStat] && PERMANENT_STATS.indexOf(Number(ivStat)) >= 0) { if (enemyIvs[ivStat] > currentIvs[ivStat] && PERMANENT_STATS.indexOf(Number(ivStat)) >= 0) {
const hexColour = const hexColour =
enemyIvs[ivStat] === 31 enemyIvs[ivStat] === 31
? getTextColor(TextStyle.PERFECT_IV, false, uiTheme) ? getTextColor(TextStyle.PERFECT_IV, false, uiTheme)
: getTextColor(TextStyle.SUMMARY_GREEN, false, uiTheme); : getTextColor(TextStyle.SUMMARY_GREEN, false, uiTheme);
const hexTextColour = Phaser.Display.Color.HexStringToColor(hexColour).color; const hexTextColour = Phaser.Display.Color.HexStringToColor(hexColour).color;
statContainer.setTint(hexTextColour); statsContainerLabels[s].setTint(hexTextColour);
} }
statContainer.setVisible(true); statsContainerLabels[s].setVisible(true);
} }
} }

View File

@ -22,7 +22,6 @@ export type StatStageChangeCallback = (
relativeChanges: number[], relativeChanges: number[],
) => void; ) => void;
// TODO: Refactor this mess of a phase
export class StatStageChangePhase extends PokemonPhase { export class StatStageChangePhase extends PokemonPhase {
public readonly phaseName = "StatStageChangePhase"; public readonly phaseName = "StatStageChangePhase";
private stats: BattleStat[]; private stats: BattleStat[];
@ -63,12 +62,13 @@ export class StatStageChangePhase extends PokemonPhase {
start() { start() {
// Check if multiple stats are being changed at the same time, then run SSCPhase for each of them // Check if multiple stats are being changed at the same time, then run SSCPhase for each of them
if (this.stats.length > 1) { if (this.stats.length > 1) {
for (const stat of this.stats) { for (let i = 0; i < this.stats.length; i++) {
const stat = [this.stats[i]];
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"StatStageChangePhase", "StatStageChangePhase",
this.battlerIndex, this.battlerIndex,
this.selfTarget, this.selfTarget,
[stat], stat,
this.stages, this.stages,
this.showMessage, this.showMessage,
this.ignoreAbilities, this.ignoreAbilities,
@ -100,18 +100,20 @@ export class StatStageChangePhase extends PokemonPhase {
} }
}); });
} }
} else if (!this.comingFromStickyWeb) {
opponentPokemon = globalScene.getPlayerField()[globalScene.currentBattle.lastPlayerInvolved];
} else { } else {
const stickyTagID = globalScene.arena.findTagsOnSide( if (!this.comingFromStickyWeb) {
(t: ArenaTag) => t.tagType === ArenaTagType.STICKY_WEB, opponentPokemon = globalScene.getPlayerField()[globalScene.currentBattle.lastPlayerInvolved];
ArenaTagSide.ENEMY, } else {
)[0].sourceId; const stickyTagID = globalScene.arena.findTagsOnSide(
globalScene.getPlayerField().forEach(e => { (t: ArenaTag) => t.tagType === ArenaTagType.STICKY_WEB,
if (e.id === stickyTagID) { ArenaTagSide.ENEMY,
opponentPokemon = e; )[0].sourceId;
} globalScene.getPlayerField().forEach(e => {
}); if (e.id === stickyTagID) {
opponentPokemon = e;
}
});
}
} }
if (!pokemon.isActive(true)) { if (!pokemon.isActive(true)) {

View File

@ -27,28 +27,29 @@ export class TrainerVictoryPhase extends BattlePhase {
const trainerType = globalScene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? const trainerType = globalScene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct?
// Validate Voucher for boss trainers // Validate Voucher for boss trainers
if ( if (vouchers.hasOwnProperty(TrainerType[trainerType])) {
vouchers.hasOwnProperty(TrainerType[trainerType]) && if (
!globalScene.validateVoucher(vouchers[TrainerType[trainerType]]) && !globalScene.validateVoucher(vouchers[TrainerType[trainerType]]) &&
globalScene.currentBattle.trainer?.config.isBoss globalScene.currentBattle.trainer?.config.isBoss
) { ) {
if (timedEventManager.getUpgradeUnlockedVouchers()) { if (timedEventManager.getUpgradeUnlockedVouchers()) {
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"ModifierRewardPhase", "ModifierRewardPhase",
[ [
modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PLUS,
modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PLUS,
modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PLUS,
modifierTypes.VOUCHER_PREMIUM, modifierTypes.VOUCHER_PREMIUM,
][vouchers[TrainerType[trainerType]].voucherType], ][vouchers[TrainerType[trainerType]].voucherType],
); );
} else { } else {
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"ModifierRewardPhase", "ModifierRewardPhase",
[modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][ [modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][
vouchers[TrainerType[trainerType]].voucherType vouchers[TrainerType[trainerType]].voucherType
], ],
); );
}
} }
} }
// Breeders in Space achievement // Breeders in Space achievement

View File

@ -16,8 +16,8 @@ export class PokerogueSessionSavedataApi extends ApiBase {
//#region Public //#region Public
/** /**
* Mark a session as cleared aka "newclear". \ * Mark a session as cleared aka "newclear".\
* _This is **NOT** the same as {@linkcode clear | clear()}._ * *This is **NOT** the same as {@linkcode clear | clear()}.*
* @param params The {@linkcode NewClearSessionSavedataRequest} to send * @param params The {@linkcode NewClearSessionSavedataRequest} to send
* @returns The raw savedata as `string`. * @returns The raw savedata as `string`.
* @throws Error if the request fails * @throws Error if the request fails
@ -94,8 +94,8 @@ export class PokerogueSessionSavedataApi extends ApiBase {
} }
/** /**
* Clears the session savedata of the given slot. \ * Clears the session savedata of the given slot.\
* _This is **NOT** the same as {@linkcode newclear | newclear()}._ * *This is **NOT** the same as {@linkcode newclear | newclear()}.*
* @param params The {@linkcode ClearSessionSavedataRequest} to send * @param params The {@linkcode ClearSessionSavedataRequest} to send
* @param sessionData The {@linkcode SessionSaveData} object * @param sessionData The {@linkcode SessionSaveData} object
*/ */

View File

@ -121,8 +121,8 @@ async function initFonts(language: string | undefined) {
} }
/** /**
* I18n money formatter with. (useful for BBCode coloring of text) \ * I18n money formatter with. (useful for BBCode coloring of text)\
* _If you don't want the BBCode tag applied, just use 'number' formatter_ * *If you don't want the BBCode tag applied, just use 'number' formatter*
* @example Input: `{{myMoneyValue, money}}` * @example Input: `{{myMoneyValue, money}}`
* Output: `@[MONEY]{₽100,000,000}` * Output: `@[MONEY]{₽100,000,000}`
* @param amount the money amount * @param amount the money amount

View File

@ -1595,7 +1595,7 @@ export class GameData {
globalScene.executeWithSeedOffset( globalScene.executeWithSeedOffset(
() => { () => {
const neutralNatures = [Nature.HARDY, Nature.DOCILE, Nature.SERIOUS, Nature.BASHFUL, Nature.QUIRKY]; const neutralNatures = [Nature.HARDY, Nature.DOCILE, Nature.SERIOUS, Nature.BASHFUL, Nature.QUIRKY];
for (const _ of defaultStarterSpecies) { for (let s = 0; s < defaultStarterSpecies.length; s++) {
defaultStarterNatures.push(randSeedItem(neutralNatures)); defaultStarterNatures.push(randSeedItem(neutralNatures));
} }
}, },

View File

@ -104,16 +104,18 @@ export function setSettingGamepad(setting: SettingGamepad, value: number): boole
case SettingGamepad.Button_Speed_Up: case SettingGamepad.Button_Speed_Up:
case SettingGamepad.Button_Slow_Down: case SettingGamepad.Button_Slow_Down:
case SettingGamepad.Button_Submit: case SettingGamepad.Button_Submit:
if (value && globalScene.ui) { if (value) {
const cancelHandler = (success = false): boolean => { if (globalScene.ui) {
globalScene.ui.revertMode(); const cancelHandler = (success = false): boolean => {
(globalScene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); globalScene.ui.revertMode();
return success; (globalScene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings();
}; return success;
globalScene.ui.setOverlayMode(UiMode.GAMEPAD_BINDING, { };
target: setting, globalScene.ui.setOverlayMode(UiMode.GAMEPAD_BINDING, {
cancelHandler: cancelHandler, target: setting,
}); cancelHandler: cancelHandler,
});
}
} }
break; break;
case SettingGamepad.Controller: case SettingGamepad.Controller:

View File

@ -167,16 +167,18 @@ export function setSettingKeyboard(setting: SettingKeyboard, value: number): boo
case SettingKeyboard.Alt_Button_Speed_Up: case SettingKeyboard.Alt_Button_Speed_Up:
case SettingKeyboard.Alt_Button_Slow_Down: case SettingKeyboard.Alt_Button_Slow_Down:
case SettingKeyboard.Alt_Button_Submit: case SettingKeyboard.Alt_Button_Submit:
if (value && globalScene.ui) { if (value) {
const cancelHandler = (success = false): boolean => { if (globalScene.ui) {
globalScene.ui.revertMode(); const cancelHandler = (success = false): boolean => {
(globalScene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); globalScene.ui.revertMode();
return success; (globalScene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings();
}; return success;
globalScene.ui.setOverlayMode(UiMode.KEYBOARD_BINDING, { };
target: setting, globalScene.ui.setOverlayMode(UiMode.KEYBOARD_BINDING, {
cancelHandler: cancelHandler, target: setting,
}); cancelHandler: cancelHandler,
});
}
} }
break; break;
} }

View File

@ -896,102 +896,104 @@ export function setSetting(setting: string, value: number): boolean {
globalScene.typeHints = Setting[index].options[value].value === "On"; globalScene.typeHints = Setting[index].options[value].value === "On";
break; break;
case SettingKeys.Language: case SettingKeys.Language:
if (value && globalScene.ui) { if (value) {
const cancelHandler = () => { if (globalScene.ui) {
globalScene.ui.revertMode(); const cancelHandler = () => {
(globalScene.ui.getHandler() as SettingsUiHandler).setOptionCursor(-1, 0, true); globalScene.ui.revertMode();
}; (globalScene.ui.getHandler() as SettingsUiHandler).setOptionCursor(-1, 0, true);
const changeLocaleHandler = (locale: string): boolean => { };
try { const changeLocaleHandler = (locale: string): boolean => {
i18next.changeLanguage(locale); try {
localStorage.setItem("prLang", locale); i18next.changeLanguage(locale);
cancelHandler(); localStorage.setItem("prLang", locale);
// Reload the whole game to apply the new locale since also some constants are translated cancelHandler();
window.location.reload(); // Reload the whole game to apply the new locale since also some constants are translated
return true; window.location.reload();
} catch (error) { return true;
console.error("Error changing locale:", error); } catch (error) {
return false; console.error("Error changing locale:", error);
} return false;
}; }
globalScene.ui.setOverlayMode(UiMode.OPTION_SELECT, { };
options: [ globalScene.ui.setOverlayMode(UiMode.OPTION_SELECT, {
{ options: [
label: "English", {
handler: () => changeLocaleHandler("en"), label: "English",
}, handler: () => changeLocaleHandler("en"),
{ },
label: "Español (ES)", {
handler: () => changeLocaleHandler("es-ES"), label: "Español (ES)",
}, handler: () => changeLocaleHandler("es-ES"),
{ },
label: "Español (LATAM)", {
handler: () => changeLocaleHandler("es-MX"), label: "Español (LATAM)",
}, handler: () => changeLocaleHandler("es-MX"),
{ },
label: "Français", {
handler: () => changeLocaleHandler("fr"), label: "Français",
}, handler: () => changeLocaleHandler("fr"),
{ },
label: "Deutsch", {
handler: () => changeLocaleHandler("de"), label: "Deutsch",
}, handler: () => changeLocaleHandler("de"),
{ },
label: "Italiano", {
handler: () => changeLocaleHandler("it"), label: "Italiano",
}, handler: () => changeLocaleHandler("it"),
{ },
label: "Português (BR)", {
handler: () => changeLocaleHandler("pt-BR"), label: "Português (BR)",
}, handler: () => changeLocaleHandler("pt-BR"),
{ },
label: "한국어", {
handler: () => changeLocaleHandler("ko"), label: "한국어",
}, handler: () => changeLocaleHandler("ko"),
{ },
label: "日本語", {
handler: () => changeLocaleHandler("ja"), label: "日本語",
}, handler: () => changeLocaleHandler("ja"),
{ },
label: "简体中文", {
handler: () => changeLocaleHandler("zh-CN"), label: "简体中文",
}, handler: () => changeLocaleHandler("zh-CN"),
{ },
label: "繁體中文", {
handler: () => changeLocaleHandler("zh-TW"), label: "繁體中文",
}, handler: () => changeLocaleHandler("zh-TW"),
{ },
label: "Català (Needs Help)", {
handler: () => changeLocaleHandler("ca"), label: "Català (Needs Help)",
}, handler: () => changeLocaleHandler("ca"),
{ },
label: "Türkçe (Needs Help)", {
handler: () => changeLocaleHandler("tr"), label: "Türkçe (Needs Help)",
}, handler: () => changeLocaleHandler("tr"),
{ },
label: "Русский (Needs Help)", {
handler: () => changeLocaleHandler("ru"), label: "Русский (Needs Help)",
}, handler: () => changeLocaleHandler("ru"),
{ },
label: "Dansk (Needs Help)", {
handler: () => changeLocaleHandler("da"), label: "Dansk (Needs Help)",
}, handler: () => changeLocaleHandler("da"),
{ },
label: "Română (Needs Help)", {
handler: () => changeLocaleHandler("ro"), label: "Română (Needs Help)",
}, handler: () => changeLocaleHandler("ro"),
{ },
label: "Tagalog (Needs Help)", {
handler: () => changeLocaleHandler("tl"), label: "Tagalog (Needs Help)",
}, handler: () => changeLocaleHandler("tl"),
{ },
label: i18next.t("settings:back"), {
handler: () => cancelHandler(), label: i18next.t("settings:back"),
}, handler: () => cancelHandler(),
], },
maxOptions: 7, ],
}); maxOptions: 7,
return false; });
return false;
}
} }
break; break;
case SettingKeys.Shop_Overlay_Opacity: case SettingKeys.Shop_Overlay_Opacity:

View File

@ -17,6 +17,8 @@ export class AdminUiHandler extends FormModalUiHandler {
private config: ModalConfig; private config: ModalConfig;
private readonly buttonGap = 10; private readonly buttonGap = 10;
// http response from the server when a username isn't found in the server
private readonly httpUserNotFoundErrorCode: number = 404;
private readonly ERR_REQUIRED_FIELD = (field: string) => { private readonly ERR_REQUIRED_FIELD = (field: string) => {
if (field === "username") { if (field === "username") {
return `${toTitleCase(field)} is required`; return `${toTitleCase(field)} is required`;

View File

@ -241,7 +241,9 @@ export class ArenaFlyout extends Phaser.GameObjects.Container {
this.fieldEffectInfo.sort((infoA, infoB) => infoA.duration - infoB.duration); this.fieldEffectInfo.sort((infoA, infoB) => infoA.duration - infoB.duration);
for (const fieldEffectInfo of this.fieldEffectInfo) { for (let i = 0; i < this.fieldEffectInfo.length; i++) {
const fieldEffectInfo = this.fieldEffectInfo[i];
// Creates a proxy object to decide which text object needs to be updated // Creates a proxy object to decide which text object needs to be updated
let textObject: Phaser.GameObjects.Text; let textObject: Phaser.GameObjects.Text;
switch (fieldEffectInfo.effectType) { switch (fieldEffectInfo.effectType) {
@ -387,7 +389,9 @@ export class ArenaFlyout extends Phaser.GameObjects.Container {
const fieldEffectInfo: ArenaEffectInfo[] = []; const fieldEffectInfo: ArenaEffectInfo[] = [];
this.fieldEffectInfo.forEach(i => fieldEffectInfo.push(i)); this.fieldEffectInfo.forEach(i => fieldEffectInfo.push(i));
for (const info of fieldEffectInfo) { for (let i = 0; i < fieldEffectInfo.length; i++) {
const info = fieldEffectInfo[i];
if (info.maxDuration === 0) { if (info.maxDuration === 0) {
continue; continue;
} }

View File

@ -68,14 +68,14 @@ export class BaseStatsOverlay extends Phaser.GameObjects.Container implements In
// show this component with infos for the specific move // show this component with infos for the specific move
show(values: number[], total: number): boolean { show(values: number[], total: number): boolean {
for (let i = 0; i < 6; i++) { for (let i = 0; i < 6; i++) {
this.statsLabels[i].setText(`${i18next.t(`pokemonInfo:Stat.${shortStats[i]}shortened`)}: ${values[i]}`); this.statsLabels[i].setText(i18next.t(`pokemonInfo:Stat.${shortStats[i]}shortened`) + ": " + `${values[i]}`);
// This accounts for base stats up to 200, might not be enough. // This accounts for base stats up to 200, might not be enough.
// TODO: change color based on value. // TODO: change color based on value.
this.statsShadows[i].setSize(values[i] / 2, 5); this.statsShadows[i].setSize(values[i] / 2, 5);
this.statsRectangles[i].setSize(values[i] / 2, 5); this.statsRectangles[i].setSize(values[i] / 2, 5);
} }
this.statsTotalLabel.setText(`${i18next.t("pokedexUiHandler:baseTotal")}: ${total}`); this.statsTotalLabel.setText(i18next.t("pokedexUiHandler:baseTotal") + ": " + `${total}`);
this.setVisible(true); this.setVisible(true);
this.active = true; this.active = true;

View File

@ -153,12 +153,16 @@ export class BattleMessageUiHandler extends MessageUiHandler {
processInput(button: Button): boolean { processInput(button: Button): boolean {
const ui = this.getUi(); const ui = this.getUi();
if (this.awaitingActionInput && (button === Button.CANCEL || button === Button.ACTION) && this.onActionInput) { if (this.awaitingActionInput) {
ui.playSelect(); if (button === Button.CANCEL || button === Button.ACTION) {
const originalOnActionInput = this.onActionInput; if (this.onActionInput) {
this.onActionInput = null; ui.playSelect();
originalOnActionInput(); const originalOnActionInput = this.onActionInput;
return true; this.onActionInput = null;
originalOnActionInput();
return true;
}
}
} }
return false; return false;

View File

@ -400,73 +400,75 @@ export class GameChallengesUiHandler extends UiHandler {
} else { } else {
success = false; success = false;
} }
} else if (this.cursorObj?.visible && !this.startCursor.visible) { } else {
switch (button) { if (this.cursorObj?.visible && !this.startCursor.visible) {
case Button.UP: switch (button) {
if (this.cursor === 0) { case Button.UP:
if (this.scrollCursor === 0) { if (this.cursor === 0) {
// When at the top of the menu and pressing UP, move to the bottommost item. if (this.scrollCursor === 0) {
if (globalScene.gameMode.challenges.length > rowsToDisplay) { // When at the top of the menu and pressing UP, move to the bottommost item.
// If there are more than 9 challenges, scroll to the bottom if (globalScene.gameMode.challenges.length > rowsToDisplay) {
// First, set the cursor to the last visible element, preparing for the scroll to the end. // If there are more than 9 challenges, scroll to the bottom
const successA = this.setCursor(rowsToDisplay - 1); // First, set the cursor to the last visible element, preparing for the scroll to the end.
// Then, adjust the scroll to display the bottommost elements of the menu. const successA = this.setCursor(rowsToDisplay - 1);
const successB = this.setScrollCursor(globalScene.gameMode.challenges.length - rowsToDisplay); // Then, adjust the scroll to display the bottommost elements of the menu.
success = successA && successB; // success is just there to play the little validation sound effect const successB = this.setScrollCursor(globalScene.gameMode.challenges.length - rowsToDisplay);
success = successA && successB; // success is just there to play the little validation sound effect
} else {
// If there are 9 or less challenges, just move to the bottom one
success = this.setCursor(globalScene.gameMode.challenges.length - 1);
}
} else { } else {
// If there are 9 or less challenges, just move to the bottom one success = this.setScrollCursor(this.scrollCursor - 1);
success = this.setCursor(globalScene.gameMode.challenges.length - 1);
} }
} else { } else {
success = this.setScrollCursor(this.scrollCursor - 1); success = this.setCursor(this.cursor - 1);
} }
} else { if (success) {
success = this.setCursor(this.cursor - 1); this.updateText();
} }
if (success) { break;
this.updateText(); case Button.DOWN:
} if (this.cursor === rowsToDisplay - 1) {
break; if (this.scrollCursor < globalScene.gameMode.challenges.length - rowsToDisplay) {
case Button.DOWN: // When at the bottom and pressing DOWN, scroll if possible.
if (this.cursor === rowsToDisplay - 1) { success = this.setScrollCursor(this.scrollCursor + 1);
if (this.scrollCursor < globalScene.gameMode.challenges.length - rowsToDisplay) { } else {
// When at the bottom and pressing DOWN, scroll if possible. // When at the bottom of a scrolling menu and pressing DOWN, move to the topmost item.
success = this.setScrollCursor(this.scrollCursor + 1); // First, set the cursor to the first visible element, preparing for the scroll to the top.
const successA = this.setCursor(0);
// Then, adjust the scroll to display the topmost elements of the menu.
const successB = this.setScrollCursor(0);
success = successA && successB; // success is just there to play the little validation sound effect
}
} else if (
globalScene.gameMode.challenges.length < rowsToDisplay &&
this.cursor === globalScene.gameMode.challenges.length - 1
) {
// When at the bottom of a non-scrolling menu and pressing DOWN, move to the topmost item.
success = this.setCursor(0);
} else { } else {
// When at the bottom of a scrolling menu and pressing DOWN, move to the topmost item. success = this.setCursor(this.cursor + 1);
// First, set the cursor to the first visible element, preparing for the scroll to the top.
const successA = this.setCursor(0);
// Then, adjust the scroll to display the topmost elements of the menu.
const successB = this.setScrollCursor(0);
success = successA && successB; // success is just there to play the little validation sound effect
} }
} else if ( if (success) {
globalScene.gameMode.challenges.length < rowsToDisplay && this.updateText();
this.cursor === globalScene.gameMode.challenges.length - 1 }
) { break;
// When at the bottom of a non-scrolling menu and pressing DOWN, move to the topmost item. case Button.LEFT:
success = this.setCursor(0); // Moves the option cursor left, if possible.
} else { success = this.getActiveChallenge().decreaseValue();
success = this.setCursor(this.cursor + 1); if (success) {
} this.updateText();
if (success) { }
this.updateText(); break;
} case Button.RIGHT:
break; // Moves the option cursor right, if possible.
case Button.LEFT: success = this.getActiveChallenge().increaseValue();
// Moves the option cursor left, if possible. if (success) {
success = this.getActiveChallenge().decreaseValue(); this.updateText();
if (success) { }
this.updateText(); break;
} }
break;
case Button.RIGHT:
// Moves the option cursor right, if possible.
success = this.getActiveChallenge().increaseValue();
if (success) {
this.updateText();
}
break;
} }
} }

View File

@ -504,11 +504,13 @@ export class DropDown extends Phaser.GameObjects.Container {
if (index === 0) { if (index === 0) {
// we are on the All option > put all other options to the newState // we are on the All option > put all other options to the newState
this.setAllOptions(newState); this.setAllOptions(newState);
} else if (newState === DropDownState.ON && this.checkForAllOn()) {
// select the "all" option if all others are selected, other unselect it
this.options[0].setOptionState(DropDownState.ON);
} else { } else {
this.options[0].setOptionState(DropDownState.OFF); // select the "all" option if all others are selected, other unselect it
if (newState === DropDownState.ON && this.checkForAllOn()) {
this.options[0].setOptionState(DropDownState.ON);
} else {
this.options[0].setOptionState(DropDownState.OFF);
}
} }
} else if (this.dropDownType === DropDownType.SINGLE) { } else if (this.dropDownType === DropDownType.SINGLE) {
if (option.state === DropDownState.OFF) { if (option.state === DropDownState.OFF) {
@ -651,8 +653,10 @@ export class DropDown extends Phaser.GameObjects.Container {
this.options[i].setDirection(SortDirection.ASC); this.options[i].setDirection(SortDirection.ASC);
this.options[i].toggle.setVisible(true); this.options[i].toggle.setVisible(true);
} }
} else if (this.defaultSettings[i]) { } else {
this.options[i].setOptionState(this.defaultSettings[i]["state"]); if (this.defaultSettings[i]) {
this.options[i].setOptionState(this.defaultSettings[i]["state"]);
}
} }
} }
@ -695,11 +699,11 @@ export class DropDown extends Phaser.GameObjects.Container {
autoSize(): void { autoSize(): void {
let maxWidth = 0; let maxWidth = 0;
let x = 0; let x = 0;
for (const option of this.options) { for (let i = 0; i < this.options.length; i++) {
const optionWidth = option.getWidth(); const optionWidth = this.options[i].getWidth();
if (optionWidth > maxWidth) { if (optionWidth > maxWidth) {
maxWidth = optionWidth; maxWidth = optionWidth;
x = option.getCurrentLabelX() ?? 0; x = this.options[i].getCurrentLabelX() ?? 0;
} }
} }
this.window.width = maxWidth + x - this.window.x + 9; this.window.width = maxWidth + x - this.window.x + 9;

View File

@ -62,12 +62,16 @@ export class EvolutionSceneHandler extends MessageUiHandler {
} }
const ui = this.getUi(); const ui = this.getUi();
if (this.awaitingActionInput && (button === Button.CANCEL || button === Button.ACTION) && this.onActionInput) { if (this.awaitingActionInput) {
ui.playSelect(); if (button === Button.CANCEL || button === Button.ACTION) {
const originalOnActionInput = this.onActionInput; if (this.onActionInput) {
this.onActionInput = null; ui.playSelect();
originalOnActionInput(); const originalOnActionInput = this.onActionInput;
return true; this.onActionInput = null;
originalOnActionInput();
return true;
}
}
} }
return false; return false;

View File

@ -130,20 +130,22 @@ export class FilterBar extends Phaser.GameObjects.Container {
* Move the leftmost dropdown to the left of the FilterBar instead of below it * Move the leftmost dropdown to the left of the FilterBar instead of below it
*/ */
offsetHybridFilters(): void { offsetHybridFilters(): void {
for (const dropDown of this.dropDowns) { for (let i = 0; i < this.dropDowns.length; i++) {
if (dropDown.dropDownType === DropDownType.HYBRID) { if (this.dropDowns[i].dropDownType === DropDownType.HYBRID) {
dropDown.autoSize(); this.dropDowns[i].autoSize();
dropDown.x = -dropDown.getWidth(); this.dropDowns[i].x = -this.dropDowns[i].getWidth();
dropDown.y = 0; this.dropDowns[i].y = 0;
} }
} }
} }
setCursor(cursor: number): void { setCursor(cursor: number): void {
if (this.lastCursor > -1 && this.dropDowns[this.lastCursor].visible) { if (this.lastCursor > -1) {
this.dropDowns[this.lastCursor].setVisible(false); if (this.dropDowns[this.lastCursor].visible) {
this.dropDowns[cursor].setVisible(true); this.dropDowns[this.lastCursor].setVisible(false);
this.dropDowns[cursor].resetCursor(); this.dropDowns[cursor].setVisible(true);
this.dropDowns[cursor].resetCursor();
}
} }
this.cursorObj.setPosition(this.labels[cursor].x - this.cursorOffset + 2, 6); this.cursorObj.setPosition(this.labels[cursor].x - this.cursorOffset + 2, 6);

View File

@ -24,12 +24,14 @@ export class FilterText extends Phaser.GameObjects.Container {
private rows: FilterTextRow[] = []; private rows: FilterTextRow[] = [];
public cursorObj: Phaser.GameObjects.Image; public cursorObj: Phaser.GameObjects.Image;
public numFilters = 0; public numFilters = 0;
private lastCursor = -1;
private uiTheme: UiTheme; private uiTheme: UiTheme;
private menuMessageBoxContainer: Phaser.GameObjects.Container; private menuMessageBoxContainer: Phaser.GameObjects.Container;
private dialogueMessageBox: Phaser.GameObjects.NineSlice; private dialogueMessageBox: Phaser.GameObjects.NineSlice;
message: any; message: any;
private readonly textPadding = 8; private readonly textPadding = 8;
private readonly defaultWordWrapWidth = 1224;
private onChange: () => void; private onChange: () => void;
@ -156,6 +158,7 @@ export class FilterText extends Phaser.GameObjects.Container {
const cursorOffset = 8; const cursorOffset = 8;
this.cursorObj.setPosition(cursorOffset, this.labels[cursor].y + 3); this.cursorObj.setPosition(cursorOffset, this.labels[cursor].y + 3);
this.lastCursor = cursor;
} }
/** /**

View File

@ -234,9 +234,9 @@ export class LoginFormUiHandler extends FormModalUiHandler {
const dataKeys = localStorageKeys.filter(ls => ls.indexOf(keyToFind) >= 0); const dataKeys = localStorageKeys.filter(ls => ls.indexOf(keyToFind) >= 0);
if (dataKeys.length > 0 && dataKeys.length <= 2) { if (dataKeys.length > 0 && dataKeys.length <= 2) {
const options: OptionSelectItem[] = []; const options: OptionSelectItem[] = [];
for (const key of dataKeys) { for (let i = 0; i < dataKeys.length; i++) {
options.push({ options.push({
label: key.replace(keyToFind, ""), label: dataKeys[i].replace(keyToFind, ""),
handler: () => { handler: () => {
globalScene.ui.revertMode(); globalScene.ui.revertMode();
this.infoContainer.disableInteractive(); this.infoContainer.disableInteractive();
@ -261,7 +261,7 @@ export class LoginFormUiHandler extends FormModalUiHandler {
} }
}); });
this.saveDownloadImage.on("pointerdown", async () => { this.saveDownloadImage.on("pointerdown", () => {
// find all data_ and sessionData keys, put them in a .txt file and download everything in a single zip // find all data_ and sessionData keys, put them in a .txt file and download everything in a single zip
const localStorageKeys = Object.keys(localStorage); // this gets the keys for localStorage const localStorageKeys = Object.keys(localStorage); // this gets the keys for localStorage
const keyToFind = "data_"; const keyToFind = "data_";
@ -270,19 +270,20 @@ export class LoginFormUiHandler extends FormModalUiHandler {
const sessionKeys = localStorageKeys.filter(ls => ls.indexOf(sessionKeyToFind) >= 0); const sessionKeys = localStorageKeys.filter(ls => ls.indexOf(sessionKeyToFind) >= 0);
if (dataKeys.length > 0 || sessionKeys.length > 0) { if (dataKeys.length > 0 || sessionKeys.length > 0) {
const zip = new JSZip(); const zip = new JSZip();
for (const dataKey of dataKeys) { for (let i = 0; i < dataKeys.length; i++) {
zip.file(dataKey + ".prsv", localStorage.getItem(dataKey)!); zip.file(dataKeys[i] + ".prsv", localStorage.getItem(dataKeys[i])!);
} }
for (const sessionKey of sessionKeys) { for (let i = 0; i < sessionKeys.length; i++) {
zip.file(sessionKey + ".prsv", localStorage.getItem(sessionKey)!); zip.file(sessionKeys[i] + ".prsv", localStorage.getItem(sessionKeys[i])!);
} }
const content = await zip.generateAsync({ type: "blob" }); zip.generateAsync({ type: "blob" }).then(content => {
const url = URL.createObjectURL(content); const url = URL.createObjectURL(content);
const a = document.createElement("a"); const a = document.createElement("a");
a.href = url; a.href = url;
a.download = "pokerogue_saves.zip"; a.download = "pokerogue_saves.zip";
a.click(); a.click();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
});
} else { } else {
return onFail(this.ERR_NO_SAVES); return onFail(this.ERR_NO_SAVES);
} }

View File

@ -108,17 +108,17 @@ export abstract class MessageUiHandler extends AwaitableUiHandler {
const textWords = text.split(" "); const textWords = text.split(" ");
let lastLineCount = 1; let lastLineCount = 1;
let newText = ""; let newText = "";
for (const textWord of textWords) { for (let w = 0; w < textWords.length; w++) {
const nextWordText = newText ? `${newText} ${textWord}` : textWord; const nextWordText = newText ? `${newText} ${textWords[w]}` : textWords[w];
if (textWord.includes("\n")) { if (textWords[w].includes("\n")) {
newText = nextWordText; newText = nextWordText;
lastLineCount++; lastLineCount++;
} else { } else {
const lineCount = this.message.runWordWrap(nextWordText).split(/\n/g).length; const lineCount = this.message.runWordWrap(nextWordText).split(/\n/g).length;
if (lineCount > lastLineCount) { if (lineCount > lastLineCount) {
lastLineCount = lineCount; lastLineCount = lineCount;
newText = `${newText}\n${textWord}`; newText = `${newText}\n${textWords[w]}`;
} else { } else {
newText = nextWordText; newText = nextWordText;
} }

View File

@ -480,10 +480,12 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler {
} }
} else if (this.cursor) { } else if (this.cursor) {
success = this.setCursor(this.cursor - 1); success = this.setCursor(this.cursor - 1);
} else if (this.rowCursor === 1 && this.options.length === 0) {
success = false;
} else { } else {
success = this.setCursor(this.getRowItems(this.rowCursor) - 1); if (this.rowCursor === 1 && this.options.length === 0) {
success = false;
} else {
success = this.setCursor(this.getRowItems(this.rowCursor) - 1);
}
} }
break; break;
case Button.RIGHT: case Button.RIGHT:
@ -512,10 +514,12 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler {
} }
} else if (this.cursor < this.getRowItems(this.rowCursor) - 1) { } else if (this.cursor < this.getRowItems(this.rowCursor) - 1) {
success = this.setCursor(this.cursor + 1); success = this.setCursor(this.cursor + 1);
} else if (this.rowCursor === 1 && this.options.length === 0) {
success = this.setRowCursor(0);
} else { } else {
success = this.setCursor(0); if (this.rowCursor === 1 && this.options.length === 0) {
success = this.setRowCursor(0);
} else {
success = this.setCursor(0);
}
} }
break; break;
} }

View File

@ -41,6 +41,8 @@ const GLOBAL_SCALE = 6;
export class MoveInfoOverlay extends Phaser.GameObjects.Container implements InfoToggle { export class MoveInfoOverlay extends Phaser.GameObjects.Container implements InfoToggle {
public active = false; public active = false;
private move: Move;
private desc: Phaser.GameObjects.Text; private desc: Phaser.GameObjects.Text;
private descScroll: Phaser.Tweens.Tween | null = null; private descScroll: Phaser.Tweens.Tween | null = null;
@ -175,6 +177,7 @@ export class MoveInfoOverlay extends Phaser.GameObjects.Container implements Inf
if (!globalScene.enableMoveInfo) { if (!globalScene.enableMoveInfo) {
return false; // move infos have been disabled // TODO:: is `false` correct? i used to be `undeefined` return false; // move infos have been disabled // TODO:: is `false` correct? i used to be `undeefined`
} }
this.move = move;
this.pow.setText(move.power >= 0 ? move.power.toString() : "---"); this.pow.setText(move.power >= 0 ? move.power.toString() : "---");
this.acc.setText(move.accuracy >= 0 ? move.accuracy.toString() : "---"); this.acc.setText(move.accuracy >= 0 ? move.accuracy.toString() : "---");
this.pp.setText(move.pp >= 0 ? move.pp.toString() : "---"); this.pp.setText(move.pp >= 0 ? move.pp.toString() : "---");

View File

@ -156,12 +156,14 @@ export class MysteryEncounterUiHandler extends UiHandler {
selected.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)) selected.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL))
) { ) {
success = false; success = false;
} else if (
(globalScene.phaseManager.getCurrentPhase() as MysteryEncounterPhase).handleOptionSelect(selected, cursor)
) {
success = true;
} else { } else {
ui.playError(); if (
(globalScene.phaseManager.getCurrentPhase() as MysteryEncounterPhase).handleOptionSelect(selected, cursor)
) {
success = true;
} else {
ui.playError();
}
} }
} else { } else {
// TODO: If we need to handle cancel option? Maybe default logic to leave/run from encounter idk // TODO: If we need to handle cancel option? Maybe default logic to leave/run from encounter idk

View File

@ -614,32 +614,34 @@ export class PartyUiHandler extends MessageUiHandler {
// TODO: Might need to check here for when this.transferMode is active. // TODO: Might need to check here for when this.transferMode is active.
private processModifierTransferModeLeftRightInput(button: Button) { private processModifierTransferModeLeftRightInput(button: Button) {
if (!this.isItemManageMode()) {
return false;
}
let success = false; let success = false;
const option = this.options[this.optionsCursor]; const option = this.options[this.optionsCursor];
if (button === Button.LEFT) { if (button === Button.LEFT) {
/** Decrease quantity for the current item and update UI */ /** Decrease quantity for the current item and update UI */
this.transferQuantities[option] = if (this.isItemManageMode()) {
this.transferQuantities[option] === 1 this.transferQuantities[option] =
? this.transferQuantitiesMax[option] this.transferQuantities[option] === 1
: this.transferQuantities[option] - 1; ? this.transferQuantitiesMax[option]
this.updateOptions(); : this.transferQuantities[option] - 1;
success = this.setCursor( this.updateOptions();
this.optionsCursor, success = this.setCursor(
); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ this.optionsCursor,
); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */
}
} }
if (button === Button.RIGHT) { if (button === Button.RIGHT) {
/** Increase quantity for the current item and update UI */ /** Increase quantity for the current item and update UI */
this.transferQuantities[option] = if (this.isItemManageMode()) {
this.transferQuantities[option] === this.transferQuantitiesMax[option] this.transferQuantities[option] =
? 1 this.transferQuantities[option] === this.transferQuantitiesMax[option]
: this.transferQuantities[option] + 1; ? 1
this.updateOptions(); : this.transferQuantities[option] + 1;
success = this.setCursor( this.updateOptions();
this.optionsCursor, success = this.setCursor(
); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ this.optionsCursor,
); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */
}
} }
return success; return success;
} }
@ -928,8 +930,10 @@ export class PartyUiHandler extends MessageUiHandler {
return this.moveOptionCursor(button); return this.moveOptionCursor(button);
} }
if ((button === Button.LEFT || button === Button.RIGHT) && this.isItemManageMode()) { if (button === Button.LEFT || button === Button.RIGHT) {
return this.processModifierTransferModeLeftRightInput(button); if (this.isItemManageMode()) {
return this.processModifierTransferModeLeftRightInput(button);
}
} }
return false; return false;
@ -1211,9 +1215,11 @@ export class PartyUiHandler extends MessageUiHandler {
isScroll = true; isScroll = true;
this.optionsScrollCursor++; this.optionsScrollCursor++;
} }
} else if (!cursor && this.optionsScrollCursor) { } else {
isScroll = true; if (!cursor && this.optionsScrollCursor) {
this.optionsScrollCursor--; isScroll = true;
this.optionsScrollCursor--;
}
} }
if (isScroll && this.optionsScrollCursor === 1) { if (isScroll && this.optionsScrollCursor === 1) {
this.optionsScrollCursor += isDown ? 1 : -1; this.optionsScrollCursor += isDown ? 1 : -1;
@ -1567,10 +1573,12 @@ export class PartyUiHandler extends MessageUiHandler {
optionName = `${modifier.active ? i18next.t("partyUiHandler:DEACTIVATE") : i18next.t("partyUiHandler:ACTIVATE")} ${modifier.type.name}`; optionName = `${modifier.active ? i18next.t("partyUiHandler:DEACTIVATE") : i18next.t("partyUiHandler:ACTIVATE")} ${modifier.type.name}`;
} else if (option === PartyOption.UNPAUSE_EVOLUTION) { } else if (option === PartyOption.UNPAUSE_EVOLUTION) {
optionName = `${pokemon.pauseEvolutions ? i18next.t("partyUiHandler:UNPAUSE_EVOLUTION") : i18next.t("partyUiHandler:PAUSE_EVOLUTION")}`; optionName = `${pokemon.pauseEvolutions ? i18next.t("partyUiHandler:UNPAUSE_EVOLUTION") : i18next.t("partyUiHandler:PAUSE_EVOLUTION")}`;
} else if (this.localizedOptions.includes(option)) {
optionName = i18next.t(`partyUiHandler:${PartyOption[option]}`);
} else { } else {
optionName = toTitleCase(PartyOption[option]); if (this.localizedOptions.includes(option)) {
optionName = i18next.t(`partyUiHandler:${PartyOption[option]}`);
} else {
optionName = toTitleCase(PartyOption[option]);
}
} }
break; break;
} }
@ -1646,11 +1654,11 @@ export class PartyUiHandler extends MessageUiHandler {
this.transferMode = false; this.transferMode = false;
this.transferAll = false; this.transferAll = false;
this.partySlots[this.transferCursor].setTransfer(false); this.partySlots[this.transferCursor].setTransfer(false);
for (const partySlot of this.partySlots) { for (let i = 0; i < this.partySlots.length; i++) {
partySlot.slotDescriptionLabel.setVisible(false); this.partySlots[i].slotDescriptionLabel.setVisible(false);
partySlot.slotHpBar.setVisible(true); this.partySlots[i].slotHpBar.setVisible(true);
partySlot.slotHpOverlay.setVisible(true); this.partySlots[i].slotHpOverlay.setVisible(true);
partySlot.slotHpText.setVisible(true); this.partySlots[i].slotHpText.setVisible(true);
} }
} }

View File

@ -259,6 +259,8 @@ export class PokedexPageUiHandler extends MessageUiHandler {
private instructionRowX = 0; private instructionRowX = 0;
private instructionRowY = 0; private instructionRowY = 0;
private instructionRowTextOffset = 9; private instructionRowTextOffset = 9;
private filterInstructionRowX = 0;
private filterInstructionRowY = 0;
private starterAttributes: StarterAttributes; private starterAttributes: StarterAttributes;
private savedStarterAttributes: StarterAttributes; private savedStarterAttributes: StarterAttributes;
@ -1067,10 +1069,12 @@ export class PokedexPageUiHandler extends MessageUiHandler {
) { ) {
starterAttributes.female = !starterAttributes.female; starterAttributes.female = !starterAttributes.female;
} }
} else if (caughtAttr & DexAttr.FEMALE) { } else {
starterAttributes.female = true; if (caughtAttr & DexAttr.FEMALE) {
} else if (caughtAttr & DexAttr.MALE) { starterAttributes.female = true;
starterAttributes.female = false; } else if (caughtAttr & DexAttr.MALE) {
starterAttributes.female = false;
}
} }
return starterAttributes; return starterAttributes;
@ -1835,8 +1839,10 @@ export class PokedexPageUiHandler extends MessageUiHandler {
if (this.isCaught() & DexAttr.VARIANT_2) { if (this.isCaught() & DexAttr.VARIANT_2) {
break; break;
} }
} else if (this.isCaught() & DexAttr.VARIANT_3) { } else {
break; if (this.isCaught() & DexAttr.VARIANT_3) {
break;
}
} }
} while (newVariant !== props.variant); } while (newVariant !== props.variant);
@ -2198,6 +2204,8 @@ export class PokedexPageUiHandler extends MessageUiHandler {
updateInstructions(): void { updateInstructions(): void {
this.instructionRowX = 0; this.instructionRowX = 0;
this.instructionRowY = 0; this.instructionRowY = 0;
this.filterInstructionRowX = 0;
this.filterInstructionRowY = 0;
this.hideInstructions(); this.hideInstructions();
this.instructionsContainer.removeAll(); this.instructionsContainer.removeAll();
this.filterInstructionsContainer.removeAll(); this.filterInstructionsContainer.removeAll();
@ -2354,8 +2362,10 @@ export class PokedexPageUiHandler extends MessageUiHandler {
const defaultDexAttr = this.getCurrentDexProps(species.speciesId); const defaultDexAttr = this.getCurrentDexProps(species.speciesId);
// Set default attributes if for some reason starterAttributes does not exist or attributes missing // Set default attributes if for some reason starterAttributes does not exist or attributes missing
const props: StarterAttributes = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); const props: StarterAttributes = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
if (starterAttributes?.variant && !Number.isNaN(starterAttributes.variant) && props.shiny) { if (starterAttributes?.variant && !Number.isNaN(starterAttributes.variant)) {
props.variant = starterAttributes.variant as Variant; if (props.shiny) {
props.variant = starterAttributes.variant as Variant;
}
} }
props.form = starterAttributes?.form ?? props.form; props.form = starterAttributes?.form ?? props.form;
props.female = starterAttributes?.female ?? props.female; props.female = starterAttributes?.female ?? props.female;
@ -2768,15 +2778,17 @@ export class PokedexPageUiHandler extends MessageUiHandler {
props += DexAttr.SHINY; props += DexAttr.SHINY;
if (this.starterAttributes?.variant !== undefined) { if (this.starterAttributes?.variant !== undefined) {
props += BigInt(Math.pow(2, this.starterAttributes?.variant)) * DexAttr.DEFAULT_VARIANT; props += BigInt(Math.pow(2, this.starterAttributes?.variant)) * DexAttr.DEFAULT_VARIANT;
/* This chunk calculates the correct variant if there's no starter preferences for it. } else {
/* This calculates the correct variant if there's no starter preferences for it.
* This gets the highest tier variant that you've caught and adds it to the temp props * This gets the highest tier variant that you've caught and adds it to the temp props
*/ */
} else if ((caughtAttr & DexAttr.VARIANT_3) > 0) { if ((caughtAttr & DexAttr.VARIANT_3) > 0) {
props += DexAttr.VARIANT_3; props += DexAttr.VARIANT_3;
} else if ((caughtAttr & DexAttr.VARIANT_2) > 0) { } else if ((caughtAttr & DexAttr.VARIANT_2) > 0) {
props += DexAttr.VARIANT_2; props += DexAttr.VARIANT_2;
} else { } else {
props += DexAttr.DEFAULT_VARIANT; props += DexAttr.DEFAULT_VARIANT;
}
} }
} else { } else {
props += DexAttr.NON_SHINY; props += DexAttr.NON_SHINY;

View File

@ -709,12 +709,11 @@ export class PokedexUiHandler extends MessageUiHandler {
} }
} }
if ( if (starterAttributes.female !== undefined) {
starterAttributes.female !== undefined && if (!(starterAttributes.female ? caughtAttr & DexAttr.FEMALE : caughtAttr & DexAttr.MALE)) {
!(starterAttributes.female ? caughtAttr & DexAttr.FEMALE : caughtAttr & DexAttr.MALE) // requested gender wasn't unlocked, purging setting
) { starterAttributes.female = undefined;
// requested gender wasn't unlocked, purging setting }
starterAttributes.female = undefined;
} }
if (starterAttributes.ability !== undefined) { if (starterAttributes.ability !== undefined) {
@ -1188,76 +1187,78 @@ export class PokedexUiHandler extends MessageUiHandler {
break; break;
} }
} }
} else if (button === Button.ACTION) {
ui.setOverlayMode(UiMode.POKEDEX_PAGE, this.lastSpecies, null, this.filteredIndices);
success = true;
} else { } else {
switch (button) { if (button === Button.ACTION) {
case Button.UP: ui.setOverlayMode(UiMode.POKEDEX_PAGE, this.lastSpecies, null, this.filteredIndices);
if (currentRow > 0) { success = true;
if (this.scrollCursor > 0 && currentRow - this.scrollCursor === 0) { } else {
this.scrollCursor--; switch (button) {
this.updateScroll(); case Button.UP:
success = this.setCursor(this.cursor); if (currentRow > 0) {
if (this.scrollCursor > 0 && currentRow - this.scrollCursor === 0) {
this.scrollCursor--;
this.updateScroll();
success = this.setCursor(this.cursor);
} else {
success = this.setCursor(this.cursor - 9);
}
} else { } else {
success = this.setCursor(this.cursor - 9); this.filterBarCursor = this.filterBar.getNearestFilter(this.pokemonContainers[this.cursor]);
this.setFilterMode(true);
success = true;
} }
} else { break;
this.filterBarCursor = this.filterBar.getNearestFilter(this.pokemonContainers[this.cursor]); case Button.DOWN:
this.setFilterMode(true); if (currentRow < numOfRows - 1 && this.cursor + 9 < this.filteredPokemonData.length) {
success = true; // not last row
} if (currentRow - this.scrollCursor === 8) {
break; // last row of visible pokemon
case Button.DOWN: this.scrollCursor++;
if (currentRow < numOfRows - 1 && this.cursor + 9 < this.filteredPokemonData.length) { this.updateScroll();
// not last row success = this.setCursor(this.cursor);
if (currentRow - this.scrollCursor === 8) { } else {
// last row of visible pokemon success = this.setCursor(this.cursor + 9);
this.scrollCursor++; }
} else if (numOfRows > 1) {
// DOWN from last row of pokemon > Wrap around to first row
this.scrollCursor = 0;
this.updateScroll(); this.updateScroll();
success = this.setCursor(this.cursor); success = this.setCursor(this.cursor % 9);
} else { } else {
success = this.setCursor(this.cursor + 9); // DOWN from single row of pokemon > Go to filters
this.filterBarCursor = this.filterBar.getNearestFilter(this.pokemonContainers[this.cursor]);
this.setFilterMode(true);
success = true;
} }
} else if (numOfRows > 1) { break;
// DOWN from last row of pokemon > Wrap around to first row case Button.LEFT:
this.scrollCursor = 0; if (this.cursor % 9 !== 0) {
this.updateScroll(); success = this.setCursor(this.cursor - 1);
success = this.setCursor(this.cursor % 9); } else {
} else { // LEFT from filtered pokemon, on the left edge
// DOWN from single row of pokemon > Go to filters this.filterTextCursor = this.filterText.getNearestFilter(this.pokemonContainers[this.cursor]);
this.filterBarCursor = this.filterBar.getNearestFilter(this.pokemonContainers[this.cursor]); this.setFilterTextMode(true);
this.setFilterMode(true); success = true;
success = true; }
break;
case Button.RIGHT:
// is not right edge
if (this.cursor % 9 < (currentRow < numOfRows - 1 ? 8 : (numberOfStarters - 1) % 9)) {
success = this.setCursor(this.cursor + 1);
} else {
// RIGHT from filtered pokemon, on the right edge
this.filterTextCursor = this.filterText.getNearestFilter(this.pokemonContainers[this.cursor]);
this.setFilterTextMode(true);
success = true;
}
break;
case Button.CYCLE_FORM: {
const species = this.pokemonContainers[this.cursor].species;
if (this.canShowFormTray) {
success = this.openFormTray(species);
}
break;
} }
break;
case Button.LEFT:
if (this.cursor % 9 !== 0) {
success = this.setCursor(this.cursor - 1);
} else {
// LEFT from filtered pokemon, on the left edge
this.filterTextCursor = this.filterText.getNearestFilter(this.pokemonContainers[this.cursor]);
this.setFilterTextMode(true);
success = true;
}
break;
case Button.RIGHT:
// is not right edge
if (this.cursor % 9 < (currentRow < numOfRows - 1 ? 8 : (numberOfStarters - 1) % 9)) {
success = this.setCursor(this.cursor + 1);
} else {
// RIGHT from filtered pokemon, on the right edge
this.filterTextCursor = this.filterText.getNearestFilter(this.pokemonContainers[this.cursor]);
this.setFilterTextMode(true);
success = true;
}
break;
case Button.CYCLE_FORM: {
const species = this.pokemonContainers[this.cursor].species;
if (this.canShowFormTray) {
success = this.openFormTray(species);
}
break;
} }
} }
} }
@ -1452,10 +1453,12 @@ export class PokedexUiHandler extends MessageUiHandler {
} else { } else {
data.passive1 = false; data.passive1 = false;
} }
} else if (starterData.passiveAttr > 0) {
data.passive2 = true;
} else { } else {
data.passive2 = false; if (starterData.passiveAttr > 0) {
data.passive2 = true;
} else {
data.passive2 = false;
}
} }
} }
@ -2331,15 +2334,17 @@ export class PokedexUiHandler extends MessageUiHandler {
props += DexAttr.SHINY; props += DexAttr.SHINY;
if (this.starterPreferences[speciesId]?.variant !== undefined) { if (this.starterPreferences[speciesId]?.variant !== undefined) {
props += BigInt(Math.pow(2, this.starterPreferences[speciesId]?.variant)) * DexAttr.DEFAULT_VARIANT; props += BigInt(Math.pow(2, this.starterPreferences[speciesId]?.variant)) * DexAttr.DEFAULT_VARIANT;
} else if ((caughtAttr & DexAttr.VARIANT_3) > 0) { } else {
/* This calculates the correct variant if there's no starter preferences for it. /* This calculates the correct variant if there's no starter preferences for it.
* This gets the highest tier variant that you've caught and adds it to the temp props * This gets the highest tier variant that you've caught and adds it to the temp props
*/ */
props += DexAttr.VARIANT_3; if ((caughtAttr & DexAttr.VARIANT_3) > 0) {
} else if ((caughtAttr & DexAttr.VARIANT_2) > 0) { props += DexAttr.VARIANT_3;
props += DexAttr.VARIANT_2; } else if ((caughtAttr & DexAttr.VARIANT_2) > 0) {
} else { props += DexAttr.VARIANT_2;
props += DexAttr.DEFAULT_VARIANT; } else {
props += DexAttr.DEFAULT_VARIANT;
}
} }
} else { } else {
props += DexAttr.NON_SHINY; props += DexAttr.NON_SHINY;

View File

@ -32,6 +32,8 @@ export class RunHistoryUiHandler extends MessageUiHandler {
private runsContainer: Phaser.GameObjects.Container; private runsContainer: Phaser.GameObjects.Container;
private runs: RunEntryContainer[]; private runs: RunEntryContainer[];
private runSelectCallback: RunSelectCallback | null;
private scrollCursor = 0; private scrollCursor = 0;
private cursorObj: Phaser.GameObjects.NineSlice | null; private cursorObj: Phaser.GameObjects.NineSlice | null;
@ -116,6 +118,7 @@ export class RunHistoryUiHandler extends MessageUiHandler {
success = true; success = true;
return success; return success;
} }
this.runSelectCallback = null;
success = true; success = true;
globalScene.ui.revertMode(); globalScene.ui.revertMode();
} else if (this.runs.length > 0) { } else if (this.runs.length > 0) {
@ -232,6 +235,7 @@ export class RunHistoryUiHandler extends MessageUiHandler {
this.runSelectContainer.setVisible(false); this.runSelectContainer.setVisible(false);
this.setScrollCursor(0); this.setScrollCursor(0);
this.clearCursor(); this.clearCursor();
this.runSelectCallback = null;
this.clearRuns(); this.clearRuns();
} }
@ -254,11 +258,13 @@ export class RunHistoryUiHandler extends MessageUiHandler {
* entryData: the data of an individual run * entryData: the data of an individual run
*/ */
class RunEntryContainer extends Phaser.GameObjects.Container { class RunEntryContainer extends Phaser.GameObjects.Container {
private slotId: number;
public entryData: RunEntry; public entryData: RunEntry;
constructor(entryData: RunEntry, slotId: number) { constructor(entryData: RunEntry, slotId: number) {
super(globalScene, 0, slotId * 56); super(globalScene, 0, slotId * 56);
this.slotId = slotId;
this.entryData = entryData; this.entryData = entryData;
this.setup(this.entryData); this.setup(this.entryData);

View File

@ -683,33 +683,32 @@ export class RunInfoUiHandler extends UiHandler {
*/ */
private challengeParser(): string[] { private challengeParser(): string[] {
const rules: string[] = []; const rules: string[] = [];
for (const challenge of this.runInfo.challenges) { for (let i = 0; i < this.runInfo.challenges.length; i++) {
if (challenge.value === 0) { if (this.runInfo.challenges[i].value !== 0) {
continue; switch (this.runInfo.challenges[i].id) {
} case Challenges.SINGLE_GENERATION:
switch (challenge.id) { rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`));
case Challenges.SINGLE_GENERATION: break;
rules.push(i18next.t(`runHistory:challengeMonoGen${challenge.value}`)); case Challenges.SINGLE_TYPE: {
break; const typeRule = PokemonType[this.runInfo.challenges[i].value - 1];
case Challenges.SINGLE_TYPE: { const typeTextColor = `[color=${TypeColor[typeRule]}]`;
const typeRule = PokemonType[challenge.value - 1]; const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`;
const typeTextColor = `[color=${TypeColor[typeRule]}]`; const typeText =
const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`; typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]";
const typeText = rules.push(typeText);
typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color][/shadow]"; break;
rules.push(typeText); }
break; case Challenges.INVERSE_BATTLE:
} rules.push(i18next.t("challenges:inverseBattle.shortName"));
case Challenges.INVERSE_BATTLE: break;
rules.push(i18next.t("challenges:inverseBattle.shortName")); default: {
break; const localizationKey = Challenges[this.runInfo.challenges[i].id]
default: { .split("_")
const localizationKey = Challenges[challenge.id] .map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.split("_") .join("");
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase())) rules.push(i18next.t(`challenges:${localizationKey}.name`));
.join(""); break;
rules.push(i18next.t(`challenges:${localizationKey}.name`)); }
break;
} }
} }
} }

View File

@ -34,6 +34,8 @@ export class AbstractSettingsUiHandler extends MessageUiHandler {
protected navigationIcons: InputsIcons; protected navigationIcons: InputsIcons;
private cursorObj: Phaser.GameObjects.NineSlice | null; private cursorObj: Phaser.GameObjects.NineSlice | null;
private reloadSettings: Array<Setting>;
private reloadRequired: boolean; private reloadRequired: boolean;
protected rowsToDisplay: number; protected rowsToDisplay: number;
@ -105,6 +107,8 @@ export class AbstractSettingsUiHandler extends MessageUiHandler {
this.settingLabels = []; this.settingLabels = [];
this.optionValueLabels = []; this.optionValueLabels = [];
this.reloadSettings = this.settings.filter(s => s?.requireReload);
let anyReloadRequired = false; let anyReloadRequired = false;
this.settings.forEach((setting, s) => { this.settings.forEach((setting, s) => {
let settingName = setting.label; let settingName = setting.label;

View File

@ -336,7 +336,7 @@ export class StarterSelectUiHandler extends MessageUiHandler {
private teraLabel: Phaser.GameObjects.Text; private teraLabel: Phaser.GameObjects.Text;
private goFilterLabel: Phaser.GameObjects.Text; private goFilterLabel: Phaser.GameObjects.Text;
/** Group holding the UI elements appearing in the instructionsContainer */ /** Group holding the UI elements appearing in the instructionsContainer */
/* TODO: Uncomment this once our testing infra supports mocks of `Phaser.GameObject.Group` /* TODO: Uncomment this once our testing infra supports mocks of `Phaser.GameObject.Group`
private instructionElemGroup: Phaser.GameObjects.Group; private instructionElemGroup: Phaser.GameObjects.Group;
*/ */
@ -1217,12 +1217,11 @@ export class StarterSelectUiHandler extends MessageUiHandler {
} }
} }
if ( if (starterAttributes.female !== undefined) {
starterAttributes.female !== undefined && if (!(starterAttributes.female ? caughtAttr & DexAttr.FEMALE : caughtAttr & DexAttr.MALE)) {
!(starterAttributes.female ? caughtAttr & DexAttr.FEMALE : caughtAttr & DexAttr.MALE) // requested gender wasn't unlocked, purging setting
) { starterAttributes.female = undefined;
// requested gender wasn't unlocked, purging setting }
starterAttributes.female = undefined;
} }
if (starterAttributes.ability !== undefined) { if (starterAttributes.ability !== undefined) {
@ -2343,9 +2342,11 @@ export class StarterSelectUiHandler extends MessageUiHandler {
// TODO: is this bang correct? // TODO: is this bang correct?
break; break;
} }
} else if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_3) { } else {
// TODO: is this bang correct? if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_3) {
break; // TODO: is this bang correct?
break;
}
} }
} while (newVariant !== props.variant); } while (newVariant !== props.variant);
starterAttributes.variant = newVariant; // store the selected variant starterAttributes.variant = newVariant; // store the selected variant
@ -2421,8 +2422,10 @@ export class StarterSelectUiHandler extends MessageUiHandler {
newAbilityIndex = (newAbilityIndex + 1) % abilityCount; newAbilityIndex = (newAbilityIndex + 1) % abilityCount;
} }
break; break;
} else if (abilityAttr & AbilityAttr.ABILITY_HIDDEN) { } else {
break; if (abilityAttr & AbilityAttr.ABILITY_HIDDEN) {
break;
}
} }
} while (newAbilityIndex !== this.abilityCursor); } while (newAbilityIndex !== this.abilityCursor);
starterAttributes.ability = newAbilityIndex; // store the selected ability starterAttributes.ability = newAbilityIndex; // store the selected ability
@ -2969,7 +2972,8 @@ export class StarterSelectUiHandler extends MessageUiHandler {
} }
// this updates icons for previously saved pokemon // this updates icons for previously saved pokemon
for (const currentFilteredContainer of this.validStarterContainers) { for (let i = 0; i < this.validStarterContainers.length; i++) {
const currentFilteredContainer = this.validStarterContainers[i];
const starterSprite = currentFilteredContainer.icon as Phaser.GameObjects.Sprite; const starterSprite = currentFilteredContainer.icon as Phaser.GameObjects.Sprite;
const currentDexAttr = this.getCurrentDexProps(currentFilteredContainer.species.speciesId); const currentDexAttr = this.getCurrentDexProps(currentFilteredContainer.species.speciesId);
@ -3558,8 +3562,10 @@ export class StarterSelectUiHandler extends MessageUiHandler {
// load default nature from stater save data, if set // load default nature from stater save data, if set
const defaultNature = starterAttributes?.nature || globalScene.gameData.getSpeciesDefaultNature(species); const defaultNature = starterAttributes?.nature || globalScene.gameData.getSpeciesDefaultNature(species);
props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
if (starterAttributes?.variant && !Number.isNaN(starterAttributes.variant) && props.shiny) { if (starterAttributes?.variant && !Number.isNaN(starterAttributes.variant)) {
props.variant = starterAttributes.variant as Variant; if (props.shiny) {
props.variant = starterAttributes.variant as Variant;
}
} }
props.formIndex = starterAttributes?.form ?? props.formIndex; props.formIndex = starterAttributes?.form ?? props.formIndex;
props.female = starterAttributes?.female ?? props.female; props.female = starterAttributes?.female ?? props.female;
@ -4344,13 +4350,13 @@ export class StarterSelectUiHandler extends MessageUiHandler {
return true; return true;
} }
/** /* This block checks to see if your party is valid
* This block checks to see if your party is valid
* It checks each pokemon against the challenge - noting that due to monotype challenges it needs to check the pokemon while ignoring their evolutions/form change items * It checks each pokemon against the challenge - noting that due to monotype challenges it needs to check the pokemon while ignoring their evolutions/form change items
*/ */
isPartyValid(): boolean { isPartyValid(): boolean {
let canStart = false; let canStart = false;
for (const species of this.starterSpecies) { for (let s = 0; s < this.starterSpecies.length; s++) {
const species = this.starterSpecies[s];
const isValidForChallenge = checkStarterValidForChallenge( const isValidForChallenge = checkStarterValidForChallenge(
species, species,
globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)),
@ -4394,15 +4400,17 @@ export class StarterSelectUiHandler extends MessageUiHandler {
props += DexAttr.SHINY; props += DexAttr.SHINY;
if (this.starterPreferences[speciesId]?.variant !== undefined) { if (this.starterPreferences[speciesId]?.variant !== undefined) {
props += BigInt(Math.pow(2, this.starterPreferences[speciesId]?.variant)) * DexAttr.DEFAULT_VARIANT; props += BigInt(Math.pow(2, this.starterPreferences[speciesId]?.variant)) * DexAttr.DEFAULT_VARIANT;
} else if ((caughtAttr & DexAttr.VARIANT_3) > 0) { } else {
/* This calculates the correct variant if there's no starter preferences for it. /* This calculates the correct variant if there's no starter preferences for it.
* This gets the highest tier variant that you've caught and adds it to the temp props * This gets the highest tier variant that you've caught and adds it to the temp props
*/ */
props += DexAttr.VARIANT_3; if ((caughtAttr & DexAttr.VARIANT_3) > 0) {
} else if ((caughtAttr & DexAttr.VARIANT_2) > 0) { props += DexAttr.VARIANT_3;
props += DexAttr.VARIANT_2; } else if ((caughtAttr & DexAttr.VARIANT_2) > 0) {
} else { props += DexAttr.VARIANT_2;
props += DexAttr.DEFAULT_VARIANT; } else {
props += DexAttr.DEFAULT_VARIANT;
}
} }
} else { } else {
props += DexAttr.NON_SHINY; props += DexAttr.NON_SHINY;

View File

@ -515,31 +515,33 @@ export class SummaryUiHandler extends UiHandler {
if (this.pokemon && this.moveCursor < this.pokemon.moveset.length) { if (this.pokemon && this.moveCursor < this.pokemon.moveset.length) {
if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) {
this.moveSelectFunction?.(this.moveCursor); this.moveSelectFunction?.(this.moveCursor);
} else if (this.selectedMoveIndex === -1) {
this.selectedMoveIndex = this.moveCursor;
this.setCursor(this.moveCursor);
} else { } else {
if (this.selectedMoveIndex !== this.moveCursor) { if (this.selectedMoveIndex === -1) {
const tempMove = this.pokemon?.moveset[this.selectedMoveIndex]; this.selectedMoveIndex = this.moveCursor;
this.pokemon.moveset[this.selectedMoveIndex] = this.pokemon.moveset[this.moveCursor]; this.setCursor(this.moveCursor);
this.pokemon.moveset[this.moveCursor] = tempMove; } else {
if (this.selectedMoveIndex !== this.moveCursor) {
const tempMove = this.pokemon?.moveset[this.selectedMoveIndex];
this.pokemon.moveset[this.selectedMoveIndex] = this.pokemon.moveset[this.moveCursor];
this.pokemon.moveset[this.moveCursor] = tempMove;
const selectedMoveRow = this.moveRowsContainer.getAt( const selectedMoveRow = this.moveRowsContainer.getAt(
this.selectedMoveIndex, this.selectedMoveIndex,
) as Phaser.GameObjects.Container; ) as Phaser.GameObjects.Container;
const switchMoveRow = this.moveRowsContainer.getAt(this.moveCursor) as Phaser.GameObjects.Container; const switchMoveRow = this.moveRowsContainer.getAt(this.moveCursor) as Phaser.GameObjects.Container;
this.moveRowsContainer.moveTo(selectedMoveRow, this.moveCursor); this.moveRowsContainer.moveTo(selectedMoveRow, this.moveCursor);
this.moveRowsContainer.moveTo(switchMoveRow, this.selectedMoveIndex); this.moveRowsContainer.moveTo(switchMoveRow, this.selectedMoveIndex);
selectedMoveRow.setY(this.moveCursor * 16); selectedMoveRow.setY(this.moveCursor * 16);
switchMoveRow.setY(this.selectedMoveIndex * 16); switchMoveRow.setY(this.selectedMoveIndex * 16);
} }
this.selectedMoveIndex = -1; this.selectedMoveIndex = -1;
if (this.selectedMoveCursorObj) { if (this.selectedMoveCursorObj) {
this.selectedMoveCursorObj.destroy(); this.selectedMoveCursorObj.destroy();
this.selectedMoveCursorObj = null; this.selectedMoveCursorObj = null;
}
} }
} }
success = true; success = true;
@ -573,76 +575,78 @@ export class SummaryUiHandler extends UiHandler {
break; break;
} }
} }
} else if (button === Button.ACTION) {
if (this.cursor === Page.MOVES) {
this.showMoveSelect();
success = true;
} else if (this.cursor === Page.PROFILE && this.pokemon?.hasPassive()) {
// if we're on the PROFILE page and this pokemon has a passive unlocked..
// Since abilities are displayed by default, all we need to do is toggle visibility on all elements to show passives
this.abilityContainer.nameText?.setVisible(!this.abilityContainer.descriptionText?.visible);
this.abilityContainer.descriptionText?.setVisible(!this.abilityContainer.descriptionText.visible);
this.abilityContainer.labelImage.setVisible(!this.abilityContainer.labelImage.visible);
this.passiveContainer.nameText?.setVisible(!this.passiveContainer.descriptionText?.visible);
this.passiveContainer.descriptionText?.setVisible(!this.passiveContainer.descriptionText.visible);
this.passiveContainer.labelImage.setVisible(!this.passiveContainer.labelImage.visible);
} else if (this.cursor === Page.STATS) {
//Show IVs
this.permStatsContainer.setVisible(!this.permStatsContainer.visible);
this.ivContainer.setVisible(!this.ivContainer.visible);
}
} else if (button === Button.CANCEL) {
if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) {
this.hideMoveSelect();
} else {
if (this.selectCallback instanceof Function) {
const selectCallback = this.selectCallback;
this.selectCallback = null;
selectCallback();
}
if (!fromPartyMode) {
ui.setMode(UiMode.MESSAGE);
} else {
ui.setMode(UiMode.PARTY);
}
}
success = true;
} else { } else {
const pages = getEnumValues(Page); if (button === Button.ACTION) {
switch (button) { if (this.cursor === Page.MOVES) {
case Button.UP: this.showMoveSelect();
case Button.DOWN: { success = true;
if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { } else if (this.cursor === Page.PROFILE && this.pokemon?.hasPassive()) {
break; // if we're on the PROFILE page and this pokemon has a passive unlocked..
} // Since abilities are displayed by default, all we need to do is toggle visibility on all elements to show passives
if (!fromPartyMode) { this.abilityContainer.nameText?.setVisible(!this.abilityContainer.descriptionText?.visible);
break; this.abilityContainer.descriptionText?.setVisible(!this.abilityContainer.descriptionText.visible);
} this.abilityContainer.labelImage.setVisible(!this.abilityContainer.labelImage.visible);
const isDown = button === Button.DOWN;
const party = globalScene.getPlayerParty(); this.passiveContainer.nameText?.setVisible(!this.passiveContainer.descriptionText?.visible);
const partyMemberIndex = this.pokemon ? party.indexOf(this.pokemon) : -1; this.passiveContainer.descriptionText?.setVisible(!this.passiveContainer.descriptionText.visible);
if ((isDown && partyMemberIndex < party.length - 1) || (!isDown && partyMemberIndex)) { this.passiveContainer.labelImage.setVisible(!this.passiveContainer.labelImage.visible);
const page = this.cursor; } else if (this.cursor === Page.STATS) {
this.clear(); //Show IVs
this.show([party[partyMemberIndex + (isDown ? 1 : -1)], this.summaryUiMode, page]); this.permStatsContainer.setVisible(!this.permStatsContainer.visible);
} this.ivContainer.setVisible(!this.ivContainer.visible);
break;
} }
case Button.LEFT: } else if (button === Button.CANCEL) {
if (this.cursor) { if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) {
success = this.setCursor(this.cursor - 1); this.hideMoveSelect();
} else {
if (this.selectCallback instanceof Function) {
const selectCallback = this.selectCallback;
this.selectCallback = null;
selectCallback();
} }
break;
case Button.RIGHT: if (!fromPartyMode) {
if (this.cursor < pages.length - 1) { ui.setMode(UiMode.MESSAGE);
success = this.setCursor(this.cursor + 1); } else {
if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.cursor === Page.MOVES) { ui.setMode(UiMode.PARTY);
this.moveSelect = true; }
}
success = true;
} else {
const pages = getEnumValues(Page);
switch (button) {
case Button.UP:
case Button.DOWN: {
if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) {
break;
} }
if (!fromPartyMode) {
break;
}
const isDown = button === Button.DOWN;
const party = globalScene.getPlayerParty();
const partyMemberIndex = this.pokemon ? party.indexOf(this.pokemon) : -1;
if ((isDown && partyMemberIndex < party.length - 1) || (!isDown && partyMemberIndex)) {
const page = this.cursor;
this.clear();
this.show([party[partyMemberIndex + (isDown ? 1 : -1)], this.summaryUiMode, page]);
}
break;
} }
break; case Button.LEFT:
if (this.cursor) {
success = this.setCursor(this.cursor - 1);
}
break;
case Button.RIGHT:
if (this.cursor < pages.length - 1) {
success = this.setCursor(this.cursor + 1);
if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.cursor === Page.MOVES) {
this.moveSelect = true;
}
}
break;
}
} }
} }

View File

@ -70,12 +70,11 @@ export class TargetSelectUiHandler extends UiHandler {
* @param user the Pokemon using the move * @param user the Pokemon using the move
*/ */
resetCursor(cursorN: number, user: Pokemon): void { resetCursor(cursorN: number, user: Pokemon): void {
if ( if (!isNullOrUndefined(cursorN)) {
!isNullOrUndefined(cursorN) && if ([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2].includes(cursorN) || user.tempSummonData.waveTurnCount === 1) {
([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2].includes(cursorN) || user.tempSummonData.waveTurnCount === 1) // Reset cursor on the first turn of a fight or if an ally was targeted last turn
) { cursorN = -1;
// Reset cursor on the first turn of a fight or if an ally was targeted last turn }
cursorN = -1;
} }
this.setCursor(this.targets.includes(cursorN) ? cursorN : this.targets[0]); this.setCursor(this.targets.includes(cursorN) ? cursorN : this.targets[0]);
} }
@ -93,11 +92,10 @@ export class TargetSelectUiHandler extends UiHandler {
if (isNullOrUndefined(this.cursor0) || this.cursor0 !== this.cursor) { if (isNullOrUndefined(this.cursor0) || this.cursor0 !== this.cursor) {
this.cursor0 = this.cursor; this.cursor0 = this.cursor;
} }
} else if ( } else if (this.fieldIndex === BattlerIndex.PLAYER_2) {
this.fieldIndex === BattlerIndex.PLAYER_2 && if (isNullOrUndefined(this.cursor1) || this.cursor1 !== this.cursor) {
(isNullOrUndefined(this.cursor1) || this.cursor1 !== this.cursor) this.cursor1 = this.cursor;
) { }
this.cursor1 = this.cursor;
} }
} else if (this.isMultipleTargets) { } else if (this.isMultipleTargets) {
success = false; success = false;

View File

@ -117,8 +117,10 @@ export function updateWindowType(windowTypeIndex: number): void {
} else if (object.texture?.key === "namebox") { } else if (object.texture?.key === "namebox") {
themedObjects.push(object); themedObjects.push(object);
} }
} else if (object instanceof Phaser.GameObjects.Sprite && object.texture?.key === "bg") { } else if (object instanceof Phaser.GameObjects.Sprite) {
themedObjects.push(object); if (object.texture?.key === "bg") {
themedObjects.push(object);
}
} }
}; };

View File

@ -374,12 +374,10 @@ export class UI extends Phaser.GameObjects.Container {
} }
shouldSkipDialogue(i18nKey: string): boolean { shouldSkipDialogue(i18nKey: string): boolean {
if ( if (i18next.exists(i18nKey)) {
i18next.exists(i18nKey) && if (globalScene.skipSeenDialogues && globalScene.gameData.getSeenDialogues()[i18nKey] === true) {
globalScene.skipSeenDialogues && return true;
globalScene.gameData.getSeenDialogues()[i18nKey] === true }
) {
return true;
} }
return false; return false;
} }

View File

@ -10,6 +10,7 @@ import { removeCookie } from "#utils/cookies";
import i18next from "i18next"; import i18next from "i18next";
export class UnavailableModalUiHandler extends ModalUiHandler { export class UnavailableModalUiHandler extends ModalUiHandler {
private reconnectTimer: NodeJS.Timeout | null;
private reconnectDuration: number; private reconnectDuration: number;
private reconnectCallback: () => void; private reconnectCallback: () => void;
@ -61,6 +62,7 @@ export class UnavailableModalUiHandler extends ModalUiHandler {
tryReconnect(): void { tryReconnect(): void {
updateUserInfo().then(response => { updateUserInfo().then(response => {
if (response[0] || [200, 400].includes(response[1])) { if (response[0] || [200, 400].includes(response[1])) {
this.reconnectTimer = null;
this.reconnectDuration = this.minTime; this.reconnectDuration = this.minTime;
globalScene.playSound("se/pb_bounce_1"); globalScene.playSound("se/pb_bounce_1");
this.reconnectCallback(); this.reconnectCallback();
@ -69,7 +71,7 @@ export class UnavailableModalUiHandler extends ModalUiHandler {
globalScene.reset(true, true); globalScene.reset(true, true);
} else { } else {
this.reconnectDuration = Math.min(this.reconnectDuration * 2, this.maxTime); // Set a max delay so it isn't infinite this.reconnectDuration = Math.min(this.reconnectDuration * 2, this.maxTime); // Set a max delay so it isn't infinite
setTimeout( this.reconnectTimer = setTimeout(
() => this.tryReconnect(), () => this.tryReconnect(),
// Adds a random factor to avoid pendulum effect during long total breakdown // Adds a random factor to avoid pendulum effect during long total breakdown
this.reconnectDuration + Math.random() * this.randVarianceTime, this.reconnectDuration + Math.random() * this.randVarianceTime,
@ -86,7 +88,7 @@ export class UnavailableModalUiHandler extends ModalUiHandler {
this.reconnectCallback = args[0]; this.reconnectCallback = args[0];
this.reconnectDuration = this.minTime; this.reconnectDuration = this.minTime;
setTimeout(() => this.tryReconnect(), this.reconnectDuration); this.reconnectTimer = setTimeout(() => this.tryReconnect(), this.reconnectDuration);
return super.show([config]); return super.show([config]);
} }

View File

@ -1,5 +1,3 @@
// biome-ignore-all lint/nursery/useUnifiedTypeSignature: Rule does not allow stuff with JSDoc comments
import type { FixedBattleConfig } from "#app/battle"; import type { FixedBattleConfig } from "#app/battle";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { pokemonEvolutions } from "#balance/pokemon-evolutions"; import { pokemonEvolutions } from "#balance/pokemon-evolutions";

View File

@ -23,11 +23,13 @@ export function getCookie(cName: string): string {
} }
const name = `${cName}=`; const name = `${cName}=`;
const ca = document.cookie.split(";"); const ca = document.cookie.split(";");
// Check all cookies in the document and see if any of them match, grabbing the first one whose value lines up for (let i = 0; i < ca.length; i++) {
for (const c of ca) { let c = ca[i];
const cTrimmed = c.trim(); while (c.charAt(0) === " ") {
if (cTrimmed.startsWith(name)) { c = c.substring(1);
return c.slice(name.length, c.length); }
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
} }
} }
return ""; return "";

View File

@ -33,8 +33,8 @@ describe("Abilities - POWER CONSTRUCT", () => {
}); });
test("check if fainted 50% Power Construct Pokemon switches to base form on arena reset", async () => { test("check if fainted 50% Power Construct Pokemon switches to base form on arena reset", async () => {
const baseForm = 2; const baseForm = 2,
const completeForm = 4; completeForm = 4;
game.override.startingWave(4).starterForms({ game.override.startingWave(4).starterForms({
[SpeciesId.ZYGARDE]: completeForm, [SpeciesId.ZYGARDE]: completeForm,
}); });
@ -59,8 +59,8 @@ describe("Abilities - POWER CONSTRUCT", () => {
}); });
test("check if fainted 10% Power Construct Pokemon switches to base form on arena reset", async () => { test("check if fainted 10% Power Construct Pokemon switches to base form on arena reset", async () => {
const baseForm = 3; const baseForm = 3,
const completeForm = 5; completeForm = 5;
game.override.startingWave(4).starterForms({ game.override.startingWave(4).starterForms({
[SpeciesId.ZYGARDE]: completeForm, [SpeciesId.ZYGARDE]: completeForm,
}); });

View File

@ -29,8 +29,8 @@ describe("Abilities - SCHOOLING", () => {
}); });
test("check if fainted pokemon switches to base form on arena reset", async () => { test("check if fainted pokemon switches to base form on arena reset", async () => {
const soloForm = 0; const soloForm = 0,
const schoolForm = 1; schoolForm = 1;
game.override.startingWave(4).starterForms({ game.override.startingWave(4).starterForms({
[SpeciesId.WISHIWASHI]: schoolForm, [SpeciesId.WISHIWASHI]: schoolForm,
}); });

View File

@ -34,8 +34,8 @@ describe("Abilities - SHIELDS DOWN", () => {
}); });
test("check if fainted pokemon switched to base form on arena reset", async () => { test("check if fainted pokemon switched to base form on arena reset", async () => {
const meteorForm = 0; const meteorForm = 0,
const coreForm = 7; coreForm = 7;
game.override.startingWave(4).starterForms({ game.override.startingWave(4).starterForms({
[SpeciesId.MINIOR]: coreForm, [SpeciesId.MINIOR]: coreForm,
}); });

View File

@ -76,9 +76,9 @@ describe("Escape chance calculations", () => {
{ pokemonSpeedRatio: 2, escapeAttempts: 3, expectedEscapeChance: 80 }, { pokemonSpeedRatio: 2, escapeAttempts: 3, expectedEscapeChance: 80 },
]; ];
for (const check of escapeChances) { for (let i = 0; i < escapeChances.length; i++) {
// set the number of escape attempts to the required amount // sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = check.escapeAttempts; game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
// set playerPokemon's speed to a multiple of the enemySpeed // set playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([ vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([
20, 20,
@ -86,10 +86,10 @@ describe("Escape chance calculations", () => {
20, 20,
20, 20,
20, 20,
check.pokemonSpeedRatio * enemySpeed, escapeChances[i].pokemonSpeedRatio * enemySpeed,
]); ]);
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts); const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
expect(chance).toBe(check.expectedEscapeChance); expect(chance).toBe(escapeChances[i].expectedEscapeChance);
} }
}); });
@ -146,9 +146,9 @@ describe("Escape chance calculations", () => {
{ pokemonSpeedRatio: 2, escapeAttempts: 10, expectedEscapeChance: 95 }, { pokemonSpeedRatio: 2, escapeAttempts: 10, expectedEscapeChance: 95 },
]; ];
for (const check of escapeChances) { for (let i = 0; i < escapeChances.length; i++) {
// sets the number of escape attempts to the required amount // sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = check.escapeAttempts; game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
// set the first playerPokemon's speed to a multiple of the enemySpeed // set the first playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([ vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([
20, 20,
@ -156,7 +156,7 @@ describe("Escape chance calculations", () => {
20, 20,
20, 20,
20, 20,
Math.floor(check.pokemonSpeedRatio * totalEnemySpeed * playerASpeedPercentage), Math.floor(escapeChances[i].pokemonSpeedRatio * totalEnemySpeed * playerASpeedPercentage),
]); ]);
// set the second playerPokemon's speed to the remaining value of speed // set the second playerPokemon's speed to the remaining value of speed
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([ vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([
@ -165,13 +165,15 @@ describe("Escape chance calculations", () => {
20, 20,
20, 20,
20, 20,
check.pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5], escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
]); ]);
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts); const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
// checks to make sure the escape values are the same // checks to make sure the escape values are the same
expect(chance).toBe(check.expectedEscapeChance); expect(chance).toBe(escapeChances[i].expectedEscapeChance);
// checks to make sure the sum of the player's speed for all pokemon is equal to the appropriate ratio of the total enemy speed // checks to make sure the sum of the player's speed for all pokemon is equal to the appropriate ratio of the total enemy speed
expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(check.pokemonSpeedRatio * totalEnemySpeed); expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed,
);
} }
}); });
@ -236,9 +238,9 @@ describe("Escape chance calculations", () => {
{ pokemonSpeedRatio: 6.1, escapeAttempts: 3, expectedEscapeChance: 25 }, { pokemonSpeedRatio: 6.1, escapeAttempts: 3, expectedEscapeChance: 25 },
]; ];
for (const check of escapeChances) { for (let i = 0; i < escapeChances.length; i++) {
// sets the number of escape attempts to the required amount // sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = check.escapeAttempts; game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
// set playerPokemon's speed to a multiple of the enemySpeed // set playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([ vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([
20, 20,
@ -246,10 +248,10 @@ describe("Escape chance calculations", () => {
20, 20,
20, 20,
20, 20,
check.pokemonSpeedRatio * enemySpeed, escapeChances[i].pokemonSpeedRatio * enemySpeed,
]); ]);
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts); const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
expect(chance).toBe(check.expectedEscapeChance); expect(chance).toBe(escapeChances[i].expectedEscapeChance);
} }
}); });
@ -319,9 +321,9 @@ describe("Escape chance calculations", () => {
{ pokemonSpeedRatio: 5.2, escapeAttempts: 2, expectedEscapeChance: 25 }, { pokemonSpeedRatio: 5.2, escapeAttempts: 2, expectedEscapeChance: 25 },
]; ];
for (const check of escapeChances) { for (let i = 0; i < escapeChances.length; i++) {
// sets the number of escape attempts to the required amount // sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = check.escapeAttempts; game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
// set the first playerPokemon's speed to a multiple of the enemySpeed // set the first playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([ vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([
20, 20,
@ -329,7 +331,7 @@ describe("Escape chance calculations", () => {
20, 20,
20, 20,
20, 20,
Math.floor(check.pokemonSpeedRatio * totalEnemySpeed * playerASpeedPercentage), Math.floor(escapeChances[i].pokemonSpeedRatio * totalEnemySpeed * playerASpeedPercentage),
]); ]);
// set the second playerPokemon's speed to the remaining value of speed // set the second playerPokemon's speed to the remaining value of speed
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([ vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([
@ -338,13 +340,15 @@ describe("Escape chance calculations", () => {
20, 20,
20, 20,
20, 20,
check.pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5], escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
]); ]);
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts); const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
// checks to make sure the escape values are the same // checks to make sure the escape values are the same
expect(chance).toBe(check.expectedEscapeChance); expect(chance).toBe(escapeChances[i].expectedEscapeChance);
// checks to make sure the sum of the player's speed for all pokemon is equal to the appropriate ratio of the total enemy speed // checks to make sure the sum of the player's speed for all pokemon is equal to the appropriate ratio of the total enemy speed
expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(check.pokemonSpeedRatio * totalEnemySpeed); expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed,
);
} }
}); });
}); });

View File

@ -139,15 +139,17 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
const multiplierHolder = new NumberHolder(1); const multiplierHolder = new NumberHolder(1);
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (globalScene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side) && move.getAttrs("CritOnlyAttr").length === 0) { if (globalScene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) {
globalScene.arena.applyTagsForSide( if (move.getAttrs("CritOnlyAttr").length === 0) {
ArenaTagType.AURORA_VEIL, globalScene.arena.applyTagsForSide(
side, ArenaTagType.AURORA_VEIL,
false, side,
attacker, false,
move.category, attacker,
multiplierHolder, move.category,
); multiplierHolder,
);
}
} }
return move.power * multiplierHolder.value; return move.power * multiplierHolder.value;

View File

@ -127,15 +127,17 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
const multiplierHolder = new NumberHolder(1); const multiplierHolder = new NumberHolder(1);
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (globalScene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side) && move.getAttrs("CritOnlyAttr").length === 0) { if (globalScene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) {
globalScene.arena.applyTagsForSide( if (move.getAttrs("CritOnlyAttr").length === 0) {
ArenaTagType.LIGHT_SCREEN, globalScene.arena.applyTagsForSide(
side, ArenaTagType.LIGHT_SCREEN,
false, side,
attacker, false,
move.category, attacker,
multiplierHolder, move.category,
); multiplierHolder,
);
}
} }
return move.power * multiplierHolder.value; return move.power * multiplierHolder.value;

View File

@ -143,8 +143,10 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
const multiplierHolder = new NumberHolder(1); const multiplierHolder = new NumberHolder(1);
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (globalScene.arena.getTagOnSide(ArenaTagType.REFLECT, side) && move.getAttrs("CritOnlyAttr").length === 0) { if (globalScene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) {
globalScene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, attacker, move.category, multiplierHolder); if (move.getAttrs("CritOnlyAttr").length === 0) {
globalScene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, attacker, move.category, multiplierHolder);
}
} }
return move.power * multiplierHolder.value; return move.power * multiplierHolder.value;

View File

@ -17,12 +17,16 @@ export class MenuManip {
private config; private config;
private settingName; private settingName;
private keycode; private keycode;
private icon;
private iconDisplayed; private iconDisplayed;
private specialCaseIcon;
constructor(config) { constructor(config) {
this.config = config; this.config = config;
this.settingName = null; this.settingName = null;
this.icon = null;
this.iconDisplayed = null; this.iconDisplayed = null;
this.specialCaseIcon = null;
} }
// TODO: Review this // TODO: Review this

View File

@ -18,10 +18,8 @@ import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"
export class ClassicModeHelper extends GameManagerHelper { export class ClassicModeHelper extends GameManagerHelper {
/** /**
* Runs the classic game to the summon phase. * Runs the classic game to the summon phase.
* @param species - An array of {@linkcode SpeciesId} to summon. * @param species - An array of {@linkcode Species} to summon.
* @returns A promise that resolves when the summon phase is reached. * @returns A promise that resolves when the summon phase is reached.
* @remarks
* Do not use this when {@linkcode startBattle} can be used!
*/ */
async runToSummon(species: SpeciesId[]): Promise<void>; async runToSummon(species: SpeciesId[]): Promise<void>;
/** /**
@ -31,7 +29,6 @@ export class ClassicModeHelper extends GameManagerHelper {
* @returns A promise that resolves when the summon phase is reached. * @returns A promise that resolves when the summon phase is reached.
* @deprecated - Specifying the starters helps prevent inconsistencies from internal RNG changes. * @deprecated - Specifying the starters helps prevent inconsistencies from internal RNG changes.
*/ */
// biome-ignore lint/nursery/useUnifiedTypeSignature: Marks the overload for deprecation
async runToSummon(): Promise<void>; async runToSummon(): Promise<void>;
async runToSummon(species: SpeciesId[] | undefined): Promise<void>; async runToSummon(species: SpeciesId[] | undefined): Promise<void>;
async runToSummon(species?: SpeciesId[]): Promise<void> { async runToSummon(species?: SpeciesId[]): Promise<void> {
@ -63,7 +60,7 @@ export class ClassicModeHelper extends GameManagerHelper {
/** /**
* Transitions to the start of a battle. * Transitions to the start of a battle.
* @param species - An array of {@linkcode SpeciesId} to start the battle with. * @param species - An array of {@linkcode Species} to start the battle with.
* @returns A promise that resolves when the battle is started. * @returns A promise that resolves when the battle is started.
*/ */
async startBattle(species: SpeciesId[]): Promise<void>; async startBattle(species: SpeciesId[]): Promise<void>;
@ -74,7 +71,6 @@ export class ClassicModeHelper extends GameManagerHelper {
* @returns A promise that resolves when the battle is started. * @returns A promise that resolves when the battle is started.
* @deprecated - Specifying the starters helps prevent inconsistencies from internal RNG changes. * @deprecated - Specifying the starters helps prevent inconsistencies from internal RNG changes.
*/ */
// biome-ignore lint/nursery/useUnifiedTypeSignature: Marks the overload for deprecation
async startBattle(): Promise<void>; async startBattle(): Promise<void>;
async startBattle(species?: SpeciesId[]): Promise<void> { async startBattle(species?: SpeciesId[]): Promise<void> {
await this.runToSummon(species); await this.runToSummon(species);

View File

@ -227,9 +227,11 @@ export class MoveHelper extends GameManagerHelper {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([]); vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([]);
console.warn("Player moveset override disabled due to use of `game.move.changeMoveset`!"); console.warn("Player moveset override disabled due to use of `game.move.changeMoveset`!");
} }
} else if (coerceArray(Overrides.OPP_MOVESET_OVERRIDE).length > 0) { } else {
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([]); if (coerceArray(Overrides.OPP_MOVESET_OVERRIDE).length > 0) {
console.warn("Enemy moveset override disabled due to use of `game.move.changeMoveset`!"); vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([]);
console.warn("Enemy moveset override disabled due to use of `game.move.changeMoveset`!");
}
} }
moveset = coerceArray(moveset); moveset = coerceArray(moveset);
expect(moveset.length, "Cannot assign more than 4 moves to a moveset!").toBeLessThanOrEqual(4); expect(moveset.length, "Cannot assign more than 4 moves to a moveset!").toBeLessThanOrEqual(4);

View File

@ -53,11 +53,13 @@ export class MockText implements MockGameObject {
wordWidthWithSpace += whiteSpaceWidth; wordWidthWithSpace += whiteSpaceWidth;
} }
// Skip printing the newline if it's the first word of the line that is greater if (wordWidthWithSpace > spaceLeft) {
// than the word wrap width. // Skip printing the newline if it's the first word of the line that is greater
if (wordWidthWithSpace > spaceLeft && j > 0) { // than the word wrap width.
result += "\n"; if (j > 0) {
spaceLeft = this.wordWrapWidth; result += "\n";
spaceLeft = this.wordWrapWidth;
}
} }
result += word; result += word;

View File

@ -2,8 +2,10 @@
* Class will intercept any text or dialogue message calls and log them for test purposes * Class will intercept any text or dialogue message calls and log them for test purposes
*/ */
export class TextInterceptor { export class TextInterceptor {
private scene;
public logs: string[] = []; public logs: string[] = [];
constructor(scene) { constructor(scene) {
this.scene = scene;
scene.messageWrapper = this; scene.messageWrapper = this;
} }