mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-21 00:52:47 +02:00
* global timed event manager * more * Music change * Add AFD track loop points * Add AFD music tracks * changed music for afd * Enable Seasonal Splash Text, adjust event values * Add daily run challenge support * update event date, change trainer shiny chance to 20% * add banners lol * fix activeeventhasbanner function * Fix banner * Update locales submodule --------- Co-authored-by: AJ Fontaine <fontbane@gmail.com> Co-authored-by: damocleas <damocleas25@gmail.com> Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> Co-authored-by: Dean <me@deann.dev> Co-authored-by: AJ Fontaine <36677462+Fontbane@users.noreply.github.com>
3739 lines
128 KiB
TypeScript
3739 lines
128 KiB
TypeScript
import { globalScene } from "#app/global-scene";
|
|
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
|
|
import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms";
|
|
import { getBerryEffectDescription, getBerryName } from "#app/data/berry";
|
|
import { allMoves, AttackMove } from "#app/data/moves/move";
|
|
import { getNatureName, getNatureStatMultiplier } from "#app/data/nature";
|
|
import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball";
|
|
import {
|
|
FormChangeItem,
|
|
pokemonFormChanges,
|
|
SpeciesFormChangeCondition,
|
|
SpeciesFormChangeItemTrigger,
|
|
} from "#app/data/pokemon-forms";
|
|
import { getStatusEffectDescriptor } from "#app/data/status-effect";
|
|
import { PokemonType } from "#enums/pokemon-type";
|
|
import type { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
|
import type Pokemon from "#app/field/pokemon";
|
|
import { getPokemonNameWithAffix } from "#app/messages";
|
|
import {
|
|
AddPokeballModifier,
|
|
AddVoucherModifier,
|
|
AttackTypeBoosterModifier,
|
|
BaseStatModifier,
|
|
BerryModifier,
|
|
BoostBugSpawnModifier,
|
|
BypassSpeedChanceModifier,
|
|
ContactHeldItemTransferChanceModifier,
|
|
CritBoosterModifier,
|
|
DamageMoneyRewardModifier,
|
|
DoubleBattleChanceBoosterModifier,
|
|
EnemyAttackStatusEffectChanceModifier,
|
|
EnemyDamageBoosterModifier,
|
|
EnemyDamageReducerModifier,
|
|
EnemyEndureChanceModifier,
|
|
EnemyFusionChanceModifier,
|
|
EnemyStatusEffectHealChanceModifier,
|
|
EnemyTurnHealModifier,
|
|
EvolutionItemModifier,
|
|
EvolutionStatBoosterModifier,
|
|
EvoTrackerModifier,
|
|
ExpBalanceModifier,
|
|
ExpBoosterModifier,
|
|
ExpShareModifier,
|
|
ExtraModifierModifier,
|
|
FlinchChanceModifier,
|
|
FusePokemonModifier,
|
|
GigantamaxAccessModifier,
|
|
HealingBoosterModifier,
|
|
HealShopCostModifier,
|
|
HiddenAbilityRateBoosterModifier,
|
|
HitHealModifier,
|
|
IvScannerModifier,
|
|
LevelIncrementBoosterModifier,
|
|
LockModifierTiersModifier,
|
|
MapModifier,
|
|
MegaEvolutionAccessModifier,
|
|
MoneyInterestModifier,
|
|
MoneyMultiplierModifier,
|
|
MoneyRewardModifier,
|
|
MultipleParticipantExpBonusModifier,
|
|
PokemonAllMovePpRestoreModifier,
|
|
PokemonBaseStatFlatModifier,
|
|
PokemonBaseStatTotalModifier,
|
|
PokemonExpBoosterModifier,
|
|
PokemonFormChangeItemModifier,
|
|
PokemonFriendshipBoosterModifier,
|
|
PokemonHeldItemModifier,
|
|
PokemonHpRestoreModifier,
|
|
PokemonIncrementingStatModifier,
|
|
PokemonInstantReviveModifier,
|
|
PokemonLevelIncrementModifier,
|
|
PokemonMoveAccuracyBoosterModifier,
|
|
PokemonMultiHitModifier,
|
|
PokemonNatureChangeModifier,
|
|
PokemonNatureWeightModifier,
|
|
PokemonPpRestoreModifier,
|
|
PokemonPpUpModifier,
|
|
PokemonStatusHealModifier,
|
|
PreserveBerryModifier,
|
|
RememberMoveModifier,
|
|
ResetNegativeStatStageModifier,
|
|
ShinyRateBoosterModifier,
|
|
SpeciesCritBoosterModifier,
|
|
SpeciesStatBoosterModifier,
|
|
SurviveDamageModifier,
|
|
SwitchEffectTransferModifier,
|
|
TempCritBoosterModifier,
|
|
TempStatStageBoosterModifier,
|
|
TerastallizeAccessModifier,
|
|
TerrastalizeModifier,
|
|
TmModifier,
|
|
TurnHealModifier,
|
|
TurnHeldItemTransferModifier,
|
|
TurnStatusEffectModifier,
|
|
type EnemyPersistentModifier,
|
|
type Modifier,
|
|
type PersistentModifier,
|
|
TempExtraModifierModifier,
|
|
CriticalCatchChanceBoosterModifier,
|
|
FieldEffectModifier,
|
|
} from "#app/modifier/modifier";
|
|
import { ModifierTier } from "#app/modifier/modifier-tier";
|
|
import Overrides from "#app/overrides";
|
|
import { Unlockables } from "#app/system/unlockables";
|
|
import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system/voucher";
|
|
import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler";
|
|
import PartyUiHandler from "#app/ui/party-ui-handler";
|
|
import { getModifierTierTextTint } from "#app/ui/text";
|
|
import {
|
|
formatMoney,
|
|
getEnumKeys,
|
|
getEnumValues,
|
|
isNullOrUndefined,
|
|
NumberHolder,
|
|
padInt,
|
|
randSeedInt,
|
|
} from "#app/utils";
|
|
import { Abilities } from "#enums/abilities";
|
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
|
import { BerryType } from "#enums/berry-type";
|
|
import { Moves } from "#enums/moves";
|
|
import { Nature } from "#enums/nature";
|
|
import { PokeballType } from "#enums/pokeball";
|
|
import { Species } from "#enums/species";
|
|
import { SpeciesFormKey } from "#enums/species-form-key";
|
|
import type { PermanentStat, TempBattleStat } from "#enums/stat";
|
|
import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat";
|
|
import { StatusEffect } from "#enums/status-effect";
|
|
import i18next from "i18next";
|
|
import { timedEventManager } from "#app/global-event-manager";
|
|
|
|
const outputModifierData = false;
|
|
const useMaxWeightForOutput = false;
|
|
|
|
export enum ModifierPoolType {
|
|
PLAYER,
|
|
WILD,
|
|
TRAINER,
|
|
ENEMY_BUFF,
|
|
DAILY_STARTER,
|
|
}
|
|
|
|
type NewModifierFunc = (type: ModifierType, args: any[]) => Modifier;
|
|
|
|
export class ModifierType {
|
|
public id: string;
|
|
public localeKey: string;
|
|
public iconImage: string;
|
|
public group: string;
|
|
public soundName: string;
|
|
public tier: ModifierTier;
|
|
protected newModifierFunc: NewModifierFunc | null;
|
|
|
|
constructor(
|
|
localeKey: string | null,
|
|
iconImage: string | null,
|
|
newModifierFunc: NewModifierFunc | null,
|
|
group?: string,
|
|
soundName?: string,
|
|
) {
|
|
this.localeKey = localeKey!; // TODO: is this bang correct?
|
|
this.iconImage = iconImage!; // TODO: is this bang correct?
|
|
this.group = group!; // TODO: is this bang correct?
|
|
this.soundName = soundName ?? "se/restore";
|
|
this.newModifierFunc = newModifierFunc;
|
|
}
|
|
|
|
get name(): string {
|
|
return i18next.t(`${this.localeKey}.name` as any);
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t(`${this.localeKey}.description` as any);
|
|
}
|
|
|
|
setTier(tier: ModifierTier): void {
|
|
this.tier = tier;
|
|
}
|
|
|
|
getOrInferTier(poolType: ModifierPoolType = ModifierPoolType.PLAYER): ModifierTier | null {
|
|
if (this.tier) {
|
|
return this.tier;
|
|
}
|
|
if (!this.id) {
|
|
return null;
|
|
}
|
|
let poolTypes: ModifierPoolType[];
|
|
switch (poolType) {
|
|
case ModifierPoolType.PLAYER:
|
|
poolTypes = [poolType, ModifierPoolType.TRAINER, ModifierPoolType.WILD];
|
|
break;
|
|
case ModifierPoolType.WILD:
|
|
poolTypes = [poolType, ModifierPoolType.PLAYER, ModifierPoolType.TRAINER];
|
|
break;
|
|
case ModifierPoolType.TRAINER:
|
|
poolTypes = [poolType, ModifierPoolType.PLAYER, ModifierPoolType.WILD];
|
|
break;
|
|
default:
|
|
poolTypes = [poolType];
|
|
break;
|
|
}
|
|
// Try multiple pool types in case of stolen items
|
|
for (const type of poolTypes) {
|
|
const pool = getModifierPoolForType(type);
|
|
for (const tier of getEnumValues(ModifierTier)) {
|
|
if (!pool.hasOwnProperty(tier)) {
|
|
continue;
|
|
}
|
|
if (pool[tier].find(m => (m as WeightedModifierType).modifierType.id === this.id)) {
|
|
return (this.tier = tier);
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Populates item id for ModifierType instance
|
|
* @param func
|
|
*/
|
|
withIdFromFunc(func: ModifierTypeFunc): ModifierType {
|
|
this.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === func)!; // TODO: is this bang correct?
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Populates item tier for ModifierType instance
|
|
* Tier is a necessary field for items that appear in player shop (determines the Pokeball visual they use)
|
|
* To find the tier, this function performs a reverse lookup of the item type in modifier pools
|
|
* It checks the weight of the item and will use the first tier for which the weight is greater than 0
|
|
* This is to allow items to be in multiple item pools depending on the conditions, for example for events
|
|
* If all tiers have a weight of 0 for the item, the first tier where the item was found is used
|
|
* @param poolType Default 'ModifierPoolType.PLAYER'. Which pool to lookup item tier from
|
|
* @param party optional. Needed to check the weight of modifiers with conditional weight (see {@linkcode WeightedModifierTypeWeightFunc})
|
|
* if not provided or empty, the weight check will be ignored
|
|
* @param rerollCount Default `0`. Used to check the weight of modifiers with conditional weight (see {@linkcode WeightedModifierTypeWeightFunc})
|
|
*/
|
|
withTierFromPool(
|
|
poolType: ModifierPoolType = ModifierPoolType.PLAYER,
|
|
party?: PlayerPokemon[],
|
|
rerollCount = 0,
|
|
): ModifierType {
|
|
let defaultTier: undefined | ModifierTier;
|
|
for (const tier of Object.values(getModifierPoolForType(poolType))) {
|
|
for (const modifier of tier) {
|
|
if (this.id === modifier.modifierType.id) {
|
|
let weight: number;
|
|
if (modifier.weight instanceof Function) {
|
|
weight = party ? modifier.weight(party, rerollCount) : 0;
|
|
} else {
|
|
weight = modifier.weight;
|
|
}
|
|
if (weight > 0) {
|
|
this.tier = modifier.modifierType.tier;
|
|
return this;
|
|
}
|
|
if (isNullOrUndefined(defaultTier)) {
|
|
// If weight is 0, keep track of the first tier where the item was found
|
|
defaultTier = modifier.modifierType.tier;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Didn't find a pool with weight > 0, fallback to first tier where the item was found, if any
|
|
if (defaultTier) {
|
|
this.tier = defaultTier;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
newModifier(...args: any[]): Modifier | null {
|
|
// biome-ignore lint/complexity/useOptionalChain: Changing to optional would coerce null return into undefined
|
|
return this.newModifierFunc && this.newModifierFunc(this, args);
|
|
}
|
|
}
|
|
|
|
type ModifierTypeGeneratorFunc = (party: Pokemon[], pregenArgs?: any[]) => ModifierType | null;
|
|
|
|
export class ModifierTypeGenerator extends ModifierType {
|
|
private genTypeFunc: ModifierTypeGeneratorFunc;
|
|
|
|
constructor(genTypeFunc: ModifierTypeGeneratorFunc) {
|
|
super(null, null, null);
|
|
this.genTypeFunc = genTypeFunc;
|
|
}
|
|
|
|
generateType(party: Pokemon[], pregenArgs?: any[]) {
|
|
const ret = this.genTypeFunc(party, pregenArgs);
|
|
if (ret) {
|
|
ret.id = this.id;
|
|
ret.setTier(this.tier);
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
export interface GeneratedPersistentModifierType {
|
|
getPregenArgs(): any[];
|
|
}
|
|
|
|
class AddPokeballModifierType extends ModifierType {
|
|
private pokeballType: PokeballType;
|
|
private count: number;
|
|
|
|
constructor(iconImage: string, pokeballType: PokeballType, count: number) {
|
|
super("", iconImage, (_type, _args) => new AddPokeballModifier(this, pokeballType, count), "pb", "se/pb_bounce_1");
|
|
this.pokeballType = pokeballType;
|
|
this.count = count;
|
|
}
|
|
|
|
get name(): string {
|
|
return i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", {
|
|
modifierCount: this.count,
|
|
pokeballName: getPokeballName(this.pokeballType),
|
|
});
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.AddPokeballModifierType.description", {
|
|
modifierCount: this.count,
|
|
pokeballName: getPokeballName(this.pokeballType),
|
|
catchRate:
|
|
getPokeballCatchMultiplier(this.pokeballType) > -1
|
|
? `${getPokeballCatchMultiplier(this.pokeballType)}x`
|
|
: "100%",
|
|
pokeballAmount: `${globalScene.pokeballCounts[this.pokeballType]}`,
|
|
});
|
|
}
|
|
}
|
|
|
|
class AddVoucherModifierType extends ModifierType {
|
|
private voucherType: VoucherType;
|
|
private count: number;
|
|
|
|
constructor(voucherType: VoucherType, count: number) {
|
|
super(
|
|
"",
|
|
getVoucherTypeIcon(voucherType),
|
|
(_type, _args) => new AddVoucherModifier(this, voucherType, count),
|
|
"voucher",
|
|
);
|
|
this.count = count;
|
|
this.voucherType = voucherType;
|
|
}
|
|
|
|
get name(): string {
|
|
return i18next.t("modifierType:ModifierType.AddVoucherModifierType.name", {
|
|
modifierCount: this.count,
|
|
voucherTypeName: getVoucherTypeName(this.voucherType),
|
|
});
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.AddVoucherModifierType.description", {
|
|
modifierCount: this.count,
|
|
voucherTypeName: getVoucherTypeName(this.voucherType),
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PokemonModifierType extends ModifierType {
|
|
public selectFilter: PokemonSelectFilter | undefined;
|
|
|
|
constructor(
|
|
localeKey: string,
|
|
iconImage: string,
|
|
newModifierFunc: NewModifierFunc,
|
|
selectFilter?: PokemonSelectFilter,
|
|
group?: string,
|
|
soundName?: string,
|
|
) {
|
|
super(localeKey, iconImage, newModifierFunc, group, soundName);
|
|
|
|
this.selectFilter = selectFilter;
|
|
}
|
|
}
|
|
|
|
export class PokemonHeldItemModifierType extends PokemonModifierType {
|
|
constructor(
|
|
localeKey: string,
|
|
iconImage: string,
|
|
newModifierFunc: NewModifierFunc,
|
|
group?: string,
|
|
soundName?: string,
|
|
) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
newModifierFunc,
|
|
(pokemon: PlayerPokemon) => {
|
|
const dummyModifier = this.newModifier(pokemon);
|
|
const matchingModifier = globalScene.findModifier(
|
|
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(dummyModifier),
|
|
) as PokemonHeldItemModifier;
|
|
const maxStackCount = dummyModifier.getMaxStackCount();
|
|
if (!maxStackCount) {
|
|
return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.inoperable", {
|
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
|
});
|
|
}
|
|
if (matchingModifier && matchingModifier.stackCount === maxStackCount) {
|
|
return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.tooMany", {
|
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
|
});
|
|
}
|
|
return null;
|
|
},
|
|
group,
|
|
soundName,
|
|
);
|
|
}
|
|
|
|
newModifier(...args: any[]): PokemonHeldItemModifier {
|
|
return super.newModifier(...args) as PokemonHeldItemModifier;
|
|
}
|
|
}
|
|
|
|
export class TerastallizeModifierType extends PokemonModifierType {
|
|
private teraType: PokemonType;
|
|
|
|
constructor(teraType: PokemonType) {
|
|
super(
|
|
"",
|
|
`${PokemonType[teraType].toLowerCase()}_tera_shard`,
|
|
(type, args) => new TerrastalizeModifier(type as TerastallizeModifierType, (args[0] as Pokemon).id, teraType),
|
|
(pokemon: PlayerPokemon) => {
|
|
if (
|
|
[pokemon.species.speciesId, pokemon.fusionSpecies?.speciesId].filter(
|
|
s => s === Species.TERAPAGOS || s === Species.OGERPON || s === Species.SHEDINJA,
|
|
).length > 0
|
|
) {
|
|
return PartyUiHandler.NoEffectMessage;
|
|
}
|
|
return null;
|
|
},
|
|
"tera_shard",
|
|
);
|
|
|
|
this.teraType = teraType;
|
|
}
|
|
|
|
get name(): string {
|
|
return i18next.t("modifierType:ModifierType.TerastallizeModifierType.name", {
|
|
teraType: i18next.t(`pokemonInfo:Type.${PokemonType[this.teraType]}`),
|
|
});
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.TerastallizeModifierType.description", {
|
|
teraType: i18next.t(`pokemonInfo:Type.${PokemonType[this.teraType]}`),
|
|
});
|
|
}
|
|
|
|
getPregenArgs(): any[] {
|
|
return [this.teraType];
|
|
}
|
|
}
|
|
|
|
export class PokemonHpRestoreModifierType extends PokemonModifierType {
|
|
protected restorePoints: number;
|
|
protected restorePercent: number;
|
|
protected healStatus: boolean;
|
|
|
|
constructor(
|
|
localeKey: string,
|
|
iconImage: string,
|
|
restorePoints: number,
|
|
restorePercent: number,
|
|
healStatus = false,
|
|
newModifierFunc?: NewModifierFunc,
|
|
selectFilter?: PokemonSelectFilter,
|
|
group?: string,
|
|
) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
newModifierFunc ||
|
|
((_type, args) =>
|
|
new PokemonHpRestoreModifier(
|
|
this,
|
|
(args[0] as PlayerPokemon).id,
|
|
this.restorePoints,
|
|
this.restorePercent,
|
|
this.healStatus,
|
|
false,
|
|
)),
|
|
selectFilter ||
|
|
((pokemon: PlayerPokemon) => {
|
|
if (
|
|
!pokemon.hp ||
|
|
(pokemon.isFullHp() && (!this.healStatus || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED))))
|
|
) {
|
|
return PartyUiHandler.NoEffectMessage;
|
|
}
|
|
return null;
|
|
}),
|
|
group || "potion",
|
|
);
|
|
|
|
this.restorePoints = restorePoints;
|
|
this.restorePercent = restorePercent;
|
|
this.healStatus = healStatus;
|
|
}
|
|
|
|
getDescription(): string {
|
|
return this.restorePoints
|
|
? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.description", {
|
|
restorePoints: this.restorePoints,
|
|
restorePercent: this.restorePercent,
|
|
})
|
|
: this.healStatus
|
|
? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.extra.fullyWithStatus")
|
|
: i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.extra.fully");
|
|
}
|
|
}
|
|
|
|
export class PokemonReviveModifierType extends PokemonHpRestoreModifierType {
|
|
constructor(localeKey: string, iconImage: string, restorePercent: number) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
0,
|
|
restorePercent,
|
|
false,
|
|
(_type, args) =>
|
|
new PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, 0, this.restorePercent, false, true),
|
|
(pokemon: PlayerPokemon) => {
|
|
if (!pokemon.isFainted()) {
|
|
return PartyUiHandler.NoEffectMessage;
|
|
}
|
|
return null;
|
|
},
|
|
"revive",
|
|
);
|
|
|
|
this.selectFilter = (pokemon: PlayerPokemon) => {
|
|
if (pokemon.hp) {
|
|
return PartyUiHandler.NoEffectMessage;
|
|
}
|
|
return null;
|
|
};
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.PokemonReviveModifierType.description", {
|
|
restorePercent: this.restorePercent,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PokemonStatusHealModifierType extends PokemonModifierType {
|
|
constructor(localeKey: string, iconImage: string) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(_type, args) => new PokemonStatusHealModifier(this, (args[0] as PlayerPokemon).id),
|
|
(pokemon: PlayerPokemon) => {
|
|
if (!pokemon.hp || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED))) {
|
|
return PartyUiHandler.NoEffectMessage;
|
|
}
|
|
return null;
|
|
},
|
|
);
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.PokemonStatusHealModifierType.description");
|
|
}
|
|
}
|
|
|
|
export abstract class PokemonMoveModifierType extends PokemonModifierType {
|
|
public moveSelectFilter: PokemonMoveSelectFilter | undefined;
|
|
|
|
constructor(
|
|
localeKey: string,
|
|
iconImage: string,
|
|
newModifierFunc: NewModifierFunc,
|
|
selectFilter?: PokemonSelectFilter,
|
|
moveSelectFilter?: PokemonMoveSelectFilter,
|
|
group?: string,
|
|
) {
|
|
super(localeKey, iconImage, newModifierFunc, selectFilter, group);
|
|
|
|
this.moveSelectFilter = moveSelectFilter;
|
|
}
|
|
}
|
|
|
|
export class PokemonPpRestoreModifierType extends PokemonMoveModifierType {
|
|
protected restorePoints: number;
|
|
|
|
constructor(localeKey: string, iconImage: string, restorePoints: number) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(_type, args) =>
|
|
new PokemonPpRestoreModifier(this, (args[0] as PlayerPokemon).id, args[1] as number, this.restorePoints),
|
|
(_pokemon: PlayerPokemon) => {
|
|
return null;
|
|
},
|
|
(pokemonMove: PokemonMove) => {
|
|
if (!pokemonMove.ppUsed) {
|
|
return PartyUiHandler.NoEffectMessage;
|
|
}
|
|
return null;
|
|
},
|
|
"ether",
|
|
);
|
|
|
|
this.restorePoints = restorePoints;
|
|
}
|
|
|
|
getDescription(): string {
|
|
return this.restorePoints > -1
|
|
? i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.description", {
|
|
restorePoints: this.restorePoints,
|
|
})
|
|
: i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.extra.fully");
|
|
}
|
|
}
|
|
|
|
export class PokemonAllMovePpRestoreModifierType extends PokemonModifierType {
|
|
protected restorePoints: number;
|
|
|
|
constructor(localeKey: string, iconImage: string, restorePoints: number) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(_type, args) => new PokemonAllMovePpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints),
|
|
(pokemon: PlayerPokemon) => {
|
|
if (!pokemon.getMoveset().filter(m => m.ppUsed).length) {
|
|
return PartyUiHandler.NoEffectMessage;
|
|
}
|
|
return null;
|
|
},
|
|
"elixir",
|
|
);
|
|
|
|
this.restorePoints = restorePoints;
|
|
}
|
|
|
|
getDescription(): string {
|
|
return this.restorePoints > -1
|
|
? i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.description", {
|
|
restorePoints: this.restorePoints,
|
|
})
|
|
: i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.extra.fully");
|
|
}
|
|
}
|
|
|
|
export class PokemonPpUpModifierType extends PokemonMoveModifierType {
|
|
protected upPoints: number;
|
|
|
|
constructor(localeKey: string, iconImage: string, upPoints: number) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(_type, args) => new PokemonPpUpModifier(this, (args[0] as PlayerPokemon).id, args[1] as number, this.upPoints),
|
|
(_pokemon: PlayerPokemon) => {
|
|
return null;
|
|
},
|
|
(pokemonMove: PokemonMove) => {
|
|
if (pokemonMove.getMove().pp < 5 || pokemonMove.ppUp >= 3 || pokemonMove.maxPpOverride) {
|
|
return PartyUiHandler.NoEffectMessage;
|
|
}
|
|
return null;
|
|
},
|
|
"ppUp",
|
|
);
|
|
|
|
this.upPoints = upPoints;
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.PokemonPpUpModifierType.description", { upPoints: this.upPoints });
|
|
}
|
|
}
|
|
|
|
export class PokemonNatureChangeModifierType extends PokemonModifierType {
|
|
protected nature: Nature;
|
|
|
|
constructor(nature: Nature) {
|
|
super(
|
|
"",
|
|
`mint_${
|
|
getEnumKeys(Stat)
|
|
.find(s => getNatureStatMultiplier(nature, Stat[s]) > 1)
|
|
?.toLowerCase() || "neutral"
|
|
}`,
|
|
(_type, args) => new PokemonNatureChangeModifier(this, (args[0] as PlayerPokemon).id, this.nature),
|
|
(pokemon: PlayerPokemon) => {
|
|
if (pokemon.getNature() === this.nature) {
|
|
return PartyUiHandler.NoEffectMessage;
|
|
}
|
|
return null;
|
|
},
|
|
"mint",
|
|
);
|
|
|
|
this.nature = nature;
|
|
}
|
|
|
|
get name(): string {
|
|
return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.name", {
|
|
natureName: getNatureName(this.nature),
|
|
});
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.description", {
|
|
natureName: getNatureName(this.nature, true, true, true),
|
|
});
|
|
}
|
|
}
|
|
|
|
export class RememberMoveModifierType extends PokemonModifierType {
|
|
constructor(localeKey: string, iconImage: string, group?: string) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(type, args) => new RememberMoveModifier(type, (args[0] as PlayerPokemon).id, args[1] as number),
|
|
(pokemon: PlayerPokemon) => {
|
|
if (!pokemon.getLearnableLevelMoves().length) {
|
|
return PartyUiHandler.NoEffectMessage;
|
|
}
|
|
return null;
|
|
},
|
|
group,
|
|
);
|
|
}
|
|
}
|
|
|
|
export class DoubleBattleChanceBoosterModifierType extends ModifierType {
|
|
private maxBattles: number;
|
|
|
|
constructor(localeKey: string, iconImage: string, maxBattles: number) {
|
|
super(localeKey, iconImage, (_type, _args) => new DoubleBattleChanceBoosterModifier(this, maxBattles), "lure");
|
|
|
|
this.maxBattles = maxBattles;
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", {
|
|
battleCount: this.maxBattles,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class TempStatStageBoosterModifierType extends ModifierType implements GeneratedPersistentModifierType {
|
|
private stat: TempBattleStat;
|
|
private nameKey: string;
|
|
private quantityKey: string;
|
|
|
|
constructor(stat: TempBattleStat) {
|
|
const nameKey = TempStatStageBoosterModifierTypeGenerator.items[stat];
|
|
super("", nameKey, (_type, _args) => new TempStatStageBoosterModifier(this, this.stat, 5));
|
|
|
|
this.stat = stat;
|
|
this.nameKey = nameKey;
|
|
this.quantityKey = stat !== Stat.ACC ? "percentage" : "stage";
|
|
}
|
|
|
|
get name(): string {
|
|
return i18next.t(`modifierType:TempStatStageBoosterItem.${this.nameKey}`);
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", {
|
|
stat: i18next.t(getStatKey(this.stat)),
|
|
amount: i18next.t(`modifierType:ModifierType.TempStatStageBoosterModifierType.extra.${this.quantityKey}`),
|
|
});
|
|
}
|
|
|
|
getPregenArgs(): any[] {
|
|
return [this.stat];
|
|
}
|
|
}
|
|
|
|
export class BerryModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType {
|
|
private berryType: BerryType;
|
|
|
|
constructor(berryType: BerryType) {
|
|
super(
|
|
"",
|
|
`${BerryType[berryType].toLowerCase()}_berry`,
|
|
(type, args) => new BerryModifier(type, (args[0] as Pokemon).id, berryType),
|
|
"berry",
|
|
);
|
|
|
|
this.berryType = berryType;
|
|
}
|
|
|
|
get name(): string {
|
|
return getBerryName(this.berryType);
|
|
}
|
|
|
|
getDescription(): string {
|
|
return getBerryEffectDescription(this.berryType);
|
|
}
|
|
|
|
getPregenArgs(): any[] {
|
|
return [this.berryType];
|
|
}
|
|
}
|
|
|
|
enum AttackTypeBoosterItem {
|
|
SILK_SCARF,
|
|
BLACK_BELT,
|
|
SHARP_BEAK,
|
|
POISON_BARB,
|
|
SOFT_SAND,
|
|
HARD_STONE,
|
|
SILVER_POWDER,
|
|
SPELL_TAG,
|
|
METAL_COAT,
|
|
CHARCOAL,
|
|
MYSTIC_WATER,
|
|
MIRACLE_SEED,
|
|
MAGNET,
|
|
TWISTED_SPOON,
|
|
NEVER_MELT_ICE,
|
|
DRAGON_FANG,
|
|
BLACK_GLASSES,
|
|
FAIRY_FEATHER,
|
|
}
|
|
|
|
export class AttackTypeBoosterModifierType
|
|
extends PokemonHeldItemModifierType
|
|
implements GeneratedPersistentModifierType
|
|
{
|
|
public moveType: PokemonType;
|
|
public boostPercent: number;
|
|
|
|
constructor(moveType: PokemonType, boostPercent: number) {
|
|
super(
|
|
"",
|
|
`${AttackTypeBoosterItem[moveType]?.toLowerCase()}`,
|
|
(_type, args) => new AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent),
|
|
);
|
|
|
|
this.moveType = moveType;
|
|
this.boostPercent = boostPercent;
|
|
}
|
|
|
|
get name(): string {
|
|
return i18next.t(`modifierType:AttackTypeBoosterItem.${AttackTypeBoosterItem[this.moveType]?.toLowerCase()}`);
|
|
}
|
|
|
|
getDescription(): string {
|
|
// TODO: Need getTypeName?
|
|
return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", {
|
|
moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`),
|
|
});
|
|
}
|
|
|
|
getPregenArgs(): any[] {
|
|
return [this.moveType];
|
|
}
|
|
}
|
|
|
|
export type SpeciesStatBoosterItem = keyof typeof SpeciesStatBoosterModifierTypeGenerator.items;
|
|
|
|
/**
|
|
* Modifier type for {@linkcode SpeciesStatBoosterModifier}
|
|
* @extends PokemonHeldItemModifierType
|
|
* @implements GeneratedPersistentModifierType
|
|
*/
|
|
export class SpeciesStatBoosterModifierType
|
|
extends PokemonHeldItemModifierType
|
|
implements GeneratedPersistentModifierType
|
|
{
|
|
private key: SpeciesStatBoosterItem;
|
|
|
|
constructor(key: SpeciesStatBoosterItem) {
|
|
const item = SpeciesStatBoosterModifierTypeGenerator.items[key];
|
|
super(
|
|
`modifierType:SpeciesBoosterItem.${key}`,
|
|
key.toLowerCase(),
|
|
(type, args) =>
|
|
new SpeciesStatBoosterModifier(type, (args[0] as Pokemon).id, item.stats, item.multiplier, item.species),
|
|
);
|
|
|
|
this.key = key;
|
|
}
|
|
|
|
getPregenArgs(): any[] {
|
|
return [this.key];
|
|
}
|
|
}
|
|
|
|
export class PokemonLevelIncrementModifierType extends PokemonModifierType {
|
|
constructor(localeKey: string, iconImage: string) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(_type, args) => new PokemonLevelIncrementModifier(this, (args[0] as PlayerPokemon).id),
|
|
(_pokemon: PlayerPokemon) => null,
|
|
);
|
|
}
|
|
|
|
getDescription(): string {
|
|
let levels = 1;
|
|
const hasCandyJar = globalScene.modifiers.find(modifier => modifier instanceof LevelIncrementBoosterModifier);
|
|
if (hasCandyJar) {
|
|
levels += hasCandyJar.stackCount;
|
|
}
|
|
return i18next.t("modifierType:ModifierType.PokemonLevelIncrementModifierType.description", { levels });
|
|
}
|
|
}
|
|
|
|
export class AllPokemonLevelIncrementModifierType extends ModifierType {
|
|
constructor(localeKey: string, iconImage: string) {
|
|
super(localeKey, iconImage, (_type, _args) => new PokemonLevelIncrementModifier(this, -1));
|
|
}
|
|
|
|
getDescription(): string {
|
|
let levels = 1;
|
|
const hasCandyJar = globalScene.modifiers.find(modifier => modifier instanceof LevelIncrementBoosterModifier);
|
|
if (hasCandyJar) {
|
|
levels += hasCandyJar.stackCount;
|
|
}
|
|
return i18next.t("modifierType:ModifierType.AllPokemonLevelIncrementModifierType.description", { levels });
|
|
}
|
|
}
|
|
|
|
export class BaseStatBoosterModifierType
|
|
extends PokemonHeldItemModifierType
|
|
implements GeneratedPersistentModifierType
|
|
{
|
|
private stat: PermanentStat;
|
|
private key: string;
|
|
|
|
constructor(stat: PermanentStat) {
|
|
const key = BaseStatBoosterModifierTypeGenerator.items[stat];
|
|
super("", key, (_type, args) => new BaseStatModifier(this, (args[0] as Pokemon).id, this.stat));
|
|
|
|
this.stat = stat;
|
|
this.key = key;
|
|
}
|
|
|
|
get name(): string {
|
|
return i18next.t(`modifierType:BaseStatBoosterItem.${this.key}`);
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.BaseStatBoosterModifierType.description", {
|
|
stat: i18next.t(getStatKey(this.stat)),
|
|
});
|
|
}
|
|
|
|
getPregenArgs(): any[] {
|
|
return [this.stat];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shuckle Juice item
|
|
*/
|
|
export class PokemonBaseStatTotalModifierType
|
|
extends PokemonHeldItemModifierType
|
|
implements GeneratedPersistentModifierType
|
|
{
|
|
private readonly statModifier: number;
|
|
|
|
constructor(statModifier: number) {
|
|
super(
|
|
"modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE",
|
|
"berry_juice",
|
|
(_type, args) => new PokemonBaseStatTotalModifier(this, (args[0] as Pokemon).id, this.statModifier),
|
|
);
|
|
this.statModifier = statModifier;
|
|
}
|
|
|
|
override getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.PokemonBaseStatTotalModifierType.description", {
|
|
increaseDecrease: i18next.t(
|
|
this.statModifier >= 0
|
|
? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.increase"
|
|
: "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.decrease",
|
|
),
|
|
blessCurse: i18next.t(
|
|
this.statModifier >= 0
|
|
? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.blessed"
|
|
: "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.cursed",
|
|
),
|
|
statValue: this.statModifier,
|
|
});
|
|
}
|
|
|
|
public getPregenArgs(): any[] {
|
|
return [this.statModifier];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Old Gateau item
|
|
*/
|
|
export class PokemonBaseStatFlatModifierType
|
|
extends PokemonHeldItemModifierType
|
|
implements GeneratedPersistentModifierType
|
|
{
|
|
private readonly statModifier: number;
|
|
private readonly stats: Stat[];
|
|
|
|
constructor(statModifier: number, stats: Stat[]) {
|
|
super(
|
|
"modifierType:ModifierType.MYSTERY_ENCOUNTER_OLD_GATEAU",
|
|
"old_gateau",
|
|
(_type, args) => new PokemonBaseStatFlatModifier(this, (args[0] as Pokemon).id, this.statModifier, this.stats),
|
|
);
|
|
this.statModifier = statModifier;
|
|
this.stats = stats;
|
|
}
|
|
|
|
override getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.PokemonBaseStatFlatModifierType.description", {
|
|
stats: this.stats.map(stat => i18next.t(getStatKey(stat))).join("/"),
|
|
statValue: this.statModifier,
|
|
});
|
|
}
|
|
|
|
public getPregenArgs(): any[] {
|
|
return [this.statModifier, this.stats];
|
|
}
|
|
}
|
|
|
|
class AllPokemonFullHpRestoreModifierType extends ModifierType {
|
|
private descriptionKey: string;
|
|
|
|
constructor(localeKey: string, iconImage: string, descriptionKey?: string, newModifierFunc?: NewModifierFunc) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
newModifierFunc || ((_type, _args) => new PokemonHpRestoreModifier(this, -1, 0, 100, false)),
|
|
);
|
|
|
|
this.descriptionKey = descriptionKey!; // TODO: is this bang correct?
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t(
|
|
`${this.descriptionKey || "modifierType:ModifierType.AllPokemonFullHpRestoreModifierType"}.description` as any,
|
|
);
|
|
}
|
|
}
|
|
|
|
class AllPokemonFullReviveModifierType extends AllPokemonFullHpRestoreModifierType {
|
|
constructor(localeKey: string, iconImage: string) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
"modifierType:ModifierType.AllPokemonFullReviveModifierType",
|
|
(_type, _args) => new PokemonHpRestoreModifier(this, -1, 0, 100, false, true),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class MoneyRewardModifierType extends ModifierType {
|
|
private moneyMultiplier: number;
|
|
private moneyMultiplierDescriptorKey: string;
|
|
|
|
constructor(localeKey: string, iconImage: string, moneyMultiplier: number, moneyMultiplierDescriptorKey: string) {
|
|
super(localeKey, iconImage, (_type, _args) => new MoneyRewardModifier(this, moneyMultiplier), "money", "se/buy");
|
|
|
|
this.moneyMultiplier = moneyMultiplier;
|
|
this.moneyMultiplierDescriptorKey = moneyMultiplierDescriptorKey;
|
|
}
|
|
|
|
getDescription(): string {
|
|
const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier));
|
|
globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount);
|
|
const formattedMoney = formatMoney(globalScene.moneyFormat, moneyAmount.value);
|
|
|
|
return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", {
|
|
moneyMultiplier: i18next.t(this.moneyMultiplierDescriptorKey as any),
|
|
moneyAmount: formattedMoney,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class ExpBoosterModifierType extends ModifierType {
|
|
private boostPercent: number;
|
|
|
|
constructor(localeKey: string, iconImage: string, boostPercent: number) {
|
|
super(localeKey, iconImage, () => new ExpBoosterModifier(this, boostPercent));
|
|
|
|
this.boostPercent = boostPercent;
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.ExpBoosterModifierType.description", {
|
|
boostPercent: this.boostPercent,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PokemonExpBoosterModifierType extends PokemonHeldItemModifierType {
|
|
private boostPercent: number;
|
|
|
|
constructor(localeKey: string, iconImage: string, boostPercent: number) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(_type, args) => new PokemonExpBoosterModifier(this, (args[0] as Pokemon).id, boostPercent),
|
|
);
|
|
|
|
this.boostPercent = boostPercent;
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.PokemonExpBoosterModifierType.description", {
|
|
boostPercent: this.boostPercent,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PokemonFriendshipBoosterModifierType extends PokemonHeldItemModifierType {
|
|
constructor(localeKey: string, iconImage: string) {
|
|
super(localeKey, iconImage, (_type, args) => new PokemonFriendshipBoosterModifier(this, (args[0] as Pokemon).id));
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.PokemonFriendshipBoosterModifierType.description");
|
|
}
|
|
}
|
|
|
|
export class PokemonMoveAccuracyBoosterModifierType extends PokemonHeldItemModifierType {
|
|
private amount: number;
|
|
|
|
constructor(localeKey: string, iconImage: string, amount: number, group?: string, soundName?: string) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(_type, args) => new PokemonMoveAccuracyBoosterModifier(this, (args[0] as Pokemon).id, amount),
|
|
group,
|
|
soundName,
|
|
);
|
|
|
|
this.amount = amount;
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.PokemonMoveAccuracyBoosterModifierType.description", {
|
|
accuracyAmount: this.amount,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PokemonMultiHitModifierType extends PokemonHeldItemModifierType {
|
|
constructor(localeKey: string, iconImage: string) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(type, args) => new PokemonMultiHitModifier(type as PokemonMultiHitModifierType, (args[0] as Pokemon).id),
|
|
);
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.PokemonMultiHitModifierType.description");
|
|
}
|
|
}
|
|
|
|
export class TmModifierType extends PokemonModifierType {
|
|
public moveId: Moves;
|
|
|
|
constructor(moveId: Moves) {
|
|
super(
|
|
"",
|
|
`tm_${PokemonType[allMoves[moveId].type].toLowerCase()}`,
|
|
(_type, args) => new TmModifier(this, (args[0] as PlayerPokemon).id),
|
|
(pokemon: PlayerPokemon) => {
|
|
if (
|
|
pokemon.compatibleTms.indexOf(moveId) === -1 ||
|
|
pokemon.getMoveset().filter(m => m.moveId === moveId).length
|
|
) {
|
|
return PartyUiHandler.NoEffectMessage;
|
|
}
|
|
return null;
|
|
},
|
|
"tm",
|
|
);
|
|
|
|
this.moveId = moveId;
|
|
}
|
|
|
|
get name(): string {
|
|
return i18next.t("modifierType:ModifierType.TmModifierType.name", {
|
|
moveId: padInt(Object.keys(tmSpecies).indexOf(this.moveId.toString()) + 1, 3),
|
|
moveName: allMoves[this.moveId].name,
|
|
});
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t(
|
|
globalScene.enableMoveInfo
|
|
? "modifierType:ModifierType.TmModifierTypeWithInfo.description"
|
|
: "modifierType:ModifierType.TmModifierType.description",
|
|
{ moveName: allMoves[this.moveId].name },
|
|
);
|
|
}
|
|
}
|
|
|
|
export class EvolutionItemModifierType extends PokemonModifierType implements GeneratedPersistentModifierType {
|
|
public evolutionItem: EvolutionItem;
|
|
|
|
constructor(evolutionItem: EvolutionItem) {
|
|
super(
|
|
"",
|
|
EvolutionItem[evolutionItem].toLowerCase(),
|
|
(_type, args) => new EvolutionItemModifier(this, (args[0] as PlayerPokemon).id),
|
|
(pokemon: PlayerPokemon) => {
|
|
if (
|
|
pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) &&
|
|
pokemonEvolutions[pokemon.species.speciesId].filter(
|
|
e =>
|
|
e.item === this.evolutionItem &&
|
|
(!e.condition || e.condition.predicate(pokemon)) &&
|
|
(e.preFormKey === null || e.preFormKey === pokemon.getFormKey()),
|
|
).length &&
|
|
pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX
|
|
) {
|
|
return null;
|
|
}
|
|
if (
|
|
pokemon.isFusion() &&
|
|
pokemon.fusionSpecies &&
|
|
pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) &&
|
|
pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(
|
|
e =>
|
|
e.item === this.evolutionItem &&
|
|
(!e.condition || e.condition.predicate(pokemon)) &&
|
|
(e.preFormKey === null || e.preFormKey === pokemon.getFusionFormKey()),
|
|
).length &&
|
|
pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return PartyUiHandler.NoEffectMessage;
|
|
},
|
|
);
|
|
|
|
this.evolutionItem = evolutionItem;
|
|
}
|
|
|
|
get name(): string {
|
|
return i18next.t(`modifierType:EvolutionItem.${EvolutionItem[this.evolutionItem]}`);
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.EvolutionItemModifierType.description");
|
|
}
|
|
|
|
getPregenArgs(): any[] {
|
|
return [this.evolutionItem];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class that represents form changing items
|
|
*/
|
|
export class FormChangeItemModifierType extends PokemonModifierType implements GeneratedPersistentModifierType {
|
|
public formChangeItem: FormChangeItem;
|
|
|
|
constructor(formChangeItem: FormChangeItem) {
|
|
super(
|
|
"",
|
|
FormChangeItem[formChangeItem].toLowerCase(),
|
|
(_type, args) => new PokemonFormChangeItemModifier(this, (args[0] as PlayerPokemon).id, formChangeItem, true),
|
|
(pokemon: PlayerPokemon) => {
|
|
// Make sure the Pokemon has alternate forms
|
|
if (
|
|
pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) &&
|
|
// Get all form changes for this species with an item trigger, including any compound triggers
|
|
pokemonFormChanges[pokemon.species.speciesId]
|
|
.filter(
|
|
fc => fc.trigger.hasTriggerType(SpeciesFormChangeItemTrigger) && fc.preFormKey === pokemon.getFormKey(),
|
|
)
|
|
// Returns true if any form changes match this item
|
|
.flatMap(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger)
|
|
.flatMap(fc => fc.item)
|
|
.includes(this.formChangeItem)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return PartyUiHandler.NoEffectMessage;
|
|
},
|
|
);
|
|
|
|
this.formChangeItem = formChangeItem;
|
|
}
|
|
|
|
get name(): string {
|
|
return i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.formChangeItem]}`);
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.FormChangeItemModifierType.description");
|
|
}
|
|
|
|
getPregenArgs(): any[] {
|
|
return [this.formChangeItem];
|
|
}
|
|
}
|
|
|
|
export class FusePokemonModifierType extends PokemonModifierType {
|
|
constructor(localeKey: string, iconImage: string) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(_type, args) => new FusePokemonModifier(this, (args[0] as PlayerPokemon).id, (args[1] as PlayerPokemon).id),
|
|
(pokemon: PlayerPokemon) => {
|
|
if (pokemon.isFusion()) {
|
|
return PartyUiHandler.NoEffectMessage;
|
|
}
|
|
return null;
|
|
},
|
|
);
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.FusePokemonModifierType.description");
|
|
}
|
|
}
|
|
|
|
class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
|
|
constructor() {
|
|
super((party: Pokemon[], pregenArgs?: any[]) => {
|
|
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) {
|
|
return new AttackTypeBoosterModifierType(pregenArgs[0] as PokemonType, 20);
|
|
}
|
|
|
|
const attackMoveTypes = party.flatMap(p =>
|
|
p
|
|
.getMoveset()
|
|
.map(m => m.getMove())
|
|
.filter(m => m instanceof AttackMove)
|
|
.map(m => m.type),
|
|
);
|
|
if (!attackMoveTypes.length) {
|
|
return null;
|
|
}
|
|
|
|
const attackMoveTypeWeights = new Map<PokemonType, number>();
|
|
let totalWeight = 0;
|
|
for (const t of attackMoveTypes) {
|
|
if (attackMoveTypeWeights.has(t)) {
|
|
if (attackMoveTypeWeights.get(t)! < 3) {
|
|
// attackMoveTypeWeights.has(t) was checked before
|
|
attackMoveTypeWeights.set(t, attackMoveTypeWeights.get(t)! + 1);
|
|
} else {
|
|
continue;
|
|
}
|
|
} else {
|
|
attackMoveTypeWeights.set(t, 1);
|
|
}
|
|
totalWeight++;
|
|
}
|
|
|
|
if (!totalWeight) {
|
|
return null;
|
|
}
|
|
|
|
let type: PokemonType;
|
|
|
|
const randInt = randSeedInt(totalWeight);
|
|
let weight = 0;
|
|
|
|
for (const t of attackMoveTypeWeights.keys()) {
|
|
const typeWeight = attackMoveTypeWeights.get(t)!; // guranteed to be defined
|
|
if (randInt <= weight + typeWeight) {
|
|
type = t;
|
|
break;
|
|
}
|
|
weight += typeWeight;
|
|
}
|
|
|
|
return new AttackTypeBoosterModifierType(type!, 20);
|
|
});
|
|
}
|
|
}
|
|
|
|
class BaseStatBoosterModifierTypeGenerator extends ModifierTypeGenerator {
|
|
public static readonly items: Record<PermanentStat, string> = {
|
|
[Stat.HP]: "hp_up",
|
|
[Stat.ATK]: "protein",
|
|
[Stat.DEF]: "iron",
|
|
[Stat.SPATK]: "calcium",
|
|
[Stat.SPDEF]: "zinc",
|
|
[Stat.SPD]: "carbos",
|
|
};
|
|
|
|
constructor() {
|
|
super((_party: Pokemon[], pregenArgs?: any[]) => {
|
|
if (pregenArgs) {
|
|
return new BaseStatBoosterModifierType(pregenArgs[0]);
|
|
}
|
|
const randStat: PermanentStat = randSeedInt(Stat.SPD + 1);
|
|
return new BaseStatBoosterModifierType(randStat);
|
|
});
|
|
}
|
|
}
|
|
|
|
class TempStatStageBoosterModifierTypeGenerator extends ModifierTypeGenerator {
|
|
public static readonly items: Record<TempBattleStat, string> = {
|
|
[Stat.ATK]: "x_attack",
|
|
[Stat.DEF]: "x_defense",
|
|
[Stat.SPATK]: "x_sp_atk",
|
|
[Stat.SPDEF]: "x_sp_def",
|
|
[Stat.SPD]: "x_speed",
|
|
[Stat.ACC]: "x_accuracy",
|
|
};
|
|
|
|
constructor() {
|
|
super((_party: Pokemon[], pregenArgs?: any[]) => {
|
|
if (pregenArgs && pregenArgs.length === 1 && TEMP_BATTLE_STATS.includes(pregenArgs[0])) {
|
|
return new TempStatStageBoosterModifierType(pregenArgs[0]);
|
|
}
|
|
const randStat: TempBattleStat = randSeedInt(Stat.ACC, Stat.ATK);
|
|
return new TempStatStageBoosterModifierType(randStat);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier type generator for {@linkcode SpeciesStatBoosterModifierType}, which
|
|
* encapsulates the logic for weighting the most useful held item from
|
|
* the current list of {@linkcode items}.
|
|
* @extends ModifierTypeGenerator
|
|
*/
|
|
class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator {
|
|
/** Object comprised of the currently available species-based stat boosting held items */
|
|
public static readonly items = {
|
|
LIGHT_BALL: {
|
|
stats: [Stat.ATK, Stat.SPATK],
|
|
multiplier: 2,
|
|
species: [Species.PIKACHU],
|
|
},
|
|
THICK_CLUB: {
|
|
stats: [Stat.ATK],
|
|
multiplier: 2,
|
|
species: [Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK],
|
|
},
|
|
METAL_POWDER: {
|
|
stats: [Stat.DEF],
|
|
multiplier: 2,
|
|
species: [Species.DITTO],
|
|
},
|
|
QUICK_POWDER: {
|
|
stats: [Stat.SPD],
|
|
multiplier: 2,
|
|
species: [Species.DITTO],
|
|
},
|
|
};
|
|
|
|
constructor() {
|
|
super((party: Pokemon[], pregenArgs?: any[]) => {
|
|
const items = SpeciesStatBoosterModifierTypeGenerator.items;
|
|
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in items) {
|
|
return new SpeciesStatBoosterModifierType(pregenArgs[0] as SpeciesStatBoosterItem);
|
|
}
|
|
|
|
const values = Object.values(items);
|
|
const keys = Object.keys(items);
|
|
const weights = keys.map(() => 0);
|
|
|
|
for (const p of party) {
|
|
const speciesId = p.getSpeciesForm(true).speciesId;
|
|
const fusionSpeciesId = p.isFusion() ? p.getFusionSpeciesForm(true).speciesId : null;
|
|
// TODO: Use commented boolean when Fling is implemented
|
|
const hasFling = false; /* p.getMoveset(true).some(m => m.moveId === Moves.FLING) */
|
|
|
|
for (const i in values) {
|
|
const checkedSpecies = values[i].species;
|
|
const checkedStats = values[i].stats;
|
|
|
|
// If party member already has the item being weighted currently, skip to the next item
|
|
const hasItem = p
|
|
.getHeldItems()
|
|
.some(
|
|
m =>
|
|
m instanceof SpeciesStatBoosterModifier &&
|
|
(m as SpeciesStatBoosterModifier).contains(checkedSpecies[0], checkedStats[0]),
|
|
);
|
|
|
|
if (!hasItem) {
|
|
if (checkedSpecies.includes(speciesId) || (!!fusionSpeciesId && checkedSpecies.includes(fusionSpeciesId))) {
|
|
// Add weight if party member has a matching species or, if applicable, a matching fusion species
|
|
weights[i]++;
|
|
} else if (checkedSpecies.includes(Species.PIKACHU) && hasFling) {
|
|
// Add weight to Light Ball if party member has Fling
|
|
weights[i]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let totalWeight = 0;
|
|
for (const weight of weights) {
|
|
totalWeight += weight;
|
|
}
|
|
|
|
if (totalWeight !== 0) {
|
|
const randInt = randSeedInt(totalWeight, 1);
|
|
let weight = 0;
|
|
|
|
for (const i in weights) {
|
|
if (weights[i] !== 0) {
|
|
const curWeight = weight + weights[i];
|
|
if (randInt <= weight + weights[i]) {
|
|
return new SpeciesStatBoosterModifierType(keys[i] as SpeciesStatBoosterItem);
|
|
}
|
|
weight = curWeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
});
|
|
}
|
|
}
|
|
|
|
class TmModifierTypeGenerator extends ModifierTypeGenerator {
|
|
constructor(tier: ModifierTier) {
|
|
super((party: Pokemon[], pregenArgs?: any[]) => {
|
|
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in Moves) {
|
|
return new TmModifierType(pregenArgs[0] as Moves);
|
|
}
|
|
const partyMemberCompatibleTms = party.map(p => {
|
|
const previousLevelMoves = p.getLearnableLevelMoves();
|
|
return (p as PlayerPokemon).compatibleTms.filter(
|
|
tm => !p.moveset.find(m => m.moveId === tm) && !previousLevelMoves.find(lm => lm === tm),
|
|
);
|
|
});
|
|
const tierUniqueCompatibleTms = partyMemberCompatibleTms
|
|
.flat()
|
|
.filter(tm => tmPoolTiers[tm] === tier)
|
|
.filter(tm => !allMoves[tm].name.endsWith(" (N)"))
|
|
.filter((tm, i, array) => array.indexOf(tm) === i);
|
|
if (!tierUniqueCompatibleTms.length) {
|
|
return null;
|
|
}
|
|
const randTmIndex = randSeedInt(tierUniqueCompatibleTms.length);
|
|
return new TmModifierType(tierUniqueCompatibleTms[randTmIndex]);
|
|
});
|
|
}
|
|
}
|
|
|
|
class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator {
|
|
constructor(rare: boolean) {
|
|
super((party: Pokemon[], pregenArgs?: any[]) => {
|
|
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in EvolutionItem) {
|
|
return new EvolutionItemModifierType(pregenArgs[0] as EvolutionItem);
|
|
}
|
|
|
|
const evolutionItemPool = [
|
|
party
|
|
.filter(
|
|
p =>
|
|
pokemonEvolutions.hasOwnProperty(p.species.speciesId) &&
|
|
(!p.pauseEvolutions || p.species.speciesId === Species.SLOWPOKE || p.species.speciesId === Species.EEVEE),
|
|
)
|
|
.flatMap(p => {
|
|
const evolutions = pokemonEvolutions[p.species.speciesId];
|
|
return evolutions.filter(
|
|
e =>
|
|
e.item !== EvolutionItem.NONE &&
|
|
(e.evoFormKey === null || (e.preFormKey || "") === p.getFormKey()) &&
|
|
(!e.condition || e.condition.predicate(p)),
|
|
);
|
|
}),
|
|
party
|
|
.filter(
|
|
p =>
|
|
p.isFusion() &&
|
|
p.fusionSpecies &&
|
|
pokemonEvolutions.hasOwnProperty(p.fusionSpecies.speciesId) &&
|
|
(!p.pauseEvolutions ||
|
|
p.fusionSpecies.speciesId === Species.SLOWPOKE ||
|
|
p.fusionSpecies.speciesId === Species.EEVEE),
|
|
)
|
|
.flatMap(p => {
|
|
const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId];
|
|
return evolutions.filter(
|
|
e =>
|
|
e.item !== EvolutionItem.NONE &&
|
|
(e.evoFormKey === null || (e.preFormKey || "") === p.getFusionFormKey()) &&
|
|
(!e.condition || e.condition.predicate(p)),
|
|
);
|
|
}),
|
|
]
|
|
.flat()
|
|
.flatMap(e => e.item)
|
|
.filter(i => (!!i && i > 50) === rare);
|
|
|
|
if (!evolutionItemPool.length) {
|
|
return null;
|
|
}
|
|
|
|
return new EvolutionItemModifierType(evolutionItemPool[randSeedInt(evolutionItemPool.length)]!); // TODO: is the bang correct?
|
|
});
|
|
}
|
|
}
|
|
|
|
class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
|
|
constructor(isRareFormChangeItem: boolean) {
|
|
super((party: Pokemon[], pregenArgs?: any[]) => {
|
|
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in FormChangeItem) {
|
|
return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem);
|
|
}
|
|
|
|
const formChangeItemPool = [
|
|
...new Set(
|
|
party
|
|
.filter(p => pokemonFormChanges.hasOwnProperty(p.species.speciesId))
|
|
.flatMap(p => {
|
|
const formChanges = pokemonFormChanges[p.species.speciesId];
|
|
let formChangeItemTriggers = formChanges
|
|
.filter(
|
|
fc =>
|
|
((fc.formKey.indexOf(SpeciesFormKey.MEGA) === -1 &&
|
|
fc.formKey.indexOf(SpeciesFormKey.PRIMAL) === -1) ||
|
|
globalScene.getModifiers(MegaEvolutionAccessModifier).length) &&
|
|
((fc.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) === -1 &&
|
|
fc.formKey.indexOf(SpeciesFormKey.ETERNAMAX) === -1) ||
|
|
globalScene.getModifiers(GigantamaxAccessModifier).length) &&
|
|
(!fc.conditions.length ||
|
|
fc.conditions.filter(cond => cond instanceof SpeciesFormChangeCondition && cond.predicate(p))
|
|
.length) &&
|
|
fc.preFormKey === p.getFormKey(),
|
|
)
|
|
.map(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger)
|
|
.filter(
|
|
t =>
|
|
t?.active &&
|
|
!globalScene.findModifier(
|
|
m =>
|
|
m instanceof PokemonFormChangeItemModifier &&
|
|
m.pokemonId === p.id &&
|
|
m.formChangeItem === t.item,
|
|
),
|
|
);
|
|
|
|
if (p.species.speciesId === Species.NECROZMA) {
|
|
// technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break...
|
|
let foundULTRA_Z = false,
|
|
foundN_LUNA = false,
|
|
foundN_SOLAR = false;
|
|
formChangeItemTriggers.forEach((fc, _i) => {
|
|
console.log("Checking ", fc.item);
|
|
switch (fc.item) {
|
|
case FormChangeItem.ULTRANECROZIUM_Z:
|
|
foundULTRA_Z = true;
|
|
break;
|
|
case FormChangeItem.N_LUNARIZER:
|
|
foundN_LUNA = true;
|
|
break;
|
|
case FormChangeItem.N_SOLARIZER:
|
|
foundN_SOLAR = true;
|
|
break;
|
|
}
|
|
});
|
|
if (foundULTRA_Z && foundN_LUNA && foundN_SOLAR) {
|
|
// all three items are present -> user hasn't acquired any of the N_*ARIZERs -> block ULTRANECROZIUM_Z acquisition.
|
|
formChangeItemTriggers = formChangeItemTriggers.filter(
|
|
fc => fc.item !== FormChangeItem.ULTRANECROZIUM_Z,
|
|
);
|
|
} else {
|
|
console.log("DID NOT FIND ");
|
|
}
|
|
}
|
|
return formChangeItemTriggers;
|
|
}),
|
|
),
|
|
]
|
|
.flat()
|
|
.flatMap(fc => fc.item)
|
|
.filter(i => (i && i < 100) === isRareFormChangeItem);
|
|
// convert it into a set to remove duplicate values, which can appear when the same species with a potential form change is in the party.
|
|
|
|
if (!formChangeItemPool.length) {
|
|
return null;
|
|
}
|
|
|
|
return new FormChangeItemModifierType(formChangeItemPool[randSeedInt(formChangeItemPool.length)]);
|
|
});
|
|
}
|
|
}
|
|
|
|
export class ContactHeldItemTransferChanceModifierType extends PokemonHeldItemModifierType {
|
|
private chancePercent: number;
|
|
|
|
constructor(localeKey: string, iconImage: string, chancePercent: number, group?: string, soundName?: string) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(type, args) => new ContactHeldItemTransferChanceModifier(type, (args[0] as Pokemon).id, chancePercent),
|
|
group,
|
|
soundName,
|
|
);
|
|
|
|
this.chancePercent = chancePercent;
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.ContactHeldItemTransferChanceModifierType.description", {
|
|
chancePercent: this.chancePercent,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class TurnHeldItemTransferModifierType extends PokemonHeldItemModifierType {
|
|
constructor(localeKey: string, iconImage: string, group?: string, soundName?: string) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(type, args) => new TurnHeldItemTransferModifier(type, (args[0] as Pokemon).id),
|
|
group,
|
|
soundName,
|
|
);
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.TurnHeldItemTransferModifierType.description");
|
|
}
|
|
}
|
|
|
|
export class EnemyAttackStatusEffectChanceModifierType extends ModifierType {
|
|
private chancePercent: number;
|
|
private effect: StatusEffect;
|
|
|
|
constructor(localeKey: string, iconImage: string, chancePercent: number, effect: StatusEffect, stackCount?: number) {
|
|
super(
|
|
localeKey,
|
|
iconImage,
|
|
(type, _args) => new EnemyAttackStatusEffectChanceModifier(type, effect, chancePercent, stackCount),
|
|
"enemy_status_chance",
|
|
);
|
|
|
|
this.chancePercent = chancePercent;
|
|
this.effect = effect;
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.EnemyAttackStatusEffectChanceModifierType.description", {
|
|
chancePercent: this.chancePercent,
|
|
statusEffect: getStatusEffectDescriptor(this.effect),
|
|
});
|
|
}
|
|
}
|
|
|
|
export class EnemyEndureChanceModifierType extends ModifierType {
|
|
private chancePercent: number;
|
|
|
|
constructor(localeKey: string, iconImage: string, chancePercent: number) {
|
|
super(localeKey, iconImage, (type, _args) => new EnemyEndureChanceModifier(type, chancePercent), "enemy_endure");
|
|
|
|
this.chancePercent = chancePercent;
|
|
}
|
|
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.EnemyEndureChanceModifierType.description", {
|
|
chancePercent: this.chancePercent,
|
|
});
|
|
}
|
|
}
|
|
|
|
export type ModifierTypeFunc = () => ModifierType;
|
|
type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number;
|
|
|
|
/**
|
|
* High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on
|
|
* classic and skip an ModifierType if current wave is greater or equal to the one passed down
|
|
* @param wave - Wave where we should stop showing the modifier
|
|
* @param defaultWeight - ModifierType default weight
|
|
* @returns A WeightedModifierTypeWeightFunc
|
|
*/
|
|
function skipInClassicAfterWave(wave: number, defaultWeight: number): WeightedModifierTypeWeightFunc {
|
|
return () => {
|
|
const gameMode = globalScene.gameMode;
|
|
const currentWave = globalScene.currentBattle.waveIndex;
|
|
return gameMode.isClassic && currentWave >= wave ? 0 : defaultWeight;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on
|
|
* classic and it will skip a ModifierType if it is the last wave pull.
|
|
* @param defaultWeight ModifierType default weight
|
|
* @returns A WeightedModifierTypeWeightFunc
|
|
*/
|
|
function skipInLastClassicWaveOrDefault(defaultWeight: number): WeightedModifierTypeWeightFunc {
|
|
return skipInClassicAfterWave(199, defaultWeight);
|
|
}
|
|
|
|
/**
|
|
* High order function that returns a WeightedModifierTypeWeightFunc to ensure Lures don't spawn on Classic 199
|
|
* or if the lure still has over 60% of its duration left
|
|
* @param maxBattles The max battles the lure type in question lasts. 10 for green, 15 for Super, 30 for Max
|
|
* @param weight The desired weight for the lure when it does spawn
|
|
* @returns A WeightedModifierTypeWeightFunc
|
|
*/
|
|
function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTypeWeightFunc {
|
|
return () => {
|
|
const lures = globalScene.getModifiers(DoubleBattleChanceBoosterModifier);
|
|
return !(globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex === 199) &&
|
|
(lures.length === 0 ||
|
|
lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0)
|
|
? weight
|
|
: 0;
|
|
};
|
|
}
|
|
class WeightedModifierType {
|
|
public modifierType: ModifierType;
|
|
public weight: number | WeightedModifierTypeWeightFunc;
|
|
public maxWeight: number | WeightedModifierTypeWeightFunc;
|
|
|
|
constructor(
|
|
modifierTypeFunc: ModifierTypeFunc,
|
|
weight: number | WeightedModifierTypeWeightFunc,
|
|
maxWeight?: number | WeightedModifierTypeWeightFunc,
|
|
) {
|
|
this.modifierType = modifierTypeFunc();
|
|
this.modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc)!; // TODO: is this bang correct?
|
|
this.weight = weight;
|
|
this.maxWeight = maxWeight || (!(weight instanceof Function) ? weight : 0);
|
|
}
|
|
|
|
setTier(tier: ModifierTier) {
|
|
this.modifierType.setTier(tier);
|
|
}
|
|
}
|
|
|
|
type BaseModifierOverride = {
|
|
name: Exclude<ModifierTypeKeys, GeneratorModifierOverride["name"]>;
|
|
count?: number;
|
|
};
|
|
|
|
/** Type for modifiers and held items that are constructed via {@linkcode ModifierTypeGenerator}. */
|
|
export type GeneratorModifierOverride = {
|
|
count?: number;
|
|
} & (
|
|
| {
|
|
name: keyof Pick<typeof modifierTypes, "SPECIES_STAT_BOOSTER">;
|
|
type?: SpeciesStatBoosterItem;
|
|
}
|
|
| {
|
|
name: keyof Pick<typeof modifierTypes, "TEMP_STAT_STAGE_BOOSTER">;
|
|
type?: TempBattleStat;
|
|
}
|
|
| {
|
|
name: keyof Pick<typeof modifierTypes, "BASE_STAT_BOOSTER">;
|
|
type?: Stat;
|
|
}
|
|
| {
|
|
name: keyof Pick<typeof modifierTypes, "MINT">;
|
|
type?: Nature;
|
|
}
|
|
| {
|
|
name: keyof Pick<typeof modifierTypes, "ATTACK_TYPE_BOOSTER" | "TERA_SHARD">;
|
|
type?: PokemonType;
|
|
}
|
|
| {
|
|
name: keyof Pick<typeof modifierTypes, "BERRY">;
|
|
type?: BerryType;
|
|
}
|
|
| {
|
|
name: keyof Pick<typeof modifierTypes, "EVOLUTION_ITEM" | "RARE_EVOLUTION_ITEM">;
|
|
type?: EvolutionItem;
|
|
}
|
|
| {
|
|
name: keyof Pick<typeof modifierTypes, "FORM_CHANGE_ITEM">;
|
|
type?: FormChangeItem;
|
|
}
|
|
| {
|
|
name: keyof Pick<typeof modifierTypes, "TM_COMMON" | "TM_GREAT" | "TM_ULTRA">;
|
|
type?: Moves;
|
|
}
|
|
);
|
|
|
|
/** Type used to construct modifiers and held items for overriding purposes. */
|
|
export type ModifierOverride = GeneratorModifierOverride | BaseModifierOverride;
|
|
|
|
export type ModifierTypeKeys = keyof typeof modifierTypes;
|
|
|
|
export const modifierTypes = {
|
|
POKEBALL: () => new AddPokeballModifierType("pb", PokeballType.POKEBALL, 5),
|
|
GREAT_BALL: () => new AddPokeballModifierType("gb", PokeballType.GREAT_BALL, 5),
|
|
ULTRA_BALL: () => new AddPokeballModifierType("ub", PokeballType.ULTRA_BALL, 5),
|
|
ROGUE_BALL: () => new AddPokeballModifierType("rb", PokeballType.ROGUE_BALL, 5),
|
|
MASTER_BALL: () => new AddPokeballModifierType("mb", PokeballType.MASTER_BALL, 1),
|
|
|
|
RARE_CANDY: () => new PokemonLevelIncrementModifierType("modifierType:ModifierType.RARE_CANDY", "rare_candy"),
|
|
RARER_CANDY: () => new AllPokemonLevelIncrementModifierType("modifierType:ModifierType.RARER_CANDY", "rarer_candy"),
|
|
|
|
EVOLUTION_ITEM: () => new EvolutionItemModifierTypeGenerator(false),
|
|
RARE_EVOLUTION_ITEM: () => new EvolutionItemModifierTypeGenerator(true),
|
|
FORM_CHANGE_ITEM: () => new FormChangeItemModifierTypeGenerator(false),
|
|
RARE_FORM_CHANGE_ITEM: () => new FormChangeItemModifierTypeGenerator(true),
|
|
|
|
EVOLUTION_TRACKER_GIMMIGHOUL: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL",
|
|
"relic_gold",
|
|
(type, args) => new EvoTrackerModifier(type, (args[0] as Pokemon).id, Species.GIMMIGHOUL, 10),
|
|
),
|
|
|
|
MEGA_BRACELET: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.MEGA_BRACELET",
|
|
"mega_bracelet",
|
|
(type, _args) => new MegaEvolutionAccessModifier(type),
|
|
),
|
|
DYNAMAX_BAND: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.DYNAMAX_BAND",
|
|
"dynamax_band",
|
|
(type, _args) => new GigantamaxAccessModifier(type),
|
|
),
|
|
TERA_ORB: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.TERA_ORB",
|
|
"tera_orb",
|
|
(type, _args) => new TerastallizeAccessModifier(type),
|
|
),
|
|
|
|
MAP: () => new ModifierType("modifierType:ModifierType.MAP", "map", (type, _args) => new MapModifier(type)),
|
|
|
|
POTION: () => new PokemonHpRestoreModifierType("modifierType:ModifierType.POTION", "potion", 20, 10),
|
|
SUPER_POTION: () =>
|
|
new PokemonHpRestoreModifierType("modifierType:ModifierType.SUPER_POTION", "super_potion", 50, 25),
|
|
HYPER_POTION: () =>
|
|
new PokemonHpRestoreModifierType("modifierType:ModifierType.HYPER_POTION", "hyper_potion", 200, 50),
|
|
MAX_POTION: () => new PokemonHpRestoreModifierType("modifierType:ModifierType.MAX_POTION", "max_potion", 0, 100),
|
|
FULL_RESTORE: () =>
|
|
new PokemonHpRestoreModifierType("modifierType:ModifierType.FULL_RESTORE", "full_restore", 0, 100, true),
|
|
|
|
REVIVE: () => new PokemonReviveModifierType("modifierType:ModifierType.REVIVE", "revive", 50),
|
|
MAX_REVIVE: () => new PokemonReviveModifierType("modifierType:ModifierType.MAX_REVIVE", "max_revive", 100),
|
|
|
|
FULL_HEAL: () => new PokemonStatusHealModifierType("modifierType:ModifierType.FULL_HEAL", "full_heal"),
|
|
|
|
SACRED_ASH: () => new AllPokemonFullReviveModifierType("modifierType:ModifierType.SACRED_ASH", "sacred_ash"),
|
|
|
|
REVIVER_SEED: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.REVIVER_SEED",
|
|
"reviver_seed",
|
|
(type, args) => new PokemonInstantReviveModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
WHITE_HERB: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.WHITE_HERB",
|
|
"white_herb",
|
|
(type, args) => new ResetNegativeStatStageModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
|
|
ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.ETHER", "ether", 10),
|
|
MAX_ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.MAX_ETHER", "max_ether", -1),
|
|
|
|
ELIXIR: () => new PokemonAllMovePpRestoreModifierType("modifierType:ModifierType.ELIXIR", "elixir", 10),
|
|
MAX_ELIXIR: () => new PokemonAllMovePpRestoreModifierType("modifierType:ModifierType.MAX_ELIXIR", "max_elixir", -1),
|
|
|
|
PP_UP: () => new PokemonPpUpModifierType("modifierType:ModifierType.PP_UP", "pp_up", 1),
|
|
PP_MAX: () => new PokemonPpUpModifierType("modifierType:ModifierType.PP_MAX", "pp_max", 3),
|
|
|
|
/*REPEL: () => new DoubleBattleChanceBoosterModifierType('Repel', 5),
|
|
SUPER_REPEL: () => new DoubleBattleChanceBoosterModifierType('Super Repel', 10),
|
|
MAX_REPEL: () => new DoubleBattleChanceBoosterModifierType('Max Repel', 25),*/
|
|
|
|
LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.LURE", "lure", 10),
|
|
SUPER_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.SUPER_LURE", "super_lure", 15),
|
|
MAX_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.MAX_LURE", "max_lure", 30),
|
|
|
|
SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterModifierTypeGenerator(),
|
|
|
|
TEMP_STAT_STAGE_BOOSTER: () => new TempStatStageBoosterModifierTypeGenerator(),
|
|
|
|
DIRE_HIT: () =>
|
|
new (class extends ModifierType {
|
|
getDescription(): string {
|
|
return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", {
|
|
stat: i18next.t("modifierType:ModifierType.DIRE_HIT.extra.raises"),
|
|
amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.stage"),
|
|
});
|
|
}
|
|
})("modifierType:ModifierType.DIRE_HIT", "dire_hit", (type, _args) => new TempCritBoosterModifier(type, 5)),
|
|
|
|
BASE_STAT_BOOSTER: () => new BaseStatBoosterModifierTypeGenerator(),
|
|
|
|
ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterModifierTypeGenerator(),
|
|
|
|
MINT: () =>
|
|
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
|
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in Nature) {
|
|
return new PokemonNatureChangeModifierType(pregenArgs[0] as Nature);
|
|
}
|
|
return new PokemonNatureChangeModifierType(randSeedInt(getEnumValues(Nature).length) as Nature);
|
|
}),
|
|
|
|
MYSTICAL_ROCK: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.MYSTICAL_ROCK",
|
|
"mystical_rock",
|
|
(type, args) => new FieldEffectModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
|
|
TERA_SHARD: () =>
|
|
new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => {
|
|
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) {
|
|
return new TerastallizeModifierType(pregenArgs[0] as PokemonType);
|
|
}
|
|
if (!globalScene.getModifiers(TerastallizeAccessModifier).length) {
|
|
return null;
|
|
}
|
|
const teraTypes: PokemonType[] = [];
|
|
for (const p of party) {
|
|
if (!(p.hasSpecies(Species.TERAPAGOS) || p.hasSpecies(Species.OGERPON) || p.hasSpecies(Species.SHEDINJA))) {
|
|
teraTypes.push(p.teraType);
|
|
}
|
|
}
|
|
let excludedType = PokemonType.UNKNOWN;
|
|
if (teraTypes.length > 0 && teraTypes.filter(t => t === teraTypes[0]).length === teraTypes.length) {
|
|
excludedType = teraTypes[0];
|
|
}
|
|
let shardType = randSeedInt(64) ? (randSeedInt(18) as PokemonType) : PokemonType.STELLAR;
|
|
while (shardType === excludedType) {
|
|
shardType = randSeedInt(64) ? (randSeedInt(18) as PokemonType) : PokemonType.STELLAR;
|
|
}
|
|
return new TerastallizeModifierType(shardType);
|
|
}),
|
|
|
|
BERRY: () =>
|
|
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
|
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) {
|
|
return new BerryModifierType(pregenArgs[0] as BerryType);
|
|
}
|
|
const berryTypes = getEnumValues(BerryType);
|
|
let randBerryType: BerryType;
|
|
const rand = randSeedInt(12);
|
|
if (rand < 2) {
|
|
randBerryType = BerryType.SITRUS;
|
|
} else if (rand < 4) {
|
|
randBerryType = BerryType.LUM;
|
|
} else if (rand < 6) {
|
|
randBerryType = BerryType.LEPPA;
|
|
} else {
|
|
randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2];
|
|
}
|
|
return new BerryModifierType(randBerryType);
|
|
}),
|
|
|
|
TM_COMMON: () => new TmModifierTypeGenerator(ModifierTier.COMMON),
|
|
TM_GREAT: () => new TmModifierTypeGenerator(ModifierTier.GREAT),
|
|
TM_ULTRA: () => new TmModifierTypeGenerator(ModifierTier.ULTRA),
|
|
|
|
MEMORY_MUSHROOM: () => new RememberMoveModifierType("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom"),
|
|
|
|
EXP_SHARE: () =>
|
|
new ModifierType("modifierType:ModifierType.EXP_SHARE", "exp_share", (type, _args) => new ExpShareModifier(type)),
|
|
EXP_BALANCE: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.EXP_BALANCE",
|
|
"exp_balance",
|
|
(type, _args) => new ExpBalanceModifier(type),
|
|
),
|
|
|
|
OVAL_CHARM: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.OVAL_CHARM",
|
|
"oval_charm",
|
|
(type, _args) => new MultipleParticipantExpBonusModifier(type),
|
|
),
|
|
|
|
EXP_CHARM: () => new ExpBoosterModifierType("modifierType:ModifierType.EXP_CHARM", "exp_charm", 25),
|
|
SUPER_EXP_CHARM: () => new ExpBoosterModifierType("modifierType:ModifierType.SUPER_EXP_CHARM", "super_exp_charm", 60),
|
|
GOLDEN_EXP_CHARM: () =>
|
|
new ExpBoosterModifierType("modifierType:ModifierType.GOLDEN_EXP_CHARM", "golden_exp_charm", 100),
|
|
|
|
LUCKY_EGG: () => new PokemonExpBoosterModifierType("modifierType:ModifierType.LUCKY_EGG", "lucky_egg", 40),
|
|
GOLDEN_EGG: () => new PokemonExpBoosterModifierType("modifierType:ModifierType.GOLDEN_EGG", "golden_egg", 100),
|
|
|
|
SOOTHE_BELL: () => new PokemonFriendshipBoosterModifierType("modifierType:ModifierType.SOOTHE_BELL", "soothe_bell"),
|
|
|
|
SCOPE_LENS: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.SCOPE_LENS",
|
|
"scope_lens",
|
|
(type, args) => new CritBoosterModifier(type, (args[0] as Pokemon).id, 1),
|
|
),
|
|
LEEK: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.LEEK",
|
|
"leek",
|
|
(type, args) =>
|
|
new SpeciesCritBoosterModifier(type, (args[0] as Pokemon).id, 2, [
|
|
Species.FARFETCHD,
|
|
Species.GALAR_FARFETCHD,
|
|
Species.SIRFETCHD,
|
|
]),
|
|
),
|
|
|
|
EVIOLITE: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.EVIOLITE",
|
|
"eviolite",
|
|
(type, args) => new EvolutionStatBoosterModifier(type, (args[0] as Pokemon).id, [Stat.DEF, Stat.SPDEF], 1.5),
|
|
),
|
|
|
|
SOUL_DEW: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.SOUL_DEW",
|
|
"soul_dew",
|
|
(type, args) => new PokemonNatureWeightModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
|
|
NUGGET: () =>
|
|
new MoneyRewardModifierType(
|
|
"modifierType:ModifierType.NUGGET",
|
|
"nugget",
|
|
1,
|
|
"modifierType:ModifierType.MoneyRewardModifierType.extra.small",
|
|
),
|
|
BIG_NUGGET: () =>
|
|
new MoneyRewardModifierType(
|
|
"modifierType:ModifierType.BIG_NUGGET",
|
|
"big_nugget",
|
|
2.5,
|
|
"modifierType:ModifierType.MoneyRewardModifierType.extra.moderate",
|
|
),
|
|
RELIC_GOLD: () =>
|
|
new MoneyRewardModifierType(
|
|
"modifierType:ModifierType.RELIC_GOLD",
|
|
"relic_gold",
|
|
10,
|
|
"modifierType:ModifierType.MoneyRewardModifierType.extra.large",
|
|
),
|
|
|
|
AMULET_COIN: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.AMULET_COIN",
|
|
"amulet_coin",
|
|
(type, _args) => new MoneyMultiplierModifier(type),
|
|
),
|
|
GOLDEN_PUNCH: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.GOLDEN_PUNCH",
|
|
"golden_punch",
|
|
(type, args) => new DamageMoneyRewardModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
COIN_CASE: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.COIN_CASE",
|
|
"coin_case",
|
|
(type, _args) => new MoneyInterestModifier(type),
|
|
),
|
|
|
|
LOCK_CAPSULE: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.LOCK_CAPSULE",
|
|
"lock_capsule",
|
|
(type, _args) => new LockModifierTiersModifier(type),
|
|
),
|
|
|
|
GRIP_CLAW: () =>
|
|
new ContactHeldItemTransferChanceModifierType("modifierType:ModifierType.GRIP_CLAW", "grip_claw", 10),
|
|
WIDE_LENS: () => new PokemonMoveAccuracyBoosterModifierType("modifierType:ModifierType.WIDE_LENS", "wide_lens", 5),
|
|
|
|
MULTI_LENS: () => new PokemonMultiHitModifierType("modifierType:ModifierType.MULTI_LENS", "zoom_lens"),
|
|
|
|
HEALING_CHARM: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.HEALING_CHARM",
|
|
"healing_charm",
|
|
(type, _args) => new HealingBoosterModifier(type, 1.1),
|
|
),
|
|
CANDY_JAR: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.CANDY_JAR",
|
|
"candy_jar",
|
|
(type, _args) => new LevelIncrementBoosterModifier(type),
|
|
),
|
|
|
|
BERRY_POUCH: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.BERRY_POUCH",
|
|
"berry_pouch",
|
|
(type, _args) => new PreserveBerryModifier(type),
|
|
),
|
|
|
|
FOCUS_BAND: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.FOCUS_BAND",
|
|
"focus_band",
|
|
(type, args) => new SurviveDamageModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
|
|
QUICK_CLAW: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.QUICK_CLAW",
|
|
"quick_claw",
|
|
(type, args) => new BypassSpeedChanceModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
|
|
KINGS_ROCK: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.KINGS_ROCK",
|
|
"kings_rock",
|
|
(type, args) => new FlinchChanceModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
|
|
LEFTOVERS: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.LEFTOVERS",
|
|
"leftovers",
|
|
(type, args) => new TurnHealModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
SHELL_BELL: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.SHELL_BELL",
|
|
"shell_bell",
|
|
(type, args) => new HitHealModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
|
|
TOXIC_ORB: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.TOXIC_ORB",
|
|
"toxic_orb",
|
|
(type, args) => new TurnStatusEffectModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
FLAME_ORB: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.FLAME_ORB",
|
|
"flame_orb",
|
|
(type, args) => new TurnStatusEffectModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
|
|
BATON: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.BATON",
|
|
"baton",
|
|
(type, args) => new SwitchEffectTransferModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
|
|
SHINY_CHARM: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.SHINY_CHARM",
|
|
"shiny_charm",
|
|
(type, _args) => new ShinyRateBoosterModifier(type),
|
|
),
|
|
ABILITY_CHARM: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.ABILITY_CHARM",
|
|
"ability_charm",
|
|
(type, _args) => new HiddenAbilityRateBoosterModifier(type),
|
|
),
|
|
CATCHING_CHARM: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.CATCHING_CHARM",
|
|
"catching_charm",
|
|
(type, _args) => new CriticalCatchChanceBoosterModifier(type),
|
|
),
|
|
|
|
IV_SCANNER: () =>
|
|
new ModifierType("modifierType:ModifierType.IV_SCANNER", "scanner", (type, _args) => new IvScannerModifier(type)),
|
|
|
|
DNA_SPLICERS: () => new FusePokemonModifierType("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers"),
|
|
|
|
MINI_BLACK_HOLE: () =>
|
|
new TurnHeldItemTransferModifierType("modifierType:ModifierType.MINI_BLACK_HOLE", "mini_black_hole"),
|
|
|
|
VOUCHER: () => new AddVoucherModifierType(VoucherType.REGULAR, 1),
|
|
VOUCHER_PLUS: () => new AddVoucherModifierType(VoucherType.PLUS, 1),
|
|
VOUCHER_PREMIUM: () => new AddVoucherModifierType(VoucherType.PREMIUM, 1),
|
|
|
|
GOLDEN_POKEBALL: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.GOLDEN_POKEBALL",
|
|
"pb_gold",
|
|
(type, _args) => new ExtraModifierModifier(type),
|
|
undefined,
|
|
"se/pb_bounce_1",
|
|
),
|
|
SILVER_POKEBALL: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.SILVER_POKEBALL",
|
|
"pb_silver",
|
|
(type, _args) => new TempExtraModifierModifier(type, 100),
|
|
undefined,
|
|
"se/pb_bounce_1",
|
|
),
|
|
|
|
ENEMY_DAMAGE_BOOSTER: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.ENEMY_DAMAGE_BOOSTER",
|
|
"wl_item_drop",
|
|
(type, _args) => new EnemyDamageBoosterModifier(type, 5),
|
|
),
|
|
ENEMY_DAMAGE_REDUCTION: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.ENEMY_DAMAGE_REDUCTION",
|
|
"wl_guard_spec",
|
|
(type, _args) => new EnemyDamageReducerModifier(type, 2.5),
|
|
),
|
|
//ENEMY_SUPER_EFFECT_BOOSTER: () => new ModifierType('Type Advantage Token', 'Increases damage of super effective attacks by 30%', (type, _args) => new EnemySuperEffectiveDamageBoosterModifier(type, 30), 'wl_custom_super_effective'),
|
|
ENEMY_HEAL: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.ENEMY_HEAL",
|
|
"wl_potion",
|
|
(type, _args) => new EnemyTurnHealModifier(type, 2, 10),
|
|
),
|
|
ENEMY_ATTACK_POISON_CHANCE: () =>
|
|
new EnemyAttackStatusEffectChanceModifierType(
|
|
"modifierType:ModifierType.ENEMY_ATTACK_POISON_CHANCE",
|
|
"wl_antidote",
|
|
5,
|
|
StatusEffect.POISON,
|
|
10,
|
|
),
|
|
ENEMY_ATTACK_PARALYZE_CHANCE: () =>
|
|
new EnemyAttackStatusEffectChanceModifierType(
|
|
"modifierType:ModifierType.ENEMY_ATTACK_PARALYZE_CHANCE",
|
|
"wl_paralyze_heal",
|
|
2.5,
|
|
StatusEffect.PARALYSIS,
|
|
10,
|
|
),
|
|
ENEMY_ATTACK_BURN_CHANCE: () =>
|
|
new EnemyAttackStatusEffectChanceModifierType(
|
|
"modifierType:ModifierType.ENEMY_ATTACK_BURN_CHANCE",
|
|
"wl_burn_heal",
|
|
5,
|
|
StatusEffect.BURN,
|
|
10,
|
|
),
|
|
ENEMY_STATUS_EFFECT_HEAL_CHANCE: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.ENEMY_STATUS_EFFECT_HEAL_CHANCE",
|
|
"wl_full_heal",
|
|
(type, _args) => new EnemyStatusEffectHealChanceModifier(type, 2.5, 10),
|
|
),
|
|
ENEMY_ENDURE_CHANCE: () =>
|
|
new EnemyEndureChanceModifierType("modifierType:ModifierType.ENEMY_ENDURE_CHANCE", "wl_reset_urge", 2),
|
|
ENEMY_FUSED_CHANCE: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.ENEMY_FUSED_CHANCE",
|
|
"wl_custom_spliced",
|
|
(type, _args) => new EnemyFusionChanceModifier(type, 1),
|
|
),
|
|
|
|
MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () =>
|
|
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
|
if (pregenArgs) {
|
|
return new PokemonBaseStatTotalModifierType(pregenArgs[0] as number);
|
|
}
|
|
return new PokemonBaseStatTotalModifierType(randSeedInt(20, 1));
|
|
}),
|
|
MYSTERY_ENCOUNTER_OLD_GATEAU: () =>
|
|
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
|
if (pregenArgs) {
|
|
return new PokemonBaseStatFlatModifierType(pregenArgs[0] as number, pregenArgs[1] as Stat[]);
|
|
}
|
|
return new PokemonBaseStatFlatModifierType(randSeedInt(20, 1), [Stat.HP, Stat.ATK, Stat.DEF]);
|
|
}),
|
|
MYSTERY_ENCOUNTER_BLACK_SLUDGE: () =>
|
|
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
|
if (pregenArgs) {
|
|
return new ModifierType(
|
|
"modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE",
|
|
"black_sludge",
|
|
(type, _args) => new HealShopCostModifier(type, pregenArgs[0] as number),
|
|
);
|
|
}
|
|
return new ModifierType(
|
|
"modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE",
|
|
"black_sludge",
|
|
(type, _args) => new HealShopCostModifier(type, 2.5),
|
|
);
|
|
}),
|
|
MYSTERY_ENCOUNTER_MACHO_BRACE: () =>
|
|
new PokemonHeldItemModifierType(
|
|
"modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE",
|
|
"macho_brace",
|
|
(type, args) => new PokemonIncrementingStatModifier(type, (args[0] as Pokemon).id),
|
|
),
|
|
MYSTERY_ENCOUNTER_GOLDEN_BUG_NET: () =>
|
|
new ModifierType(
|
|
"modifierType:ModifierType.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET",
|
|
"golden_net",
|
|
(type, _args) => new BoostBugSpawnModifier(type),
|
|
),
|
|
};
|
|
|
|
interface ModifierPool {
|
|
[tier: string]: WeightedModifierType[];
|
|
}
|
|
|
|
/**
|
|
* Used to check if the player has max of a given ball type in Classic
|
|
* @param ballType The {@linkcode PokeballType} being checked
|
|
* @returns boolean: true if the player has the maximum of a given ball type
|
|
*/
|
|
function hasMaximumBalls(ballType: PokeballType): boolean {
|
|
return globalScene.gameMode.isClassic && globalScene.pokeballCounts[ballType] >= MAX_PER_TYPE_POKEBALLS;
|
|
}
|
|
|
|
const modifierPool: ModifierPool = {
|
|
[ModifierTier.COMMON]: [
|
|
new WeightedModifierType(modifierTypes.POKEBALL, () => (hasMaximumBalls(PokeballType.POKEBALL) ? 0 : 6), 6),
|
|
new WeightedModifierType(modifierTypes.RARE_CANDY, 2),
|
|
new WeightedModifierType(
|
|
modifierTypes.POTION,
|
|
(party: Pokemon[]) => {
|
|
const thresholdPartyMemberCount = Math.min(
|
|
party.filter(p => p.getInverseHp() >= 10 && p.getHpRatio() <= 0.875 && !p.isFainted()).length,
|
|
3,
|
|
);
|
|
return thresholdPartyMemberCount * 3;
|
|
},
|
|
9,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.SUPER_POTION,
|
|
(party: Pokemon[]) => {
|
|
const thresholdPartyMemberCount = Math.min(
|
|
party.filter(p => p.getInverseHp() >= 25 && p.getHpRatio() <= 0.75 && !p.isFainted()).length,
|
|
3,
|
|
);
|
|
return thresholdPartyMemberCount;
|
|
},
|
|
3,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.ETHER,
|
|
(party: Pokemon[]) => {
|
|
const thresholdPartyMemberCount = Math.min(
|
|
party.filter(
|
|
p =>
|
|
p.hp &&
|
|
!p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) &&
|
|
p
|
|
.getMoveset()
|
|
.filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2))
|
|
.length,
|
|
).length,
|
|
3,
|
|
);
|
|
return thresholdPartyMemberCount * 3;
|
|
},
|
|
9,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.MAX_ETHER,
|
|
(party: Pokemon[]) => {
|
|
const thresholdPartyMemberCount = Math.min(
|
|
party.filter(
|
|
p =>
|
|
p.hp &&
|
|
!p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) &&
|
|
p
|
|
.getMoveset()
|
|
.filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2))
|
|
.length,
|
|
).length,
|
|
3,
|
|
);
|
|
return thresholdPartyMemberCount;
|
|
},
|
|
3,
|
|
),
|
|
new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)),
|
|
new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4),
|
|
new WeightedModifierType(modifierTypes.BERRY, 2),
|
|
new WeightedModifierType(modifierTypes.TM_COMMON, 2),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.COMMON);
|
|
return m;
|
|
}),
|
|
[ModifierTier.GREAT]: [
|
|
new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6),
|
|
new WeightedModifierType(modifierTypes.PP_UP, 2),
|
|
new WeightedModifierType(
|
|
modifierTypes.FULL_HEAL,
|
|
(party: Pokemon[]) => {
|
|
const statusEffectPartyMemberCount = Math.min(
|
|
party.filter(
|
|
p =>
|
|
p.hp &&
|
|
!!p.status &&
|
|
!p.getHeldItems().some(i => {
|
|
if (i instanceof TurnStatusEffectModifier) {
|
|
return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect;
|
|
}
|
|
return false;
|
|
}),
|
|
).length,
|
|
3,
|
|
);
|
|
return statusEffectPartyMemberCount * 6;
|
|
},
|
|
18,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.REVIVE,
|
|
(party: Pokemon[]) => {
|
|
const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3);
|
|
return faintedPartyMemberCount * 9;
|
|
},
|
|
27,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.MAX_REVIVE,
|
|
(party: Pokemon[]) => {
|
|
const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3);
|
|
return faintedPartyMemberCount * 3;
|
|
},
|
|
9,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.SACRED_ASH,
|
|
(party: Pokemon[]) => {
|
|
return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0;
|
|
},
|
|
1,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.HYPER_POTION,
|
|
(party: Pokemon[]) => {
|
|
const thresholdPartyMemberCount = Math.min(
|
|
party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.625 && !p.isFainted()).length,
|
|
3,
|
|
);
|
|
return thresholdPartyMemberCount * 3;
|
|
},
|
|
9,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.MAX_POTION,
|
|
(party: Pokemon[]) => {
|
|
const thresholdPartyMemberCount = Math.min(
|
|
party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length,
|
|
3,
|
|
);
|
|
return thresholdPartyMemberCount;
|
|
},
|
|
3,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.FULL_RESTORE,
|
|
(party: Pokemon[]) => {
|
|
const statusEffectPartyMemberCount = Math.min(
|
|
party.filter(
|
|
p =>
|
|
p.hp &&
|
|
!!p.status &&
|
|
!p.getHeldItems().some(i => {
|
|
if (i instanceof TurnStatusEffectModifier) {
|
|
return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect;
|
|
}
|
|
return false;
|
|
}),
|
|
).length,
|
|
3,
|
|
);
|
|
const thresholdPartyMemberCount = Math.floor(
|
|
(Math.min(party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length, 3) +
|
|
statusEffectPartyMemberCount) /
|
|
2,
|
|
);
|
|
return thresholdPartyMemberCount;
|
|
},
|
|
3,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.ELIXIR,
|
|
(party: Pokemon[]) => {
|
|
const thresholdPartyMemberCount = Math.min(
|
|
party.filter(
|
|
p =>
|
|
p.hp &&
|
|
!p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) &&
|
|
p
|
|
.getMoveset()
|
|
.filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2))
|
|
.length,
|
|
).length,
|
|
3,
|
|
);
|
|
return thresholdPartyMemberCount * 3;
|
|
},
|
|
9,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.MAX_ELIXIR,
|
|
(party: Pokemon[]) => {
|
|
const thresholdPartyMemberCount = Math.min(
|
|
party.filter(
|
|
p =>
|
|
p.hp &&
|
|
!p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) &&
|
|
p
|
|
.getMoveset()
|
|
.filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2))
|
|
.length,
|
|
).length,
|
|
3,
|
|
);
|
|
return thresholdPartyMemberCount;
|
|
},
|
|
3,
|
|
),
|
|
new WeightedModifierType(modifierTypes.DIRE_HIT, 4),
|
|
new WeightedModifierType(modifierTypes.SUPER_LURE, lureWeightFunc(15, 4)),
|
|
new WeightedModifierType(modifierTypes.NUGGET, skipInLastClassicWaveOrDefault(5)),
|
|
new WeightedModifierType(
|
|
modifierTypes.EVOLUTION_ITEM,
|
|
() => {
|
|
return Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15), 8);
|
|
},
|
|
8,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.MAP,
|
|
() => (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex < 180 ? 2 : 0),
|
|
2,
|
|
),
|
|
new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2),
|
|
new WeightedModifierType(modifierTypes.TM_GREAT, 3),
|
|
new WeightedModifierType(
|
|
modifierTypes.MEMORY_MUSHROOM,
|
|
(party: Pokemon[]) => {
|
|
if (!party.find(p => p.getLearnableLevelMoves().length)) {
|
|
return 0;
|
|
}
|
|
const highestPartyLevel = party
|
|
.map(p => p.level)
|
|
.reduce((highestLevel: number, level: number) => Math.max(highestLevel, level), 1);
|
|
return Math.min(Math.ceil(highestPartyLevel / 20), 4);
|
|
},
|
|
4,
|
|
),
|
|
new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3),
|
|
new WeightedModifierType(modifierTypes.TERA_SHARD, (party: Pokemon[]) =>
|
|
party.filter(
|
|
p => !(p.hasSpecies(Species.TERAPAGOS) || p.hasSpecies(Species.OGERPON) || p.hasSpecies(Species.SHEDINJA)),
|
|
).length > 0
|
|
? 1
|
|
: 0,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.DNA_SPLICERS,
|
|
(party: Pokemon[]) => {
|
|
if (party.filter(p => !p.fusionSpecies).length > 1) {
|
|
if (globalScene.gameMode.isSplicedOnly) {
|
|
return 4;
|
|
}
|
|
if (globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) {
|
|
return 2;
|
|
}
|
|
}
|
|
return 0;
|
|
},
|
|
4,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.VOUCHER,
|
|
(_party: Pokemon[], rerollCount: number) => (!globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0),
|
|
1,
|
|
),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.GREAT);
|
|
return m;
|
|
}),
|
|
[ModifierTier.ULTRA]: [
|
|
new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15),
|
|
new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)),
|
|
new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)),
|
|
new WeightedModifierType(modifierTypes.PP_MAX, 3),
|
|
new WeightedModifierType(modifierTypes.MINT, 4),
|
|
new WeightedModifierType(
|
|
modifierTypes.RARE_EVOLUTION_ITEM,
|
|
() => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15) * 4, 32),
|
|
32,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.FORM_CHANGE_ITEM,
|
|
() => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6,
|
|
24,
|
|
),
|
|
new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)),
|
|
new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => {
|
|
const { gameMode, gameData } = globalScene;
|
|
if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) {
|
|
return party.some(p => {
|
|
// Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd
|
|
if (
|
|
!p.isMax() &&
|
|
(p.getSpeciesForm(true).speciesId in pokemonEvolutions ||
|
|
(p.isFusion() && p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions))
|
|
) {
|
|
// Check if Pokemon is already holding an Eviolite
|
|
return !p.getHeldItems().some(i => i.type.id === "EVIOLITE");
|
|
}
|
|
return false;
|
|
})
|
|
? 10
|
|
: 0;
|
|
}
|
|
return 0;
|
|
}),
|
|
new WeightedModifierType(modifierTypes.SPECIES_STAT_BOOSTER, 12),
|
|
new WeightedModifierType(
|
|
modifierTypes.LEEK,
|
|
(party: Pokemon[]) => {
|
|
const checkedSpecies = [Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD];
|
|
// If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear
|
|
return party.some(
|
|
p =>
|
|
!p.getHeldItems().some(i => i instanceof SpeciesCritBoosterModifier) &&
|
|
(checkedSpecies.includes(p.getSpeciesForm(true).speciesId) ||
|
|
(p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId))),
|
|
)
|
|
? 12
|
|
: 0;
|
|
},
|
|
12,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.TOXIC_ORB,
|
|
(party: Pokemon[]) => {
|
|
return party.some(p => {
|
|
const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB");
|
|
|
|
if (!isHoldingOrb) {
|
|
const moveset = p
|
|
.getMoveset(true)
|
|
.filter(m => !isNullOrUndefined(m))
|
|
.map(m => m.moveId);
|
|
const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true);
|
|
|
|
// Moves that take advantage of obtaining the actual status effect
|
|
const hasStatusMoves = [Moves.FACADE, Moves.PSYCHO_SHIFT].some(m => moveset.includes(m));
|
|
// Moves that take advantage of being able to give the target a status orb
|
|
// TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented
|
|
const hasItemMoves = [
|
|
/* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */
|
|
].some(m => moveset.includes(m));
|
|
|
|
if (canSetStatus) {
|
|
// Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb
|
|
const hasGeneralAbility = [
|
|
Abilities.QUICK_FEET,
|
|
Abilities.GUTS,
|
|
Abilities.MARVEL_SCALE,
|
|
Abilities.MAGIC_GUARD,
|
|
].some(a => p.hasAbility(a, false, true));
|
|
const hasSpecificAbility = [Abilities.TOXIC_BOOST, Abilities.POISON_HEAL].some(a =>
|
|
p.hasAbility(a, false, true),
|
|
);
|
|
const hasOppositeAbility = [Abilities.FLARE_BOOST].some(a => p.hasAbility(a, false, true));
|
|
|
|
return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves;
|
|
}
|
|
return hasItemMoves;
|
|
}
|
|
|
|
return false;
|
|
})
|
|
? 10
|
|
: 0;
|
|
},
|
|
10,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.FLAME_ORB,
|
|
(party: Pokemon[]) => {
|
|
return party.some(p => {
|
|
const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB");
|
|
|
|
if (!isHoldingOrb) {
|
|
const moveset = p
|
|
.getMoveset(true)
|
|
.filter(m => !isNullOrUndefined(m))
|
|
.map(m => m.moveId);
|
|
const canSetStatus = p.canSetStatus(StatusEffect.BURN, true, true, null, true);
|
|
|
|
// Moves that take advantage of obtaining the actual status effect
|
|
const hasStatusMoves = [Moves.FACADE, Moves.PSYCHO_SHIFT].some(m => moveset.includes(m));
|
|
// Moves that take advantage of being able to give the target a status orb
|
|
// TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented
|
|
const hasItemMoves = [
|
|
/* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */
|
|
].some(m => moveset.includes(m));
|
|
|
|
if (canSetStatus) {
|
|
// Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb
|
|
const hasGeneralAbility = [
|
|
Abilities.QUICK_FEET,
|
|
Abilities.GUTS,
|
|
Abilities.MARVEL_SCALE,
|
|
Abilities.MAGIC_GUARD,
|
|
].some(a => p.hasAbility(a, false, true));
|
|
const hasSpecificAbility = [Abilities.FLARE_BOOST].some(a => p.hasAbility(a, false, true));
|
|
const hasOppositeAbility = [Abilities.TOXIC_BOOST, Abilities.POISON_HEAL].some(a =>
|
|
p.hasAbility(a, false, true),
|
|
);
|
|
|
|
return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves;
|
|
}
|
|
return hasItemMoves;
|
|
}
|
|
|
|
return false;
|
|
})
|
|
? 10
|
|
: 0;
|
|
},
|
|
10,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.MYSTICAL_ROCK,
|
|
(party: Pokemon[]) => {
|
|
return party.some(p => {
|
|
const moveset = p.getMoveset(true).map(m => m.moveId);
|
|
|
|
const hasAbility = [
|
|
Abilities.DRIZZLE,
|
|
Abilities.ORICHALCUM_PULSE,
|
|
Abilities.DRIZZLE,
|
|
Abilities.SAND_STREAM,
|
|
Abilities.SAND_SPIT,
|
|
Abilities.SNOW_WARNING,
|
|
Abilities.ELECTRIC_SURGE,
|
|
Abilities.HADRON_ENGINE,
|
|
Abilities.PSYCHIC_SURGE,
|
|
Abilities.GRASSY_SURGE,
|
|
Abilities.SEED_SOWER,
|
|
Abilities.MISTY_SURGE,
|
|
].some(a => p.hasAbility(a, false, true));
|
|
|
|
const hasMoves = [
|
|
Moves.SUNNY_DAY,
|
|
Moves.RAIN_DANCE,
|
|
Moves.SANDSTORM,
|
|
Moves.SNOWSCAPE,
|
|
Moves.HAIL,
|
|
Moves.CHILLY_RECEPTION,
|
|
Moves.ELECTRIC_TERRAIN,
|
|
Moves.PSYCHIC_TERRAIN,
|
|
Moves.GRASSY_TERRAIN,
|
|
Moves.MISTY_TERRAIN,
|
|
].some(m => moveset.includes(m));
|
|
|
|
return hasAbility || hasMoves;
|
|
})
|
|
? 10
|
|
: 0;
|
|
},
|
|
10,
|
|
),
|
|
new WeightedModifierType(modifierTypes.REVIVER_SEED, 4),
|
|
new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)),
|
|
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9),
|
|
new WeightedModifierType(modifierTypes.TM_ULTRA, 11),
|
|
new WeightedModifierType(modifierTypes.RARER_CANDY, 4),
|
|
new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)),
|
|
new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)),
|
|
new WeightedModifierType(modifierTypes.EXP_CHARM, skipInLastClassicWaveOrDefault(8)),
|
|
new WeightedModifierType(modifierTypes.EXP_SHARE, skipInLastClassicWaveOrDefault(10)),
|
|
new WeightedModifierType(
|
|
modifierTypes.TERA_ORB,
|
|
() =>
|
|
!globalScene.gameMode.isClassic
|
|
? Math.min(Math.max(Math.floor(globalScene.currentBattle.waveIndex / 50) * 2, 1), 4)
|
|
: 0,
|
|
4,
|
|
),
|
|
new WeightedModifierType(modifierTypes.QUICK_CLAW, 3),
|
|
new WeightedModifierType(modifierTypes.WIDE_LENS, 7),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.ULTRA);
|
|
return m;
|
|
}),
|
|
[ModifierTier.ROGUE]: [
|
|
new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16),
|
|
new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)),
|
|
new WeightedModifierType(modifierTypes.LEFTOVERS, 3),
|
|
new WeightedModifierType(modifierTypes.SHELL_BELL, 3),
|
|
new WeightedModifierType(modifierTypes.BERRY_POUCH, 4),
|
|
new WeightedModifierType(modifierTypes.GRIP_CLAW, 5),
|
|
new WeightedModifierType(modifierTypes.SCOPE_LENS, 4),
|
|
new WeightedModifierType(modifierTypes.BATON, 2),
|
|
new WeightedModifierType(modifierTypes.SOUL_DEW, 7),
|
|
new WeightedModifierType(modifierTypes.CATCHING_CHARM, () => (!globalScene.gameMode.isClassic ? 4 : 0), 4),
|
|
new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)),
|
|
new WeightedModifierType(modifierTypes.FOCUS_BAND, 5),
|
|
new WeightedModifierType(modifierTypes.KINGS_ROCK, 3),
|
|
new WeightedModifierType(modifierTypes.LOCK_CAPSULE, () => (globalScene.gameMode.isClassic ? 0 : 3)),
|
|
new WeightedModifierType(modifierTypes.SUPER_EXP_CHARM, skipInLastClassicWaveOrDefault(8)),
|
|
new WeightedModifierType(
|
|
modifierTypes.RARE_FORM_CHANGE_ITEM,
|
|
() => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6,
|
|
24,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.MEGA_BRACELET,
|
|
() => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9,
|
|
36,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.DYNAMAX_BAND,
|
|
() => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9,
|
|
36,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.VOUCHER_PLUS,
|
|
(_party: Pokemon[], rerollCount: number) =>
|
|
!globalScene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0,
|
|
3,
|
|
),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.ROGUE);
|
|
return m;
|
|
}),
|
|
[ModifierTier.MASTER]: [
|
|
new WeightedModifierType(modifierTypes.MASTER_BALL, () => (hasMaximumBalls(PokeballType.MASTER_BALL) ? 0 : 24), 24),
|
|
new WeightedModifierType(modifierTypes.SHINY_CHARM, 14),
|
|
new WeightedModifierType(modifierTypes.HEALING_CHARM, 18),
|
|
new WeightedModifierType(modifierTypes.MULTI_LENS, 18),
|
|
new WeightedModifierType(
|
|
modifierTypes.VOUCHER_PREMIUM,
|
|
(_party: Pokemon[], rerollCount: number) =>
|
|
!globalScene.gameMode.isDaily && !globalScene.gameMode.isEndless && !globalScene.gameMode.isSplicedOnly
|
|
? Math.max(5 - rerollCount * 2, 0)
|
|
: 0,
|
|
5,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.DNA_SPLICERS,
|
|
(party: Pokemon[]) =>
|
|
!(globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) &&
|
|
!globalScene.gameMode.isSplicedOnly &&
|
|
party.filter(p => !p.fusionSpecies).length > 1
|
|
? 24
|
|
: 0,
|
|
24,
|
|
),
|
|
new WeightedModifierType(
|
|
modifierTypes.MINI_BLACK_HOLE,
|
|
() =>
|
|
globalScene.gameMode.isDaily ||
|
|
(!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE))
|
|
? 1
|
|
: 0,
|
|
1,
|
|
),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.MASTER);
|
|
return m;
|
|
}),
|
|
};
|
|
|
|
const wildModifierPool: ModifierPool = {
|
|
[ModifierTier.COMMON]: [new WeightedModifierType(modifierTypes.BERRY, 1)].map(m => {
|
|
m.setTier(ModifierTier.COMMON);
|
|
return m;
|
|
}),
|
|
[ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1)].map(m => {
|
|
m.setTier(ModifierTier.GREAT);
|
|
return m;
|
|
}),
|
|
[ModifierTier.ULTRA]: [
|
|
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10),
|
|
new WeightedModifierType(modifierTypes.WHITE_HERB, 0),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.ULTRA);
|
|
return m;
|
|
}),
|
|
[ModifierTier.ROGUE]: [new WeightedModifierType(modifierTypes.LUCKY_EGG, 4)].map(m => {
|
|
m.setTier(ModifierTier.ROGUE);
|
|
return m;
|
|
}),
|
|
[ModifierTier.MASTER]: [new WeightedModifierType(modifierTypes.GOLDEN_EGG, 1)].map(m => {
|
|
m.setTier(ModifierTier.MASTER);
|
|
return m;
|
|
}),
|
|
};
|
|
|
|
const trainerModifierPool: ModifierPool = {
|
|
[ModifierTier.COMMON]: [
|
|
new WeightedModifierType(modifierTypes.BERRY, 8),
|
|
new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.COMMON);
|
|
return m;
|
|
}),
|
|
[ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3)].map(m => {
|
|
m.setTier(ModifierTier.GREAT);
|
|
return m;
|
|
}),
|
|
[ModifierTier.ULTRA]: [
|
|
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10),
|
|
new WeightedModifierType(modifierTypes.WHITE_HERB, 0),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.ULTRA);
|
|
return m;
|
|
}),
|
|
[ModifierTier.ROGUE]: [
|
|
new WeightedModifierType(modifierTypes.FOCUS_BAND, 2),
|
|
new WeightedModifierType(modifierTypes.LUCKY_EGG, 4),
|
|
new WeightedModifierType(modifierTypes.QUICK_CLAW, 1),
|
|
new WeightedModifierType(modifierTypes.GRIP_CLAW, 1),
|
|
new WeightedModifierType(modifierTypes.WIDE_LENS, 1),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.ROGUE);
|
|
return m;
|
|
}),
|
|
[ModifierTier.MASTER]: [
|
|
new WeightedModifierType(modifierTypes.KINGS_ROCK, 1),
|
|
new WeightedModifierType(modifierTypes.LEFTOVERS, 1),
|
|
new WeightedModifierType(modifierTypes.SHELL_BELL, 1),
|
|
new WeightedModifierType(modifierTypes.SCOPE_LENS, 1),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.MASTER);
|
|
return m;
|
|
}),
|
|
};
|
|
|
|
const enemyBuffModifierPool: ModifierPool = {
|
|
[ModifierTier.COMMON]: [
|
|
new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 9),
|
|
new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 9),
|
|
new WeightedModifierType(modifierTypes.ENEMY_ATTACK_POISON_CHANCE, 3),
|
|
new WeightedModifierType(modifierTypes.ENEMY_ATTACK_PARALYZE_CHANCE, 3),
|
|
new WeightedModifierType(modifierTypes.ENEMY_ATTACK_BURN_CHANCE, 3),
|
|
new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 9),
|
|
new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 4),
|
|
new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.COMMON);
|
|
return m;
|
|
}),
|
|
[ModifierTier.GREAT]: [
|
|
new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 5),
|
|
new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 5),
|
|
new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 5),
|
|
new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 5),
|
|
new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.GREAT);
|
|
return m;
|
|
}),
|
|
[ModifierTier.ULTRA]: [
|
|
new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 10),
|
|
new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 10),
|
|
new WeightedModifierType(modifierTypes.ENEMY_HEAL, 10),
|
|
new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 10),
|
|
new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 10),
|
|
new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 5),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.ULTRA);
|
|
return m;
|
|
}),
|
|
[ModifierTier.ROGUE]: [].map((m: WeightedModifierType) => {
|
|
m.setTier(ModifierTier.ROGUE);
|
|
return m;
|
|
}),
|
|
[ModifierTier.MASTER]: [].map((m: WeightedModifierType) => {
|
|
m.setTier(ModifierTier.MASTER);
|
|
return m;
|
|
}),
|
|
};
|
|
|
|
const dailyStarterModifierPool: ModifierPool = {
|
|
[ModifierTier.COMMON]: [
|
|
new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1),
|
|
new WeightedModifierType(modifierTypes.BERRY, 3),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.COMMON);
|
|
return m;
|
|
}),
|
|
[ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 5)].map(m => {
|
|
m.setTier(ModifierTier.GREAT);
|
|
return m;
|
|
}),
|
|
[ModifierTier.ULTRA]: [
|
|
new WeightedModifierType(modifierTypes.REVIVER_SEED, 4),
|
|
new WeightedModifierType(modifierTypes.SOOTHE_BELL, 1),
|
|
new WeightedModifierType(modifierTypes.SOUL_DEW, 1),
|
|
new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, 1),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.ULTRA);
|
|
return m;
|
|
}),
|
|
[ModifierTier.ROGUE]: [
|
|
new WeightedModifierType(modifierTypes.GRIP_CLAW, 5),
|
|
new WeightedModifierType(modifierTypes.BATON, 2),
|
|
new WeightedModifierType(modifierTypes.FOCUS_BAND, 5),
|
|
new WeightedModifierType(modifierTypes.QUICK_CLAW, 3),
|
|
new WeightedModifierType(modifierTypes.KINGS_ROCK, 3),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.ROGUE);
|
|
return m;
|
|
}),
|
|
[ModifierTier.MASTER]: [
|
|
new WeightedModifierType(modifierTypes.LEFTOVERS, 1),
|
|
new WeightedModifierType(modifierTypes.SHELL_BELL, 1),
|
|
].map(m => {
|
|
m.setTier(ModifierTier.MASTER);
|
|
return m;
|
|
}),
|
|
};
|
|
|
|
export function getModifierType(modifierTypeFunc: ModifierTypeFunc): ModifierType {
|
|
const modifierType = modifierTypeFunc();
|
|
if (!modifierType.id) {
|
|
modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc)!; // TODO: is this bang correct?
|
|
}
|
|
return modifierType;
|
|
}
|
|
|
|
let modifierPoolThresholds = {};
|
|
let ignoredPoolIndexes = {};
|
|
|
|
let dailyStarterModifierPoolThresholds = {};
|
|
// biome-ignore lint/correctness/noUnusedVariables: TODO explain why this is marked as OK
|
|
let ignoredDailyStarterPoolIndexes = {};
|
|
|
|
let enemyModifierPoolThresholds = {};
|
|
// biome-ignore lint/correctness/noUnusedVariables: TODO explain why this is marked as OK
|
|
let enemyIgnoredPoolIndexes = {};
|
|
|
|
let enemyBuffModifierPoolThresholds = {};
|
|
// biome-ignore lint/correctness/noUnusedVariables: TODO explain why this is marked as OK
|
|
let enemyBuffIgnoredPoolIndexes = {};
|
|
|
|
export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool {
|
|
let pool: ModifierPool;
|
|
switch (poolType) {
|
|
case ModifierPoolType.PLAYER:
|
|
pool = modifierPool;
|
|
break;
|
|
case ModifierPoolType.WILD:
|
|
pool = wildModifierPool;
|
|
break;
|
|
case ModifierPoolType.TRAINER:
|
|
pool = trainerModifierPool;
|
|
break;
|
|
case ModifierPoolType.ENEMY_BUFF:
|
|
pool = enemyBuffModifierPool;
|
|
break;
|
|
case ModifierPoolType.DAILY_STARTER:
|
|
pool = dailyStarterModifierPool;
|
|
break;
|
|
}
|
|
return pool;
|
|
}
|
|
|
|
const tierWeights = [768 / 1024, 195 / 1024, 48 / 1024, 12 / 1024, 1 / 1024];
|
|
/**
|
|
* Allows a unit test to check if an item exists in the Modifier Pool. Checks the pool directly, rather than attempting to reroll for the item.
|
|
*/
|
|
export const itemPoolChecks: Map<ModifierTypeKeys, boolean | undefined> = new Map();
|
|
|
|
export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: ModifierPoolType, rerollCount = 0) {
|
|
const pool = getModifierPoolForType(poolType);
|
|
itemPoolChecks.forEach((_v, k) => {
|
|
itemPoolChecks.set(k, false);
|
|
});
|
|
|
|
const ignoredIndexes = {};
|
|
const modifierTableData = {};
|
|
const thresholds = Object.fromEntries(
|
|
new Map(
|
|
Object.keys(pool).map(t => {
|
|
ignoredIndexes[t] = [];
|
|
const thresholds = new Map();
|
|
const tierModifierIds: string[] = [];
|
|
let tierMaxWeight = 0;
|
|
let i = 0;
|
|
pool[t].reduce((total: number, modifierType: WeightedModifierType) => {
|
|
const weightedModifierType = modifierType as WeightedModifierType;
|
|
const existingModifiers = globalScene.findModifiers(
|
|
m => m.type.id === weightedModifierType.modifierType.id,
|
|
poolType === ModifierPoolType.PLAYER,
|
|
);
|
|
const itemModifierType =
|
|
weightedModifierType.modifierType instanceof ModifierTypeGenerator
|
|
? weightedModifierType.modifierType.generateType(party)
|
|
: weightedModifierType.modifierType;
|
|
const weight =
|
|
!existingModifiers.length ||
|
|
itemModifierType instanceof PokemonHeldItemModifierType ||
|
|
itemModifierType instanceof FormChangeItemModifierType ||
|
|
existingModifiers.find(m => m.stackCount < m.getMaxStackCount(true))
|
|
? weightedModifierType.weight instanceof Function
|
|
? // biome-ignore lint/complexity/noBannedTypes: TODO: refactor to not use Function type
|
|
(weightedModifierType.weight as Function)(party, rerollCount)
|
|
: (weightedModifierType.weight as number)
|
|
: 0;
|
|
if (weightedModifierType.maxWeight) {
|
|
const modifierId = weightedModifierType.modifierType.id;
|
|
tierModifierIds.push(modifierId);
|
|
const outputWeight = useMaxWeightForOutput ? weightedModifierType.maxWeight : weight;
|
|
modifierTableData[modifierId] = {
|
|
weight: outputWeight,
|
|
tier: Number.parseInt(t),
|
|
tierPercent: 0,
|
|
totalPercent: 0,
|
|
};
|
|
tierMaxWeight += outputWeight;
|
|
}
|
|
if (weight) {
|
|
total += weight;
|
|
} else {
|
|
ignoredIndexes[t].push(i++);
|
|
return total;
|
|
}
|
|
if (itemPoolChecks.has(modifierType.modifierType.id as ModifierTypeKeys)) {
|
|
itemPoolChecks.set(modifierType.modifierType.id as ModifierTypeKeys, true);
|
|
}
|
|
thresholds.set(total, i++);
|
|
return total;
|
|
}, 0);
|
|
for (const id of tierModifierIds) {
|
|
modifierTableData[id].tierPercent = Math.floor((modifierTableData[id].weight / tierMaxWeight) * 10000) / 100;
|
|
}
|
|
return [t, Object.fromEntries(thresholds)];
|
|
}),
|
|
),
|
|
);
|
|
for (const id of Object.keys(modifierTableData)) {
|
|
modifierTableData[id].totalPercent =
|
|
Math.floor(modifierTableData[id].tierPercent * tierWeights[modifierTableData[id].tier] * 100) / 100;
|
|
modifierTableData[id].tier = ModifierTier[modifierTableData[id].tier];
|
|
}
|
|
if (outputModifierData) {
|
|
console.table(modifierTableData);
|
|
}
|
|
switch (poolType) {
|
|
case ModifierPoolType.PLAYER:
|
|
modifierPoolThresholds = thresholds;
|
|
ignoredPoolIndexes = ignoredIndexes;
|
|
break;
|
|
case ModifierPoolType.WILD:
|
|
case ModifierPoolType.TRAINER:
|
|
enemyModifierPoolThresholds = thresholds;
|
|
enemyIgnoredPoolIndexes = ignoredIndexes;
|
|
break;
|
|
case ModifierPoolType.ENEMY_BUFF:
|
|
enemyBuffModifierPoolThresholds = thresholds;
|
|
enemyBuffIgnoredPoolIndexes = ignoredIndexes;
|
|
break;
|
|
case ModifierPoolType.DAILY_STARTER:
|
|
dailyStarterModifierPoolThresholds = thresholds;
|
|
ignoredDailyStarterPoolIndexes = ignoredIndexes;
|
|
break;
|
|
}
|
|
}
|
|
|
|
export interface CustomModifierSettings {
|
|
guaranteedModifierTiers?: ModifierTier[];
|
|
guaranteedModifierTypeOptions?: ModifierTypeOption[];
|
|
guaranteedModifierTypeFuncs?: ModifierTypeFunc[];
|
|
fillRemaining?: boolean;
|
|
/** Set to negative value to disable rerolls completely in shop */
|
|
rerollMultiplier?: number;
|
|
allowLuckUpgrades?: boolean;
|
|
}
|
|
|
|
export function getModifierTypeFuncById(id: string): ModifierTypeFunc {
|
|
return modifierTypes[id];
|
|
}
|
|
|
|
/**
|
|
* Generates modifier options for a {@linkcode SelectModifierPhase}
|
|
* @param count Determines the number of items to generate
|
|
* @param party Party is required for generating proper modifier pools
|
|
* @param modifierTiers (Optional) If specified, rolls items in the specified tiers. Commonly used for tier-locking with Lock Capsule.
|
|
* @param customModifierSettings (Optional) If specified, can customize the item shop rewards further.
|
|
* - `guaranteedModifierTypeOptions?: ModifierTypeOption[]` If specified, will override the first X items to be specific modifier options (these should be pre-genned).
|
|
* - `guaranteedModifierTypeFuncs?: ModifierTypeFunc[]` If specified, will override the next X items to be auto-generated from specific modifier functions (these don't have to be pre-genned).
|
|
* - `guaranteedModifierTiers?: ModifierTier[]` If specified, will override the next X items to be the specified tier. These can upgrade with luck.
|
|
* - `fillRemaining?: boolean` Default 'false'. If set to true, will fill the remainder of shop items that were not overridden by the 3 options above, up to the 'count' param value.
|
|
* - Example: `count = 4`, `customModifierSettings = { guaranteedModifierTiers: [ModifierTier.GREAT], fillRemaining: true }`,
|
|
* - The first item in the shop will be `GREAT` tier, and the remaining 3 items will be generated normally.
|
|
* - If `fillRemaining = false` in the same scenario, only 1 `GREAT` tier item will appear in the shop (regardless of `count` value).
|
|
* - `rerollMultiplier?: number` If specified, can adjust the amount of money required for a shop reroll. If set to a negative value, the shop will not allow rerolls at all.
|
|
* - `allowLuckUpgrades?: boolean` Default `true`, if `false` will prevent set item tiers from upgrading via luck
|
|
*/
|
|
export function getPlayerModifierTypeOptions(
|
|
count: number,
|
|
party: PlayerPokemon[],
|
|
modifierTiers?: ModifierTier[],
|
|
customModifierSettings?: CustomModifierSettings,
|
|
): ModifierTypeOption[] {
|
|
const options: ModifierTypeOption[] = [];
|
|
const retryCount = Math.min(count * 5, 50);
|
|
if (!customModifierSettings) {
|
|
new Array(count).fill(0).map((_, i) => {
|
|
options.push(
|
|
getModifierTypeOptionWithRetry(
|
|
options,
|
|
retryCount,
|
|
party,
|
|
modifierTiers && modifierTiers.length > i ? modifierTiers[i] : undefined,
|
|
),
|
|
);
|
|
});
|
|
} else {
|
|
// Guaranteed mod options first
|
|
if (
|
|
customModifierSettings?.guaranteedModifierTypeOptions &&
|
|
customModifierSettings.guaranteedModifierTypeOptions.length > 0
|
|
) {
|
|
options.push(...customModifierSettings.guaranteedModifierTypeOptions!);
|
|
}
|
|
|
|
// Guaranteed mod functions second
|
|
if (
|
|
customModifierSettings.guaranteedModifierTypeFuncs &&
|
|
customModifierSettings.guaranteedModifierTypeFuncs.length > 0
|
|
) {
|
|
customModifierSettings.guaranteedModifierTypeFuncs!.forEach((mod, _i) => {
|
|
const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === mod) as string;
|
|
let guaranteedMod: ModifierType = modifierTypes[modifierId]?.();
|
|
|
|
// Populates item id and tier
|
|
guaranteedMod = guaranteedMod
|
|
.withIdFromFunc(modifierTypes[modifierId])
|
|
.withTierFromPool(ModifierPoolType.PLAYER, party);
|
|
|
|
const modType =
|
|
guaranteedMod instanceof ModifierTypeGenerator ? guaranteedMod.generateType(party) : guaranteedMod;
|
|
if (modType) {
|
|
const option = new ModifierTypeOption(modType, 0);
|
|
options.push(option);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Guaranteed tiers third
|
|
if (customModifierSettings.guaranteedModifierTiers && customModifierSettings.guaranteedModifierTiers.length > 0) {
|
|
const allowLuckUpgrades = customModifierSettings.allowLuckUpgrades ?? true;
|
|
for (const tier of customModifierSettings.guaranteedModifierTiers) {
|
|
options.push(getModifierTypeOptionWithRetry(options, retryCount, party, tier, allowLuckUpgrades));
|
|
}
|
|
}
|
|
|
|
// Fill remaining
|
|
if (options.length < count && customModifierSettings.fillRemaining) {
|
|
while (options.length < count) {
|
|
options.push(getModifierTypeOptionWithRetry(options, retryCount, party, undefined));
|
|
}
|
|
}
|
|
}
|
|
|
|
overridePlayerModifierTypeOptions(options, party);
|
|
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
* Will generate a {@linkcode ModifierType} from the {@linkcode ModifierPoolType.PLAYER} pool, attempting to retry duplicated items up to retryCount
|
|
* @param existingOptions Currently generated options
|
|
* @param retryCount How many times to retry before allowing a dupe item
|
|
* @param party Current player party, used to calculate items in the pool
|
|
* @param tier If specified will generate item of tier
|
|
* @param allowLuckUpgrades `true` to allow items to upgrade tiers (the little animation that plays and is affected by luck)
|
|
*/
|
|
function getModifierTypeOptionWithRetry(
|
|
existingOptions: ModifierTypeOption[],
|
|
retryCount: number,
|
|
party: PlayerPokemon[],
|
|
tier?: ModifierTier,
|
|
allowLuckUpgrades?: boolean,
|
|
): ModifierTypeOption {
|
|
allowLuckUpgrades = allowLuckUpgrades ?? true;
|
|
let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, tier, undefined, 0, allowLuckUpgrades);
|
|
let r = 0;
|
|
while (
|
|
existingOptions.length &&
|
|
++r < retryCount &&
|
|
existingOptions.filter(o => o.type.name === candidate?.type.name || o.type.group === candidate?.type.group).length
|
|
) {
|
|
candidate = getNewModifierTypeOption(
|
|
party,
|
|
ModifierPoolType.PLAYER,
|
|
candidate?.type.tier ?? tier,
|
|
candidate?.upgradeCount,
|
|
0,
|
|
allowLuckUpgrades,
|
|
);
|
|
}
|
|
return candidate!;
|
|
}
|
|
|
|
/**
|
|
* Replaces the {@linkcode ModifierType} of the entries within {@linkcode options} with any
|
|
* {@linkcode ModifierOverride} entries listed in {@linkcode Overrides.ITEM_REWARD_OVERRIDE}
|
|
* up to the smallest amount of entries between {@linkcode options} and the override array.
|
|
* @param options Array of naturally rolled {@linkcode ModifierTypeOption}s
|
|
* @param party Array of the player's current party
|
|
*/
|
|
export function overridePlayerModifierTypeOptions(options: ModifierTypeOption[], party: PlayerPokemon[]) {
|
|
const minLength = Math.min(options.length, Overrides.ITEM_REWARD_OVERRIDE.length);
|
|
for (let i = 0; i < minLength; i++) {
|
|
const override: ModifierOverride = Overrides.ITEM_REWARD_OVERRIDE[i];
|
|
const modifierFunc = modifierTypes[override.name];
|
|
let modifierType: ModifierType | null = modifierFunc();
|
|
|
|
if (modifierType instanceof ModifierTypeGenerator) {
|
|
const pregenArgs = "type" in override && override.type !== null ? [override.type] : undefined;
|
|
modifierType = modifierType.generateType(party, pregenArgs);
|
|
}
|
|
|
|
if (modifierType) {
|
|
options[i].type = modifierType.withIdFromFunc(modifierFunc).withTierFromPool(ModifierPoolType.PLAYER, party);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getPlayerShopModifierTypeOptionsForWave(waveIndex: number, baseCost: number): ModifierTypeOption[] {
|
|
if (!(waveIndex % 10)) {
|
|
return [];
|
|
}
|
|
|
|
const options = [
|
|
[
|
|
new ModifierTypeOption(modifierTypes.POTION(), 0, baseCost * 0.2),
|
|
new ModifierTypeOption(modifierTypes.ETHER(), 0, baseCost * 0.4),
|
|
new ModifierTypeOption(modifierTypes.REVIVE(), 0, baseCost * 2),
|
|
],
|
|
[
|
|
new ModifierTypeOption(modifierTypes.SUPER_POTION(), 0, baseCost * 0.45),
|
|
new ModifierTypeOption(modifierTypes.FULL_HEAL(), 0, baseCost),
|
|
],
|
|
[
|
|
new ModifierTypeOption(modifierTypes.ELIXIR(), 0, baseCost),
|
|
new ModifierTypeOption(modifierTypes.MAX_ETHER(), 0, baseCost),
|
|
],
|
|
[
|
|
new ModifierTypeOption(modifierTypes.HYPER_POTION(), 0, baseCost * 0.8),
|
|
new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75),
|
|
new ModifierTypeOption(modifierTypes.MEMORY_MUSHROOM(), 0, baseCost * 4),
|
|
],
|
|
[
|
|
new ModifierTypeOption(modifierTypes.MAX_POTION(), 0, baseCost * 1.5),
|
|
new ModifierTypeOption(modifierTypes.MAX_ELIXIR(), 0, baseCost * 2.5),
|
|
],
|
|
[new ModifierTypeOption(modifierTypes.FULL_RESTORE(), 0, baseCost * 2.25)],
|
|
[new ModifierTypeOption(modifierTypes.SACRED_ASH(), 0, baseCost * 10)],
|
|
];
|
|
return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat();
|
|
}
|
|
|
|
export function getEnemyBuffModifierForWave(
|
|
tier: ModifierTier,
|
|
enemyModifiers: PersistentModifier[],
|
|
): EnemyPersistentModifier {
|
|
let tierStackCount: number;
|
|
switch (tier) {
|
|
case ModifierTier.ULTRA:
|
|
tierStackCount = 5;
|
|
break;
|
|
case ModifierTier.GREAT:
|
|
tierStackCount = 3;
|
|
break;
|
|
default:
|
|
tierStackCount = 1;
|
|
break;
|
|
}
|
|
|
|
const retryCount = 50;
|
|
let candidate = getNewModifierTypeOption([], ModifierPoolType.ENEMY_BUFF, tier);
|
|
let r = 0;
|
|
let matchingModifier: PersistentModifier | undefined;
|
|
while (
|
|
++r < retryCount &&
|
|
(matchingModifier = enemyModifiers.find(m => m.type.id === candidate?.type?.id)) &&
|
|
matchingModifier.getMaxStackCount() < matchingModifier.stackCount + (r < 10 ? tierStackCount : 1)
|
|
) {
|
|
candidate = getNewModifierTypeOption([], ModifierPoolType.ENEMY_BUFF, tier);
|
|
}
|
|
|
|
const modifier = candidate?.type?.newModifier() as EnemyPersistentModifier;
|
|
modifier.stackCount = tierStackCount;
|
|
|
|
return modifier;
|
|
}
|
|
|
|
export function getEnemyModifierTypesForWave(
|
|
waveIndex: number,
|
|
count: number,
|
|
party: EnemyPokemon[],
|
|
poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER,
|
|
upgradeChance = 0,
|
|
): PokemonHeldItemModifierType[] {
|
|
const ret = new Array(count)
|
|
.fill(0)
|
|
.map(
|
|
() =>
|
|
getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0)
|
|
?.type as PokemonHeldItemModifierType,
|
|
);
|
|
if (!(waveIndex % 1000)) {
|
|
ret.push(getModifierType(modifierTypes.MINI_BLACK_HOLE) as PokemonHeldItemModifierType);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
export function getDailyRunStarterModifiers(party: PlayerPokemon[]): PokemonHeldItemModifier[] {
|
|
const ret: PokemonHeldItemModifier[] = [];
|
|
for (const p of party) {
|
|
for (let m = 0; m < 3; m++) {
|
|
const tierValue = randSeedInt(64);
|
|
|
|
let tier: ModifierTier;
|
|
if (tierValue > 25) {
|
|
tier = ModifierTier.COMMON;
|
|
} else if (tierValue > 12) {
|
|
tier = ModifierTier.GREAT;
|
|
} else if (tierValue > 4) {
|
|
tier = ModifierTier.ULTRA;
|
|
} else if (tierValue) {
|
|
tier = ModifierTier.ROGUE;
|
|
} else {
|
|
tier = ModifierTier.MASTER;
|
|
}
|
|
|
|
const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier)?.type?.newModifier(
|
|
p,
|
|
) as PokemonHeldItemModifier;
|
|
ret.push(modifier);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Generates a ModifierType from the specified pool
|
|
* @param party party of the trainer using the item
|
|
* @param poolType PLAYER/WILD/TRAINER
|
|
* @param tier If specified, will override the initial tier of an item (can still upgrade with luck)
|
|
* @param upgradeCount If defined, means that this is a new ModifierType being generated to override another via luck upgrade. Used for recursive logic
|
|
* @param retryCount Max allowed tries before the next tier down is checked for a valid ModifierType
|
|
* @param allowLuckUpgrades Default true. If false, will not allow ModifierType to randomly upgrade to next tier
|
|
*/
|
|
function getNewModifierTypeOption(
|
|
party: Pokemon[],
|
|
poolType: ModifierPoolType,
|
|
tier?: ModifierTier,
|
|
upgradeCount?: number,
|
|
retryCount = 0,
|
|
allowLuckUpgrades = true,
|
|
): ModifierTypeOption | null {
|
|
const player = !poolType;
|
|
const pool = getModifierPoolForType(poolType);
|
|
let thresholds: object;
|
|
switch (poolType) {
|
|
case ModifierPoolType.PLAYER:
|
|
thresholds = modifierPoolThresholds;
|
|
break;
|
|
case ModifierPoolType.WILD:
|
|
thresholds = enemyModifierPoolThresholds;
|
|
break;
|
|
case ModifierPoolType.TRAINER:
|
|
thresholds = enemyModifierPoolThresholds;
|
|
break;
|
|
case ModifierPoolType.ENEMY_BUFF:
|
|
thresholds = enemyBuffModifierPoolThresholds;
|
|
break;
|
|
case ModifierPoolType.DAILY_STARTER:
|
|
thresholds = dailyStarterModifierPoolThresholds;
|
|
break;
|
|
}
|
|
if (tier === undefined) {
|
|
const tierValue = randSeedInt(1024);
|
|
if (!upgradeCount) {
|
|
upgradeCount = 0;
|
|
}
|
|
if (player && tierValue && allowLuckUpgrades) {
|
|
const partyLuckValue = getPartyLuckValue(party);
|
|
const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4));
|
|
let upgraded = false;
|
|
do {
|
|
upgraded = randSeedInt(upgradeOdds) < 4;
|
|
if (upgraded) {
|
|
upgradeCount++;
|
|
}
|
|
} while (upgraded);
|
|
}
|
|
|
|
if (tierValue > 255) {
|
|
tier = ModifierTier.COMMON;
|
|
} else if (tierValue > 60) {
|
|
tier = ModifierTier.GREAT;
|
|
} else if (tierValue > 12) {
|
|
tier = ModifierTier.ULTRA;
|
|
} else if (tierValue) {
|
|
tier = ModifierTier.ROGUE;
|
|
} else {
|
|
tier = ModifierTier.MASTER;
|
|
}
|
|
|
|
tier += upgradeCount;
|
|
while (tier && (!modifierPool.hasOwnProperty(tier) || !modifierPool[tier].length)) {
|
|
tier--;
|
|
if (upgradeCount) {
|
|
upgradeCount--;
|
|
}
|
|
}
|
|
} else if (upgradeCount === undefined && player) {
|
|
upgradeCount = 0;
|
|
if (tier < ModifierTier.MASTER && allowLuckUpgrades) {
|
|
const partyLuckValue = getPartyLuckValue(party);
|
|
const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4));
|
|
while (modifierPool.hasOwnProperty(tier + upgradeCount + 1) && modifierPool[tier + upgradeCount + 1].length) {
|
|
if (randSeedInt(upgradeOdds) < 4) {
|
|
upgradeCount++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
tier += upgradeCount;
|
|
}
|
|
} else if (retryCount === 10 && tier) {
|
|
retryCount = 0;
|
|
tier--;
|
|
}
|
|
|
|
const tierThresholds = Object.keys(thresholds[tier]);
|
|
const totalWeight = Number.parseInt(tierThresholds[tierThresholds.length - 1]);
|
|
const value = randSeedInt(totalWeight);
|
|
let index: number | undefined;
|
|
for (const t of tierThresholds) {
|
|
const threshold = Number.parseInt(t);
|
|
if (value < threshold) {
|
|
index = thresholds[tier][threshold];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index === undefined) {
|
|
return null;
|
|
}
|
|
|
|
if (player) {
|
|
console.log(index, ignoredPoolIndexes[tier].filter(i => i <= index).length, ignoredPoolIndexes[tier]);
|
|
}
|
|
let modifierType: ModifierType | null = pool[tier][index].modifierType;
|
|
if (modifierType instanceof ModifierTypeGenerator) {
|
|
modifierType = (modifierType as ModifierTypeGenerator).generateType(party);
|
|
if (modifierType === null) {
|
|
if (player) {
|
|
console.log(ModifierTier[tier], upgradeCount);
|
|
}
|
|
return getNewModifierTypeOption(party, poolType, tier, upgradeCount, ++retryCount);
|
|
}
|
|
}
|
|
|
|
console.log(modifierType, !player ? "(enemy)" : "");
|
|
|
|
return new ModifierTypeOption(modifierType as ModifierType, upgradeCount!); // TODO: is this bang correct?
|
|
}
|
|
|
|
export function getDefaultModifierTypeForTier(tier: ModifierTier): ModifierType {
|
|
let modifierType: ModifierType | WeightedModifierType = modifierPool[tier || ModifierTier.COMMON][0];
|
|
if (modifierType instanceof WeightedModifierType) {
|
|
modifierType = (modifierType as WeightedModifierType).modifierType;
|
|
}
|
|
return modifierType;
|
|
}
|
|
|
|
export class ModifierTypeOption {
|
|
public type: ModifierType;
|
|
public upgradeCount: number;
|
|
public cost: number;
|
|
|
|
constructor(type: ModifierType, upgradeCount: number, cost = 0) {
|
|
this.type = type;
|
|
this.upgradeCount = upgradeCount;
|
|
this.cost = Math.min(Math.round(cost), Number.MAX_SAFE_INTEGER);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates the team's luck value.
|
|
* @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 {
|
|
if (globalScene.gameMode.isDaily) {
|
|
const DailyLuck = new NumberHolder(0);
|
|
globalScene.executeWithSeedOffset(
|
|
() => {
|
|
DailyLuck.value = randSeedInt(15); // Random number between 0 and 14
|
|
},
|
|
0,
|
|
globalScene.seed,
|
|
);
|
|
return DailyLuck.value;
|
|
}
|
|
const eventSpecies = timedEventManager.getEventLuckBoostedSpecies();
|
|
const luck = Phaser.Math.Clamp(
|
|
party
|
|
.map(p => (p.isAllowedInBattle() ? p.getLuck() + (eventSpecies.includes(p.species.speciesId) ? 1 : 0) : 0))
|
|
.reduce((total: number, value: number) => (total += value), 0),
|
|
0,
|
|
14,
|
|
);
|
|
return Math.min(timedEventManager.getEventLuckBoost() + (luck ?? 0), 14);
|
|
}
|
|
|
|
export function getLuckString(luckValue: number): string {
|
|
return ["D", "C", "C+", "B-", "B", "B+", "A-", "A", "A+", "A++", "S", "S+", "SS", "SS+", "SSS"][luckValue];
|
|
}
|
|
|
|
export function getLuckTextTint(luckValue: number): number {
|
|
let modifierTier: ModifierTier;
|
|
if (luckValue > 11) {
|
|
modifierTier = ModifierTier.LUXURY;
|
|
} else if (luckValue > 9) {
|
|
modifierTier = ModifierTier.MASTER;
|
|
} else if (luckValue > 5) {
|
|
modifierTier = ModifierTier.ROGUE;
|
|
} else if (luckValue > 2) {
|
|
modifierTier = ModifierTier.ULTRA;
|
|
} else if (luckValue) {
|
|
modifierTier = ModifierTier.GREAT;
|
|
} else {
|
|
modifierTier = ModifierTier.COMMON;
|
|
}
|
|
return getModifierTierTextTint(modifierTier);
|
|
}
|