This commit is contained in:
Sirz Benjie 2025-09-22 20:08:59 -05:00 committed by GitHub
commit 63563766c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 1217 additions and 317 deletions

View File

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

View File

@ -7,8 +7,8 @@ import type { TrainerPartyTemplate } from "#trainers/trainer-party-template";
export type PartyTemplateFunc = () => TrainerPartyTemplate;
export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon;
export type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[];
export type GenAIFunc = (party: EnemyPokemon[]) => void;
export type GenModifiersFunc = (party: readonly EnemyPokemon[]) => PersistentModifier[];
export type GenAIFunc = (party: readonly EnemyPokemon[]) => void;
export interface TrainerTierPools {
[key: number]: SpeciesId[];

524
src/@types/typed-arrays.ts Normal file
View File

@ -0,0 +1,524 @@
/*
* SPDX-FileCopyrightText: 2025 Pagefault Games
* SPDX-FileContributor: SirzBenjie
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* Collection of utility types for working with
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray | TypedArray}
* with enhanced type safety and usability.
* @module
*/
/**
* Union type of all {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray | TypedArray}s
*/
export type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| BigInt64Array
| BigUint64Array;
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array | Int8Array}
*
* @remarks
* Is to `Int8Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyInt8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
extends Omit<Int8Array, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyInt8Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array | Uint8Array}
*
* @remarks
* Is to `Uint8Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyUint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
extends Omit<Uint8Array, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyUint8Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray | Uint8ClampedArray}
*
* @remarks
* Is to `Uint8ClampedArray` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyUint8ClampedArray<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
extends Omit<Uint8ClampedArray<TArrayBuffer>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyUint8ClampedArray<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array | Int16Array}
*
* @remarks
* Is to `Int16Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyInt16Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
extends Omit<Int16Array<TArrayBuffer>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyInt16Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array | Uint16Array}
*
* @remarks
* Is to `Uint16Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyUint16Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
extends Omit<Uint16Array<TArrayBuffer>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyUint16Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array | Int32Array}
*
* @remarks
* Is to `Int32Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyInt32Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
extends Omit<Int32Array<TArrayBuffer>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyInt32Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array | Uint32Array}
*
* @remarks
* Is to `Uint32Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyUint32Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
extends Omit<Uint32Array<TArrayBuffer>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyUint32Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array | Float32Array}
*
* @remarks
* Is to `Float32Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyFloat32Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
extends Omit<Float32Array<TArrayBuffer>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyFloat32Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array | Float64Array}
*
* @remarks
* Is to `Float64Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyFloat64Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
extends Omit<Float64Array, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyFloat64Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array | BigInt64Array}
*
* @remarks
* Is to `BigInt64Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyBigInt64Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
extends Omit<BigInt64Array, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyBigInt64Array<TArrayBuffer>;
readonly [index: number]: bigint;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array | BigUint64Array}
*
* @remarks
* Is to `BigUint64Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyBigUint64Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
extends Omit<BigUint64Array, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyBigUint64Array<TArrayBuffer>;
readonly [index: number]: bigint;
}
export type ReadonlyTypedArray =
| ReadonlyInt8Array
| ReadonlyUint8Array
| ReadonlyUint8ClampedArray
| ReadonlyInt16Array
| ReadonlyUint16Array
| ReadonlyInt32Array
| ReadonlyUint32Array
| ReadonlyFloat32Array
| ReadonlyFloat64Array
| ReadonlyBigInt64Array
| ReadonlyBigUint64Array;
/**
* Either {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array | BigInt64Array}
* or {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array | BigUint64Array}
*/
export type BigIntArray = BigInt64Array | BigUint64Array;
export type ReadonlyBigIntArray = ReadonlyBigInt64Array | ReadonlyBigUint64Array;
/** Any {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray | TypedArray} whose elements are not `bigint`s */
export type NumberCompatibleTypedArray = Exclude<TypedArray, BigIntArray>;
export type ReadonlyNumberCompatibleTypedArray = Exclude<ReadonlyTypedArray, ReadonlyBigIntArray>;
/**
* A partial interface of `Uint8Array` where methods that return the array type have been modified to return a more specific type.
*
* @privateRemarks
* Excludes methods that return a new array (e.g. `subarray`, `slice`, `toReversed`, `toSorted`, `with`) as these cannot be resolved by typescript
* @internal
*/
interface PartialUint8Array<T extends number> extends Uint8Array {
at(index: number): T | undefined; // ES2022
entries(): ArrayIterator<[number, T]>;
fill(value: T, start?: number, end?: number): this;
find(predicate: (value: T, index: number, array: this) => boolean, thisArg?: any): T | undefined;
findLast(predicate: (value: number, index: number, array: this) => boolean, thisArg?: any): T | undefined;
forEach(callbackfn: (value: T, index: number, array: this) => void, thisArg?: any): void;
includes(searchElement: T, fromIndex?: number): boolean;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
set(array: ArrayLike<T>, offset?: number): void;
some(predicate: (value: T, index: number, obj: this) => boolean, thisArg?: any): boolean;
sort(compareFn?: (a: T, b: T) => number): this;
values(): ArrayIterator<T>;
[Symbol.iterator](): ArrayIterator<T>;
[index: number]: T;
}
/**
* A `Uint8Array` whose elements typescript considers to be of type `T`, allowing for type-safe iteration and access.
*
* @remarks
* Useful to leverage the benefits of `TypedArrays` without losing type information. Typescript will consider the elements to be of type `T` instead of just `number`.
* @typeParam T - The specific numeric type of the elements in the array.
*/
// @ts-expect-error - These methods _will_ error, as we are overriding the return type to be more specific in a way that makes typescript unhappy
export interface GenericUint8Array<T extends number> extends PartialUint8Array<T> {
// map(callbackfn: (value: T, index: number, array: this) => T, thisArg?: any): GenericUint8Array<T>;
// this method does not trigger a typescript error on its own, but if we add in `toReversed` it causes issues....
subarray(begin?: number, end?: number): GenericUint8Array<T>;
toReversed(): GenericUint8Array<T>;
toSorted(compareFn?: (a: T, b: T) => number): GenericUint8Array<T>;
filter(predicate: (value: T, index: number, array: this) => any, thisArg?: any): GenericUint8Array<T>;
}
/**
* A partial interface of `Uint16Array` where methods that return the array type have been modified to return a more specific type.
*
* @privateRemarks
* Excludes methods that return a new array (e.g. `subarray`, `slice`, `toReversed`, `toSorted`, `with`) as these cannot be resolved by typescript
* @internal
*/
interface PartialUint16Array<T extends number> extends Uint16Array {
at(index: number): T | undefined; // ES2022
entries(): ArrayIterator<[number, T]>;
fill(value: T, start?: number, end?: number): this;
find(predicate: (value: T, index: number, array: this) => boolean, thisArg?: any): T | undefined;
findLast(predicate: (value: number, index: number, array: this) => boolean, thisArg?: any): T | undefined;
forEach(callbackfn: (value: T, index: number, array: this) => void, thisArg?: any): void;
includes(searchElement: T, fromIndex?: number): boolean;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
set(array: ArrayLike<T>, offset?: number): void;
some(predicate: (value: T, index: number, obj: this) => boolean, thisArg?: any): boolean;
sort(compareFn?: (a: T, b: T) => number): this;
values(): ArrayIterator<T>;
[Symbol.iterator](): ArrayIterator<T>;
[index: number]: T;
}
/**
* A `Uint16Array` whose elements typescript considers to be of type `T`, allowing for type-safe iteration and access.
*
* @remarks
* Useful to leverage the benefits of `TypedArrays` without losing type information. Typescript will consider the elements to be of type `T` instead of just `number`.
* @typeParam T - The specific numeric type of the elements in the array.
*/
// @ts-expect-error - These methods _will_ error, as we are overriding the return type to be more specific in a way that makes typescript unhappy
export interface GenericUint16Array<T extends number> extends PartialUint16Array<T> {
// map(callbackfn: (value: T, index: number, array: this) => T, thisArg?: any): GenericUint16Array<T>;
// this method does not trigger a typescript error on its own, but if we add in `toReversed` it causes issues....
subarray(begin?: number, end?: number): GenericUint16Array<T>;
toReversed(): GenericUint16Array<T>;
toSorted(compareFn?: (a: T, b: T) => number): GenericUint16Array<T>;
filter(predicate: (value: T, index: number, array: this) => any, thisArg?: any): GenericUint16Array<T>;
}
/**
* A partial interface of `Uint32Array` where methods that return the array type have been modified to return a more specific type.
*
* @privateRemarks
* Excludes methods that return a new array (e.g. `subarray`, `slice`, `toReversed`, `toSorted`, `with`) as these cannot be resolved by typescript
* @internal
*/
interface PartialUint32Array<T extends number> extends Uint32Array {
at(index: number): T | undefined; // ES2022
entries(): ArrayIterator<[number, T]>;
fill(value: T, start?: number, end?: number): this;
find(predicate: (value: T, index: number, array: this) => boolean, thisArg?: any): T | undefined;
findLast(predicate: (value: number, index: number, array: this) => boolean, thisArg?: any): T | undefined;
forEach(callbackfn: (value: T, index: number, array: this) => void, thisArg?: any): void;
includes(searchElement: T, fromIndex?: number): boolean;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
set(array: ArrayLike<T>, offset?: number): void;
some(predicate: (value: T, index: number, obj: this) => boolean, thisArg?: any): boolean;
sort(compareFn?: (a: T, b: T) => number): this;
values(): ArrayIterator<T>;
[Symbol.iterator](): ArrayIterator<T>;
[index: number]: T;
}
/**
* A `Uint32Array` whose elements typescript considers to be of type `T`, allowing for type-safe iteration and access.
*
* @remarks
* Useful to leverage the benefits of `TypedArrays` without losing type information. Typescript will consider the elements to be of type `T` instead of just `number`.
* @typeParam T - The specific numeric type of the elements in the array.
*/
// @ts-expect-error - These methods _will_ error, as we are overriding the return type to be more specific in a way that makes typescript unhappy
export interface GenericUint32Array<T extends number> extends PartialUint32Array<T> {
// map(callbackfn: (value: T, index: number, array: this) => T, thisArg?: any): GenericUint32Array<T>;
// this method does not trigger a typescript error on its own, but if we add in `toReversed` it causes issues....
subarray(begin?: number, end?: number): GenericUint32Array<T>;
toReversed(): GenericUint32Array<T>;
toSorted(compareFn?: (a: T, b: T) => number): GenericUint32Array<T>;
filter(predicate: (value: T, index: number, array: this) => any, thisArg?: any): GenericUint32Array<T>;
}
/**
* A partial interface of `Int8Array` where methods that return the array type have been modified to return a more specific type.
*
* @privateRemarks
* Excludes methods that return a new array (e.g. `subarray`, `slice`, `toReversed`, `toSorted`, `with`) as these cannot be resolved by typescript
* @internal
*/
interface PartialInt8Array<T extends number> extends Int8Array {
at(index: number): T | undefined; // ES2022
entries(): ArrayIterator<[number, T]>;
fill(value: T, start?: number, end?: number): this;
find(predicate: (value: T, index: number, array: this) => boolean, thisArg?: any): T | undefined;
findLast(predicate: (value: number, index: number, array: this) => boolean, thisArg?: any): T | undefined;
forEach(callbackfn: (value: T, index: number, array: this) => void, thisArg?: any): void;
includes(searchElement: T, fromIndex?: number): boolean;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
set(array: ArrayLike<T>, offset?: number): void;
some(predicate: (value: T, index: number, obj: this) => boolean, thisArg?: any): boolean;
sort(compareFn?: (a: T, b: T) => number): this;
values(): ArrayIterator<T>;
[Symbol.iterator](): ArrayIterator<T>;
[index: number]: T;
}
/**
* A `Int8Array` whose elements typescript considers to be of type `T`, allowing for type-safe iteration and access.
*
* @remarks
* Useful to leverage the benefits of `TypedArrays` without losing type information. Typescript will consider the elements to be of type `T` instead of just `number`.
* @typeParam T - The specific numeric type of the elements in the array.
*/
// @ts-expect-error - These methods _will_ error, as we are overriding the return type to be more specific in a way that makes typescript unhappy
export interface GenericInt8Array<T extends number> extends PartialInt8Array<T> {
// map(callbackfn: (value: T, index: number, array: this) => T, thisArg?: any): GenericInt8Array<T>;
// this method does not trigger a typescript error on its own, but if we add in `toReversed` it causes issues....
subarray(begin?: number, end?: number): GenericInt8Array<T>;
toReversed(): GenericInt8Array<T>;
toSorted(compareFn?: (a: T, b: T) => number): GenericInt8Array<T>;
filter(predicate: (value: T, index: number, array: this) => any, thisArg?: any): GenericInt8Array<T>;
}
/**
* A partial interface of `Int16Array` where methods that return the array type have been modified to return a more specific type.
*
* @privateRemarks
* Excludes methods that return a new array (e.g. `subarray`, `slice`, `toReversed`, `toSorted`, `with`) as these cannot be resolved by typescript
* @internal
*/
interface PartialInt16Array<T extends number> extends Int16Array {
at(index: number): T | undefined; // ES2022
entries(): ArrayIterator<[number, T]>;
fill(value: T, start?: number, end?: number): this;
find(predicate: (value: T, index: number, array: this) => boolean, thisArg?: any): T | undefined;
findLast(predicate: (value: number, index: number, array: this) => boolean, thisArg?: any): T | undefined;
forEach(callbackfn: (value: T, index: number, array: this) => void, thisArg?: any): void;
includes(searchElement: T, fromIndex?: number): boolean;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
set(array: ArrayLike<T>, offset?: number): void;
some(predicate: (value: T, index: number, obj: this) => boolean, thisArg?: any): boolean;
sort(compareFn?: (a: T, b: T) => number): this;
values(): ArrayIterator<T>;
[Symbol.iterator](): ArrayIterator<T>;
[index: number]: T;
}
/**
* A `Int16Array` whose elements typescript considers to be of type `T`, allowing for type-safe iteration and access.
*
* @remarks
* Useful to leverage the benefits of `TypedArrays` without losing type information. Typescript will consider the elements to be of type `T` instead of just `number`.
* @typeParam T - The specific numeric type of the elements in the array.
*/
// @ts-expect-error - These methods _will_ error, as we are overriding the return type to be more specific in a way that makes typescript unhappy
export interface GenericInt16Array<T extends number> extends PartialInt16Array<T> {
// map(callbackfn: (value: T, index: number, array: this) => T, thisArg?: any): GenericInt16Array<T>;
// this method does not trigger a typescript error on its own, but if we add in `toReversed` it causes issues....
subarray(begin?: number, end?: number): GenericInt16Array<T>;
toReversed(): GenericInt16Array<T>;
toSorted(compareFn?: (a: T, b: T) => number): GenericInt16Array<T>;
filter(predicate: (value: T, index: number, array: this) => any, thisArg?: any): GenericInt16Array<T>;
}
/**
* A partial interface of `Int32Array` where methods that return the array type have been modified to return a more specific type.
*
* @privateRemarks
* Excludes methods that return a new array (e.g. `subarray`, `slice`, `toReversed`, `toSorted`, `with`) as these cannot be resolved by typescript
* @internal
*/
interface PartialInt32Array<T extends number> extends Int32Array {
at(index: number): T | undefined; // ES2022
entries(): ArrayIterator<[number, T]>;
fill(value: T, start?: number, end?: number): this;
find(predicate: (value: T, index: number, array: this) => boolean, thisArg?: any): T | undefined;
findLast(predicate: (value: number, index: number, array: this) => boolean, thisArg?: any): T | undefined;
forEach(callbackfn: (value: T, index: number, array: this) => void, thisArg?: any): void;
includes(searchElement: T, fromIndex?: number): boolean;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
set(array: ArrayLike<T>, offset?: number): void;
some(predicate: (value: T, index: number, obj: this) => boolean, thisArg?: any): boolean;
sort(compareFn?: (a: T, b: T) => number): this;
values(): ArrayIterator<T>;
[Symbol.iterator](): ArrayIterator<T>;
[index: number]: T;
}
/**
* A `Int32Array` whose elements typescript considers to be of type `T`, allowing for type-safe iteration and access.
*
* @remarks
* Useful to leverage the benefits of `TypedArrays` without losing type information. Typescript will consider the elements to be of type `T` instead of just `number`.
* @typeParam T - The specific numeric type of the elements in the array.
*/
// @ts-expect-error - These methods _will_ error, as we are overriding the return type to be more specific in a way that makes typescript unhappy
export interface GenericInt32Array<T extends number> extends PartialInt32Array<T> {
// map(callbackfn: (value: T, index: number, array: this) => T, thisArg?: any): GenericInt32Array<T>;
// this method does not trigger a typescript error on its own, but if we add in `toReversed` it causes issues....
subarray(begin?: number, end?: number): GenericInt32Array<T>;
toReversed(): GenericInt32Array<T>;
toSorted(compareFn?: (a: T, b: T) => number): GenericInt32Array<T>;
filter(predicate: (value: T, index: number, array: this) => any, thisArg?: any): GenericInt32Array<T>;
}
/**
* A readonly version of {@link GenericUint8Array} where elements are of type `T`.
* @typeParam T - The specific numeric type of the elements in the array.
* @remarks
* Is to `GenericUint8Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyGenericUint8Array<T extends number>
extends Omit<GenericUint8Array<T>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyGenericUint8Array<T>;
readonly [index: number]: T;
}
/**
* A readonly version of {@link GenericUint16Array} where elements are of type `T`.
* @typeParam T - The specific numeric type of the elements in the array.
* @remarks
* Is to `GenericUint16Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyGenericUint16Array<T extends number>
extends Omit<GenericUint16Array<T>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyGenericUint16Array<T>;
readonly [index: number]: T;
}
/**
* A readonly version of {@link GenericUint32Array} where elements are of type `T`.
* @typeParam T - The specific numeric type of the elements in the array.
* @remarks
* Is to `GenericUint32Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyGenericUint32Array<T extends number>
extends Omit<GenericUint32Array<T>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyGenericUint32Array<T>;
readonly [index: number]: T;
}
/**
* A readonly version of {@link GenericInt8Array} where elements are of type `T`.
* @typeParam T - The specific numeric type of the elements in the array.
* @remarks
* Is to `GenericInt8Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyGenericInt8Array<T extends number>
extends Omit<GenericInt8Array<T>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyGenericInt8Array<T>;
readonly [index: number]: T;
}
/**
* A readonly version of {@link GenericInt16Array} where elements are of type `T`.
* @typeParam T - The specific numeric type of the elements in the array.
* @remarks
* Is to `GenericInt16Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyGenericInt16Array<T extends number>
extends Omit<GenericInt16Array<T>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyGenericInt16Array<T>;
readonly [index: number]: T;
}
/**
* A readonly version of {@link GenericInt32Array} where elements are of type `T`.
* @typeParam T - The specific numeric type of the elements in the array.
* @remarks
* Is to `GenericInt32Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyGenericInt32Array<T extends number>
extends Omit<GenericInt32Array<T>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyGenericInt32Array<T>;
readonly [index: number]: T;
}
/**
* A union type of all `GenericTypedArray`s
*/
export type ReadonlyGenericArray<T extends number = number> =
| ReadonlyGenericUint8Array<T>
| ReadonlyGenericUint16Array<T>
| ReadonlyGenericUint32Array<T>
| ReadonlyGenericInt8Array<T>
| ReadonlyGenericInt16Array<T>
| ReadonlyGenericInt32Array<T>;

