From 6077cfff1721b6e24afbbdf397554a8379743cae Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Sun, 3 Aug 2025 22:57:54 -0400 Subject: [PATCH 1/4] Added type safety to reward generators --- src/@types/all-reward-type.ts | 127 +++++ src/@types/rewards.ts | 27 +- src/data/data-lists.ts | 6 +- src/items/all-rewards.ts | 3 +- src/items/reward-pool-utils.ts | 7 +- src/items/reward-utils.ts | 82 ++- src/items/reward.ts | 630 ++++++++++++------------ src/phases/trainer-item-reward-phase.ts | 5 +- src/utils/common.ts | 4 +- 9 files changed, 541 insertions(+), 350 deletions(-) create mode 100644 src/@types/all-reward-type.ts diff --git a/src/@types/all-reward-type.ts b/src/@types/all-reward-type.ts new file mode 100644 index 00000000000..97e791651c0 --- /dev/null +++ b/src/@types/all-reward-type.ts @@ -0,0 +1,127 @@ +import type { RewardId } from "#enums/reward-id"; +import type { + AddMoneyReward, + AddPokeballReward, + AddVoucherReward, + AllPokemonFullReviveReward, + AllPokemonLevelIncrementReward, + AttackTypeBoosterRewardGenerator, + BaseStatBoosterRewardGenerator, + BerryRewardGenerator, + EvolutionItemRewardGenerator, + FormChangeItemRewardGenerator, + FusePokemonReward, + LapsingTrainerItemReward, + MintRewardGenerator, + PokemonAllMovePpRestoreReward, + PokemonHpRestoreReward, + PokemonLevelIncrementReward, + PokemonPpRestoreReward, + PokemonPpUpReward, + PokemonReviveReward, + PokemonStatusHealReward, + RememberMoveReward, + SpeciesStatBoosterRewardGenerator, + TempStatStageBoosterRewardGenerator, + TeraTypeRewardGenerator, + TmRewardGenerator, +} from "#items/reward"; + +/** + * The type of the `allRewards` const object. + * @todo Make `allRewards` a const object and replace all references to this with `typeof allRewards` + */ +export type allRewardsType = { + // Pokeball rewards + [RewardId.POKEBALL]: () => AddPokeballReward; + [RewardId.GREAT_BALL]: () => AddPokeballReward; + [RewardId.ULTRA_BALL]: () => AddPokeballReward; + [RewardId.ROGUE_BALL]: () => AddPokeballReward; + [RewardId.MASTER_BALL]: () => AddPokeballReward; + + // Voucher rewards + [RewardId.VOUCHER]: () => AddVoucherReward; + [RewardId.VOUCHER_PLUS]: () => AddVoucherReward; + [RewardId.VOUCHER_PREMIUM]: () => AddVoucherReward; + + // Money rewards + [RewardId.NUGGET]: () => AddMoneyReward; + [RewardId.BIG_NUGGET]: () => AddMoneyReward; + [RewardId.RELIC_GOLD]: () => AddMoneyReward; + + // Party-wide consumables + [RewardId.RARER_CANDY]: () => AllPokemonLevelIncrementReward; + [RewardId.SACRED_ASH]: () => AllPokemonFullReviveReward; + + // Pokemon consumables + [RewardId.RARE_CANDY]: () => PokemonLevelIncrementReward; + + [RewardId.EVOLUTION_ITEM]: () => EvolutionItemRewardGenerator; + [RewardId.RARE_EVOLUTION_ITEM]: () => EvolutionItemRewardGenerator; + + [RewardId.POTION]: () => PokemonHpRestoreReward; + [RewardId.SUPER_POTION]: () => PokemonHpRestoreReward; + [RewardId.HYPER_POTION]: () => PokemonHpRestoreReward; + [RewardId.MAX_POTION]: () => PokemonHpRestoreReward; + [RewardId.FULL_RESTORE]: () => PokemonHpRestoreReward; + + [RewardId.REVIVE]: () => PokemonReviveReward; + [RewardId.MAX_REVIVE]: () => PokemonReviveReward; + + [RewardId.FULL_HEAL]: () => PokemonStatusHealReward; + + [RewardId.ETHER]: () => PokemonPpRestoreReward; + [RewardId.MAX_ETHER]: () => PokemonPpRestoreReward; + + [RewardId.ELIXIR]: () => PokemonAllMovePpRestoreReward; + [RewardId.MAX_ELIXIR]: () => PokemonAllMovePpRestoreReward; + + [RewardId.PP_UP]: () => PokemonPpUpReward; + [RewardId.PP_MAX]: () => PokemonPpUpReward; + + /* + [RewardId.REPEL]: () => DoubleBattleChanceBoosterReward, + [RewardId.SUPER_REPEL]: () => DoubleBattleChanceBoosterReward, + [RewardId.MAX_REPEL]: () => DoubleBattleChanceBoosterReward, + */ + + [RewardId.MINT]: () => MintRewardGenerator; + + [RewardId.TERA_SHARD]: () => TeraTypeRewardGenerator; + + [RewardId.TM_COMMON]: () => TmRewardGenerator; + [RewardId.TM_GREAT]: () => TmRewardGenerator; + [RewardId.TM_ULTRA]: () => TmRewardGenerator; + + [RewardId.MEMORY_MUSHROOM]: () => RememberMoveReward; + + [RewardId.DNA_SPLICERS]: () => FusePokemonReward; + + // Form change items + [RewardId.FORM_CHANGE_ITEM]: () => FormChangeItemRewardGenerator; + [RewardId.RARE_FORM_CHANGE_ITEM]: () => FormChangeItemRewardGenerator; + + // Held items + + [RewardId.SPECIES_STAT_BOOSTER]: () => SpeciesStatBoosterRewardGenerator; + [RewardId.RARE_SPECIES_STAT_BOOSTER]: () => SpeciesStatBoosterRewardGenerator; + + [RewardId.BASE_STAT_BOOSTER]: () => BaseStatBoosterRewardGenerator; + + [RewardId.ATTACK_TYPE_BOOSTER]: () => AttackTypeBoosterRewardGenerator; + + [RewardId.BERRY]: () => BerryRewardGenerator; + + // [RewardId.MINI_BLACK_HOLE]: () => HeldItemReward, + + // Trainer items + + [RewardId.LURE]: () => LapsingTrainerItemReward; + [RewardId.SUPER_LURE]: () => LapsingTrainerItemReward; + [RewardId.MAX_LURE]: () => LapsingTrainerItemReward; + + [RewardId.TEMP_STAT_STAGE_BOOSTER]: () => TempStatStageBoosterRewardGenerator; + + [RewardId.DIRE_HIT]: () => LapsingTrainerItemReward; + // [RewardId.GOLDEN_POKEBALL]: () => TrainerItemReward, +}; diff --git a/src/@types/rewards.ts b/src/@types/rewards.ts index acf4749a3da..19c400c549a 100644 --- a/src/@types/rewards.ts +++ b/src/@types/rewards.ts @@ -3,19 +3,36 @@ import type { RewardId } from "#enums/reward-id"; import type { TrainerItemId } from "#enums/trainer-item-id"; import type { Pokemon } from "#field/pokemon"; import type { Reward, RewardGenerator } from "#items/reward"; +import type { allRewardsType } from "#types/all-reward-type"; export type RewardFunc = () => Reward | RewardGenerator; +// TODO: Remove party from arguments can be accessed from `globalScene` export type WeightedRewardWeightFunc = (party: Pokemon[], rerollCount?: number) => number; export type RewardPoolId = RewardId | HeldItemId | TrainerItemId; -export type RewardGeneratorSpecs = { - id: RewardId; - args: RewardGeneratorArgs; +type allRewardsInstanceMap = { + [k in keyof allRewardsType as ReturnType extends RewardGenerator ? k : never]: ReturnType< + allRewardsType[k] + >; }; -// TODO: fix this with correctly typed args for different RewardIds -export type RewardSpecs = RewardPoolId | RewardGeneratorSpecs; +export type RewardGeneratorArgMap = { + [k in keyof allRewardsInstanceMap]: Exclude[0], undefined>; +}; + +/** Union type containing all `RewardId`s corresponding to valid {@linkcode RewardGenerator}s. */ +export type RewardGeneratorId = keyof allRewardsInstanceMap; + +// TODO: SOrt out which types can and cannot be exported +export type RewardGeneratorSpecs = { + id: T; + args: RewardGeneratorArgMap[T]; +}; + +export type RewardSpecs = T extends RewardGeneratorId + ? T | RewardGeneratorSpecs + : T; export type RewardPoolEntry = { id: RewardPoolId; diff --git a/src/data/data-lists.ts b/src/data/data-lists.ts index a8db163fa69..6602d40ab75 100644 --- a/src/data/data-lists.ts +++ b/src/data/data-lists.ts @@ -1,12 +1,11 @@ import type { Ability } from "#abilities/ability"; import type { PokemonSpecies } from "#data/pokemon-species"; import type { HeldItemId } from "#enums/held-item-id"; -import type { RewardId } from "#enums/reward-id"; import type { TrainerItemId } from "#enums/trainer-item-id"; import type { HeldItem } from "#items/held-item"; import type { TrainerItem } from "#items/trainer-item"; import type { Move } from "#moves/move"; -import type { RewardFunc } from "#types/rewards"; +import type { allRewardsType } from "#types/all-reward-type"; export const allAbilities: Ability[] = []; export const allMoves: Move[] = []; @@ -14,4 +13,5 @@ export const allSpecies: PokemonSpecies[] = []; export const allHeldItems: Record = {}; export const allTrainerItems: Record = {}; -export const allRewards: Record = {}; +// TODO: Consider moving into `all-rewards.ts` as a const object - files should not be importing this +export const allRewards: allRewardsType = {}; diff --git a/src/items/all-rewards.ts b/src/items/all-rewards.ts index 4ab8def39a1..caf95f719d8 100644 --- a/src/items/all-rewards.ts +++ b/src/items/all-rewards.ts @@ -132,7 +132,8 @@ export function initRewards() { allRewards[RewardId.PP_MAX] = () => new PokemonPpUpReward("modifierType:ModifierType.PP_MAX", "pp_max", RewardId.PP_MAX, 3); - /*REPEL] = () => new DoubleBattleChanceBoosterReward('Repel', 5), + /* + REPEL] = () => new DoubleBattleChanceBoosterReward('Repel', 5), SUPER_REPEL] = () => new DoubleBattleChanceBoosterReward('Super Repel', 10), MAX_REPEL] = () => new DoubleBattleChanceBoosterReward('Max Repel', 25),*/ diff --git a/src/items/reward-pool-utils.ts b/src/items/reward-pool-utils.ts index 4ad5df55b9a..eb56770060e 100644 --- a/src/items/reward-pool-utils.ts +++ b/src/items/reward-pool-utils.ts @@ -8,7 +8,7 @@ import { isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common import { getPartyLuckValue } from "#utils/party"; import type { RewardOption } from "./reward"; import { rewardPool, rewardPoolWeights } from "./reward-pools"; -import { generateRewardOptionFromId, generateRewardOptionFromSpecs, isTrainerItemId } from "./reward-utils"; +import { generateRewardOptionFromId, isTrainerItemId } from "./reward-utils"; /* This file still contains several functions to generate rewards from pools. The hierarchy of these functions is explained here. @@ -160,7 +160,7 @@ export function generatePlayerRewardOptions( if (customRewardSettings?.guaranteedRewardSpecs && customRewardSettings.guaranteedRewardSpecs.length > 0) { for (const specs of customRewardSettings.guaranteedRewardSpecs) { - const rewardOption = generateRewardOptionFromSpecs(specs); + const rewardOption = generateRewardOptionFromId(specs); if (rewardOption) { options.push(rewardOption); } @@ -287,13 +287,12 @@ function getNewRewardOption( * Replaces the {@linkcode Reward} of the entries within {@linkcode options} with any * up to the smallest amount of entries between {@linkcode options} and the override array. * @param options Array of naturally rolled {@linkcode RewardOption}s - * @param party Array of the player's current party */ export function overridePlayerRewardOptions(options: RewardOption[]) { const minLength = Math.min(options.length, Overrides.REWARD_OVERRIDE.length); for (let i = 0; i < minLength; i++) { const specs: RewardSpecs = Overrides.REWARD_OVERRIDE[i]; - const rewardOption = generateRewardOptionFromSpecs(specs); + const rewardOption = generateRewardOptionFromId(specs); if (rewardOption) { options[i] = rewardOption; } diff --git a/src/items/reward-utils.ts b/src/items/reward-utils.ts index 7f229b65c94..9a73ec45f2a 100644 --- a/src/items/reward-utils.ts +++ b/src/items/reward-utils.ts @@ -1,10 +1,15 @@ -import { globalScene } from "#app/global-scene"; import { allRewards } from "#data/data-lists"; import type { HeldItemId } from "#enums/held-item-id"; import { getRewardCategory, RewardCategoryId, RewardId } from "#enums/reward-id"; import type { RarityTier } from "#enums/reward-tier"; import type { TrainerItemId } from "#enums/trainer-item-id"; -import type { RewardFunc, RewardPoolId, RewardSpecs } from "#types/rewards"; +import type { + RewardFunc, + RewardGeneratorArgMap, + RewardGeneratorId, + RewardGeneratorSpecs, + RewardPoolId, +} from "#types/rewards"; import { heldItemRarities } from "./held-item-default-tiers"; import { HeldItemReward, @@ -33,22 +38,73 @@ export function isRememberMoveReward(reward: Reward): reward is RememberMoveRewa } /** - * Generates a Reward from a given function + * Generates a Reward from a given function. * @param rewardFunc + */ +function generateReward(rewardFunc: () => Reward): Reward | null; +/** + * Generates a Reward from a given function + * @param generator * @param pregenArgs Can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. */ -export function generateReward(rewardFunc: RewardFunc, pregenArgs?: any[]): Reward | null { +function generateReward( + generator: () => T, + pregenArgs?: Parameters[0], +): Reward | null; +function generateReward(rewardFunc: RewardFunc, pregenArgs?: any[]): Reward | null { const reward = rewardFunc(); - return reward instanceof RewardGenerator ? reward.generateReward(globalScene.getPlayerParty(), pregenArgs) : reward; + return reward instanceof RewardGenerator ? reward.generateReward(pregenArgs) : reward; } +/** + * Dynamically generate a {@linkcode RewardOption} from a given ID. + * @param specs - The {@linkcode RewardGeneratorSpecs} used to generate the reward + * @param cost - The monetary cost of selecting the option; default `0` + * @param tierOverride - An optional {@linkcode RarityTier} to override the option's rarity + * @param upgradeCount - The number of tier upgrades having occurred; default `0` + * @returns The generated {@linkcode RewardOption}, or `null` if no reward could be generated + * @todo Remove `null` from signature eventually + */ +export function generateRewardOptionFromId( + specs: RewardGeneratorSpecs, + cost?: number, + tierOverride?: RarityTier, + upgradeCount?: number, +): RewardOption | null; +/** + * Dynamically generate a {@linkcode RewardOption} from a given ID. + * @param id - The {@linkcode GeneratorRewardId} to generate a reward for + * @param cost - The monetary cost of selecting the option; default `0` + * @param tierOverride - An optional {@linkcode RarityTier} to override the option's rarity + * @param upgradeCount - The number of tier upgrades having occurred; default `0` + * @param pregenArgs - Optional arguments used to seed the generator. + * @returns The generated {@linkcode RewardOption}, or `null` if no reward could be generated + */ +export function generateRewardOptionFromId( + id: T, + cost?: number, + tierOverride?: RarityTier, + upgradeCount?: number, + pregenArgs?: RewardGeneratorArgMap[T], +): RewardOption | null; export function generateRewardOptionFromId( - id: RewardPoolId, + id: Exclude, + cost?: number, + tierOverride?: RarityTier, + upgradeCount?: number, +): RewardOption | null; +export function generateRewardOptionFromId( + id: RewardGeneratorSpecs | RewardPoolId, cost = 0, tierOverride?: RarityTier, upgradeCount = 0, - pregenArgs?: any[], + pregenArgs?: unknown, ): RewardOption | null { + // Destructure specs into objects + if (typeof id === "object") { + ({ id, args: pregenArgs } = id); + } + if (isHeldItemId(id)) { const reward = new HeldItemReward(id); const tier = tierOverride ?? heldItemRarities[id]; @@ -61,6 +117,7 @@ export function generateRewardOptionFromId( return new RewardOption(reward, upgradeCount, tier, cost); } + // TODO: This narrows to `any` const rewardFunc = allRewards[id]; const reward = generateReward(rewardFunc, pregenArgs); if (reward) { @@ -70,17 +127,6 @@ export function generateRewardOptionFromId( return null; } -export function generateRewardOptionFromSpecs( - specs: RewardSpecs, - cost = 0, - overrideTier?: RarityTier, -): RewardOption | null { - if (typeof specs === "number") { - return generateRewardOptionFromId(specs, cost, overrideTier); - } - return generateRewardOptionFromId(specs.id, cost, overrideTier, 0, specs.args); -} - export function getPlayerShopRewardOptionsForWave(waveIndex: number, baseCost: number): RewardOption[] { if (!(waveIndex % 10)) { return []; diff --git a/src/items/reward.ts b/src/items/reward.ts index b4fcc9a1735..4c72955b5b5 100644 --- a/src/items/reward.ts +++ b/src/items/reward.ts @@ -11,16 +11,16 @@ import { getNatureName, getNatureStatMultiplier } from "#data/nature"; import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#data/pokeball"; import { pokemonFormChanges, SpeciesFormChangeCondition } from "#data/pokemon-forms"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { BerryType } from "#enums/berry-type"; +import type { BerryType } from "#enums/berry-type"; import { FormChangeItem } from "#enums/form-change-item"; import { HeldItemId } from "#enums/held-item-id"; import { LearnMoveType } from "#enums/learn-move-type"; -import { MoveId } from "#enums/move-id"; +import type { MoveId } from "#enums/move-id"; import { Nature } from "#enums/nature"; import type { PokeballType } from "#enums/pokeball"; import { PokemonType } from "#enums/pokemon-type"; import { RewardId } from "#enums/reward-id"; -import { RarityTier } from "#enums/reward-tier"; +import type { RarityTier } from "#enums/reward-tier"; import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesId } from "#enums/species-id"; import type { PermanentStat, TempBattleStat } from "#enums/stat"; @@ -32,7 +32,7 @@ import { permanentStatToHeldItem, statBoostItems } from "#items/base-stat-booste import { berryTypeToHeldItem } from "#items/berry"; import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#items/held-item-pool"; import { formChangeItemName } from "#items/item-utility"; -import { SPECIES_STAT_BOOSTER_ITEMS, type SpeciesStatBoostHeldItem } from "#items/stat-booster"; +import type { SpeciesStatBoosterItemId, SpeciesStatBoostHeldItem } from "#items/stat-booster"; import { TrainerItemEffect, tempStatToTrainerItem } from "#items/trainer-item"; import type { PokemonMove } from "#moves/pokemon-move"; import { getVoucherTypeIcon, getVoucherTypeName, type VoucherType } from "#system/voucher"; @@ -43,51 +43,58 @@ import { formatMoney, NumberHolder, padInt, randSeedInt, randSeedItem } from "#u import { getEnumKeys, getEnumValues } from "#utils/enums"; import i18next from "i18next"; -/* -The term "Reward" refers to items the player can access in the post-battle screen (although -they may be used in other places of the code as well). +/** + * @module + * The term "Reward" refers to items the player can access in the post-battle screen (although + * they may be used in other places of the code as well). -Examples include (but are not limited to): -- Potions and other healing items -- Held items and trainer items -- Money items such as nugget and ancient relic + * Examples include (but are not limited to): + * - Potions and other healing items + * - Held items and trainer items + * - Money items such as nugget and ancient relic -Rewards have a basic structure with a name, description, and icon. These are used to display -the reward in the reward select screen. All rewards have an .apply() method, which applies the -effect, for example: -- Apply healing to a pokemon -- Assign a held item to a pokemon, or a trainer item to the player -- Add money + * Rewards have a basic structure with a name, description, and icon. These are used to display + * the reward in the reward select screen. All rewards have an .apply() method, which applies the + * effect, for example: + * - Apply healing to a pokemon + * - Assign a held item to a pokemon, or a trainer item to the player + * - Add money -Some rewards, once clicked, simply have their effect---these are Rewards that add money, pokéball, -vouchers, or global effect such as Sacred Ash. -Most rewards require extra parameters. They are divided into subclasses depending on the parameters -that they need, in particular: -- PokemonReward requires to pass a Pokemon (to apply healing, assign item...) -- PokemonMoveReward requires to pass a Pokemon and a move (for Elixir, or PP Up) -Plus some edge cases for Memory Mushroom and DNA Splicers. + * Some rewards, once clicked, simply have their effect---these are Rewards that add money, pokéball, + * vouchers, or global effect such as Sacred Ash. + * Most rewards require extra parameters. They are divided into subclasses depending on the parameters + * that they need, in particular: + * - PokemonReward requires to pass a Pokemon (to apply healing, assign item...) + * - PokemonMoveReward requires to pass a Pokemon and a move (for Elixir, or PP Up) + * Plus some edge cases for Memory Mushroom and DNA Splicers. -The parameters to be passed are generated by the .applyReward() function in SelectRewardPhase. -This function takes care of opening the party screen and letting the player select a party pokemon, -a move, etc. depending on what is required. Once the parameters are generated, instead of calling -.apply() directly, we call the .applyReward() method in BattleScene, which also plays the sound. -[This method could perhaps be removed]. + * The parameters to be passed are generated by the .applyReward() function in {@linkcode SelectRewardPhase}. + * This function takes care of opening the party screen and letting the player select a party pokemon, + * a move, etc. depending on what is required. Once the parameters are generated, instead of calling + * .apply() directly, we call the .applyReward() method in BattleScene, which also plays the sound. + * [This method could perhaps be removed]. -Rewards are assigned RewardId, and there are also RewardCategoryId. For example, TM is a RewardCategoryId, -while CommonTM, RareTM etc are RewardIds. There is _not_ a RewardId for _each_ move. Similarly, -some specific categories of held items are assigned their own RewardId, but they all fall under a single -RewardCategoryId. + * Rewards are assigned RewardId, and there are also RewardCategoryId. For example, TM is a RewardCategoryId, + * while CommonTM, RareTM etc are RewardIds. There is _not_ a RewardId for _each_ move. Similarly, + * some specific categories of held items are assigned their own RewardId, but they all fall under a single + * RewardCategoryId. -rewardInitObj plays a similar role to allHeldItems, except instead of containing all possible reward -instances, it instead contains functions that generate those rewards. Here, the keys used are strings -rather than RewardId, the difference exists because here we want to distinguish unique held items -for example. The entries of rewardInitObj are used in the RewardPool. + * rewardInitObj plays a similar role to allHeldItems, except instead of containing all possible reward + * instances, it instead contains functions that generate those rewards. Here, the keys used are strings + * rather than RewardId, the difference exists because here we want to distinguish unique held items + * for example. The entries of rewardInitObj are used in the RewardPool. -There are some more derived classes, in particular: -RewardGenerator, which creates Reward instances from a certain group (e.g. TMs, nature mints, or berries); -and RewardOption, which is displayed during the select reward phase at the end of each encounter. + * There are some more derived classes, in particular: + * RewardGenerator, which creates Reward instances from a certain group (e.g. TMs, nature mints, or berries); + * and RewardOption, which is displayed during the select reward phase at the end of each encounter. */ +/** + * Type helper to exactly match objects and nothing else. + * @todo merge with `Exact` later on + */ +type MatchExact = T extends object ? Exact : T; + export abstract class Reward { // TODO: If all we care about for categorization is the reward's ID's _category_, why not do it there? // TODO: Make abstract and readonly @@ -121,8 +128,8 @@ export abstract class Reward { /** * Check whether this reward should be applied. */ - // TODO: This is erroring on stuff of typ - shouldApply(_params: Exact[0]>): boolean { + // TODO: This is erroring on stuff with `undefined` + shouldApply(_params: MatchExact[0]>): boolean { return true; } @@ -131,25 +138,18 @@ export abstract class Reward { abstract apply(_params?: unknown): void; } -// TODO: Can this return null? -// TODO: Make this generic based on T -type RewardGeneratorFunc = (party: Pokemon[], pregenArgs?: any[]) => T | null; - -export abstract class RewardGenerator { - private genRewardFunc: RewardGeneratorFunc; - public id: RewardId; - - constructor(genRewardFunc: RewardGeneratorFunc) { - this.genRewardFunc = genRewardFunc; - } - - generateReward(party: Pokemon[], pregenArgs?: any[]) { - const ret = this.genRewardFunc(party, pregenArgs); - if (ret && this.id) { - ret.id = this.id; - } - return ret; - } +/** + * A {@linkcode RewardGenerator} represents a dynamic generator for a given type of reward. + * These can be customized by lieu of {@linkcode generateReward} to alter the generation result. + */ +export abstract class RewardGenerator { + /** + * Dynamically generate a new reward. + * @param pregenArgs - An optional argument taken by super classes to customize the reward generated. + * @returns The generated reward, or `null` if none are able to be produced + */ + // TODO: Remove null from signature in favor of adding a condition or similar (reduces bangs needed) + abstract generateReward(pregenArgs?: unknown): Reward | null; } export class AddPokeballReward extends Reward { @@ -440,6 +440,7 @@ export class ChangeTeraTypeReward extends PokemonReward { }); } + // TODO: What is this for? getPregenArgs(): any[] { return [this.teraType]; } @@ -467,6 +468,9 @@ export class ChangeTeraTypeReward extends PokemonReward { } } +// todo: denest +// TODO: Consider removing `revive` from the signature of PokemonHealPhase in the wake of this +// (was only used for revives) function restorePokemonHp( pokemon: Pokemon, percentToRestore: number, @@ -858,59 +862,55 @@ export class RememberMoveReward extends PokemonReward { } export class BerryRewardGenerator extends RewardGenerator { - constructor() { - super((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { - const item = berryTypeToHeldItem[pregenArgs[0] as BerryType]; - return new HeldItemReward(item); - } - const item = getNewBerryHeldItem(); + override generateReward(pregenArgs?: BerryType): HeldItemReward { + if (pregenArgs !== undefined) { + const item = berryTypeToHeldItem[pregenArgs]; return new HeldItemReward(item); - }); - this.id = RewardId.BERRY; + } + const item = getNewBerryHeldItem(); + return new HeldItemReward(item); } } export class MintRewardGenerator extends RewardGenerator { - constructor() { - super((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in Nature) { - return new PokemonNatureChangeReward(pregenArgs[0] as Nature); - } - return new PokemonNatureChangeReward(randSeedItem(getEnumValues(Nature))); - }); - this.id = RewardId.MINT; + override generateReward(pregenArgs?: Nature) { + if (pregenArgs !== undefined) { + return new PokemonNatureChangeReward(pregenArgs); + } + return new PokemonNatureChangeReward(randSeedItem(getEnumValues(Nature))); } } export class TeraTypeRewardGenerator extends RewardGenerator { - constructor() { - super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { - return new ChangeTeraTypeReward(pregenArgs[0] as PokemonType); + override generateReward(pregenArgs?: PokemonType) { + if (pregenArgs !== undefined) { + return new ChangeTeraTypeReward(pregenArgs[0]); + } + if (!globalScene.trainerItems.hasItem(TrainerItemId.TERA_ORB)) { + return null; + } + + const shardType = this.getTeraType(); + return new ChangeTeraTypeReward(shardType); + } + + private getTeraType(): PokemonType { + // If all party members have a given Tera Type, omit it from the pool + const excludedType = globalScene.getPlayerParty().reduce((prevType, p) => { + if ( + // Ignore Pokemon with fixed Tera Types + p.hasSpecies(SpeciesId.TERAPAGOS) || + p.hasSpecies(SpeciesId.OGERPON) || + p.hasSpecies(SpeciesId.SHEDINJA) + ) { + return prevType; } - if (!globalScene.trainerItems.hasItem(TrainerItemId.TERA_ORB)) { - return null; - } - const teraTypes: PokemonType[] = []; - for (const p of party) { - if ( - !(p.hasSpecies(SpeciesId.TERAPAGOS) || p.hasSpecies(SpeciesId.OGERPON) || p.hasSpecies(SpeciesId.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 ChangeTeraTypeReward(shardType); - }); - this.id = RewardId.TERA_SHARD; + return prevType === p.teraType ? prevType : PokemonType.UNKNOWN; + }, PokemonType.UNKNOWN); + + const validTypes = getEnumValues(PokemonType).filter(t => t !== excludedType); + // 1/64 chance for tera stellar + return randSeedInt(64) ? randSeedItem(validTypes) : PokemonType.STELLAR; } } @@ -1220,29 +1220,23 @@ export class FusePokemonReward extends PokemonReward { } export class AttackTypeBoosterRewardGenerator extends RewardGenerator { - constructor() { - super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { - return new AttackTypeBoosterReward(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT); - } + override generateReward(pregenArgs?: PokemonType) { + if (pregenArgs !== undefined) { + return new AttackTypeBoosterReward(pregenArgs, TYPE_BOOST_ITEM_BOOST_PERCENT); + } - const item = getNewAttackTypeBoosterHeldItem(party); + const item = getNewAttackTypeBoosterHeldItem(globalScene.getPlayerParty()); - return item ? new HeldItemReward(item) : null; - }); - this.id = RewardId.ATTACK_TYPE_BOOSTER; + return item ? new HeldItemReward(item) : null; } } export class BaseStatBoosterRewardGenerator extends RewardGenerator { - constructor() { - super((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { - return new BaseStatBoosterReward(pregenArgs[0]); - } - return new HeldItemReward(getNewVitaminHeldItem()); - }); - this.id = RewardId.BASE_STAT_BOOSTER; + override generateReward(pregenArgs?: PermanentStat) { + if (pregenArgs !== undefined) { + return new BaseStatBoosterReward(pregenArgs); + } + return new HeldItemReward(getNewVitaminHeldItem()); } } @@ -1256,15 +1250,8 @@ export class TempStatStageBoosterRewardGenerator extends RewardGenerator { [Stat.ACC]: "x_accuracy", }; - constructor() { - super((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && TEMP_BATTLE_STATS.includes(pregenArgs[0])) { - return new LapsingTrainerItemReward(tempStatToTrainerItem[pregenArgs[0]]); - } - const randStat: TempBattleStat = randSeedInt(Stat.ACC, Stat.ATK); - return new LapsingTrainerItemReward(tempStatToTrainerItem[randStat]); - }); - this.id = RewardId.TEMP_STAT_STAGE_BOOSTER; + override generateReward(pregenArgs?: TempBattleStat) { + return new LapsingTrainerItemReward(tempStatToTrainerItem[pregenArgs ?? randSeedItem(TEMP_BATTLE_STATS)]); } } @@ -1277,232 +1264,243 @@ export class TempStatStageBoosterRewardGenerator extends RewardGenerator { export class SpeciesStatBoosterRewardGenerator extends RewardGenerator { /** Object comprised of the currently available species-based stat boosting held items */ + private rare: boolean; constructor(rare: boolean) { - super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in SPECIES_STAT_BOOSTER_ITEMS) { - return new HeldItemReward(pregenArgs[0] as HeldItemId); - } + super(); + this.rare = rare; + } + override generateReward(pregenArgs?: SpeciesStatBoosterItemId) { + if (pregenArgs !== undefined) { + return new HeldItemReward(pregenArgs); + } - // Get a pool of items based on the rarity. - const tierItems = rare - ? [HeldItemId.LIGHT_BALL, HeldItemId.THICK_CLUB, HeldItemId.METAL_POWDER, HeldItemId.QUICK_POWDER] - : [HeldItemId.DEEP_SEA_SCALE, HeldItemId.DEEP_SEA_TOOTH]; + // Get a pool of items based on the rarity. + const tierItems = this.rare + ? [HeldItemId.LIGHT_BALL, HeldItemId.THICK_CLUB, HeldItemId.METAL_POWDER, HeldItemId.QUICK_POWDER] + : [HeldItemId.DEEP_SEA_SCALE, HeldItemId.DEEP_SEA_TOOTH]; - const weights = new Array(tierItems.length).fill(0); + const weights = new Array(tierItems.length).fill(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 === MoveId.FLING) */ + for (const p of globalScene.getPlayerParty()) { + 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 === MoveId.FLING) */ - for (const i in tierItems) { - const checkedSpecies = (allHeldItems[tierItems[i]] as SpeciesStatBoostHeldItem).species; + for (const i in tierItems) { + const checkedSpecies = (allHeldItems[tierItems[i]] as SpeciesStatBoostHeldItem).species; - // If party member already has the item being weighted currently, skip to the next item - const hasItem = p.heldItemManager.hasItem(tierItems[i]); + // If party member already has the item being weighted currently, skip to the next item + const hasItem = p.heldItemManager.hasItem(tierItems[i]); - 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(SpeciesId.PIKACHU) && hasFling) { - // Add weight to Light Ball if party member has Fling - weights[i]++; - } + 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(SpeciesId.PIKACHU) && hasFling) { + // Add weight to Light Ball if party member has Fling + weights[i]++; } } } + } - // TODO: Replace this with a helper function - let totalWeight = 0; - for (const weight of weights) { - totalWeight += weight; - } + // TODO: Replace this with a helper function + let totalWeight = 0; + for (const weight of weights) { + totalWeight += weight; + } - if (totalWeight !== 0) { - const randInt = randSeedInt(totalWeight, 1); - let weight = 0; + 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 HeldItemReward(tierItems[i]); - } - weight = curWeight; + for (const i in weights) { + if (weights[i] !== 0) { + const curWeight = weight + weights[i]; + if (randInt <= weight + weights[i]) { + return new HeldItemReward(tierItems[i]); } + weight = curWeight; } } + } - return null; - }); - this.id = rare ? RewardId.SPECIES_STAT_BOOSTER : RewardId.RARE_SPECIES_STAT_BOOSTER; + return null; } } export class TmRewardGenerator extends RewardGenerator { + private tier: RarityTier; constructor(tier: RarityTier) { - super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in MoveId) { - return new TmReward(pregenArgs[0] as MoveId); - } - 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; - } - // TODO: should this use `randSeedItem`? - const randTmIndex = randSeedInt(tierUniqueCompatibleTms.length); - return new TmReward(tierUniqueCompatibleTms[randTmIndex]); + super(); + this.tier = tier; + } + + override generateReward(pregenArgs?: MoveId) { + if (pregenArgs !== undefined) { + return new TmReward(pregenArgs); + } + + const party = globalScene.getPlayerParty(); + 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), + ); }); - this.id = - tier === RarityTier.COMMON - ? RewardId.TM_COMMON - : tier === RarityTier.GREAT - ? RewardId.TM_GREAT - : RewardId.TM_ULTRA; + const tierUniqueCompatibleTms = partyMemberCompatibleTms + .flat() + .filter(tm => tmPoolTiers[tm] === this.tier) + .filter(tm => !allMoves[tm].name.endsWith(" (N)")) + .filter((tm, i, array) => array.indexOf(tm) === i); + if (!tierUniqueCompatibleTms.length) { + return null; + } + + const randTmIndex = randSeedItem(tierUniqueCompatibleTms); + return new TmReward(randTmIndex); } } export class EvolutionItemRewardGenerator extends RewardGenerator { - constructor(rare: boolean, id: RewardId) { - super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in EvolutionItem) { - return new EvolutionItemReward(pregenArgs[0] as EvolutionItem); - } + private rare: boolean; + constructor(rare: boolean) { + super(); + this.rare = rare; + } - const evolutionItemPool = [ - party - .filter( - p => - pokemonEvolutions.hasOwnProperty(p.species.speciesId) && - (!p.pauseEvolutions || - p.species.speciesId === SpeciesId.SLOWPOKE || - p.species.speciesId === SpeciesId.EEVEE || - p.species.speciesId === SpeciesId.KIRLIA || - p.species.speciesId === SpeciesId.SNORUNT), - ) - .flatMap(p => { - const evolutions = pokemonEvolutions[p.species.speciesId]; - return evolutions.filter(e => e.isValidItemEvolution(p)); - }), - party - .filter( - p => - p.isFusion() && - p.fusionSpecies && - pokemonEvolutions.hasOwnProperty(p.fusionSpecies.speciesId) && - (!p.pauseEvolutions || - p.fusionSpecies.speciesId === SpeciesId.SLOWPOKE || - p.fusionSpecies.speciesId === SpeciesId.EEVEE || - p.fusionSpecies.speciesId === SpeciesId.KIRLIA || - p.fusionSpecies.speciesId === SpeciesId.SNORUNT), - ) - .flatMap(p => { - const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId]; - return evolutions.filter(e => e.isValidItemEvolution(p, true)); - }), - ] - .flat() - .flatMap(e => e.evoItem) - .filter(i => !!i && i > 50 === rare); + override generateReward(pregenArgs?: EvolutionItem) { + if (pregenArgs !== undefined) { + return new EvolutionItemReward(pregenArgs); + } - if (!evolutionItemPool.length) { - return null; - } + const party = globalScene.getPlayerParty(); - // TODO: should this use `randSeedItem`? - return new EvolutionItemReward(evolutionItemPool[randSeedInt(evolutionItemPool.length)]!); // TODO: is the bang correct? - }); - this.id = id; + const evolutionItemPool = [ + party + .filter( + p => + pokemonEvolutions.hasOwnProperty(p.species.speciesId) && + (!p.pauseEvolutions || + p.species.speciesId === SpeciesId.SLOWPOKE || + p.species.speciesId === SpeciesId.EEVEE || + p.species.speciesId === SpeciesId.KIRLIA || + p.species.speciesId === SpeciesId.SNORUNT), + ) + .flatMap(p => { + const evolutions = pokemonEvolutions[p.species.speciesId]; + return evolutions.filter(e => e.isValidItemEvolution(p)); + }), + party + .filter( + p => + p.isFusion() && + p.fusionSpecies && + pokemonEvolutions.hasOwnProperty(p.fusionSpecies.speciesId) && + (!p.pauseEvolutions || + p.fusionSpecies.speciesId === SpeciesId.SLOWPOKE || + p.fusionSpecies.speciesId === SpeciesId.EEVEE || + p.fusionSpecies.speciesId === SpeciesId.KIRLIA || + p.fusionSpecies.speciesId === SpeciesId.SNORUNT), + ) + .flatMap(p => { + const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId]; + return evolutions.filter(e => e.isValidItemEvolution(p, true)); + }), + ] + .flat() + .flatMap(e => e.evoItem) + .filter(i => !!i && i > 50 === this.rare); + + if (!evolutionItemPool.length) { + return null; + } + + return new EvolutionItemReward(randSeedItem(evolutionItemPool)); } } export class FormChangeItemRewardGenerator extends RewardGenerator { - constructor(isRareFormChangeItem: boolean, id: RewardId) { - super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in FormChangeItem) { - return new FormChangeItemReward(pregenArgs[0] as FormChangeItem); - } + private isRareFormChangeItem: boolean; - 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.trainerItems.hasItem(TrainerItemId.MEGA_BRACELET)) && - ((fc.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) === -1 && - fc.formKey.indexOf(SpeciesFormKey.ETERNAMAX) === -1) || - globalScene.trainerItems.hasItem(TrainerItemId.DYNAMAX_BAND)) && - (!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 && !p.heldItemManager.hasFormChangeItem(t.item)); + constructor(isRareFormChangeItem: boolean) { + super(); + this.isRareFormChangeItem = isRareFormChangeItem; + } - if (p.species.speciesId === SpeciesId.NECROZMA) { - // technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break... - 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 "); + override generateReward(pregenArgs?: FormChangeItem) { + if (pregenArgs !== undefined) { + return new FormChangeItemReward(pregenArgs); + } + const party = globalScene.getPlayerParty(); + + // TODO: REFACTOR THIS FUCKERY PLEASE + 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.trainerItems.hasItem(TrainerItemId.MEGA_BRACELET)) && + ((fc.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) === -1 && + fc.formKey.indexOf(SpeciesFormKey.ETERNAMAX) === -1) || + globalScene.trainerItems.hasItem(TrainerItemId.DYNAMAX_BAND)) && + (!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 && !p.heldItemManager.hasFormChangeItem(t.item)); + + if (p.species.speciesId === SpeciesId.NECROZMA) { + // technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break... + 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. + } + return formChangeItemTriggers; + }), + ), + ] + .flat() + .flatMap(fc => fc.item) + .filter(i => (i && i < 100) === this.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; - } + if (!formChangeItemPool.length) { + return null; + } - // TODO: should this use `randSeedItem`? - return new FormChangeItemReward(formChangeItemPool[randSeedInt(formChangeItemPool.length)]); - }); - this.id = id; + return new FormChangeItemReward(randSeedItem(formChangeItemPool)); } } diff --git a/src/phases/trainer-item-reward-phase.ts b/src/phases/trainer-item-reward-phase.ts index 033a022958f..22171b44128 100644 --- a/src/phases/trainer-item-reward-phase.ts +++ b/src/phases/trainer-item-reward-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import type { Reward } from "#items/reward"; +import { type Reward, RewardGenerator } from "#items/reward"; import { BattlePhase } from "#phases/battle-phase"; import type { RewardFunc } from "#types/rewards"; import i18next from "i18next"; @@ -13,7 +13,8 @@ export class RewardPhase extends BattlePhase { constructor(rewardFunc: RewardFunc) { super(); - this.reward = rewardFunc(); + const reward = rewardFunc(); + this.reward = reward instanceof RewardGenerator ? reward.generateReward() : reward; } start() { diff --git a/src/utils/common.ts b/src/utils/common.ts index 1730a1ce82b..a522d9cac22 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -125,7 +125,9 @@ export function randItem(items: T[]): T { return items.length === 1 ? items[0] : items[randInt(items.length)]; } -export function randSeedItem(items: T[]): T { +export function randSeedItem(items: T[] | readonly T[]): T { + // TODO: Resolve this later + // @ts-expect-error - phaser is dumb af return items.length === 1 ? items[0] : Phaser.Math.RND.pick(items); } From 8a4b89d04b5dc7f3fe403160a303024a419c2b24 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Sun, 3 Aug 2025 22:58:52 -0400 Subject: [PATCH 2/4] Removed duplicate class member so biome won't yell at meeeeeee --- src/battle-scene.ts | 22 +++------------------- src/ui/party-ui-handler.ts | 1 + 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f0a544aa6d7..3111641fc74 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -69,8 +69,8 @@ import { HeldItemPoolType, RewardPoolType } from "#enums/reward-pool-type"; import { ShopCursorTarget } from "#enums/shop-cursor-target"; import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; -import { TrainerItemId } from "#enums/trainer-item-id"; import { TextStyle } from "#enums/text-style"; +import { TrainerItemId } from "#enums/trainer-item-id"; import type { TrainerSlot } from "#enums/trainer-slot"; import { TrainerType } from "#enums/trainer-type"; import { TrainerVariant } from "#enums/trainer-variant"; @@ -2643,6 +2643,8 @@ export class BattleScene extends SceneBase { this.playSound(soundName); } + // TODO: fix later + // @ts-expect-error if (!reward.shouldApply(params)) { return false; } @@ -2780,7 +2782,6 @@ export class BattleScene extends SceneBase { }); } - // TODO @Wlowscha: Fix this /** * Attempt to discard one or more copies of a held item. @@ -2799,23 +2800,6 @@ export class BattleScene extends SceneBase { return this.removeModifier(itemModifier); } - /** - * Attempt to discard one or more copies of a held item. - * @param itemModifier - The {@linkcode PokemonHeldItemModifier} being discarded - * @param discardQuantity - The number of copies to remove (up to the amount currently held); default `1` - * @returns Whether the item was successfully discarded. - * Removing fewer items than requested is still considered a success. - */ - tryDiscardHeldItemModifier(itemModifier: PokemonHeldItemModifier, discardQuantity = 1): boolean { - const countTaken = Math.min(discardQuantity, itemModifier.stackCount); - itemModifier.stackCount -= countTaken; - - if (itemModifier.stackCount > 0) { - return true; - } - - return this.removeModifier(itemModifier); - } canTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, transferQuantity = 1): boolean { const mod = itemModifier.clone() as PokemonHeldItemModifier; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 20096719198..d87de6998d6 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -2160,6 +2160,7 @@ class PartyDiscardModeButton extends Phaser.GameObjects.Container { * @remarks * This will also reveal the button if it is currently hidden. */ + // TODO: Reminder to fix public toggleIcon(partyMode: PartyUiMode.MODIFIER_TRANSFER | PartyUiMode.DISCARD): void { this.setActive(true).setVisible(true); switch (partyMode) { From 704209d7a8562cac24b2e6935d1dca168f8c60c2 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Tue, 5 Aug 2025 07:14:20 -0400 Subject: [PATCH 3/4] Update all-reward-type.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/@types/all-reward-type.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/@types/all-reward-type.ts b/src/@types/all-reward-type.ts index 97e791651c0..e5fdcf5b625 100644 --- a/src/@types/all-reward-type.ts +++ b/src/@types/all-reward-type.ts @@ -79,12 +79,6 @@ export type allRewardsType = { [RewardId.PP_UP]: () => PokemonPpUpReward; [RewardId.PP_MAX]: () => PokemonPpUpReward; - /* - [RewardId.REPEL]: () => DoubleBattleChanceBoosterReward, - [RewardId.SUPER_REPEL]: () => DoubleBattleChanceBoosterReward, - [RewardId.MAX_REPEL]: () => DoubleBattleChanceBoosterReward, - */ - [RewardId.MINT]: () => MintRewardGenerator; [RewardId.TERA_SHARD]: () => TeraTypeRewardGenerator; From 933450297c30d11d947c454e03171eecfaafd355 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Tue, 5 Aug 2025 07:14:27 -0400 Subject: [PATCH 4/4] Update all-rewards.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/items/all-rewards.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/items/all-rewards.ts b/src/items/all-rewards.ts index caf95f719d8..10b3f140977 100644 --- a/src/items/all-rewards.ts +++ b/src/items/all-rewards.ts @@ -132,11 +132,6 @@ export function initRewards() { allRewards[RewardId.PP_MAX] = () => new PokemonPpUpReward("modifierType:ModifierType.PP_MAX", "pp_max", RewardId.PP_MAX, 3); - /* - REPEL] = () => new DoubleBattleChanceBoosterReward('Repel', 5), - SUPER_REPEL] = () => new DoubleBattleChanceBoosterReward('Super Repel', 10), - MAX_REPEL] = () => new DoubleBattleChanceBoosterReward('Max Repel', 25),*/ - allRewards[RewardId.MINT] = () => new MintRewardGenerator(); allRewards[RewardId.TERA_SHARD] = () => new TeraTypeRewardGenerator();