mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-09-23 23:13:42 +02:00
Even more array improvements
This commit is contained in:
parent
8003215c39
commit
ac2f09ad33
@ -7,8 +7,8 @@ import type { TrainerPartyTemplate } from "#trainers/trainer-party-template";
|
|||||||
|
|
||||||
export type PartyTemplateFunc = () => TrainerPartyTemplate;
|
export type PartyTemplateFunc = () => TrainerPartyTemplate;
|
||||||
export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon;
|
export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon;
|
||||||
export type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[];
|
export type GenModifiersFunc = (party: readonly EnemyPokemon[]) => PersistentModifier[];
|
||||||
export type GenAIFunc = (party: EnemyPokemon[]) => void;
|
export type GenAIFunc = (party: readonly EnemyPokemon[]) => void;
|
||||||
|
|
||||||
export interface TrainerTierPools {
|
export interface TrainerTierPools {
|
||||||
[key: number]: SpeciesId[];
|
[key: number]: SpeciesId[];
|
||||||
|
@ -526,24 +526,25 @@ export class FixedBattleConfig {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to generate a random trainer for evil team trainers and the elite 4/champion
|
* 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 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 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
|
* @param seedOffset - The seed offset to use for the random generation of the trainer
|
||||||
* @returns the generated trainer
|
* @returns A function to get a random trainer
|
||||||
*/
|
*/
|
||||||
export function getRandomTrainerFunc(
|
export function getRandomTrainerFunc(
|
||||||
trainerPool: (TrainerType | TrainerType[])[],
|
trainerPool: readonly (TrainerType | readonly TrainerType[])[],
|
||||||
randomGender = false,
|
randomGender = false,
|
||||||
seedOffset = 0,
|
seedOffset = 0,
|
||||||
): GetTrainerFunc {
|
): GetTrainerFunc {
|
||||||
return () => {
|
return () => {
|
||||||
const rand = randSeedInt(trainerPool.length);
|
const rand = randSeedInt(trainerPool.length);
|
||||||
const trainerTypes: TrainerType[] = [];
|
const trainerTypes: TrainerType[] = new Array(trainerPool.length);
|
||||||
|
|
||||||
globalScene.executeWithSeedOffset(() => {
|
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;
|
const trainerType = Array.isArray(trainerPoolEntry) ? randSeedItem(trainerPoolEntry) : trainerPoolEntry;
|
||||||
trainerTypes.push(trainerType);
|
trainerTypes[i] = trainerType;
|
||||||
}
|
}
|
||||||
}, seedOffset);
|
}, seedOffset);
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ export const TYPE_BOOST_ITEM_BOOST_PERCENT = 20;
|
|||||||
/**
|
/**
|
||||||
* The default species that a new player can choose from
|
* The default species that a new player can choose from
|
||||||
*/
|
*/
|
||||||
export const defaultStarterSpecies: SpeciesId[] = [
|
export const defaultStarterSpecies: readonly SpeciesId[] = [
|
||||||
SpeciesId.BULBASAUR,
|
SpeciesId.BULBASAUR,
|
||||||
SpeciesId.CHARMANDER,
|
SpeciesId.CHARMANDER,
|
||||||
SpeciesId.SQUIRTLE,
|
SpeciesId.SQUIRTLE,
|
||||||
|
@ -5,6 +5,7 @@ import { PokemonType } from "#enums/pokemon-type";
|
|||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { TimeOfDay } from "#enums/time-of-day";
|
import { TimeOfDay } from "#enums/time-of-day";
|
||||||
import { TrainerType } from "#enums/trainer-type";
|
import { TrainerType } from "#enums/trainer-type";
|
||||||
|
import type { Mutable } from "#types/type-helpers";
|
||||||
import { randSeedInt } from "#utils/common";
|
import { randSeedInt } from "#utils/common";
|
||||||
import { getEnumValues } from "#utils/enums";
|
import { getEnumValues } from "#utils/enums";
|
||||||
import { toCamelCase } from "#utils/strings";
|
import { toCamelCase } from "#utils/strings";
|
||||||
@ -88,19 +89,19 @@ export enum BiomePoolTier {
|
|||||||
export const uncatchableSpecies: SpeciesId[] = [];
|
export const uncatchableSpecies: SpeciesId[] = [];
|
||||||
|
|
||||||
interface SpeciesTree {
|
interface SpeciesTree {
|
||||||
[key: number]: SpeciesId[]
|
readonly [key: number]: SpeciesId[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PokemonPools {
|
export interface PokemonPools {
|
||||||
[key: number]: (SpeciesId | SpeciesTree)[]
|
readonly [key: number]: (SpeciesId | SpeciesTree)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BiomeTierPokemonPools {
|
interface BiomeTierPokemonPools {
|
||||||
[key: number]: PokemonPools
|
readonly [key: number]: PokemonPools
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BiomePokemonPools {
|
interface BiomePokemonPools {
|
||||||
[key: number]: BiomeTierPokemonPools
|
readonly [key: number]: BiomeTierPokemonPools
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BiomeTierTod {
|
export interface BiomeTierTod {
|
||||||
@ -110,17 +111,17 @@ export interface BiomeTierTod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CatchableSpecies{
|
export interface CatchableSpecies{
|
||||||
[key: number]: BiomeTierTod[]
|
readonly [key: number]: readonly BiomeTierTod[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const catchableSpecies: CatchableSpecies = {};
|
export const catchableSpecies: CatchableSpecies = {};
|
||||||
|
|
||||||
export interface BiomeTierTrainerPools {
|
export interface BiomeTierTrainerPools {
|
||||||
[key: number]: TrainerType[]
|
readonly [key: number]: readonly TrainerType[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BiomeTrainerPools {
|
export interface BiomeTrainerPools {
|
||||||
[key: number]: BiomeTierTrainerPools
|
readonly [key: number]: BiomeTierTrainerPools
|
||||||
}
|
}
|
||||||
|
|
||||||
export const biomePokemonPools: BiomePokemonPools = {
|
export const biomePokemonPools: BiomePokemonPools = {
|
||||||
@ -7621,12 +7622,10 @@ export function initBiomes() {
|
|||||||
? biomeLinks[biome] as (BiomeId | [ BiomeId, number ])[]
|
? biomeLinks[biome] as (BiomeId | [ BiomeId, number ])[]
|
||||||
: [ biomeLinks[biome] as BiomeId ];
|
: [ biomeLinks[biome] as BiomeId ];
|
||||||
for (const linkedBiomeEntry of linkedBiomes) {
|
for (const linkedBiomeEntry of linkedBiomes) {
|
||||||
const linkedBiome = !Array.isArray(linkedBiomeEntry)
|
const linkedBiome = Array.isArray(linkedBiomeEntry)
|
||||||
? linkedBiomeEntry as BiomeId
|
? linkedBiomeEntry[0] : linkedBiomeEntry as BiomeId;
|
||||||
: linkedBiomeEntry[0];
|
const biomeChance = Array.isArray(linkedBiomeEntry)
|
||||||
const biomeChance = !Array.isArray(linkedBiomeEntry)
|
? linkedBiomeEntry[1] : 1;
|
||||||
? 1
|
|
||||||
: linkedBiomeEntry[1];
|
|
||||||
if (!biomeDepths.hasOwnProperty(linkedBiome) || biomeChance < biomeDepths[linkedBiome][1] || (depth < biomeDepths[linkedBiome][0] && biomeChance === biomeDepths[linkedBiome][1])) {
|
if (!biomeDepths.hasOwnProperty(linkedBiome) || biomeChance < biomeDepths[linkedBiome][1] || (depth < biomeDepths[linkedBiome][0] && biomeChance === biomeDepths[linkedBiome][1])) {
|
||||||
biomeDepths[linkedBiome] = [ depth + 1, biomeChance ];
|
biomeDepths[linkedBiome] = [ depth + 1, biomeChance ];
|
||||||
traverseBiome(linkedBiome, depth + 1);
|
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 ];
|
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)) {
|
for (const biome of getEnumValues(BiomeId)) {
|
||||||
biomePokemonPools[biome] = {};
|
(biomePokemonPools[biome] as Mutable<typeof biomePokemonPools[number]>) = {};
|
||||||
biomeTrainerPools[biome] = {};
|
(biomeTrainerPools[biome] as Mutable<typeof biomeTrainerPools[number]>) = {};
|
||||||
|
|
||||||
for (const tier of getEnumValues(BiomePoolTier)) {
|
for (const tier of getEnumValues(BiomePoolTier)) {
|
||||||
biomePokemonPools[biome][tier] = {};
|
(biomePokemonPools[biome][tier] as Mutable<typeof biomePokemonPools[number][number]>) = {};
|
||||||
biomeTrainerPools[biome][tier] = [];
|
(biomeTrainerPools[biome][tier] as Mutable<typeof biomeTrainerPools[number][number]>) = [];
|
||||||
|
|
||||||
for (const tod of getEnumValues(TimeOfDay)) {
|
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);
|
uncatchableSpecies.push(speciesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mutableSpecies = Mutable<typeof catchableSpecies[SpeciesId]>;
|
||||||
// array of biome options for the current species
|
// array of biome options for the current species
|
||||||
catchableSpecies[speciesId] = [];
|
(catchableSpecies[speciesId] as mutableSpecies) = [];
|
||||||
|
|
||||||
for (const b of biomeEntries) {
|
for (const b of biomeEntries) {
|
||||||
const biome = b[0];
|
const biome = b[0];
|
||||||
@ -7675,7 +7675,7 @@ export function initBiomes() {
|
|||||||
: [ b[2] ]
|
: [ b[2] ]
|
||||||
: [ TimeOfDay.ALL ];
|
: [ TimeOfDay.ALL ];
|
||||||
|
|
||||||
catchableSpecies[speciesId].push({
|
(catchableSpecies[speciesId] as mutableSpecies).push({
|
||||||
biome: biome as BiomeId,
|
biome: biome as BiomeId,
|
||||||
tier: tier as BiomePoolTier,
|
tier: tier as BiomePoolTier,
|
||||||
tod: timesOfDay as TimeOfDay[]
|
tod: timesOfDay as TimeOfDay[]
|
||||||
@ -7735,12 +7735,13 @@ export function initBiomes() {
|
|||||||
};
|
};
|
||||||
for (let s = 1; s < entry.length; s++) {
|
for (let s = 1; s < entry.length; s++) {
|
||||||
const speciesId = entry[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 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);
|
const level = prevolution.level - (prevolution.level === 1 ? 1 : 0) + (prevolution.wildDelay * 10) - (tier >= BiomePoolTier.BOSS ? 10 : 0);
|
||||||
if (!newEntry.hasOwnProperty(level)) {
|
if (newEntry.hasOwnProperty(level)) {
|
||||||
newEntry[level] = [ speciesId ];
|
|
||||||
} else {
|
|
||||||
newEntry[level].push(speciesId);
|
newEntry[level].push(speciesId);
|
||||||
|
} else {
|
||||||
|
newEntry[level] = [ speciesId ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
biomeTierTimePool[e] = newEntry;
|
biomeTierTimePool[e] = newEntry;
|
||||||
@ -7763,7 +7764,7 @@ export function initBiomes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const biomeTierPool = biomeTrainerPools[biome][tier];
|
const biomeTierPool = biomeTrainerPools[biome][tier];
|
||||||
biomeTierPool.push(trainerType);
|
(biomeTierPool as Mutable<typeof biomeTierPool>).push(trainerType);
|
||||||
}
|
}
|
||||||
//outputPools();
|
//outputPools();
|
||||||
}
|
}
|
||||||
|
@ -2154,8 +2154,8 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class WeatherHighestStatBoostTag extends HighestStatBoostTag {
|
export class WeatherHighestStatBoostTag extends HighestStatBoostTag {
|
||||||
#weatherTypes: WeatherType[];
|
readonly #weatherTypes: readonly WeatherType[];
|
||||||
public get weatherTypes(): WeatherType[] {
|
public get weatherTypes(): readonly WeatherType[] {
|
||||||
return this.#weatherTypes;
|
return this.#weatherTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2166,8 +2166,8 @@ export class WeatherHighestStatBoostTag extends HighestStatBoostTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class TerrainHighestStatBoostTag extends HighestStatBoostTag {
|
export class TerrainHighestStatBoostTag extends HighestStatBoostTag {
|
||||||
#terrainTypes: TerrainType[];
|
readonly #terrainTypes: readonly TerrainType[];
|
||||||
public get terrainTypes(): TerrainType[] {
|
public get terrainTypes(): readonly TerrainType[] {
|
||||||
return this.#terrainTypes;
|
return this.#terrainTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,14 +485,14 @@ export class Egg {
|
|||||||
* and being the same each time
|
* and being the same each time
|
||||||
*/
|
*/
|
||||||
let totalWeight = 0;
|
let totalWeight = 0;
|
||||||
const speciesWeights: number[] = [];
|
const speciesWeights = new Array<number>(speciesPool.length);
|
||||||
for (const speciesId of speciesPool) {
|
for (const [idx, speciesId] of speciesPool.entries()) {
|
||||||
// Accounts for species that have starter costs outside of the normal range for their EggTier
|
// 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 speciesCostClamped = Phaser.Math.Clamp(speciesStarterCosts[speciesId], minStarterValue, maxStarterValue);
|
||||||
const weight = Math.floor(
|
const weight = Math.floor(
|
||||||
(((maxStarterValue - speciesCostClamped) / (maxStarterValue - minStarterValue + 1)) * 1.5 + 1) * 100,
|
(((maxStarterValue - speciesCostClamped) / (maxStarterValue - minStarterValue + 1)) * 1.5 + 1) * 100,
|
||||||
);
|
);
|
||||||
speciesWeights.push(totalWeight + weight);
|
speciesWeights[idx] = totalWeight + weight;
|
||||||
totalWeight += weight;
|
totalWeight += weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import { SpeciesId } from "#enums/species-id";
|
|||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import type { Pokemon } from "#field/pokemon";
|
import type { Pokemon } from "#field/pokemon";
|
||||||
import type { Constructor, nil } from "#types/common";
|
import type { Constructor, nil } from "#types/common";
|
||||||
|
import type { Mutable } from "#types/type-helpers";
|
||||||
|
|
||||||
export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean;
|
export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean;
|
||||||
export type SpeciesFormChangeConditionEnforceFunc = (p: Pokemon) => void;
|
export type SpeciesFormChangeConditionEnforceFunc = (p: Pokemon) => void;
|
||||||
@ -116,7 +117,7 @@ function getSpeciesDependentFormChangeCondition(species: SpeciesId): SpeciesForm
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface PokemonFormChanges {
|
interface PokemonFormChanges {
|
||||||
[key: string]: SpeciesFormChange[];
|
[key: string]: readonly SpeciesFormChange[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// biome-ignore format: manually formatted
|
// biome-ignore format: manually formatted
|
||||||
@ -608,6 +609,6 @@ export function initPokemonForms() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
formChanges.push(...newFormChanges);
|
(formChanges as Mutable<typeof formChanges>).push(...newFormChanges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,9 +240,9 @@ export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
|
|||||||
/** The ability that triggers the form change */
|
/** The ability that triggers the form change */
|
||||||
public ability: AbilityId;
|
public ability: AbilityId;
|
||||||
/** The list of weathers that trigger the form change */
|
/** 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();
|
super();
|
||||||
this.ability = ability;
|
this.ability = ability;
|
||||||
this.weathers = weathers;
|
this.weathers = weathers;
|
||||||
@ -278,9 +278,9 @@ export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChange
|
|||||||
/** The ability that triggers the form change*/
|
/** The ability that triggers the form change*/
|
||||||
public ability: AbilityId;
|
public ability: AbilityId;
|
||||||
/** The list of weathers that will also trigger a form change to original form */
|
/** 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();
|
super();
|
||||||
this.ability = ability;
|
this.ability = ability;
|
||||||
this.weathers = weathers;
|
this.weathers = weathers;
|
||||||
@ -310,9 +310,10 @@ export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChange
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string {
|
export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string {
|
||||||
const isMega = formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1;
|
const formKey = formChange.formKey;
|
||||||
const isGmax = formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1;
|
const isMega = formKey.indexOf(SpeciesFormKey.MEGA) > -1;
|
||||||
const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -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;
|
const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey;
|
||||||
if (isMega) {
|
if (isMega) {
|
||||||
return i18next.t("battlePokemonForm:megaChange", {
|
return i18next.t("battlePokemonForm:megaChange", {
|
||||||
|
@ -2140,7 +2140,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* Suppresses an ability and calls its onlose attributes
|
* Suppresses an ability and calls its onlose attributes
|
||||||
*/
|
*/
|
||||||
public suppressAbility() {
|
public suppressAbility() {
|
||||||
[true, false].forEach(passive => applyOnLoseAbAttrs({ pokemon: this, passive }));
|
applyOnLoseAbAttrs({ pokemon: this, passive: true });
|
||||||
|
applyOnLoseAbAttrs({ pokemon: this, passive: false });
|
||||||
this.summonData.abilitySuppressed = true;
|
this.summonData.abilitySuppressed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -642,14 +642,14 @@ export class Trainer extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genModifiers(party: EnemyPokemon[]): PersistentModifier[] {
|
genModifiers(party: readonly EnemyPokemon[]): PersistentModifier[] {
|
||||||
if (this.config.genModifiersFunc) {
|
if (this.config.genModifiersFunc) {
|
||||||
return this.config.genModifiersFunc(party);
|
return this.config.genModifiersFunc(party);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
genAI(party: EnemyPokemon[]) {
|
genAI(party: readonly EnemyPokemon[]) {
|
||||||
if (this.config.genAIFuncs) {
|
if (this.config.genAIFuncs) {
|
||||||
this.config.genAIFuncs.forEach(f => f(party));
|
this.config.genAIFuncs.forEach(f => f(party));
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
export class ModifierTypeGenerator extends ModifierType {
|
||||||
private genTypeFunc: ModifierTypeGeneratorFunc;
|
private genTypeFunc: ModifierTypeGeneratorFunc;
|
||||||
@ -287,7 +287,7 @@ export class ModifierTypeGenerator extends ModifierType {
|
|||||||
this.genTypeFunc = genTypeFunc;
|
this.genTypeFunc = genTypeFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
generateType(party: Pokemon[], pregenArgs?: any[]) {
|
generateType(party: readonly Pokemon[], pregenArgs?: any[]) {
|
||||||
const ret = this.genTypeFunc(party, pregenArgs);
|
const ret = this.genTypeFunc(party, pregenArgs);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
ret.id = this.id;
|
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 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);
|
const pool = getModifierPoolForType(poolType);
|
||||||
itemPoolChecks.forEach((_v, k) => {
|
itemPoolChecks.forEach((_v, k) => {
|
||||||
itemPoolChecks.set(k, false);
|
itemPoolChecks.set(k, false);
|
||||||
@ -2906,7 +2910,7 @@ export class ModifierTypeOption {
|
|||||||
* @param party The player's party.
|
* @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.
|
* @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) {
|
if (globalScene.gameMode.isDaily) {
|
||||||
const DailyLuck = new NumberHolder(0);
|
const DailyLuck = new NumberHolder(0);
|
||||||
globalScene.executeWithSeedOffset(
|
globalScene.executeWithSeedOffset(
|
||||||
|
@ -12,7 +12,7 @@ export abstract class BattlePhase extends Phase {
|
|||||||
const tintSprites = globalScene.currentBattle.trainer.getTintSprites();
|
const tintSprites = globalScene.currentBattle.trainer.getTintSprites();
|
||||||
for (let i = 0; i < sprites.length; i++) {
|
for (let i = 0; i < sprites.length; i++) {
|
||||||
const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2;
|
const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2;
|
||||||
[sprites[i], tintSprites[i]].map(sprite => {
|
[sprites[i], tintSprites[i]].forEach(sprite => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
sprite.x = trainerSlot || sprites.length < 2 ? 0 : i ? 16 : -16;
|
sprite.x = trainerSlot || sprites.length < 2 ? 0 : i ? 16 : -16;
|
||||||
}
|
}
|
||||||
|
@ -18,59 +18,59 @@ export enum EventType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface EventBanner {
|
interface EventBanner {
|
||||||
bannerKey?: string;
|
readonly bannerKey?: string;
|
||||||
xOffset?: number;
|
readonly xOffset?: number;
|
||||||
yOffset?: number;
|
readonly yOffset?: number;
|
||||||
scale?: number;
|
readonly scale?: number;
|
||||||
availableLangs?: string[];
|
readonly availableLangs?: readonly string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EventEncounter {
|
interface EventEncounter {
|
||||||
species: SpeciesId;
|
readonly species: SpeciesId;
|
||||||
blockEvolution?: boolean;
|
readonly blockEvolution?: boolean;
|
||||||
formIndex?: number;
|
readonly formIndex?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EventMysteryEncounterTier {
|
interface EventMysteryEncounterTier {
|
||||||
mysteryEncounter: MysteryEncounterType;
|
readonly mysteryEncounter: MysteryEncounterType;
|
||||||
tier?: MysteryEncounterTier;
|
readonly tier?: MysteryEncounterTier;
|
||||||
disable?: boolean;
|
readonly disable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EventWaveReward {
|
interface EventWaveReward {
|
||||||
wave: number;
|
readonly wave: number;
|
||||||
type: string;
|
readonly type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventMusicReplacement = [string, string];
|
type EventMusicReplacement = readonly [string, string];
|
||||||
|
|
||||||
interface EventChallenge {
|
interface EventChallenge {
|
||||||
challenge: Challenges;
|
readonly challenge: Challenges;
|
||||||
value: number;
|
readonly value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TimedEvent extends EventBanner {
|
interface TimedEvent extends EventBanner {
|
||||||
name: string;
|
readonly name: string;
|
||||||
eventType: EventType;
|
readonly eventType: EventType;
|
||||||
shinyMultiplier?: number;
|
readonly shinyMultiplier?: number;
|
||||||
classicFriendshipMultiplier?: number;
|
readonly classicFriendshipMultiplier?: number;
|
||||||
luckBoost?: number;
|
readonly luckBoost?: number;
|
||||||
upgradeUnlockedVouchers?: boolean;
|
readonly upgradeUnlockedVouchers?: boolean;
|
||||||
startDate: Date;
|
readonly startDate: Date;
|
||||||
endDate: Date;
|
readonly endDate: Date;
|
||||||
eventEncounters?: EventEncounter[];
|
readonly eventEncounters?: readonly EventEncounter[];
|
||||||
delibirdyBuff?: string[];
|
readonly delibirdyBuff?: readonly string[];
|
||||||
weather?: WeatherPoolEntry[];
|
readonly weather?: readonly WeatherPoolEntry[];
|
||||||
mysteryEncounterTierChanges?: EventMysteryEncounterTier[];
|
readonly mysteryEncounterTierChanges?: readonly EventMysteryEncounterTier[];
|
||||||
luckBoostedSpecies?: SpeciesId[];
|
readonly luckBoostedSpecies?: readonly SpeciesId[];
|
||||||
boostFusions?: boolean; //MODIFIER REWORK PLEASE
|
readonly boostFusions?: boolean; //MODIFIER REWORK PLEASE
|
||||||
classicWaveRewards?: EventWaveReward[]; // Rival battle rewards
|
readonly classicWaveRewards?: readonly EventWaveReward[]; // Rival battle rewards
|
||||||
trainerShinyChance?: number; // Odds over 65536 of trainer mon generating as shiny
|
readonly trainerShinyChance?: number; // Odds over 65536 of trainer mon generating as shiny
|
||||||
music?: EventMusicReplacement[];
|
readonly music?: readonly EventMusicReplacement[];
|
||||||
dailyRunChallenges?: EventChallenge[];
|
readonly dailyRunChallenges?: readonly EventChallenge[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const timedEvents: TimedEvent[] = [
|
const timedEvents: readonly TimedEvent[] = [
|
||||||
{
|
{
|
||||||
name: "Winter Holiday Update",
|
name: "Winter Holiday Update",
|
||||||
eventType: EventType.SHINY,
|
eventType: EventType.SHINY,
|
||||||
@ -385,7 +385,8 @@ const timedEvents: TimedEvent[] = [
|
|||||||
|
|
||||||
export class TimedEventManager {
|
export class TimedEventManager {
|
||||||
isActive(event: TimedEvent) {
|
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 {
|
activeEvent(): TimedEvent | undefined {
|
||||||
@ -427,19 +428,17 @@ export class TimedEventManager {
|
|||||||
|
|
||||||
getEventBannerLangs(): string[] {
|
getEventBannerLangs(): string[] {
|
||||||
const ret: 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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
getEventEncounters(): EventEncounter[] {
|
getEventEncounters(): EventEncounter[] {
|
||||||
const ret: EventEncounter[] = [];
|
const ret: EventEncounter[] = [];
|
||||||
timedEvents
|
for (const te of timedEvents) {
|
||||||
.filter(te => this.isActive(te))
|
if (this.isActive(te) && te.eventEncounters != null) {
|
||||||
.map(te => {
|
|
||||||
if (te.eventEncounters != null) {
|
|
||||||
ret.push(...te.eventEncounters);
|
ret.push(...te.eventEncounters);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -472,13 +471,11 @@ export class TimedEventManager {
|
|||||||
*/
|
*/
|
||||||
getDelibirdyBuff(): string[] {
|
getDelibirdyBuff(): string[] {
|
||||||
const ret: string[] = [];
|
const ret: string[] = [];
|
||||||
timedEvents
|
for (const te of timedEvents) {
|
||||||
.filter(te => this.isActive(te))
|
if (this.isActive(te) && te.delibirdyBuff != null) {
|
||||||
.map(te => {
|
|
||||||
if (te.delibirdyBuff != null) {
|
|
||||||
ret.push(...te.delibirdyBuff);
|
ret.push(...te.delibirdyBuff);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,39 +485,35 @@ export class TimedEventManager {
|
|||||||
*/
|
*/
|
||||||
getWeather(): WeatherPoolEntry[] {
|
getWeather(): WeatherPoolEntry[] {
|
||||||
const ret: WeatherPoolEntry[] = [];
|
const ret: WeatherPoolEntry[] = [];
|
||||||
timedEvents
|
for (const te of timedEvents) {
|
||||||
.filter(te => this.isActive(te))
|
if (this.isActive(te) && te.weather != null) {
|
||||||
.map(te => {
|
|
||||||
if (te.weather != null) {
|
|
||||||
ret.push(...te.weather);
|
ret.push(...te.weather);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllMysteryEncounterChanges(): EventMysteryEncounterTier[] {
|
getAllMysteryEncounterChanges(): EventMysteryEncounterTier[] {
|
||||||
const ret: EventMysteryEncounterTier[] = [];
|
const ret: EventMysteryEncounterTier[] = [];
|
||||||
timedEvents
|
for (const te of timedEvents) {
|
||||||
.filter(te => this.isActive(te))
|
if (this.isActive(te) && te.mysteryEncounterTierChanges != null) {
|
||||||
.map(te => {
|
|
||||||
if (te.mysteryEncounterTierChanges != null) {
|
|
||||||
ret.push(...te.mysteryEncounterTierChanges);
|
ret.push(...te.mysteryEncounterTierChanges);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
getEventMysteryEncountersDisabled(): MysteryEncounterType[] {
|
getEventMysteryEncountersDisabled(): MysteryEncounterType[] {
|
||||||
const ret: MysteryEncounterType[] = [];
|
const ret: MysteryEncounterType[] = [];
|
||||||
timedEvents
|
for (const te of timedEvents) {
|
||||||
.filter(te => this.isActive(te) && te.mysteryEncounterTierChanges != null)
|
if (this.isActive(te) && te.mysteryEncounterTierChanges != null) {
|
||||||
.map(te => {
|
for (const metc of te.mysteryEncounterTierChanges) {
|
||||||
te.mysteryEncounterTierChanges?.map(metc => {
|
|
||||||
if (metc.disable) {
|
if (metc.disable) {
|
||||||
ret.push(metc.mysteryEncounter);
|
ret.push(metc.mysteryEncounter);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,15 +522,15 @@ export class TimedEventManager {
|
|||||||
normal: MysteryEncounterTier,
|
normal: MysteryEncounterTier,
|
||||||
): MysteryEncounterTier {
|
): MysteryEncounterTier {
|
||||||
let ret = normal;
|
let ret = normal;
|
||||||
timedEvents
|
for (const te of timedEvents) {
|
||||||
.filter(te => this.isActive(te) && te.mysteryEncounterTierChanges != null)
|
if (this.isActive(te) && te.mysteryEncounterTierChanges != null) {
|
||||||
.map(te => {
|
for (const metc of te.mysteryEncounterTierChanges) {
|
||||||
te.mysteryEncounterTierChanges?.map(metc => {
|
|
||||||
if (metc.mysteryEncounter === encounterType) {
|
if (metc.mysteryEncounter === encounterType) {
|
||||||
ret = metc.tier ?? normal;
|
ret = metc.tier ?? normal;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -551,15 +544,16 @@ export class TimedEventManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getEventLuckBoostedSpecies(): SpeciesId[] {
|
getEventLuckBoostedSpecies(): SpeciesId[] {
|
||||||
const ret: SpeciesId[] = [];
|
const ret = new Set<SpeciesId>();
|
||||||
timedEvents
|
|
||||||
.filter(te => this.isActive(te))
|
for (const te of timedEvents) {
|
||||||
.map(te => {
|
if (this.isActive(te) && te.luckBoostedSpecies != null) {
|
||||||
if (te.luckBoostedSpecies != null) {
|
for (const s of te.luckBoostedSpecies) {
|
||||||
ret.push(...te.luckBoostedSpecies.filter(s => !ret.includes(s)));
|
ret.add(s);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return ret;
|
}
|
||||||
|
return Array.from(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
areFusionsBoosted(): boolean {
|
areFusionsBoosted(): boolean {
|
||||||
@ -574,45 +568,50 @@ export class TimedEventManager {
|
|||||||
*/
|
*/
|
||||||
getFixedBattleEventRewards(wave: number): string[] {
|
getFixedBattleEventRewards(wave: number): string[] {
|
||||||
const ret: string[] = [];
|
const ret: string[] = [];
|
||||||
timedEvents
|
for (const te of timedEvents) {
|
||||||
.filter(te => this.isActive(te) && te.classicWaveRewards != null)
|
if (this.isActive(te) && te.classicWaveRewards != null) {
|
||||||
.map(te => {
|
ret.push(...te.classicWaveRewards.filter(cwr => cwr.wave === wave).map(cwr => cwr.type));
|
||||||
ret.push(...te.classicWaveRewards!.filter(cwr => cwr.wave === wave).map(cwr => cwr.type));
|
}
|
||||||
});
|
}
|
||||||
return ret;
|
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 {
|
getClassicTrainerShinyChance(): number {
|
||||||
let ret = 0;
|
let ret = 0;
|
||||||
const tsEvents = timedEvents.filter(te => this.isActive(te) && te.trainerShinyChance != null);
|
for (const te of timedEvents) {
|
||||||
tsEvents.map(t => (ret += t.trainerShinyChance!));
|
const shinyChance = te.trainerShinyChance;
|
||||||
|
if (shinyChance && this.isActive(te)) {
|
||||||
|
ret += shinyChance;
|
||||||
|
}
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
getEventBgmReplacement(bgm: string): string {
|
getEventBgmReplacement(bgm: string): string {
|
||||||
let ret = bgm;
|
let ret = bgm;
|
||||||
timedEvents.map(te => {
|
for (const te of timedEvents) {
|
||||||
if (this.isActive(te) && te.music != null) {
|
if (this.isActive(te) && te.music != null) {
|
||||||
te.music.map(mr => {
|
for (const mr of te.music) {
|
||||||
if (mr[0] === bgm) {
|
if (mr[0] === bgm) {
|
||||||
console.log(`it is ${te.name} so instead of ${mr[0]} we play ${mr[1]}`);
|
console.log(`it is ${te.name} so instead of ${mr[0]} we play ${mr[1]}`);
|
||||||
ret = mr[1];
|
ret = mr[1];
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
return ret;
|
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 {
|
startEventChallenges(): void {
|
||||||
const challenges = this.activeEvent()?.dailyRunChallenges;
|
for (const eventChal of this.activeEvent()?.dailyRunChallenges ?? []) {
|
||||||
challenges?.forEach((eventChal: EventChallenge) =>
|
globalScene.gameMode.setChallengeValue(eventChal.challenge, eventChal.value);
|
||||||
globalScene.gameMode.setChallengeValue(eventChal.challenge, eventChal.value),
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,8 +240,8 @@ export class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
private passive: AbilityId;
|
private passive: AbilityId;
|
||||||
private hasPassive: boolean;
|
private hasPassive: boolean;
|
||||||
private hasAbilities: number[];
|
private hasAbilities: number[];
|
||||||
private biomes: BiomeTierTod[];
|
private biomes: readonly BiomeTierTod[];
|
||||||
private preBiomes: BiomeTierTod[];
|
private preBiomes: readonly BiomeTierTod[];
|
||||||
private baseStats: number[];
|
private baseStats: number[];
|
||||||
private baseTotal: number;
|
private baseTotal: number;
|
||||||
private evolutions: SpeciesFormEvolution[];
|
private evolutions: SpeciesFormEvolution[];
|
||||||
@ -893,7 +893,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function to ensure that forms appear in the appropriate biome and tod
|
// 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) {
|
if (speciesId === SpeciesId.BURMY || speciesId === SpeciesId.WORMADAM) {
|
||||||
return biomes.filter(b => {
|
return biomes.filter(b => {
|
||||||
const formIndex = (() => {
|
const formIndex = (() => {
|
||||||
|
@ -16,22 +16,23 @@ interface hasPokemon {
|
|||||||
* @returns The sorted array of {@linkcode Pokemon}
|
* @returns The sorted array of {@linkcode Pokemon}
|
||||||
*/
|
*/
|
||||||
export function sortInSpeedOrder<T extends Pokemon | hasPokemon>(pokemonList: T[], shuffleFirst = true): T[] {
|
export function sortInSpeedOrder<T extends Pokemon | hasPokemon>(pokemonList: T[], shuffleFirst = true): T[] {
|
||||||
pokemonList = shuffleFirst ? shufflePokemonList(pokemonList) : pokemonList;
|
if (shuffleFirst) {
|
||||||
|
shufflePokemonList(pokemonList);
|
||||||
|
}
|
||||||
sortBySpeed(pokemonList);
|
sortBySpeed(pokemonList);
|
||||||
return pokemonList;
|
return pokemonList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Shuffle the list of pokemon *in place*
|
||||||
* @param pokemonList - The array of Pokemon or objects containing Pokemon
|
* @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[] {
|
function shufflePokemonList<T extends Pokemon | hasPokemon>(pokemonList: T[]): T[] {
|
||||||
// This is seeded with the current turn to prevent an inconsistency where it
|
// This is seeded with the current turn to prevent an inconsistency where it
|
||||||
// was varying based on how long since you last reloaded
|
// was varying based on how long since you last reloaded
|
||||||
globalScene.executeWithSeedOffset(
|
globalScene.executeWithSeedOffset(
|
||||||
() => {
|
() => randSeedShuffle(pokemonList),
|
||||||
pokemonList = randSeedShuffle(pokemonList);
|
|
||||||
},
|
|
||||||
globalScene.currentBattle.turn * 1000 + pokemonList.length,
|
globalScene.currentBattle.turn * 1000 + pokemonList.length,
|
||||||
globalScene.waveSeed,
|
globalScene.waveSeed,
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user