View File

@ -118,6 +118,7 @@ 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 { AbilityBar } from "#ui/ability-bar";
@ -132,7 +133,6 @@ import { UI } from "#ui/ui";
import { addUiThemeOverrides } from "#ui/ui-theme";
import {
BooleanHolder,
type Constructor,
fixedInt,
formatMoney,
getIvsFromId,

View File

@ -526,24 +526,25 @@ export class FixedBattleConfig {
/**
* Helper function to generate a random trainer for evil team trainers and the elite 4/champion
* @param trainerPool The TrainerType or list of TrainerTypes that can possibly be generated
* @param randomGender whether or not to randomly (50%) generate a female trainer (for use with evil team grunts)
* @param seedOffset the seed offset to use for the random generation of the trainer
* @returns the generated trainer
* @param trainerPool - An array of trainer types to choose from. If an entry is an array, a random trainer type will be chosen from that array
* @param randomGender - Whether or not to randomly (50%) generate a female trainer (for use with evil team grunts)
* @param seedOffset - The seed offset to use for the random generation of the trainer
* @returns A function to get a random trainer
*/
export function getRandomTrainerFunc(
trainerPool: (TrainerType | TrainerType[])[],
trainerPool: readonly (TrainerType | readonly TrainerType[])[],
randomGender = false,
seedOffset = 0,
): GetTrainerFunc {
return () => {
const rand = randSeedInt(trainerPool.length);
const trainerTypes: TrainerType[] = [];
const trainerTypes: TrainerType[] = new Array(trainerPool.length);
globalScene.executeWithSeedOffset(() => {
for (const trainerPoolEntry of trainerPool) {
for (let i = 0; i < trainerPool.length; i++) {
const trainerPoolEntry = trainerPool[i];
const trainerType = Array.isArray(trainerPoolEntry) ? randSeedItem(trainerPoolEntry) : trainerPoolEntry;
trainerTypes.push(trainerType);
trainerTypes[i] = trainerType;
}
}, seedOffset);

View File

@ -23,7 +23,7 @@ export const TYPE_BOOST_ITEM_BOOST_PERCENT = 20;
/**
* The default species that a new player can choose from
*/
export const defaultStarterSpecies: SpeciesId[] = [
export const defaultStarterSpecies: readonly SpeciesId[] = [
SpeciesId.BULBASAUR,
SpeciesId.CHARMANDER,
SpeciesId.SQUIRTLE,

View File

@ -62,18 +62,11 @@ 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 { Constructor } from "#utils/common";
import {
BooleanHolder,
coerceArray,
NumberHolder,
randSeedFloat,
randSeedInt,
randSeedItem,
toDmgValue,
} from "#utils/common";
import { coerceArray } from "#utils/array";
import { BooleanHolder, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
import { toCamelCase } from "#utils/strings";
import i18next from "i18next";
@ -1217,7 +1210,7 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
private chance: number;
private effects: StatusEffect[];
private readonly effects: readonly StatusEffect[];
constructor(chance: number, ...effects: StatusEffect[]) {
super(true);
@ -2187,7 +2180,7 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
private contactRequired: boolean;
private chance: number;
private effects: StatusEffect[];
private readonly effects: readonly StatusEffect[];
constructor(contactRequired: boolean, chance: number, ...effects: StatusEffect[]) {
super();
@ -2947,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 immuneEffects: StatusEffect[];
private readonly immuneEffects: readonly StatusEffect[];
private statusHealed: StatusEffect;
/**
@ -2960,7 +2953,8 @@ export class PostSummonHealStatusAbAttr extends PostSummonRemoveEffectAbAttr {
public override canApply({ pokemon }: AbAttrBaseParams): boolean {
const status = pokemon.status?.effect;
return status != null && (this.immuneEffects.length === 0 || this.immuneEffects.includes(status));
const immuneEffects = this.immuneEffects;
return status != null && (immuneEffects.length === 0 || immuneEffects.includes(status));
}
public override apply({ pokemon }: AbAttrBaseParams): void {
@ -3056,7 +3050,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr {
* Removes supplied status effects from the user's field.
*/
export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAttr {
private statusEffect: StatusEffect[];
private readonly statusEffect: readonly StatusEffect[];
/**
* @param statusEffect - The status effects to be removed from the user's field.
@ -3644,7 +3638,7 @@ export class PreSetStatusAbAttr extends AbAttr {
* Provides immunity to status effects to specified targets.
*/
export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr {
protected immuneEffects: StatusEffect[];
protected readonly immuneEffects: readonly StatusEffect[];
/**
* @param immuneEffects - An array of {@linkcode StatusEffect}s to prevent application.
@ -3712,7 +3706,7 @@ export interface UserFieldStatusEffectImmunityAbAttrParams extends AbAttrBasePar
*/
export class UserFieldStatusEffectImmunityAbAttr extends CancelInteractionAbAttr {
private declare readonly _: never;
protected immuneEffects: StatusEffect[];
protected readonly immuneEffects: readonly StatusEffect[];
/**
* @param immuneEffects - An array of {@linkcode StatusEffect}s to prevent application.
@ -4005,7 +3999,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 effects: StatusEffect[];
private readonly effects: readonly StatusEffect[];
/**
* @param effects - The status effect(s) that will be blocked from damaging the ability pokemon
@ -4546,7 +4540,7 @@ export class PostTurnAbAttr extends AbAttr {
* @sealed
*/
export class PostTurnStatusHealAbAttr extends PostTurnAbAttr {
private effects: StatusEffect[];
private readonly effects: readonly StatusEffect[];
/**
* @param effects - The status effect(s) that will qualify healing the ability pokemon
@ -5815,10 +5809,10 @@ export interface IgnoreTypeStatusEffectImmunityAbAttrParams extends AbAttrParams
* @sealed
*/
export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr {
private statusEffect: StatusEffect[];
private defenderType: 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 = statusEffect;
@ -6706,7 +6700,7 @@ function getPokemonWithWeatherBasedForms() {
// biome-ignore format: prevent biome from removing the newlines (e.g. prevent `new Ability(...).attr(...)`)
export function initAbilities() {
allAbilities.push(
(allAbilities as Ability[]).push(
new Ability(AbilityId.NONE, 3),
new Ability(AbilityId.STENCH, 3)
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => !move.hasAttr("FlinchAttr") && !move.hitsSubstitute(user, target) ? 10 : 0, BattlerTagType.FLINCHED),

View File

@ -5,6 +5,7 @@ import { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id";
import { TimeOfDay } from "#enums/time-of-day";
import { TrainerType } from "#enums/trainer-type";
import type { Mutable } from "#types/type-helpers";
import { randSeedInt } from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { toCamelCase } from "#utils/strings";
@ -88,19 +89,19 @@ export enum BiomePoolTier {
export const uncatchableSpecies: SpeciesId[] = [];
interface SpeciesTree {
[key: number]: SpeciesId[]
readonly [key: number]: SpeciesId[]
}
export interface PokemonPools {
[key: number]: (SpeciesId | SpeciesTree)[]
readonly [key: number]: (SpeciesId | SpeciesTree)[]
}
interface BiomeTierPokemonPools {
[key: number]: PokemonPools
readonly [key: number]: PokemonPools
}
interface BiomePokemonPools {
[key: number]: BiomeTierPokemonPools
readonly [key: number]: BiomeTierPokemonPools
}
export interface BiomeTierTod {
@ -110,17 +111,17 @@ export interface BiomeTierTod {
}
export interface CatchableSpecies{
[key: number]: BiomeTierTod[]
readonly [key: number]: readonly BiomeTierTod[]
}
export const catchableSpecies: CatchableSpecies = {};
export interface BiomeTierTrainerPools {
[key: number]: TrainerType[]
readonly [key: number]: readonly TrainerType[]
}
export interface BiomeTrainerPools {
[key: number]: BiomeTierTrainerPools
readonly [key: number]: BiomeTierTrainerPools
}
export const biomePokemonPools: BiomePokemonPools = {
@ -7621,12 +7622,10 @@ export function initBiomes() {
? biomeLinks[biome] as (BiomeId | [ BiomeId, number ])[]
: [ biomeLinks[biome] as BiomeId ];
for (const linkedBiomeEntry of linkedBiomes) {
const linkedBiome = !Array.isArray(linkedBiomeEntry)
? linkedBiomeEntry as BiomeId
: linkedBiomeEntry[0];
const biomeChance = !Array.isArray(linkedBiomeEntry)
? 1
: linkedBiomeEntry[1];
const linkedBiome = Array.isArray(linkedBiomeEntry)
? linkedBiomeEntry[0] : linkedBiomeEntry as BiomeId;
const biomeChance = Array.isArray(linkedBiomeEntry)
? linkedBiomeEntry[1] : 1;
if (!biomeDepths.hasOwnProperty(linkedBiome) || biomeChance < biomeDepths[linkedBiome][1] || (depth < biomeDepths[linkedBiome][0] && biomeChance === biomeDepths[linkedBiome][1])) {
biomeDepths[linkedBiome] = [ depth + 1, biomeChance ];
traverseBiome(linkedBiome, depth + 1);
@ -7638,15 +7637,15 @@ export function initBiomes() {
biomeDepths[BiomeId.END] = [ Object.values(biomeDepths).map(d => d[0]).reduce((max: number, value: number) => Math.max(max, value), 0) + 1, 1 ];
for (const biome of getEnumValues(BiomeId)) {
biomePokemonPools[biome] = {};
biomeTrainerPools[biome] = {};
(biomePokemonPools[biome] as Mutable<typeof biomePokemonPools[number]>) = {};
(biomeTrainerPools[biome] as Mutable<typeof biomeTrainerPools[number]>) = {};
for (const tier of getEnumValues(BiomePoolTier)) {
biomePokemonPools[biome][tier] = {};
biomeTrainerPools[biome][tier] = [];
(biomePokemonPools[biome][tier] as Mutable<typeof biomePokemonPools[number][number]>) = {};
(biomeTrainerPools[biome][tier] as Mutable<typeof biomeTrainerPools[number][number]>) = [];
for (const tod of getEnumValues(TimeOfDay)) {
biomePokemonPools[biome][tier][tod] = [];
(biomePokemonPools[biome][tier][tod] as Mutable<typeof biomePokemonPools[number][number][number]>) = [];
}
}
}
@ -7663,8 +7662,9 @@ export function initBiomes() {
uncatchableSpecies.push(speciesId);
}
type mutableSpecies = Mutable<typeof catchableSpecies[SpeciesId]>;
// array of biome options for the current species
catchableSpecies[speciesId] = [];
(catchableSpecies[speciesId] as mutableSpecies) = [];
for (const b of biomeEntries) {
const biome = b[0];
@ -7675,7 +7675,7 @@ export function initBiomes() {
: [ b[2] ]
: [ TimeOfDay.ALL ];
catchableSpecies[speciesId].push({
(catchableSpecies[speciesId] as mutableSpecies).push({
biome: biome as BiomeId,
tier: tier as BiomePoolTier,
tod: timesOfDay as TimeOfDay[]
@ -7735,12 +7735,13 @@ export function initBiomes() {
};
for (let s = 1; s < entry.length; s++) {
const speciesId = entry[s];
// biome-ignore lint/nursery/noShadow: one-off
const prevolution = entry.flatMap((s: string | number) => pokemonEvolutions[s]).find(e => e && e.speciesId === speciesId);
const level = prevolution.level - (prevolution.level === 1 ? 1 : 0) + (prevolution.wildDelay * 10) - (tier >= BiomePoolTier.BOSS ? 10 : 0);
if (!newEntry.hasOwnProperty(level)) {
newEntry[level] = [ speciesId ];
} else {
if (newEntry.hasOwnProperty(level)) {
newEntry[level].push(speciesId);
} else {
newEntry[level] = [ speciesId ];
}
}
biomeTierTimePool[e] = newEntry;
@ -7763,7 +7764,7 @@ export function initBiomes() {
}
const biomeTierPool = biomeTrainerPools[biome][tier];
biomeTierPool.push(trainerType);
(biomeTierPool as Mutable<typeof biomeTierPool>).push(trainerType);
}
//outputPools();
}

View File

@ -14,7 +14,8 @@ import { TimeOfDay } from "#enums/time-of-day";
import { WeatherType } from "#enums/weather-type";
import type { Pokemon } from "#field/pokemon";
import type { SpeciesStatBoosterItem, SpeciesStatBoosterModifierType } from "#modifiers/modifier-type";
import { coerceArray, randSeedInt } from "#utils/common";
import { coerceArray } from "#utils/array";
import { randSeedInt } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import { toCamelCase } from "#utils/strings";
import i18next from "i18next";

View File

@ -8,7 +8,7 @@ import { SpeciesId } from "#enums/species-id";
// biome-ignore format: manually formatted
export function initSpecies() {
allSpecies.push(
(allSpecies as PokemonSpecies[]).push(
new PokemonSpecies(SpeciesId.BULBASAUR, 1, false, false, false, "Seed Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.7, 6.9, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CHLOROPHYLL, 318, 45, 49, 49, 65, 65, 45, 45, 50, 64, GrowthRate.MEDIUM_SLOW, 87.5, false),
new PokemonSpecies(SpeciesId.IVYSAUR, 1, false, false, false, "Seed Pokémon", PokemonType.GRASS, PokemonType.POISON, 1, 13, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CHLOROPHYLL, 405, 60, 62, 63, 80, 80, 60, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false),
new PokemonSpecies(SpeciesId.VENUSAUR, 1, false, false, false, "Seed Pokémon", PokemonType.GRASS, PokemonType.POISON, 2, 100, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CHLOROPHYLL, 525, 80, 82, 83, 100, 100, 80, 45, 50, 263, GrowthRate.MEDIUM_SLOW, 87.5, true, true,

View File

@ -7,7 +7,9 @@ import { AnimBlendType, AnimFocus, AnimFrameTarget, ChargeAnim, CommonAnim } fro
import { MoveFlags } from "#enums/move-flags";
import { MoveId } from "#enums/move-id";
import type { Pokemon } from "#field/pokemon";
import { coerceArray, getFrameMs, type nil } from "#utils/common";
import type { nil } from "#types/common";
import { coerceArray } from "#utils/array";
import { getFrameMs } from "#utils/common";
import { getEnumKeys, getEnumValues } from "#utils/enums";
import { toKebabCase } from "#utils/strings";
import Phaser from "phaser";

View File

@ -88,7 +88,8 @@ import type {
TypeBoostTagType,
} from "#types/battler-tags";
import type { Mutable } from "#types/type-helpers";
import { BooleanHolder, coerceArray, getFrameMs, NumberHolder, toDmgValue } from "#utils/common";
import { coerceArray } from "#utils/array";
import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#utils/common";
import { toCamelCase } from "#utils/strings";
/** Interface containing the serializable fields of BattlerTag */
@ -2153,8 +2154,8 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
}
export class WeatherHighestStatBoostTag extends HighestStatBoostTag {
#weatherTypes: WeatherType[];
public get weatherTypes(): WeatherType[] {
readonly #weatherTypes: readonly WeatherType[];
public get weatherTypes(): readonly WeatherType[] {
return this.#weatherTypes;
}
@ -2165,8 +2166,8 @@ export class WeatherHighestStatBoostTag extends HighestStatBoostTag {
}
export class TerrainHighestStatBoostTag extends HighestStatBoostTag {
#terrainTypes: TerrainType[];
public get terrainTypes(): TerrainType[] {
readonly #terrainTypes: readonly TerrainType[];
public get terrainTypes(): readonly TerrainType[] {
return this.#terrainTypes;
}

View File

@ -3,9 +3,9 @@ import type { PokemonSpecies } from "#data/pokemon-species";
import type { ModifierTypes } from "#modifiers/modifier-type";
import type { Move } from "#moves/move";
export const allAbilities: Ability[] = [];
export const allMoves: Move[] = [];
export const allSpecies: PokemonSpecies[] = [];
export const allAbilities: readonly Ability[] = [];
export const allMoves: readonly Move[] = [];
export const allSpecies: readonly PokemonSpecies[] = [];
// TODO: Figure out what this is used for and provide an appropriate tsdoc comment
export const modifierTypes = {} as ModifierTypes;

View File

@ -485,14 +485,14 @@ export class Egg {
* and being the same each time
*/
let totalWeight = 0;
const speciesWeights: number[] = [];
for (const speciesId of speciesPool) {
const speciesWeights = new Array<number>(speciesPool.length);
for (const [idx, speciesId] of speciesPool.entries()) {
// Accounts for species that have starter costs outside of the normal range for their EggTier
const speciesCostClamped = Phaser.Math.Clamp(speciesStarterCosts[speciesId], minStarterValue, maxStarterValue);
const weight = Math.floor(
(((maxStarterValue - speciesCostClamped) / (maxStarterValue - minStarterValue + 1)) * 1.5 + 1) * 100,
);
speciesWeights.push(totalWeight + weight);
speciesWeights[idx] = totalWeight + weight;
totalWeight += weight;
}

View File

@ -87,13 +87,16 @@ 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, coerceArray, 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";
import i18next from "i18next";
import { applyChallenges } from "#utils/challenge-utils";
import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
import type { AbstractConstructor } from "#types/type-helpers";
import type { ReadonlyGenericUint8Array } from "#types/typed-arrays";
/**
* A function used to conditionally determine execution of a given {@linkcode MoveAttr}.
@ -2594,7 +2597,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
* Used for {@linkcode Moves.TRI_ATTACK} and {@linkcode Moves.DIRE_CLAW}.
*/
export class MultiStatusEffectAttr extends StatusEffectAttr {
public effects: StatusEffect[];
public readonly effects: readonly StatusEffect[];
constructor(effects: StatusEffect[], selfTarget?: boolean) {
super(effects[0], selfTarget);
@ -2924,7 +2927,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
*/
export class HealStatusEffectAttr extends MoveEffectAttr {
/** List of Status Effects to cure */
private effects: StatusEffect[];
private readonly effects: readonly StatusEffect[];
/**
* @param selfTarget - Whether this move targets the user
@ -2932,7 +2935,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
*/
constructor(selfTarget: boolean, effects: StatusEffect | StatusEffect[]) {
super(selfTarget, { lastHitOnly: true });
this.effects = coerceArray(effects)
this.effects = coerceArray(effects);
}
/**
@ -8391,7 +8394,7 @@ const MoveAttrs = Object.freeze({
export type MoveAttrConstructorMap = typeof MoveAttrs;
export function initMoves() {
allMoves.push(
(allMoves as Move[]).push(
new SelfStatusMove(MoveId.NONE, PokemonType.NORMAL, MoveCategory.STATUS, -1, -1, 0, 1),
new AttackMove(MoveId.POUND, PokemonType.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1),
new AttackMove(MoveId.KARATE_CHOP, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 50, 100, 25, -1, 0, 1)

View File

@ -78,7 +78,7 @@ const POOL_1_POKEMON = [
SpeciesId.RIBOMBEE,
SpeciesId.SPIDOPS,
SpeciesId.LOKIX,
];
] as const;
const POOL_2_POKEMON = [
SpeciesId.SCYTHER,
@ -104,41 +104,20 @@ const POOL_2_POKEMON = [
SpeciesId.CENTISKORCH,
SpeciesId.FROSMOTH,
SpeciesId.KLEAVOR,
];
] as const;
const POOL_3_POKEMON: { species: SpeciesId; formIndex?: number }[] = [
{
species: SpeciesId.PINSIR,
formIndex: 1,
},
{
species: SpeciesId.SCIZOR,
formIndex: 1,
},
{
species: SpeciesId.HERACROSS,
formIndex: 1,
},
{
species: SpeciesId.ORBEETLE,
formIndex: 1,
},
{
species: SpeciesId.CENTISKORCH,
formIndex: 1,
},
{
species: SpeciesId.DURANT,
},
{
species: SpeciesId.VOLCARONA,
},
{
species: SpeciesId.GOLISOPOD,
},
];
const POOL_3_POKEMON = [
{ species: SpeciesId.PINSIR, formIndex: 1 },
{ species: SpeciesId.SCIZOR, formIndex: 1 },
{ species: SpeciesId.HERACROSS, formIndex: 1 },
{ species: SpeciesId.ORBEETLE, formIndex: 1 },
{ species: SpeciesId.CENTISKORCH, formIndex: 1 },
{ species: SpeciesId.DURANT } as { species: SpeciesId.DURANT; formIndex: undefined },
{ species: SpeciesId.VOLCARONA } as { species: SpeciesId.VOLCARONA; formIndex: undefined },
{ species: SpeciesId.GOLISOPOD } as { species: SpeciesId.GOLISOPOD; formIndex: undefined },
] as const;
const POOL_4_POKEMON = [SpeciesId.GENESECT, SpeciesId.SLITHER_WING, SpeciesId.BUZZWOLE, SpeciesId.PHEROMOSA];
const POOL_4_POKEMON = [SpeciesId.GENESECT, SpeciesId.SLITHER_WING, SpeciesId.BUZZWOLE, SpeciesId.PHEROMOSA] as const;
const PHYSICAL_TUTOR_MOVES = [
MoveId.MEGAHORN,
@ -146,7 +125,7 @@ const PHYSICAL_TUTOR_MOVES = [
MoveId.BUG_BITE,
MoveId.FIRST_IMPRESSION,
MoveId.LUNGE,
];
] as const;
const SPECIAL_TUTOR_MOVES = [
MoveId.SILVER_WIND,
@ -154,7 +133,7 @@ const SPECIAL_TUTOR_MOVES = [
MoveId.BUG_BUZZ,
MoveId.POLLEN_PUFF,
MoveId.STRUGGLE_BUG,
];
] as const;
const STATUS_TUTOR_MOVES = [
MoveId.STRING_SHOT,
@ -162,14 +141,20 @@ const STATUS_TUTOR_MOVES = [
MoveId.RAGE_POWDER,
MoveId.STICKY_WEB,
MoveId.SILK_TRAP,
];
] as const;
const MISC_TUTOR_MOVES = [MoveId.LEECH_LIFE, MoveId.U_TURN, MoveId.HEAL_ORDER, MoveId.QUIVER_DANCE, MoveId.INFESTATION];
const MISC_TUTOR_MOVES = [
MoveId.LEECH_LIFE,
MoveId.U_TURN,
MoveId.HEAL_ORDER,
MoveId.QUIVER_DANCE,
MoveId.INFESTATION,
] as const;
/**
* Wave breakpoints that determine how strong to make the Bug-Type Superfan's team
*/
const WAVE_LEVEL_BREAKPOINTS = [30, 50, 70, 100, 120, 140, 160];
const WAVE_LEVEL_BREAKPOINTS = [30, 50, 70, 100, 120, 140, 160] as const;
/**
* Bug Type Superfan encounter.
@ -517,8 +502,8 @@ function getTrainerConfigForWave(waveIndex: number) {
const config = trainerConfigs[TrainerType.BUG_TYPE_SUPERFAN].clone();
config.name = i18next.t("trainerNames:bugTypeSuperfan");
let pool3Copy = POOL_3_POKEMON.slice(0);
pool3Copy = randSeedShuffle(pool3Copy);
const pool3Copy = randSeedShuffle(POOL_3_POKEMON.slice());
// Bang is fine here, as we know pool3Copy has at least 1 entry
const pool3Mon = pool3Copy.pop()!;
if (waveIndex < WAVE_LEVEL_BREAKPOINTS[0]) {
@ -579,7 +564,6 @@ function getTrainerConfigForWave(waveIndex: number) {
}),
);
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[5]) {
pool3Copy = randSeedShuffle(pool3Copy);
const pool3Mon2 = pool3Copy.pop()!;
config
.setPartyTemplates(new TrainerPartyTemplate(5, PartyMemberStrength.AVERAGE))
@ -657,7 +641,6 @@ function getTrainerConfigForWave(waveIndex: number) {
)
.setPartyMemberFunc(4, getRandomPartyMemberFunc(POOL_4_POKEMON, TrainerSlot.TRAINER, true));
} else {
pool3Copy = randSeedShuffle(pool3Copy);
const pool3Mon2 = pool3Copy.pop()!;
config
.setPartyTemplates(

View File

@ -220,7 +220,7 @@ async function summonPlayerPokemon() {
false,
true,
);
wobbuffet.ivs = [0, 0, 0, 0, 0, 0];
wobbuffet.ivs.fill(0);
wobbuffet.setNature(Nature.MILD);
wobbuffet.setAlpha(0);
wobbuffet.setVisible(false);

View File

@ -15,7 +15,7 @@ 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 { coerceArray } from "#utils/common";
import { coerceArray } from "#utils/array";
export interface EncounterRequirement {
meetsRequirement(): boolean; // Boolean to see if a requirement is met
@ -696,11 +696,11 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
}
export class StatusEffectRequirement extends EncounterPokemonRequirement {
requiredStatusEffect: 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;
@ -717,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 => {
@ -761,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;
@ -792,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 =>
@ -877,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

@ -25,7 +25,8 @@ import {
StatusEffectRequirement,
WaveRangeRequirement,
} from "#mystery-encounters/mystery-encounter-requirements";
import { coerceArray, randSeedInt } from "#utils/common";
import { coerceArray } from "#utils/array";
import { randSeedInt } from "#utils/common";
import { capitalizeFirstLetter } from "#utils/strings";
export interface EncounterStartOfBattleEffect {

View File

@ -3,7 +3,7 @@ import type { MoveId } from "#enums/move-id";
import type { PlayerPokemon } from "#field/pokemon";
import { PokemonMove } from "#moves/pokemon-move";
import { EncounterPokemonRequirement } from "#mystery-encounters/mystery-encounter-requirements";
import { coerceArray } from "#utils/common";
import { coerceArray } from "#utils/array";
/**
* {@linkcode CanLearnMoveRequirement} options

View File

@ -49,7 +49,8 @@ import type { HeldModifierConfig } from "#types/held-modifier-config";
import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstract-option-select-ui-handler";
import type { PartyOption, PokemonSelectFilter } from "#ui/party-ui-handler";
import { PartyUiMode } from "#ui/party-ui-handler";
import { coerceArray, randomString, randSeedInt, randSeedItem } from "#utils/common";
import { coerceArray } from "#utils/array";
import { randomString, randSeedInt, randSeedItem } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import i18next from "i18next";

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, nil } from "#types/common";
import type { Mutable } from "#types/type-helpers";
export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean;
export type SpeciesFormChangeConditionEnforceFunc = (p: Pokemon) => void;
@ -35,7 +36,7 @@ export class SpeciesFormChange {
public formKey: string;
public trigger: SpeciesFormChangeTrigger;
public quiet: boolean;
public readonly conditions: SpeciesFormChangeCondition[];
public readonly conditions: readonly SpeciesFormChangeCondition[];
constructor(
speciesId: SpeciesId,
@ -116,7 +117,7 @@ function getSpeciesDependentFormChangeCondition(species: SpeciesId): SpeciesForm
}
interface PokemonFormChanges {
[key: string]: SpeciesFormChange[];
[key: string]: readonly SpeciesFormChange[];
}
// biome-ignore format: manually formatted
@ -608,6 +609,6 @@ export function initPokemonForms() {
);
}
}
formChanges.push(...newFormChanges);
(formChanges as Mutable<typeof formChanges>).push(...newFormChanges);
}
}

View File

@ -11,7 +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 Constructor, coerceArray } from "#utils/common";
import type { Constructor } from "#types/common";
import { coerceArray } from "#utils/array";
import { toCamelCase } from "#utils/strings";
import i18next from "i18next";
@ -121,7 +122,7 @@ export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger {
}
export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigger {
public statusEffects: StatusEffect[];
public readonly statusEffects: readonly StatusEffect[];
public invert: boolean;
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
@ -239,9 +240,9 @@ export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
/** The ability that triggers the form change */
public ability: AbilityId;
/** The list of weathers that trigger the form change */
public weathers: WeatherType[];
public readonly weathers: readonly WeatherType[];
constructor(ability: AbilityId, weathers: WeatherType[]) {
constructor(ability: AbilityId, weathers: readonly WeatherType[]) {
super();
this.ability = ability;
this.weathers = weathers;
@ -277,9 +278,9 @@ export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChange
/** The ability that triggers the form change*/
public ability: AbilityId;
/** The list of weathers that will also trigger a form change to original form */
public weathers: WeatherType[];
public readonly weathers: readonly WeatherType[];
constructor(ability: AbilityId, weathers: WeatherType[]) {
constructor(ability: AbilityId, weathers: readonly WeatherType[]) {
super();
this.ability = ability;
this.weathers = weathers;
@ -309,9 +310,10 @@ export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChange
}
export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string {
const isMega = formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1;
const isGmax = formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1;
const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1;
const formKey = formChange.formKey;
const isMega = formKey.indexOf(SpeciesFormKey.MEGA) > -1;
const isGmax = formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1;
const isEmax = formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1;
const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey;
if (isMega) {
return i18next.t("battlePokemonForm:megaChange", {

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

@ -44,7 +44,8 @@ import type {
TrainerTierPools,
} from "#types/trainer-funcs";
import type { Mutable } from "#types/type-helpers";
import { coerceArray, randSeedInt, randSeedIntRange, randSeedItem } from "#utils/common";
import { coerceArray } from "#utils/array";
import { randSeedInt, randSeedIntRange, randSeedItem } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import { toCamelCase, toTitleCase } from "#utils/strings";
import i18next from "i18next";
@ -999,7 +1000,7 @@ let t = 0;
* @param postProcess
*/
export function getRandomPartyMemberFunc(
speciesPool: SpeciesId[],
speciesPool: readonly SpeciesId[],
trainerSlot: TrainerSlot = TrainerSlot.TRAINER,
ignoreEvolution = false,
postProcess?: (enemyPokemon: EnemyPokemon) => void,

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 {
@ -587,7 +588,7 @@ export class Arena {
overrideTint(): [number, number, number] {
switch (Overrides.ARENA_TINT_OVERRIDE) {
case TimeOfDay.DUSK:
return [98, 48, 73].map(c => Math.round((c + 128) / 2)) as [number, number, number];
return [113, 88, 101];
case TimeOfDay.NIGHT:
return [64, 64, 64];
case TimeOfDay.DAWN:

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene";
import { Pokemon } from "#field/pokemon";
import { coerceArray, fixedInt, randInt } from "#utils/common";
import { coerceArray } from "#utils/array";
import { fixedInt, randInt } from "#utils/common";
export class PokemonSpriteSparkleHandler {
private sprites: Set<Phaser.GameObjects.Sprite>;

View File

@ -141,6 +141,7 @@ 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";
@ -151,11 +152,10 @@ 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 } from "#utils/array";
import { applyChallenges } from "#utils/challenge-utils";
import {
BooleanHolder,
type Constructor,
coerceArray,
deltaRgb,
fixedInt,
getIvsFromId,
@ -2141,7 +2141,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
* Suppresses an ability and calls its onlose attributes
*/
public suppressAbility() {
[true, false].forEach(passive => applyOnLoseAbAttrs({ pokemon: this, passive }));
applyOnLoseAbAttrs({ pokemon: this, passive: true });
applyOnLoseAbAttrs({ pokemon: this, passive: false });
this.summonData.abilitySuppressed = true;
}

View File

@ -642,14 +642,14 @@ export class Trainer extends Phaser.GameObjects.Container {
}
}
genModifiers(party: EnemyPokemon[]): PersistentModifier[] {
genModifiers(party: readonly EnemyPokemon[]): PersistentModifier[] {
if (this.config.genModifiersFunc) {
return this.config.genModifiersFunc(party);
}
return [];
}
genAI(party: EnemyPokemon[]) {
genAI(party: readonly EnemyPokemon[]) {
if (this.config.genAIFuncs) {
this.config.genAIFuncs.forEach(f => f(party));
}

View File

@ -277,7 +277,7 @@ export class ModifierType {
}
}
type ModifierTypeGeneratorFunc = (party: Pokemon[], pregenArgs?: any[]) => ModifierType | null;
type ModifierTypeGeneratorFunc = (party: readonly Pokemon[], pregenArgs?: any[]) => ModifierType | null;
export class ModifierTypeGenerator extends ModifierType {
private genTypeFunc: ModifierTypeGeneratorFunc;
@ -287,7 +287,7 @@ export class ModifierTypeGenerator extends ModifierType {
this.genTypeFunc = genTypeFunc;
}
generateType(party: Pokemon[], pregenArgs?: any[]) {
generateType(party: readonly Pokemon[], pregenArgs?: any[]) {
const ret = this.genTypeFunc(party, pregenArgs);
if (ret) {
ret.id = this.id;
@ -2360,7 +2360,11 @@ const tierWeights = [768 / 1024, 195 / 1024, 48 / 1024, 12 / 1024, 1 / 1024];
*/
export const itemPoolChecks: Map<ModifierTypeKeys, boolean | undefined> = new Map();
export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: ModifierPoolType, rerollCount = 0) {
export function regenerateModifierPoolThresholds(
party: readonly Pokemon[],
poolType: ModifierPoolType,
rerollCount = 0,
) {
const pool = getModifierPoolForType(poolType);
itemPoolChecks.forEach((_v, k) => {
itemPoolChecks.set(k, false);
@ -2906,7 +2910,7 @@ export class ModifierTypeOption {
* @param party The player's party.
* @returns A number between 0 and 14 based on the party's total luck value, or a random number between 0 and 14 if the player is in Daily Run mode.
*/
export function getPartyLuckValue(party: Pokemon[]): number {
export function getPartyLuckValue(party: readonly Pokemon[]): number {
if (globalScene.gameMode.isDaily) {
const DailyLuck = new NumberHolder(0);
globalScene.executeWithSeedOffset(

View File

@ -12,7 +12,7 @@ export abstract class BattlePhase extends Phase {
const tintSprites = globalScene.currentBattle.trainer.getTintSprites();
for (let i = 0; i < sprites.length; i++) {
const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2;
[sprites[i], tintSprites[i]].map(sprite => {
[sprites[i], tintSprites[i]].forEach(sprite => {
if (visible) {
sprite.x = trainerSlot || sprites.length < 2 ? 0 : i ? 16 : -16;
}

View File

@ -125,7 +125,7 @@ export class EncounterPhase extends BattlePhase {
!!globalScene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies),
);
if (globalScene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
battle.enemyParty[e].ivs = new Array(6).fill(31);
battle.enemyParty[e].ivs.fill(31);
}
globalScene
.getPlayerParty()

View File

@ -37,9 +37,9 @@ import { getMoveTargets, isFieldTargeted } from "#moves/move-utils";
import { PokemonMove } from "#moves/pokemon-move";
import { PokemonPhase } from "#phases/pokemon-phase";
import { DamageAchv } from "#system/achv";
import type { nil } from "#types/common";
import type { DamageResult } from "#types/damage-result";
import type { TurnMove } from "#types/turn-move";
import type { nil } from "#utils/common";
import { BooleanHolder, NumberHolder } from "#utils/common";
import i18next from "i18next";

View File

@ -1,4 +1,4 @@
import { coerceArray } from "#utils/common";
import { coerceArray } from "#utils/array";
let manifest: object;

View File

@ -1,4 +1,4 @@
import { coerceArray } from "#utils/common";
import { coerceArray } from "#utils/array";
export const legacyCompatibleImages: string[] = [];

View File

@ -7,8 +7,8 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id";
import { TextStyle } from "#enums/text-style";
import { WeatherType } from "#enums/weather-type";
import type { nil } from "#types/common";
import { addTextObject } from "#ui/text";
import type { nil } from "#utils/common";
import i18next from "i18next";
export enum EventType {
@ -18,59 +18,59 @@ export enum EventType {
}
interface EventBanner {
bannerKey?: string;
xOffset?: number;
yOffset?: number;
scale?: number;
availableLangs?: string[];
readonly bannerKey?: string;
readonly xOffset?: number;
readonly yOffset?: number;
readonly scale?: number;
readonly availableLangs?: readonly string[];
}
interface EventEncounter {
species: SpeciesId;
blockEvolution?: boolean;
formIndex?: number;
readonly species: SpeciesId;
readonly blockEvolution?: boolean;
readonly formIndex?: number;
}
interface EventMysteryEncounterTier {
mysteryEncounter: MysteryEncounterType;
tier?: MysteryEncounterTier;
disable?: boolean;
readonly mysteryEncounter: MysteryEncounterType;
readonly tier?: MysteryEncounterTier;
readonly disable?: boolean;
}
interface EventWaveReward {
wave: number;
type: string;
readonly wave: number;
readonly type: string;
}
type EventMusicReplacement = [string, string];
type EventMusicReplacement = readonly [string, string];
interface EventChallenge {
challenge: Challenges;
value: number;
readonly challenge: Challenges;
readonly value: number;
}
interface TimedEvent extends EventBanner {
name: string;
eventType: EventType;
shinyMultiplier?: number;
classicFriendshipMultiplier?: number;
luckBoost?: number;
upgradeUnlockedVouchers?: boolean;
startDate: Date;
endDate: Date;
eventEncounters?: EventEncounter[];
delibirdyBuff?: string[];
weather?: WeatherPoolEntry[];
mysteryEncounterTierChanges?: EventMysteryEncounterTier[];
luckBoostedSpecies?: SpeciesId[];
boostFusions?: boolean; //MODIFIER REWORK PLEASE
classicWaveRewards?: EventWaveReward[]; // Rival battle rewards
trainerShinyChance?: number; // Odds over 65536 of trainer mon generating as shiny
music?: EventMusicReplacement[];
dailyRunChallenges?: EventChallenge[];
readonly name: string;
readonly eventType: EventType;
readonly shinyMultiplier?: number;
readonly classicFriendshipMultiplier?: number;
readonly luckBoost?: number;
readonly upgradeUnlockedVouchers?: boolean;
readonly startDate: Date;
readonly endDate: Date;
readonly eventEncounters?: readonly EventEncounter[];
readonly delibirdyBuff?: readonly string[];
readonly weather?: readonly WeatherPoolEntry[];
readonly mysteryEncounterTierChanges?: readonly EventMysteryEncounterTier[];
readonly luckBoostedSpecies?: readonly SpeciesId[];
readonly boostFusions?: boolean; //MODIFIER REWORK PLEASE
readonly classicWaveRewards?: readonly EventWaveReward[]; // Rival battle rewards
readonly trainerShinyChance?: number; // Odds over 65536 of trainer mon generating as shiny
readonly music?: readonly EventMusicReplacement[];
readonly dailyRunChallenges?: readonly EventChallenge[];
}
const timedEvents: TimedEvent[] = [
const timedEvents: readonly TimedEvent[] = [
{
name: "Winter Holiday Update",
eventType: EventType.SHINY,
@ -385,7 +385,8 @@ const timedEvents: TimedEvent[] = [
export class TimedEventManager {
isActive(event: TimedEvent) {
return event.startDate < new Date() && new Date() < event.endDate;
const now = new Date();
return event.startDate < now && now < event.endDate;
}
activeEvent(): TimedEvent | undefined {
@ -427,19 +428,17 @@ export class TimedEventManager {
getEventBannerLangs(): string[] {
const ret: string[] = [];
ret.push(...timedEvents.find(te => this.isActive(te) && te.availableLangs != null)?.availableLangs!);
ret.push(...(timedEvents.find(te => this.isActive(te) && te.availableLangs != null)?.availableLangs ?? []));
return ret;
}
getEventEncounters(): EventEncounter[] {
const ret: EventEncounter[] = [];
timedEvents
.filter(te => this.isActive(te))
.map(te => {
if (te.eventEncounters != null) {
ret.push(...te.eventEncounters);
}
});
for (const te of timedEvents) {
if (this.isActive(te) && te.eventEncounters != null) {
ret.push(...te.eventEncounters);
}
}
return ret;
}
@ -472,13 +471,11 @@ export class TimedEventManager {
*/
getDelibirdyBuff(): string[] {
const ret: string[] = [];
timedEvents
.filter(te => this.isActive(te))
.map(te => {
if (te.delibirdyBuff != null) {
ret.push(...te.delibirdyBuff);
}
});
for (const te of timedEvents) {
if (this.isActive(te) && te.delibirdyBuff != null) {
ret.push(...te.delibirdyBuff);
}
}
return ret;
}
@ -488,39 +485,35 @@ export class TimedEventManager {
*/
getWeather(): WeatherPoolEntry[] {
const ret: WeatherPoolEntry[] = [];
timedEvents
.filter(te => this.isActive(te))
.map(te => {
if (te.weather != null) {
ret.push(...te.weather);
}
});
for (const te of timedEvents) {
if (this.isActive(te) && te.weather != null) {
ret.push(...te.weather);
}
}
return ret;
}
getAllMysteryEncounterChanges(): EventMysteryEncounterTier[] {
const ret: EventMysteryEncounterTier[] = [];
timedEvents
.filter(te => this.isActive(te))
.map(te => {
if (te.mysteryEncounterTierChanges != null) {
ret.push(...te.mysteryEncounterTierChanges);
}
});
for (const te of timedEvents) {
if (this.isActive(te) && te.mysteryEncounterTierChanges != null) {
ret.push(...te.mysteryEncounterTierChanges);
}
}
return ret;
}
getEventMysteryEncountersDisabled(): MysteryEncounterType[] {
const ret: MysteryEncounterType[] = [];
timedEvents
.filter(te => this.isActive(te) && te.mysteryEncounterTierChanges != null)
.map(te => {
te.mysteryEncounterTierChanges?.map(metc => {
for (const te of timedEvents) {
if (this.isActive(te) && te.mysteryEncounterTierChanges != null) {
for (const metc of te.mysteryEncounterTierChanges) {
if (metc.disable) {
ret.push(metc.mysteryEncounter);
}
});
});
}
}
}
return ret;
}
@ -529,15 +522,15 @@ export class TimedEventManager {
normal: MysteryEncounterTier,
): MysteryEncounterTier {
let ret = normal;
timedEvents
.filter(te => this.isActive(te) && te.mysteryEncounterTierChanges != null)
.map(te => {
te.mysteryEncounterTierChanges?.map(metc => {
for (const te of timedEvents) {
if (this.isActive(te) && te.mysteryEncounterTierChanges != null) {
for (const metc of te.mysteryEncounterTierChanges) {
if (metc.mysteryEncounter === encounterType) {
ret = metc.tier ?? normal;
}
});
});
}
}
}
return ret;
}
@ -551,15 +544,16 @@ export class TimedEventManager {
}
getEventLuckBoostedSpecies(): SpeciesId[] {
const ret: SpeciesId[] = [];
timedEvents
.filter(te => this.isActive(te))
.map(te => {
if (te.luckBoostedSpecies != null) {
ret.push(...te.luckBoostedSpecies.filter(s => !ret.includes(s)));
const ret = new Set<SpeciesId>();
for (const te of timedEvents) {
if (this.isActive(te) && te.luckBoostedSpecies != null) {
for (const s of te.luckBoostedSpecies) {
ret.add(s);
}
});
return ret;
}
}
return Array.from(ret);
}
areFusionsBoosted(): boolean {
@ -574,45 +568,50 @@ export class TimedEventManager {
*/
getFixedBattleEventRewards(wave: number): string[] {
const ret: string[] = [];
timedEvents
.filter(te => this.isActive(te) && te.classicWaveRewards != null)
.map(te => {
ret.push(...te.classicWaveRewards!.filter(cwr => cwr.wave === wave).map(cwr => cwr.type));
});
for (const te of timedEvents) {
if (this.isActive(te) && te.classicWaveRewards != null) {
ret.push(...te.classicWaveRewards.filter(cwr => cwr.wave === wave).map(cwr => cwr.type));
}
}
return ret;
}
// Gets the extra shiny chance for trainers due to event (odds/65536)
/**
* Get the extra shiny chance for trainers due to event
*/
getClassicTrainerShinyChance(): number {
let ret = 0;
const tsEvents = timedEvents.filter(te => this.isActive(te) && te.trainerShinyChance != null);
tsEvents.map(t => (ret += t.trainerShinyChance!));
for (const te of timedEvents) {
const shinyChance = te.trainerShinyChance;
if (shinyChance && this.isActive(te)) {
ret += shinyChance;
}
}
return ret;
}
getEventBgmReplacement(bgm: string): string {
let ret = bgm;
timedEvents.map(te => {
for (const te of timedEvents) {
if (this.isActive(te) && te.music != null) {
te.music.map(mr => {
for (const mr of te.music) {
if (mr[0] === bgm) {
console.log(`it is ${te.name} so instead of ${mr[0]} we play ${mr[1]}`);
ret = mr[1];
}
});
}
}
});
}
return ret;
}
/**
* Activates any challenges on {@linkcode globalScene.gameMode} for the currently active event
* Activate any challenges on {@linkcode globalScene.gameMode} for the currently active event
*/
startEventChallenges(): void {
const challenges = this.activeEvent()?.dailyRunChallenges;
challenges?.forEach((eventChal: EventChallenge) =>
globalScene.gameMode.setChallengeValue(eventChal.challenge, eventChal.value),
);
for (const eventChal of this.activeEvent()?.dailyRunChallenges ?? []) {
globalScene.gameMode.setChallengeValue(eventChal.challenge, eventChal.value);
}
}
}

View File

@ -54,6 +54,13 @@ declare module "phaser" {
}
}
namespace Math {
interface RandomDataGenerator {
pick<T>(array: ArrayLike<T>): T;
weightedPick<T>(array: ArrayLike<T>): T;
}
}
namespace Input {
namespace Gamepad {
interface GamepadPlugin {

View File

@ -240,8 +240,8 @@ export class PokedexPageUiHandler extends MessageUiHandler {
private passive: AbilityId;
private hasPassive: boolean;
private hasAbilities: number[];
private biomes: BiomeTierTod[];
private preBiomes: BiomeTierTod[];
private biomes: readonly BiomeTierTod[];
private preBiomes: readonly BiomeTierTod[];
private baseStats: number[];
private baseTotal: number;
private evolutions: SpeciesFormEvolution[];
@ -893,7 +893,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
}
// Function to ensure that forms appear in the appropriate biome and tod
sanitizeBiomes(biomes: BiomeTierTod[], speciesId: number): BiomeTierTod[] {
sanitizeBiomes(biomes: readonly BiomeTierTod[], speciesId: number): readonly BiomeTierTod[] {
if (speciesId === SpeciesId.BURMY || speciesId === SpeciesId.WORMADAM) {
return biomes.filter(b => {
const formIndex = (() => {

View File

@ -1,5 +1,6 @@
import { globalScene } from "#app/global-scene";
import { coerceArray, fixedInt } from "#utils/common";
import { coerceArray } from "#utils/array";
import { fixedInt } from "#utils/common";
export enum PokemonIconAnimMode {
NONE,

101
src/utils/array.ts Normal file
View File

@ -0,0 +1,101 @@
import type {
ReadonlyBigInt64Array,
ReadonlyBigIntArray,
ReadonlyBigUint64Array,
ReadonlyGenericArray,
ReadonlyNumberCompatibleTypedArray,
ReadonlyTypedArray,
TypedArray,
} from "#types/typed-arrays";
/**
* 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 readonly any[] ? T : [T];
export function coerceArray<T>(input: T): T | [T] {
return Array.isArray(input) ? input : [input];
}
/**
* Type guard to check if an input is a typed array, defined as an `ArrayBufferView` that is not a `DataView`.
*/
export function isTypedArray(input: unknown): input is TypedArray {
return ArrayBuffer.isView(input) && !(input instanceof DataView);
}
/**
* Get a subarray view of the first `n` elements of an array-like object, to use
* with constructing a new one, with minimal allocaitons.
*
* @remarks
* This is primarily useful for setting elements of a `TypedArray` using its `set` method.
*
* @privateRemarks
* Note that if this is used with a tuple type, typescript will improperly set
* the return type to be a tuple of the same length as input.
*
* @param arr - The array-like object to take elements from
* @param n - The maximum number of elements to take. If
* @returns An array-like object whose `length` property is guaranteed to be <= `n`
*
* @typeParam T - The element type of the array-like object
* @typeParam A - The type of the array-like object
*
* @example
* ```
* const arr = new Uint8Array(3);
* const other = [1, 2, 3, 4, 5];
* // Using arr.set(other) would throw, as other.length > arr.length
* arr.set(subArray(other, arr.length));
*
* @throws {TypeError}
* If `arr` is not an array or typed array (though typescript should prevent this)
*/
export function subArray<const A extends ReadonlyTypedArray | readonly unknown[]>(arr: A, n: number): typeof arr {
if (arr.length <= n) {
return arr;
}
const len = Math.min(arr.length, n);
if (Array.isArray(arr)) {
// The only path with a new allocation
return arr.slice(0, len) as typeof arr;
}
if (isTypedArray(arr)) {
return arr.subarray(0, len) as typeof arr;
}
throw new TypeError("Expecting an array or typed array");
}
/**
* Store multiple values in the typed array, from input values from a specified array, without
* the possibility of a `RangeError` being thrown.
*
* @remarks
* Almost equivalent to calling `target.set(source, offset)`, though ensures that
* `RangeError` can never be thrown by clamping the number of elements taken from `source`
* to the available space in `target` starting at `offset`.
*
* @param target - The typed array to set values in
* @param source - The array-like object to take values from
* @param offset - The offset in `target` to start writing values at, default `0`
*
* @see {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set | TypedArray#set}
*/
export function setTypedArray<const T extends ReadonlyTypedArray | ReadonlyGenericArray>(
target: T,
source: T extends ReadonlyBigInt64Array | ReadonlyBigUint64Array
? ReadonlyBigIntArray | readonly bigint[]
: ReadonlyNumberCompatibleTypedArray | readonly number[],
offset = 0,
): void {
if (offset < 0 || offset >= target.length) {
return;
}
// @ts-expect-error - TS can't link the conditional type of source to the conditional type of target
// in the body here, despite the fact that the function signature guarantees it.
target.set(subArray(source, target.length - offset), offset);
}

View File

@ -3,8 +3,6 @@ import { MoneyFormat } from "#enums/money-format";
import type { Variant } from "#sprites/variant";
import i18next from "i18next";
export type nil = null | undefined;
export const MissingTextureKey = "__MISSING";
// TODO: Draft tests for these utility functions
@ -125,29 +123,25 @@ export function randSeedFloat(): number {
return Phaser.Math.RND.frac();
}
export function randItem<T>(items: T[]): T {
export function randItem<T>(items: ArrayLike<T>): T {
return items.length === 1 ? items[0] : items[randInt(items.length)];
}
export function randSeedItem<T>(items: T[]): T {
export function randSeedItem<T>(items: ArrayLike<T>): T {
return items.length === 1 ? items[0] : Phaser.Math.RND.pick(items);
}
/**
* Shuffle a list using the seeded rng. Utilises the Fisher-Yates algorithm.
* Shuffle a list in place using the seeded rng and the Fisher-Yates algorithm.
* @param items An array of items.
* @returns A new shuffled array of items.
* @returns `items` shuffled in place.
*/
export function randSeedShuffle<T>(items: T[]): T[] {
if (items.length <= 1) {
return items;
}
const newArray = items.slice(0);
for (let i = items.length - 1; i > 0; i--) {
const j = Phaser.Math.RND.integerInRange(0, i);
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
[items[i], items[j]] = [items[j], items[i]];
}
return newArray;
return items;
}
export function getFrameMs(frameCount: number): number {
@ -176,7 +170,7 @@ 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): number[] {
export function getIvsFromId(id: number): [number, number, number, number, number, number] {
return [
(id & 0x3e000000) >>> 25,
(id & 0x01f00000) >>> 20,
@ -292,9 +286,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;
@ -343,7 +334,7 @@ export function rgbToHsv(r: number, g: number, b: number) {
* @param rgb1 First RGB color in array
* @param rgb2 Second RGB color in array
*/
export function deltaRgb(rgb1: number[], rgb2: number[]): number {
export function deltaRgb(rgb1: readonly number[], rgb2: readonly number[]): number {
const [r1, g1, b1] = rgb1;
const [r2, g2, b2] = rgb2;
const drp2 = Math.pow(r1 - r2, 2);
@ -367,7 +358,7 @@ export function rgbHexToRgba(hex: string) {
};
}
export function rgbaToInt(rgba: number[]): number {
export function rgbaToInt(rgba: readonly number[]): number {
return (rgba[0] << 24) + (rgba[1] << 16) + (rgba[2] << 8) + rgba[3];
}
@ -506,12 +497,3 @@ export function getShinyDescriptor(variant: Variant): string {
return i18next.t("common:commonShiny");
}
}
/**
* 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 | [T] {
return Array.isArray(input) ? input : [input];
}

View File

@ -67,6 +67,7 @@ export function isBareObject(obj: any): boolean {
if (typeof obj !== "object") {
return false;
}
// biome-ignore lint/suspicious/useGuardForIn: Checking a bare object should include prototype chain
for (const _ in obj) {
return false;
}

View File

@ -16,22 +16,23 @@ interface hasPokemon {
* @returns The sorted array of {@linkcode Pokemon}
*/
export function sortInSpeedOrder<T extends Pokemon | hasPokemon>(pokemonList: T[], shuffleFirst = true): T[] {
pokemonList = shuffleFirst ? shufflePokemonList(pokemonList) : pokemonList;
if (shuffleFirst) {
shufflePokemonList(pokemonList);
}
sortBySpeed(pokemonList);
return pokemonList;
}
/**
* Shuffle the list of pokemon *in place*
* @param pokemonList - The array of Pokemon or objects containing Pokemon
* @returns The shuffled array
* @returns The same array instance that was passed in, shuffled.
*/
function shufflePokemonList<T extends Pokemon | hasPokemon>(pokemonList: T[]): T[] {
// This is seeded with the current turn to prevent an inconsistency where it
// was varying based on how long since you last reloaded
globalScene.executeWithSeedOffset(
() => {
pokemonList = randSeedShuffle(pokemonList);
},
() => randSeedShuffle(pokemonList),
globalScene.currentBattle.turn * 1000 + pokemonList.length,
globalScene.waveSeed,
);

View File

@ -166,8 +166,8 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
// Mock stats by replacing entries in copy with desired values for specific stats
const stats = {
enemy: [[...enemyParty[0].stats], [...enemyParty[1].stats]],
player: [[...party[0].stats], [...party[1].stats]],
enemy: [enemyParty[0].stats.slice(), enemyParty[1].stats.slice()],
player: [party[0].stats.slice(), party[1].stats.slice()],
};
// Ensure survival by reducing enemy Sp. Atk and boosting party Sp. Def
@ -220,8 +220,8 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
// Mock stats by replacing entries in copy with desired values for specific stats
const stats = {
enemy: [[...enemyParty[0].stats], [...enemyParty[1].stats]],
player: [[...party[0].stats], [...party[1].stats]],
enemy: [enemyParty[0].stats.slice(), enemyParty[1].stats.slice()],
player: [party[0].stats.slice(), party[1].stats.slice()],
};
// Ensure survival by reducing enemy Sp. Atk and boosting party Sp. Def

View File

@ -12,7 +12,7 @@ import type { CommandPhase } from "#phases/command-phase";
import type { EnemyCommandPhase } from "#phases/enemy-command-phase";
import { MoveEffectPhase } from "#phases/move-effect-phase";
import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper";
import { coerceArray } from "#utils/common";
import { coerceArray } from "#utils/array";
import { toTitleCase } from "#utils/strings";
import type { MockInstance } from "vitest";
import { expect, vi } from "vitest";

View File

@ -20,7 +20,8 @@ import { WeatherType } from "#enums/weather-type";
import type { ModifierOverride } from "#modifiers/modifier-type";
import type { Variant } from "#sprites/variant";
import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper";
import { coerceArray, shiftCharCodes } from "#utils/common";
import { coerceArray } from "#utils/array";
import { shiftCharCodes } from "#utils/common";
import chalk from "chalk";
import { vi } from "vitest";

View File

@ -7,7 +7,7 @@ import Overrides from "#app/overrides";
import { MoveId } from "#enums/move-id";
import { getEnumStr } from "#test/test-utils/string-utils";
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
import { coerceArray } from "#utils/common";
import { coerceArray } from "#utils/array";
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
/**

View File

@ -1,6 +1,6 @@
import { DEBUG_COLOR, NEW_TURN_COLOR, TRACE_COLOR, UI_MSG_COLOR } from "#app/constants/colors";
import { inferColorFormat } from "#test/test-utils/mocks/mock-console/infer-color";
import { coerceArray } from "#utils/common";
import { coerceArray } from "#utils/array";
import { type InspectOptions, inspect } from "node:util";
import chalk, { type ChalkInstance } from "chalk";

View File

@ -1,6 +1,6 @@
import type { MockGameObject } from "#test/test-utils/mocks/mock-game-object";
import type { MockTextureManager } from "#test/test-utils/mocks/mock-texture-manager";
import { coerceArray } from "#utils/common";
import { coerceArray } from "#utils/array";
export class MockContainer implements MockGameObject {
protected x: number;

View File

@ -1,5 +1,5 @@
import type { MockGameObject } from "#test/test-utils/mocks/mock-game-object";
import { coerceArray } from "#utils/common";
import { coerceArray } from "#utils/array";
export class MockRectangle implements MockGameObject {
private fillColor;

View File

@ -1,5 +1,5 @@
import type { MockGameObject } from "#test/test-utils/mocks/mock-game-object";
import { coerceArray } from "#utils/common";
import { coerceArray } from "#utils/array";
import Phaser from "phaser";
type Frame = Phaser.Textures.Frame;

279
test/utils/array.test.ts Normal file
View File

@ -0,0 +1,279 @@
import type { ReadonlyUint8Array } from "#types/typed-arrays";
import { coerceArray, isTypedArray, setTypedArray, subArray } from "#utils/array";
import { describe, expect, expectTypeOf, it } from "vitest";
/**
* Unit tests for the utility methods in `src/utils/array.ts`
* @module
*/
describe("Utils - Array", () => {
describe("subArray", () => {
it("returns the same array if length <= n (plain array)", () => {
const arr = [1, 2, 3];
const result = subArray(arr, 5);
expect(result).toBe(arr);
expect(result).toEqual([1, 2, 3]);
});
it("returns a sliced array if length > n (plain array)", () => {
const arr = [1, 2, 3, 4, 5];
const result = subArray(arr, 3);
expect(result).not.toBe(arr);
expect(result).toEqual([1, 2, 3]);
});
it("returns the same typed array if length <= n", () => {
const arr = new Uint8Array([1, 2, 3]);
const result = subArray(arr, 5);
expect(result).toBe(arr);
expect(Array.from(result)).toEqual([1, 2, 3]);
});
it("returns a subarray if length > n (typed array)", () => {
const arr = new Uint8Array([1, 2, 3, 4, 5]);
const result = subArray(arr, 2);
expect(result).not.toBe(arr);
expect(Array.from(result)).toEqual([1, 2]);
});
it("returns empty array if n is 0 (plain array)", () => {
const arr = [1, 2, 3];
const result = subArray(arr, 0);
expect(result).toEqual([]);
});
it("returns empty typed array if n is 0", () => {
const arr = new Uint8Array([1, 2, 3]);
const result = subArray(arr, 0);
expect(Array.from(result)).toEqual([]);
});
it("throws TypeError for non-array-like input", () => {
// @ts-expect-error
expect(() => subArray({ length: 4 }, 2)).toThrow(TypeError);
});
describe("output type inference", () => {
it("plain array input", () => {
const arr = [1, 2, 3, 4];
const result = subArray(arr, 2);
expectTypeOf(result).toEqualTypeOf<number[]>();
});
it("typed array input", () => {
const arr = new Uint8Array([1, 2, 3, 4]);
const result = subArray(arr, 2);
// @ts-expect-error We get a questionable error about Uint8Array not being assignable to Uint8Array...
expectTypeOf(result).toEqualTypeOf<Uint8Array>();
});
it("readonly array input", () => {
const arr: readonly number[] = [1, 2, 3, 4];
const result = subArray(arr, 2);
expectTypeOf(result).toEqualTypeOf<readonly number[]>();
});
it("readonly typed array input", () => {
const arr = new Uint8Array([1, 2, 3, 4]) as ReadonlyUint8Array;
const result = subArray(arr, 2);
expectTypeOf(result).toEqualTypeOf<ReadonlyUint8Array>();
});
});
});
describe("setTypedArray", () => {
it("sets values from source to target with no offset (fits exactly)", () => {
const target = new Uint8Array(3);
const source = [1, 2, 3];
setTypedArray(target, source);
expect(Array.from(target)).toEqual([1, 2, 3]);
});
it("sets values from source to target with offset", () => {
const target = new Uint8Array([0, 0, 0, 0, 0]);
const source = [9, 8];
setTypedArray(target, source, 2);
expect(Array.from(target)).toEqual([0, 0, 9, 8, 0]);
});
it("clamps source if it would overflow target", () => {
const target = new Uint8Array(4);
const source = [1, 2, 3, 4, 5, 6];
setTypedArray(target, source, 2);
expect(Array.from(target)).toEqual([0, 0, 1, 2]);
});
it("does nothing if offset < 0", () => {
const target = new Uint8Array([1, 2, 3]);
const source = [4, 5, 6];
setTypedArray(target, source, -1);
expect(Array.from(target)).toEqual([1, 2, 3]);
});
it("does nothing if offset >= target.length", () => {
const target = new Uint8Array([1, 2, 3]);
const source = [4, 5, 6];
setTypedArray(target, source, 3);
expect(Array.from(target)).toEqual([1, 2, 3]);
});
it("does nothing if source is empty", () => {
const target = new Uint8Array([1, 2, 3]);
const source: number[] = [];
setTypedArray(target, source, 1);
expect(Array.from(target)).toEqual([1, 2, 3]);
});
it("works with typed array as source", () => {
const target = new Uint8Array(4);
const source = new Uint8Array([7, 8, 9]);
setTypedArray(target, source, 1);
expect(Array.from(target)).toEqual([0, 7, 8, 9]);
});
it("clamps source typed array if it would overflow target", () => {
const target = new Uint8Array(3);
const source = new Uint8Array([1, 2, 3, 4, 5]);
setTypedArray(target, source, 1);
expect(Array.from(target)).toEqual([0, 1, 2]);
});
it("works with BigUint64Array and bigint[]", () => {
if (typeof BigUint64Array !== "undefined") {
const target = new BigUint64Array(3);
const source = [1n, 2n, 3n, 4n];
setTypedArray(target, source, 1);
expect(Array.from(target)).toEqual([0n, 1n, 2n]);
}
});
});
describe("coerceArray", () => {
it("returns the same array if input is already an array", () => {
const arr = [1, 2, 3];
const result = coerceArray(arr);
expect(result).toBe(arr);
expect(result).toEqual([1, 2, 3]);
});
it("wraps a non-array input in an array", () => {
const input = 42;
const result = coerceArray(input);
expect(result).toEqual([42]);
});
it("wraps an object in an array", () => {
const obj = { a: 1 };
const result = coerceArray(obj);
expect(result).toEqual([{ a: 1 }]);
});
it("wraps a string in an array", () => {
const str = "hello";
const result = coerceArray(str);
expect(result).toEqual(["hello"]);
});
it("wraps null in an array", () => {
const result = coerceArray(null);
expect(result).toEqual([null]);
});
it("wraps undefined in an array", () => {
const result = coerceArray(undefined);
expect(result).toEqual([undefined]);
});
it("returns the same array for empty array input", () => {
const arr: number[] = [];
const result = coerceArray(arr);
expect(result).toBe(arr);
expect(result).toEqual([]);
});
describe("typing", () => {
it("infers correct type for array input", () => {
const arr = [1, 2, 3];
const result = coerceArray(arr);
expectTypeOf(result).toEqualTypeOf<number[]>();
});
it("infers correct type for non-array input", () => {
const input = "test";
const result = coerceArray(input);
expectTypeOf(result).toEqualTypeOf<[string]>();
});
it("infers correct type for object input", () => {
const obj = { key: "value" };
const result = coerceArray(obj);
expectTypeOf(result).toEqualTypeOf<[{ key: string }]>();
});
it("infers correct type for null input", () => {
const result = coerceArray(null);
expectTypeOf(result).toEqualTypeOf<[null]>();
});
it("infers correct type for undefined input", () => {
const result = coerceArray(undefined);
expectTypeOf(result).toEqualTypeOf<[undefined]>();
});
it("infers correct type for empty array input", () => {
const arr: number[] = [];
const result = coerceArray(arr);
expectTypeOf(result).toEqualTypeOf<number[]>();
});
});
});
describe("isTypedArray", () => {
it.each([
["Int8Array", Int8Array],
["Uint8ClampedArray", Uint8ClampedArray],
["Uint16Array", Uint16Array],
["Uint32Array", Uint32Array],
["Float64Array", Float64Array],
["Float32Array", Float32Array],
])("returns true for %s", (_, ArrayType) => {
if (typeof ArrayType !== "undefined") {
const arr = new ArrayType([1, 2, 3]);
expect(isTypedArray(arr)).toBe(true);
}
});
it.each([
["BigUint64Array", BigUint64Array],
["BigInt64Array", BigInt64Array],
])("returns true for %s", (_, ArrayType) => {
const arr = new ArrayType([1n, 2n, 3n]);
expect(isTypedArray(arr)).toBe(true);
});
it.each([
["strings", "hello"],
["null", null],
["undefined", undefined],
["a number", 123],
["an object", { a: 1 }],
["a plain array", [1, 2, 3]],
["a DataView", new DataView(new ArrayBuffer(8))],
["an ArrayLike", { length: 2, 0: 1, 1: 2 }],
])("returns false for %s", (_, input) => {
expect(isTypedArray(input)).toBe(false);
});
it("returns true for an object that extends a typed array", () => {
class MyUint8Array extends Uint8Array {
customMethod() {
return "custom";
}
}
const arr = new MyUint8Array([1, 2, 3]);
expect(isTypedArray(arr)).toBe(true);
});
});
});