Revert use of typed arrays

This commit is contained in:
Sirz Benjie 2025-09-22 12:41:40 -05:00
parent 9921b57ef3
commit ba7be773dc
No known key found for this signature in database
GPG Key ID: 4A524B4D196C759E
26 changed files with 164 additions and 154 deletions

View File

@ -1 +1,4 @@
export type ConditionFn = (args?: any[]) => boolean;
/** Alias for the constructor of a class */
export type Constructor<T> = new (...args: unknown[]) => T;

View File

@ -123,7 +123,7 @@ export interface Starter {
pokerus: boolean;
nickname?: string;
teraType?: PokemonType;
ivs: Uint8Array;
ivs: number[];
}
export type RunHistoryData = Record<number, RunEntry>;

View File

@ -118,9 +118,9 @@ import type { TrainerData } from "#system/trainer-data";
import type { Voucher } from "#system/voucher";
import { vouchers } from "#system/voucher";
import { trainerConfigs } from "#trainers/trainer-config";
import type { Constructor } from "#types/common";
import type { HeldModifierConfig } from "#types/held-modifier-config";
import type { Localizable } from "#types/locales";
import type { ReadonlyUint8Array } from "#types/typed-arrays";
import { AbilityBar } from "#ui/ability-bar";
import { ArenaFlyout } from "#ui/arena-flyout";
import { CandyBar } from "#ui/candy-bar";
@ -133,7 +133,6 @@ import { UI } from "#ui/ui";
import { addUiThemeOverrides } from "#ui/ui-theme";
import {
BooleanHolder,
type Constructor,
fixedInt,
formatMoney,
getIvsFromId,
@ -867,7 +866,7 @@ export class BattleScene extends SceneBase {
gender?: Gender,
shiny?: boolean,
variant?: Variant,
ivs?: ReadonlyUint8Array | number[],
ivs?: number[],
nature?: Nature,
dataSource?: Pokemon | PokemonData,
postProcess?: (playerPokemon: PlayerPokemon) => void,
@ -898,12 +897,12 @@ export class BattleScene extends SceneBase {
if (Overrides.IVS_OVERRIDE.some(value => !isBetween(value, 0, 31))) {
throw new Error("All IVs in the player IV override must be between 0 and 31!");
}
pokemon.ivs = new Uint8Array(Overrides.IVS_OVERRIDE);
pokemon.ivs = Overrides.IVS_OVERRIDE;
} else {
if (!isBetween(Overrides.IVS_OVERRIDE, 0, 31)) {
throw new Error("The Player IV override must be a value between 0 and 31!");
}
pokemon.ivs = new Uint8Array(6).fill(Overrides.IVS_OVERRIDE);
pokemon.ivs = new Array(6).fill(Overrides.IVS_OVERRIDE);
}
if (Overrides.NATURE_OVERRIDE !== null) {
@ -963,12 +962,12 @@ export class BattleScene extends SceneBase {
if (Overrides.ENEMY_IVS_OVERRIDE.some(value => !isBetween(value, 0, 31))) {
throw new Error("All IVs in the enemy IV override must be between 0 and 31!");
}
pokemon.ivs = new Uint8Array(Overrides.ENEMY_IVS_OVERRIDE);
pokemon.ivs = Overrides.ENEMY_IVS_OVERRIDE;
} else {
if (!isBetween(Overrides.ENEMY_IVS_OVERRIDE, 0, 31)) {
throw new Error("The Enemy IV override must be a value between 0 and 31!");
}
pokemon.ivs = new Uint8Array(6).fill(Overrides.ENEMY_IVS_OVERRIDE);
pokemon.ivs = new Array(6).fill(Overrides.ENEMY_IVS_OVERRIDE);
}
if (Overrides.ENEMY_NATURE_OVERRIDE !== null) {

View File

@ -62,11 +62,10 @@ import type {
PokemonDefendCondition,
PokemonStatStageChangeCondition,
} from "#types/ability-types";
import type { Constructor } from "#types/common";
import type { Localizable } from "#types/locales";
import type { Closed, Exact } from "#types/type-helpers";
import type { GenericUint8Array, ReadonlyGenericInt8Array, ReadonlyGenericUint8Array } from "#types/typed-arrays";
import { coerceArray } from "#utils/array";
import type { Constructor } from "#utils/common";
import { BooleanHolder, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
import { toCamelCase } from "#utils/strings";
import i18next from "i18next";
@ -1211,13 +1210,13 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
private chance: number;
private readonly effects: ReadonlyGenericUint8Array<StatusEffect>;
private readonly effects: readonly StatusEffect[];
constructor(chance: number, ...effects: StatusEffect[]) {
super(true);
this.chance = chance;
this.effects = new Uint8Array(effects);
this.effects = effects;
}
override canApply({ pokemon, move, opponent: attacker }: PostMoveInteractionAbAttrParams): boolean {
@ -2181,14 +2180,14 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
private contactRequired: boolean;
private chance: number;
private readonly effects: ReadonlyGenericUint8Array<StatusEffect>;
private readonly effects: readonly StatusEffect[];
constructor(contactRequired: boolean, chance: number, ...effects: StatusEffect[]) {
super();
this.contactRequired = contactRequired;
this.chance = chance;
this.effects = new Uint8Array(effects);
this.effects = effects;
}
override canApply(params: PostMoveInteractionAbAttrParams): boolean {
@ -2941,7 +2940,7 @@ export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr {
* Heals a status effect if the Pokemon is afflicted with it upon switch in (or gain)
*/
export class PostSummonHealStatusAbAttr extends PostSummonRemoveEffectAbAttr {
private readonly immuneEffects: ReadonlyGenericUint8Array<StatusEffect>;
private readonly immuneEffects: readonly StatusEffect[];
private statusHealed: StatusEffect;
/**
@ -2949,7 +2948,7 @@ export class PostSummonHealStatusAbAttr extends PostSummonRemoveEffectAbAttr {
*/
constructor(...immuneEffects: StatusEffect[]) {
super();
this.immuneEffects = new Uint8Array(immuneEffects);
this.immuneEffects = immuneEffects;
}
public override canApply({ pokemon }: AbAttrBaseParams): boolean {
@ -3050,7 +3049,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr {
* Removes supplied status effects from the user's field.
*/
export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAttr {
private readonly statusEffect: ReadonlyGenericUint8Array<StatusEffect>;
private readonly statusEffect: readonly StatusEffect[];
/**
* @param statusEffect - The status effects to be removed from the user's field.
@ -3058,7 +3057,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt
constructor(...statusEffect: StatusEffect[]) {
super(false);
this.statusEffect = new Uint8Array(statusEffect);
this.statusEffect = statusEffect;
}
override canApply({ pokemon }: AbAttrBaseParams): boolean {
@ -3638,7 +3637,7 @@ export class PreSetStatusAbAttr extends AbAttr {
* Provides immunity to status effects to specified targets.
*/
export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr {
protected readonly immuneEffects: ReadonlyGenericUint8Array<StatusEffect>;
protected readonly immuneEffects: readonly StatusEffect[];
/**
* @param immuneEffects - An array of {@linkcode StatusEffect}s to prevent application.
@ -3647,7 +3646,7 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr {
constructor(...immuneEffects: StatusEffect[]) {
super();
this.immuneEffects = new Uint8Array(immuneEffects);
this.immuneEffects = immuneEffects;
}
override canApply({ effect, cancelled }: PreSetStatusAbAttrParams): boolean {
@ -3706,7 +3705,7 @@ export interface UserFieldStatusEffectImmunityAbAttrParams extends AbAttrBasePar
*/
export class UserFieldStatusEffectImmunityAbAttr extends CancelInteractionAbAttr {
private declare readonly _: never;
protected readonly immuneEffects: ReadonlyGenericUint8Array<StatusEffect>;
protected readonly immuneEffects: readonly StatusEffect[];
/**
* @param immuneEffects - An array of {@linkcode StatusEffect}s to prevent application.
@ -3715,7 +3714,7 @@ export class UserFieldStatusEffectImmunityAbAttr extends CancelInteractionAbAttr
constructor(...immuneEffects: StatusEffect[]) {
super();
this.immuneEffects = new Uint8Array(immuneEffects);
this.immuneEffects = immuneEffects;
}
override canApply({ effect, cancelled }: UserFieldStatusEffectImmunityAbAttrParams): boolean {
@ -3999,7 +3998,7 @@ export class BlockNonDirectDamageAbAttr extends CancelInteractionAbAttr {
* This attribute will block any status damage that you put in the parameter.
*/
export class BlockStatusDamageAbAttr extends CancelInteractionAbAttr {
private readonly effects: ReadonlyGenericUint8Array<StatusEffect>;
private readonly effects: readonly StatusEffect[];
/**
* @param effects - The status effect(s) that will be blocked from damaging the ability pokemon
@ -4007,7 +4006,7 @@ export class BlockStatusDamageAbAttr extends CancelInteractionAbAttr {
constructor(...effects: StatusEffect[]) {
super(false);
this.effects = new Uint8Array(effects);
this.effects = effects;
}
override canApply({ pokemon, cancelled }: AbAttrParamsWithCancel): boolean {
@ -4540,7 +4539,7 @@ export class PostTurnAbAttr extends AbAttr {
* @sealed
*/
export class PostTurnStatusHealAbAttr extends PostTurnAbAttr {
private readonly effects: GenericUint8Array<StatusEffect>;
private readonly effects: readonly StatusEffect[];
/**
* @param effects - The status effect(s) that will qualify healing the ability pokemon
@ -4548,7 +4547,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr {
constructor(...effects: StatusEffect[]) {
super(false);
this.effects = new Uint8Array(effects);
this.effects = effects;
}
override canApply({ pokemon }: AbAttrBaseParams): boolean {
@ -5809,14 +5808,14 @@ export interface IgnoreTypeStatusEffectImmunityAbAttrParams extends AbAttrParams
* @sealed
*/
export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr {
private readonly statusEffect: ReadonlyGenericUint8Array<StatusEffect>;
private readonly defenderType: ReadonlyGenericInt8Array<PokemonType>;
private readonly statusEffect: readonly StatusEffect[];
private readonly defenderType: readonly PokemonType[];
constructor(statusEffect: StatusEffect[], defenderType: PokemonType[]) {
constructor(statusEffect: readonly StatusEffect[], defenderType: readonly PokemonType[]) {
super(false);
this.statusEffect = new Uint8Array(statusEffect);
this.defenderType = new Int8Array(defenderType);
this.statusEffect = statusEffect;
this.defenderType = defenderType;
}
override canApply({ statusEffect, defenderType, cancelled }: IgnoreTypeStatusEffectImmunityAbAttrParams): boolean {

View File

@ -87,7 +87,8 @@ import type { AttackMoveResult } from "#types/attack-move-result";
import type { Localizable } from "#types/locales";
import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString, MoveMessageFunc } from "#types/move-types";
import type { TurnMove } from "#types/turn-move";
import { BooleanHolder, type Constructor, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
import { BooleanHolder, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
import type { Constructor } from "#types/common";
import { coerceArray } from "#utils/array";
import { getEnumValues } from "#utils/enums";
import { toCamelCase, toTitleCase } from "#utils/strings";
@ -2596,11 +2597,11 @@ export class StatusEffectAttr extends MoveEffectAttr {
* Used for {@linkcode Moves.TRI_ATTACK} and {@linkcode Moves.DIRE_CLAW}.
*/
export class MultiStatusEffectAttr extends StatusEffectAttr {
public readonly effects: ReadonlyGenericUint8Array<StatusEffect>;
public readonly effects: readonly StatusEffect[];
constructor(effects: StatusEffect[], selfTarget?: boolean) {
super(effects[0], selfTarget);
this.effects = new Uint8Array(effects);
this.effects = effects;
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
@ -2926,7 +2927,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
*/
export class HealStatusEffectAttr extends MoveEffectAttr {
/** List of Status Effects to cure */
private readonly effects: ReadonlyGenericUint8Array<StatusEffect>;
private readonly effects: readonly StatusEffect[];
/**
* @param selfTarget - Whether this move targets the user
@ -2934,7 +2935,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
*/
constructor(selfTarget: boolean, effects: StatusEffect | StatusEffect[]) {
super(selfTarget, { lastHitOnly: true });
this.effects = new Uint8Array(coerceArray(effects));
this.effects = coerceArray(effects);
}
/**

View File

@ -15,7 +15,6 @@ import { WeatherType } from "#enums/weather-type";
import type { PlayerPokemon } from "#field/pokemon";
import { AttackTypeBoosterModifier } from "#modifiers/modifier";
import type { AttackTypeBoosterModifierType } from "#modifiers/modifier-type";
import type { ReadonlyGenericUint8Array } from "#types/typed-arrays";
import { coerceArray } from "#utils/array";
export interface EncounterRequirement {
@ -697,15 +696,15 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
}
export class StatusEffectRequirement extends EncounterPokemonRequirement {
requiredStatusEffect: ReadonlyGenericUint8Array<StatusEffect>;
requiredStatusEffect: readonly StatusEffect[];
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon = 1, invertQuery = false) {
constructor(statusEffect: StatusEffect | readonly StatusEffect[], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredStatusEffect = new Uint8Array(coerceArray(statusEffect));
this.requiredStatusEffect = coerceArray(statusEffect);
}
override meetsRequirement(): boolean {
@ -718,7 +717,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
return x;
}
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: readonly PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter(pokemon => {
return this.requiredStatusEffect.some(statusEffect => {
@ -762,11 +761,11 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
* If you want to trigger the event based on the form change enabler, use PersistentModifierRequirement.
*/
export class CanFormChangeWithItemRequirement extends EncounterPokemonRequirement {
requiredFormChangeItem: FormChangeItem[];
requiredFormChangeItem: readonly FormChangeItem[];
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(formChangeItem: FormChangeItem | FormChangeItem[], minNumberOfPokemon = 1, invertQuery = false) {
constructor(formChangeItem: FormChangeItem | readonly FormChangeItem[], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -793,7 +792,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
);
}
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: readonly PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter(
pokemon =>
@ -878,13 +877,13 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
}
export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRequirement {
requiredHeldItemTypes: PokemonType[];
requiredHeldItemTypes: readonly PokemonType[];
minNumberOfPokemon: number;
invertQuery: boolean;
requireTransferable: boolean;
constructor(
heldItemTypes: PokemonType | PokemonType[],
heldItemTypes: PokemonType | readonly PokemonType[],
minNumberOfPokemon = 1,
invertQuery = false,
requireTransferable = true,

View File

@ -319,7 +319,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
// Set IVs
if (config.ivs) {
enemyPokemon.ivs = new Uint8Array(config.ivs);
enemyPokemon.ivs = config.ivs;
}
// Set Status

View File

@ -24,7 +24,8 @@ import { SpeciesFormKey } from "#enums/species-form-key";
import { SpeciesId } from "#enums/species-id";
import { WeatherType } from "#enums/weather-type";
import type { Pokemon } from "#field/pokemon";
import type { Constructor, nil } from "#utils/common";
import type { Constructor } from "#types/common";
import type { nil } from "#utils/common";
export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean;
export type SpeciesFormChangeConditionEnforceFunc = (p: Pokemon) => void;

View File

@ -11,9 +11,8 @@ import type { TimeOfDay } from "#enums/time-of-day";
import { WeatherType } from "#enums/weather-type";
import type { Pokemon } from "#field/pokemon";
import type { PokemonFormChangeItemModifier } from "#modifiers/modifier";
import type { ReadonlyGenericUint8Array } from "#types/typed-arrays";
import type { Constructor } from "#types/common";
import { coerceArray } from "#utils/array";
import type { Constructor } from "#utils/common";
import { toCamelCase } from "#utils/strings";
import i18next from "i18next";
@ -123,12 +122,12 @@ export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger {
}
export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigger {
public readonly statusEffects: ReadonlyGenericUint8Array<StatusEffect>;
public readonly statusEffects: readonly StatusEffect[];
public invert: boolean;
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
super();
this.statusEffects = new Uint8Array(coerceArray(statusEffects));
this.statusEffects = coerceArray(statusEffects);
this.invert = invert;
// this.description = i18next.t("pokemonEvolutions:forms.statusEffect");
}

View File

@ -16,7 +16,6 @@ import type { AttackMoveResult } from "#types/attack-move-result";
import type { IllusionData } from "#types/illusion-data";
import type { TurnMove } from "#types/turn-move";
import type { CoerceNullPropertiesToUndefined } from "#types/type-helpers";
import { setTypedArray } from "#utils/array";
import { getPokemonSpeciesForm } from "#utils/pokemon-utils";
/**
@ -130,7 +129,7 @@ export class PokemonSummonData {
public passiveAbility: AbilityId | undefined;
public gender: Gender | undefined;
public fusionGender: Gender | undefined;
public stats: Uint32Array = new Uint32Array(6);
public stats: number[] = [0, 0, 0, 0, 0, 0];
public moveset: PokemonMove[] | null;
// If not initialized this value will not be populated from save data.
@ -166,11 +165,6 @@ export class PokemonSummonData {
continue;
}
if (key === "stats") {
setTypedArray(this.stats, source.stats);
continue;
}
if (key === "illusion" && typeof value === "object") {
// Make a copy so as not to mutate provided value
const illusionData = {
@ -227,10 +221,8 @@ export class PokemonSummonData {
// We coerce null to undefined in the type, as the for loop below replaces `null` with `undefined`
...(this as Omit<
CoerceNullPropertiesToUndefined<PokemonSummonData>,
"speciesForm" | "fusionSpeciesForm" | "illusion" | "stats"
"speciesForm" | "fusionSpeciesForm" | "illusion"
>),
// TypedArrays do not serialize to JSON as an array.
stats: Array.from(this.stats),
speciesForm: speciesForm == null ? undefined : { id: speciesForm.speciesId, formIdx: speciesForm.formIndex },
fusionSpeciesForm:
fusionSpeciesForm == null

View File

@ -1,7 +1,7 @@
import { DelayedAttackTag, type PositionalTag, WishTag } from "#data/positional-tags/positional-tag";
import { PositionalTagType } from "#enums/positional-tag-type";
import type { Constructor } from "#types/common";
import type { ObjectValues } from "#types/type-helpers";
import type { Constructor } from "#utils/common";
/**
* Load the attributes of a {@linkcode PositionalTag}.

View File

@ -35,8 +35,9 @@ import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEven
import type { Pokemon } from "#field/pokemon";
import { FieldEffectModifier } from "#modifiers/modifier";
import type { Move } from "#moves/move";
import type { Constructor } from "#types/common";
import type { AbstractConstructor } from "#types/type-helpers";
import { type Constructor, NumberHolder, randSeedInt } from "#utils/common";
import { NumberHolder, randSeedInt } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
export class Arena {

View File

@ -141,22 +141,21 @@ import type { PokemonData } from "#system/pokemon-data";
import { RibbonData } from "#system/ribbons/ribbon-data";
import { awardRibbonsToSpeciesLine } from "#system/ribbons/ribbon-methods";
import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#types/ability-types";
import type { Constructor } from "#types/common";
import type { getAttackDamageParams, getBaseDamageParams } from "#types/damage-params";
import type { DamageCalculationResult, DamageResult } from "#types/damage-result";
import type { IllusionData } from "#types/illusion-data";
import type { StarterDataEntry, StarterMoveset } from "#types/save-data";
import type { TurnMove } from "#types/turn-move";
import type { ReadonlyUint8Array } from "#types/typed-arrays";
import { BattleInfo } from "#ui/battle-info";
import { EnemyBattleInfo } from "#ui/enemy-battle-info";
import type { PartyOption } from "#ui/party-ui-handler";
import { PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler";
import { PlayerBattleInfo } from "#ui/player-battle-info";
import { coerceArray, setTypedArray } from "#utils/array";
import { coerceArray } from "#utils/array";
import { applyChallenges } from "#utils/challenge-utils";
import {
BooleanHolder,
type Constructor,
deltaRgb,
fixedInt,
getIvsFromId,
@ -204,8 +203,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
public levelExp: number;
public gender: Gender;
public hp: number;
public stats = Uint32Array.of(1, 1, 1, 1, 1, 1);
public ivs = Uint8Array.of(0, 0, 0, 0, 0, 0);
public stats: number[];
public ivs: number[];
public nature: Nature;
public moveset: PokemonMove[];
/**
@ -312,7 +311,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
gender?: Gender,
shiny?: boolean,
variant?: Variant,
ivs?: ReadonlyUint8Array | number[],
ivs?: number[],
nature?: Nature,
dataSource?: Pokemon | PokemonData,
) {
@ -346,8 +345,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
if (dataSource) {
this.id = dataSource.id;
this.hp = dataSource.hp;
setTypedArray(this.stats, dataSource.stats);
setTypedArray(this.ivs, dataSource.ivs ?? getIvsFromId(dataSource.id));
this.stats = dataSource.stats;
this.ivs = dataSource.ivs;
this.passive = !!dataSource.passive;
if (this.variant === undefined) {
this.variant = 0;
@ -386,7 +385,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
this.stellarTypesBoosted = dataSource.stellarTypesBoosted ?? [];
} else {
this.id = randSeedInt(4294967296);
setTypedArray(this.ivs, ivs ?? getIvsFromId(this.id));
this.ivs = ivs || getIvsFromId(this.id);
if (this.gender === undefined) {
this.gender = this.species.generateGender();
@ -1320,7 +1319,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
* @param bypassSummonData - Whether to prefer actual stats (`true`) or in-battle overridden stats (`false`); default `true`
* @returns The numeric values of this {@linkcode Pokemon}'s stats as an array.
*/
getStats(bypassSummonData = true): Uint32Array {
getStats(bypassSummonData = true): number[] {
if (!bypassSummonData) {
// Only grab summon data stats if nonzero
return this.summonData.stats.map((s, i) => s || this.stats[i]);
@ -1552,6 +1551,10 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
}
calculateStats(): void {
if (!this.stats) {
this.stats = [0, 0, 0, 0, 0, 0];
}
// Get and manipulate base stats
const baseStats = this.calculateBaseStats();
// Using base stats, calculate and store stats one by one
@ -1584,7 +1587,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder);
}
statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, 0xffffffff);
statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER);
this.setStat(s, statHolder.value);
}
@ -5701,7 +5704,7 @@ export class PlayerPokemon extends Pokemon {
gender?: Gender,
shiny?: boolean,
variant?: Variant,
ivs?: ReadonlyUint8Array | number[],
ivs?: number[],
nature?: Nature,
dataSource?: Pokemon | PokemonData,
) {
@ -6321,9 +6324,9 @@ export class EnemyPokemon extends Pokemon {
if (this.hasTrainer() && globalScene.currentBattle) {
const { waveIndex } = globalScene.currentBattle;
const ivs = new Uint8Array(6);
for (let i = 0; i < 6; i++) {
ivs[i] = this.randBattleSeedIntRange(Math.floor(waveIndex / 10), 31);
const ivs: number[] = [];
while (ivs.length < 6) {
ivs.push(randSeedIntRange(Math.floor(waveIndex / 10), 31));
}
this.ivs = ivs;
}

View File

@ -1914,7 +1914,7 @@ export class GameData {
_unlockSpeciesNature(species.speciesId);
}
updateSpeciesDexIvs(speciesId: SpeciesId, ivs: Uint8Array): void {
updateSpeciesDexIvs(speciesId: SpeciesId, ivs: number[]): void {
let dexEntry: DexEntry;
do {
dexEntry = globalScene.gameData.dexData[speciesId];

View File

@ -99,8 +99,8 @@ export class PokemonData {
this.levelExp = source.levelExp;
this.gender = source.gender;
this.hp = source.hp;
this.stats = Array.from(source.stats);
this.ivs = Array.from(source.ivs);
this.stats = source.stats;
this.ivs = source.ivs;
// TODO: Can't we move some of this verification stuff to an upgrade script?
this.nature = source.nature ?? Nature.HARDY;
@ -162,7 +162,7 @@ export class PokemonData {
this.gender,
this.shiny,
this.variant,
new Uint8Array(this.ivs.slice(0, 6)),
this.ivs,
this.nature,
this,
playerPokemon => {

View File

@ -104,7 +104,7 @@ export class StatsContainer extends Phaser.GameObjects.Container {
}
}
updateIvs(ivs: Uint8Array | number[], originalIvs?: number[]): void {
updateIvs(ivs: number[], originalIvs?: number[]): void {
if (ivs) {
const ivChartData = new Array(6)
.fill(null)

View File

@ -195,7 +195,7 @@ export class BattleMessageUiHandler extends MessageUiHandler {
super.showDialogue(text, name, delay, callback, callbackDelay, prompt, promptDelay);
}
promptLevelUpStats(partyMemberIndex: number, prevStats: ArrayLike<number>, showTotals: boolean): Promise<void> {
promptLevelUpStats(partyMemberIndex: number, prevStats: number[], showTotals: boolean): Promise<void> {
return new Promise(resolve => {
if (!globalScene.showLevelUpStats) {
return resolve();
@ -219,7 +219,7 @@ export class BattleMessageUiHandler extends MessageUiHandler {
});
}
promptIvs(pokemonId: number, ivs: ArrayLike<number>): Promise<void> {
promptIvs(pokemonId: number, ivs: number[]): Promise<void> {
return new Promise(resolve => {
globalScene.executeWithSeedOffset(() => {
let levelUpStatsValuesText = "";

View File

@ -2759,7 +2759,7 @@ export class StarterSelectUiHandler extends MessageUiHandler {
pokerus: this.pokerusSpecies.includes(species),
nickname: this.starterPreferences[species.speciesId]?.nickname,
teraType,
ivs: new Uint8Array(dexEntry.ivs),
ivs: dexEntry.ivs,
};
this.starters.push(starter);

View File

@ -12,7 +12,7 @@ import type {
* If the input isn't already an array, turns it into one.
* @returns An array with the same type as the type of the input
*/
export function coerceArray<T>(input: T): T extends any[] ? T : [T];
export function coerceArray<T>(input: T): T extends readonly any[] ? T : [T];
export function coerceArray<T>(input: T): T | [T] {
return Array.isArray(input) ? input : [input];
}

View File

@ -176,15 +176,15 @@ export function getPlayTimeString(totalSeconds: number): string {
* @param id 32-bit number
* @returns An array of six numbers corresponding to 5-bit chunks from {@linkcode id}
*/
export function getIvsFromId(id: number): Uint8Array {
return Uint8Array.of(
export function getIvsFromId(id: number): [number, number, number, number, number, number] {
return [
(id & 0x3e000000) >>> 25,
(id & 0x01f00000) >>> 20,
(id & 0x000f8000) >>> 15,
(id & 0x00007c00) >>> 10,
(id & 0x000003e0) >>> 5,
id & 0x0000001f,
);
];
}
export function formatLargeNumber(count: number, threshold: number): string {
@ -292,9 +292,6 @@ export async function localPing(): Promise<void> {
}
}
/** Alias for the constructor of a class */
export type Constructor<T> = new (...args: unknown[]) => T;
export class BooleanHolder {
public value: boolean;

View File

@ -38,7 +38,7 @@ describe("Abilities - Beast Boost", () => {
const playerPokemon = game.field.getPlayerPokemon();
// Set the pokemon's highest stat to DEF, so it should be picked by Beast Boost
vi.spyOn(playerPokemon, "stats", "get").mockReturnValue(Uint32Array.of(10000, 100, 1000, 200, 100, 100));
vi.spyOn(playerPokemon, "stats", "get").mockReturnValue([10000, 100, 1000, 200, 100, 100]);
console.log(playerPokemon.stats);
expect(playerPokemon.getStatStage(Stat.DEF)).toBe(0);
@ -56,7 +56,7 @@ describe("Abilities - Beast Boost", () => {
const playerPokemon = game.field.getPlayerPokemon();
// If the opponent uses Guard Split, the pokemon's second highest stat (SPATK) should be chosen
vi.spyOn(playerPokemon, "stats", "get").mockReturnValue(Uint32Array.of(10000, 100, 201, 200, 100, 100));
vi.spyOn(playerPokemon, "stats", "get").mockReturnValue([10000, 100, 201, 200, 100, 100]);
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(0);
@ -75,7 +75,7 @@ describe("Abilities - Beast Boost", () => {
const playerPokemon = game.field.getPlayerPokemon();
// Set up tie between SPATK, SPDEF, and SPD, where SPATK should win
vi.spyOn(playerPokemon, "stats", "get").mockReturnValue(Uint32Array.of(10000, 1, 1, 100, 100, 100));
vi.spyOn(playerPokemon, "stats", "get").mockReturnValue([10000, 1, 1, 100, 100, 100]);
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(0);

View File

@ -39,8 +39,8 @@ describe("Battle order", () => {
const enemyPokemon = game.field.getEnemyPokemon();
const enemyStartHp = enemyPokemon.hp;
vi.spyOn(playerPokemon, "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 50)); // set playerPokemon's speed to 50
vi.spyOn(enemyPokemon, "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 150)); // set enemyPokemon's speed to 150
vi.spyOn(playerPokemon, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 50]); // set playerPokemon's speed to 50
vi.spyOn(enemyPokemon, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150]); // set enemyPokemon's speed to 150
game.move.select(MoveId.TACKLE);
await game.phaseInterceptor.to("MoveEndPhase", false);
@ -55,8 +55,8 @@ describe("Battle order", () => {
const playerStartHp = playerPokemon.hp;
const enemyPokemon = game.field.getEnemyPokemon();
const enemyStartHp = enemyPokemon.hp;
vi.spyOn(playerPokemon, "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 150)); // set playerPokemon's speed to 150
vi.spyOn(enemyPokemon, "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 50)); // set enemyPokemon's speed to 50
vi.spyOn(playerPokemon, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150]); // set playerPokemon's speed to 150
vi.spyOn(enemyPokemon, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 50]); // set enemyPokemon's speed to 50
game.move.select(MoveId.TACKLE);
@ -74,8 +74,8 @@ describe("Battle order", () => {
const enemyPokemon = game.scene.getEnemyField();
const enemyHps = enemyPokemon.map(p => p.hp);
playerPokemon.forEach(p => vi.spyOn(p, "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 50))); // set both playerPokemons' speed to 50
enemyPokemon.forEach(p => vi.spyOn(p, "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 150))); // set both enemyPokemons' speed to 150
playerPokemon.forEach(p => vi.spyOn(p, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 50])); // set both playerPokemons' speed to 50
enemyPokemon.forEach(p => vi.spyOn(p, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150])); // set both enemyPokemons' speed to 150
game.move.select(MoveId.TACKLE);
game.move.select(MoveId.TACKLE, 1);
@ -96,9 +96,9 @@ describe("Battle order", () => {
const playerPokemon = game.scene.getPlayerField();
const enemyPokemon = game.scene.getEnemyField();
playerPokemon.forEach(p => vi.spyOn(p, "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 100))); //set both playerPokemons' speed to 100
vi.spyOn(enemyPokemon[0], "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 100)); // set enemyPokemon's speed to 100
vi.spyOn(enemyPokemon[1], "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 150)); // set enemyPokemon's speed to 150
playerPokemon.forEach(p => vi.spyOn(p, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 100])); //set both playerPokemons' speed to 100
vi.spyOn(enemyPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 100]); // set enemyPokemon's speed to 100
vi.spyOn(enemyPokemon[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150]); // set enemyPokemon's speed to 150
game.move.select(MoveId.TACKLE);
game.move.select(MoveId.TACKLE, 1);
@ -114,10 +114,10 @@ describe("Battle order", () => {
const playerPokemon = game.scene.getPlayerField();
const enemyPokemon = game.scene.getEnemyField();
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 100)); // set one playerPokemon's speed to 100
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 150)); // set other playerPokemon's speed to 150
vi.spyOn(enemyPokemon[0], "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 100)); // set one enemyPokemon's speed to 100
vi.spyOn(enemyPokemon[1], "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, 150)); // set other enemyPokemon's speed to 150
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 100]); // set one playerPokemon's speed to 100
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150]); // set other playerPokemon's speed to 150
vi.spyOn(enemyPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 100]); // set one enemyPokemon's speed to 100
vi.spyOn(enemyPokemon[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150]); // set other enemyPokemon's speed to 150
game.move.select(MoveId.TACKLE);
game.move.select(MoveId.TACKLE, 1);

View File

@ -38,7 +38,7 @@ describe("Escape chance calculations", () => {
const enemyField = game.scene.getEnemyField();
const enemySpeed = 100;
// set enemyPokemon's speed to 100
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, enemySpeed));
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemySpeed]);
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
@ -81,9 +81,14 @@ describe("Escape chance calculations", () => {
// set the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = check.escapeAttempts;
// set playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue(
Uint32Array.of(20, 20, 20, 20, 20, check.pokemonSpeedRatio * enemySpeed),
);
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([
20,
20,
20,
20,
20,
check.pokemonSpeedRatio * enemySpeed,
]);
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
expect(chance).toBe(check.expectedEscapeChance);
}
@ -102,9 +107,9 @@ describe("Escape chance calculations", () => {
// this is used to find the ratio of the player's first pokemon
const playerASpeedPercentage = 0.4;
// set enemyAPokemon's speed to 70
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, enemyASpeed));
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyASpeed]);
// set enemyBPokemon's speed to 30
vi.spyOn(enemyField[1], "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, enemyBSpeed));
vi.spyOn(enemyField[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyBSpeed]);
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
@ -146,20 +151,23 @@ describe("Escape chance calculations", () => {
// sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = check.escapeAttempts;
// set the first playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue(
Uint32Array.of(
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([
20,
20,
20,
20,
20,
Math.floor(check.pokemonSpeedRatio * totalEnemySpeed * playerASpeedPercentage),
),
);
]);
// set the second playerPokemon's speed to the remaining value of speed
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue(
Uint32Array.of(20, 20, 20, 20, 20, check.pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5]),
);
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([
20,
20,
20,
20,
20,
check.pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
]);
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
// checks to make sure the escape values are the same
expect(chance).toBe(check.expectedEscapeChance);
@ -176,7 +184,7 @@ describe("Escape chance calculations", () => {
const enemyField = game.scene.getEnemyField()!;
const enemySpeed = 100;
// set enemyPokemon's speed to 100
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, enemySpeed));
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemySpeed]);
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
@ -233,9 +241,14 @@ describe("Escape chance calculations", () => {
// sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = check.escapeAttempts;
// set playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue(
Uint32Array.of(20, 20, 20, 20, 20, check.pokemonSpeedRatio * enemySpeed),
);
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([
20,
20,
20,
20,
20,
check.pokemonSpeedRatio * enemySpeed,
]);
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
expect(chance).toBe(check.expectedEscapeChance);
}
@ -254,9 +267,9 @@ describe("Escape chance calculations", () => {
// this is used to find the ratio of the player's first pokemon
const playerASpeedPercentage = 0.8;
// set enemyAPokemon's speed to 70
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, enemyASpeed));
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyASpeed]);
// set enemyBPokemon's speed to 30
vi.spyOn(enemyField[1], "stats", "get").mockReturnValue(Uint32Array.of(20, 20, 20, 20, 20, enemyBSpeed));
vi.spyOn(enemyField[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyBSpeed]);
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
@ -311,20 +324,23 @@ describe("Escape chance calculations", () => {
// sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = check.escapeAttempts;
// set the first playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue(
Uint32Array.of(
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([
20,
20,
20,
20,
20,
Math.floor(check.pokemonSpeedRatio * totalEnemySpeed * playerASpeedPercentage),
),
);
]);
// set the second playerPokemon's speed to the remaining value of speed
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue(
Uint32Array.of(20, 20, 20, 20, 20, check.pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5]),
);
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([
20,
20,
20,
20,
20,
check.pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
]);
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
// checks to make sure the escape values are the same
expect(chance).toBe(check.expectedEscapeChance);

View File

@ -45,10 +45,10 @@ describe("Moves - Rollout", () => {
await game.classicMode.startBattle();
const playerPkm = game.field.getPlayerPokemon();
vi.spyOn(playerPkm, "stats", "get").mockReturnValue(Uint32Array.of(500000, 1, 1, 1, 1, 1)); // HP, ATK, DEF, SPATK, SPDEF, SPD
vi.spyOn(playerPkm, "stats", "get").mockReturnValue([500000, 1, 1, 1, 1, 1]); // HP, ATK, DEF, SPATK, SPDEF, SPD
const enemyPkm = game.field.getEnemyPokemon();
vi.spyOn(enemyPkm, "stats", "get").mockReturnValue(Uint32Array.of(500000, 1, 1, 1, 1, 1)); // HP, ATK, DEF, SPATK, SPDEF, SPD
vi.spyOn(enemyPkm, "stats", "get").mockReturnValue([500000, 1, 1, 1, 1, 1]); // HP, ATK, DEF, SPATK, SPDEF, SPD
vi.spyOn(enemyPkm, "getHeldItems").mockReturnValue([]); //no berries
enemyPkm.hp = enemyPkm.getMaxHp();

View File

@ -144,7 +144,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
expect(game).toBeAtPhase("CommandPhase");
expect(game.field.getEnemyPokemon().species.speciesId).toBe(SpeciesId.WOBBUFFET);
expect(game.field.getEnemyPokemon().ivs).toEqual(Uint8Array.of(0, 0, 0, 0, 0, 0));
expect(game.field.getEnemyPokemon().ivs).toEqual([0, 0, 0, 0, 0, 0]);
expect(game.field.getEnemyPokemon().nature).toBe(Nature.MILD);
game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => {

View File

@ -122,7 +122,7 @@ describe("Part-Timer - Mystery Encounter", () => {
// Override party levels to 50 so stats can be fully reflective
scene.getPlayerParty().forEach(p => {
p.level = 50;
p.ivs = Uint8Array.of(20, 20, 20, 20, 20, 20);
p.ivs = [20, 20, 20, 20, 20, 20];
p.calculateStats();
});
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 2 });
@ -168,7 +168,7 @@ describe("Part-Timer - Mystery Encounter", () => {
// Override party levels to 50 so stats can be fully reflective
scene.getPlayerParty().forEach(p => {
p.level = 50;
p.ivs = Uint8Array.of(0, 0, 0, 0, 0, 0);
p.ivs = [0, 0, 0, 0, 0, 0];
p.calculateStats();
});
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 3 });
@ -188,7 +188,7 @@ describe("Part-Timer - Mystery Encounter", () => {
// Override party levels to 50 so stats can be fully reflective
scene.getPlayerParty().forEach(p => {
p.level = 50;
p.ivs = Uint8Array.of(20, 20, 20, 20, 20, 20);
p.ivs = [20, 20, 20, 20, 20, 20];
p.calculateStats();
});
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 4 });