diff --git a/scripts/create-test/boilerplates/reward.ts b/scripts/create-test/boilerplates/reward.ts new file mode 100644 index 00000000000..a4e31c07950 --- /dev/null +++ b/scripts/create-test/boilerplates/reward.ts @@ -0,0 +1,47 @@ +import { AbilityId } from "#enums/ability-id"; +import { MoveId } from "#enums/move-id"; +import { RewardId } from "#enums/reward-id"; +import { SpeciesId } from "#enums/species-id"; +import { BerryHeldItem } from "#items/berry"; +import { HeldItemReward } from "#items/reward"; +import { GameManager } from "#test/test-utils/game-manager"; +import { generateRewardForTest } from "#test/test-utils/reward-test-utils"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("{{description}}", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(AbilityId.BALL_FETCH) + .battleStyle("single") + .criticalHits(false) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH) + .startingLevel(100) + .enemyLevel(100); + }); + + it("should do XYZ when applied", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const reward = generateRewardForTest(RewardId.BERRY); + expect(reward).toBeInstanceOf(HeldItemReward); + game.scene.applyReward(reward, []); + expect(true).toBe(true); + }); +}); diff --git a/src/@types/rewards.ts b/src/@types/rewards.ts index acf4749a3da..c4696ba28ed 100644 --- a/src/@types/rewards.ts +++ b/src/@types/rewards.ts @@ -2,20 +2,34 @@ 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 { Pokemon } from "#field/pokemon"; -import type { Reward, RewardGenerator } from "#items/reward"; +import type { allRewardsType } from "#items/all-rewards"; +import type { RewardGenerator } from "#items/reward"; -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 allRewardGenerators = { + [k in keyof allRewardsType as allRewardsType[k] extends RewardGenerator ? k : never]: allRewardsType[k]; }; -// TODO: fix this with correctly typed args for different RewardIds -export type RewardSpecs = RewardPoolId | RewardGeneratorSpecs; +type RewardGeneratorArgMap = { + [k in keyof allRewardGenerators]: Exclude[0], undefined>; +}; + +/** Union type containing all {@linkcode RewardId}s corresponding to valid {@linkcode RewardGenerator}s. */ +type RewardGeneratorId = keyof allRewardGenerators; + +type RewardGeneratorSpecs = { + id: T; + args: RewardGeneratorArgMap[T]; +}; + +/** Union type used to specify fixed rewards used in generation. */ +export type RewardSpecs = T extends RewardGeneratorId + ? T | RewardGeneratorSpecs + : T; export type RewardPoolEntry = { id: RewardPoolId; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f0a544aa6d7..1e791389922 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"; @@ -86,7 +86,7 @@ import { applyHeldItems } from "#items/all-held-items"; import { type ApplyTrainerItemsParams, applyTrainerItems } from "#items/apply-trainer-items"; import type { HeldItemConfiguration } from "#items/held-item-data-types"; import { assignEnemyHeldItemsForWave, assignItemsFromConfiguration } from "#items/held-item-pool"; -import type { Reward } from "#items/reward"; +import type { MatchExact, Reward } from "#items/reward"; import { getRewardPoolForType } from "#items/reward-pool-utils"; import { type EnemyAttackStatusEffectChanceTrainerItem, TrainerItemEffect } from "#items/trainer-item"; import { @@ -2636,7 +2636,11 @@ export class BattleScene extends SceneBase { applyTrainerItems(effect, this.trainerItems, params); } - applyReward(reward: T, params: Parameters[0], playSound?: boolean): boolean { + applyReward( + reward: T, + params: MatchExact[0]>, + playSound?: boolean, + ): boolean { const soundName = reward.soundName; if (playSound && !this.sound.get(soundName)) { @@ -2780,7 +2784,6 @@ export class BattleScene extends SceneBase { }); } - // TODO @Wlowscha: Fix this /** * Attempt to discard one or more copies of a held item. @@ -2799,23 +2802,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/data/data-lists.ts b/src/data/data-lists.ts index a8db163fa69..25b34e11b49 100644 --- a/src/data/data-lists.ts +++ b/src/data/data-lists.ts @@ -1,12 +1,10 @@ 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"; export const allAbilities: Ability[] = []; export const allMoves: Move[] = []; @@ -14,4 +12,3 @@ export const allSpecies: PokemonSpecies[] = []; export const allHeldItems: Record = {}; export const allTrainerItems: Record = {}; -export const allRewards: Record = {}; diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 3b30be5c354..14095371f95 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -3,13 +3,12 @@ import { globalScene } from "#app/global-scene"; import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions"; import { signatureSpecies } from "#balance/signature-species"; import { tmSpecies } from "#balance/tms"; -import { allRewards } from "#data/data-lists"; import { doubleBattleDialogue } from "#data/double-battle-dialogue"; import { Gender } from "#data/gender"; import type { PokemonSpecies, PokemonSpeciesFilter } from "#data/pokemon-species"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; -import { PartyMemberStrength } from "#enums/party-member-strength"; +import type { PartyMemberStrength } from "#enums/party-member-strength"; import { PokeballType } from "#enums/pokeball"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; @@ -19,6 +18,8 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerType } from "#enums/trainer-type"; import { TrainerVariant } from "#enums/trainer-variant"; import type { EnemyPokemon } from "#field/pokemon"; +import { allRewards } from "#items/all-rewards"; +import type { Reward, RewardGenerator } from "#items/reward"; import { PokemonMove } from "#moves/pokemon-move"; import { getIsInitialized, initI18n } from "#plugins/i18n"; import type { EvilTeam } from "#trainers/evil-admin-trainer-pools"; @@ -28,10 +29,9 @@ import { getGymLeaderPartyTemplate, getWavePartyTemplate, TrainerPartyCompoundTemplate, - TrainerPartyTemplate, + type TrainerPartyTemplate, trainerPartyTemplates, } from "#trainers/trainer-party-template"; -import type { RewardFunc } from "#types/rewards"; import type { GenAIFunc, GenTrainerItemsFunc, @@ -109,7 +109,7 @@ export class TrainerConfig { public victoryBgm: string; public genTrainerItemsFunc: GenTrainerItemsFunc; public genAIFuncs: GenAIFunc[] = []; - public rewardFuncs: RewardFunc[] = []; + public rewardFuncs: (Reward | RewardGenerator)[] = []; public partyTemplates: TrainerPartyTemplate[]; public partyTemplateFunc: PartyTemplateFunc; public partyMemberFuncs: PartyMemberFuncs = {}; @@ -501,7 +501,7 @@ export class TrainerConfig { return this; } - setRewardFuncs(...rewardFuncs: (() => RewardFunc)[]): TrainerConfig { + setRewardFuncs(...rewardFuncs: (Reward | RewardGenerator)[]): TrainerConfig { this.rewardFuncs = rewardFuncs.map(func => () => { const rewardFunc = func(); const reward = rewardFunc(); diff --git a/src/enums/reward-id.ts b/src/enums/reward-id.ts index f8179828665..fd53a64c6d5 100644 --- a/src/enums/reward-id.ts +++ b/src/enums/reward-id.ts @@ -52,19 +52,17 @@ export const RewardId = { MEMORY_MUSHROOM: 0x2B03, DNA_SPLICERS: 0x2B04, - HELD_ITEM: 0x2C01, - SPECIES_STAT_BOOSTER: 0x2C02, - RARE_SPECIES_STAT_BOOSTER: 0x2C03, - BASE_STAT_BOOSTER: 0x2C04, - ATTACK_TYPE_BOOSTER: 0x2C05, - BERRY: 0x2C06, + SPECIES_STAT_BOOSTER: 0x2C01, + RARE_SPECIES_STAT_BOOSTER: 0x2C02, + BASE_STAT_BOOSTER: 0x2C03, + ATTACK_TYPE_BOOSTER: 0x2C04, + BERRY: 0x2C05, - TRAINER_ITEM: 0x2D01, - TEMP_STAT_STAGE_BOOSTER: 0x2D02, - DIRE_HIT: 0x2D03, - LURE: 0x2D04, - SUPER_LURE: 0x2D05, - MAX_LURE: 0x2D06, + TEMP_STAT_STAGE_BOOSTER: 0x2D01, + DIRE_HIT: 0x2D02, + LURE: 0x2D03, + SUPER_LURE: 0x2D04, + MAX_LURE: 0x2D05, FORM_CHANGE_ITEM: 0x2E01, RARE_FORM_CHANGE_ITEM: 0x2E02, diff --git a/src/init/init.ts b/src/init/init.ts index 4f6731213da..2b8961562da 100644 --- a/src/init/init.ts +++ b/src/init/init.ts @@ -7,7 +7,6 @@ import { initChallenges } from "#data/challenge"; import { initTrainerTypeDialogue } from "#data/dialogue"; import { initPokemonForms } from "#data/pokemon-forms"; import { initHeldItems } from "#items/all-held-items"; -import { initRewards } from "#items/all-rewards"; import { initTrainerItems } from "#items/all-trainer-items"; import { initHeldItemPools } from "#items/init-held-item-pools"; import { initRewardPools } from "#items/init-reward-pools"; @@ -45,6 +44,5 @@ function initItems() { initHeldItemPools(); initTrainerItems(); initTrainerItemPools(); - initRewards(); initRewardPools(); } diff --git a/src/items/all-rewards.ts b/src/items/all-rewards.ts index 4ab8def39a1..d89ee2fbab4 100644 --- a/src/items/all-rewards.ts +++ b/src/items/all-rewards.ts @@ -1,4 +1,3 @@ -import { allRewards } from "#data/data-lists"; import { PokeballType } from "#enums/pokeball"; import { RewardId } from "#enums/reward-id"; import { RarityTier } from "#enums/reward-tier"; @@ -18,6 +17,7 @@ import { FusePokemonReward, LapsingTrainerItemReward, MintRewardGenerator, + NoneReward, PokemonAllMovePpRestoreReward, PokemonHpRestoreReward, PokemonLevelIncrementReward, @@ -26,157 +26,166 @@ import { PokemonReviveReward, PokemonStatusHealReward, RememberMoveReward, + type Reward, + type RewardGenerator, SpeciesStatBoosterRewardGenerator, TempStatStageBoosterRewardGenerator, TeraTypeRewardGenerator, TmRewardGenerator, } from "./reward"; -export function initRewards() { +// TODO: Move to `reward-utils.ts` and un-exportt +export const allRewards = { + [RewardId.NONE]: new NoneReward(), + // Pokeball rewards - allRewards[RewardId.POKEBALL] = () => new AddPokeballReward("pb", PokeballType.POKEBALL, 5, RewardId.POKEBALL); - allRewards[RewardId.GREAT_BALL] = () => new AddPokeballReward("gb", PokeballType.GREAT_BALL, 5, RewardId.GREAT_BALL); - allRewards[RewardId.ULTRA_BALL] = () => new AddPokeballReward("ub", PokeballType.ULTRA_BALL, 5, RewardId.ULTRA_BALL); - allRewards[RewardId.ROGUE_BALL] = () => new AddPokeballReward("rb", PokeballType.ROGUE_BALL, 5, RewardId.ROGUE_BALL); - allRewards[RewardId.MASTER_BALL] = () => - new AddPokeballReward("mb", PokeballType.MASTER_BALL, 1, RewardId.MASTER_BALL); + [RewardId.POKEBALL]: new AddPokeballReward("pb", PokeballType.POKEBALL, 5, RewardId.POKEBALL), + [RewardId.GREAT_BALL]: new AddPokeballReward("gb", PokeballType.GREAT_BALL, 5, RewardId.GREAT_BALL), + [RewardId.ULTRA_BALL]: new AddPokeballReward("ub", PokeballType.ULTRA_BALL, 5, RewardId.ULTRA_BALL), + [RewardId.ROGUE_BALL]: new AddPokeballReward("rb", PokeballType.ROGUE_BALL, 5, RewardId.ROGUE_BALL), + [RewardId.MASTER_BALL]: new AddPokeballReward("mb", PokeballType.MASTER_BALL, 1, RewardId.MASTER_BALL), // Voucher rewards - allRewards[RewardId.VOUCHER] = () => new AddVoucherReward(VoucherType.REGULAR, 1, RewardId.VOUCHER); - allRewards[RewardId.VOUCHER_PLUS] = () => new AddVoucherReward(VoucherType.PLUS, 1, RewardId.VOUCHER_PLUS); - allRewards[RewardId.VOUCHER_PREMIUM] = () => new AddVoucherReward(VoucherType.PREMIUM, 1, RewardId.VOUCHER_PREMIUM); + [RewardId.VOUCHER]: new AddVoucherReward(VoucherType.REGULAR, 1, RewardId.VOUCHER), + [RewardId.VOUCHER_PLUS]: new AddVoucherReward(VoucherType.PLUS, 1, RewardId.VOUCHER_PLUS), + [RewardId.VOUCHER_PREMIUM]: new AddVoucherReward(VoucherType.PREMIUM, 1, RewardId.VOUCHER_PREMIUM), // Money rewards - allRewards[RewardId.NUGGET] = () => - new AddMoneyReward( - "modifierType:ModifierType.NUGGET", - "nugget", - 1, - "modifierType:ModifierType.MoneyRewardModifierType.extra.small", - RewardId.NUGGET, - ); - allRewards[RewardId.BIG_NUGGET] = () => - new AddMoneyReward( - "modifierType:ModifierType.BIG_NUGGET", - "big_nugget", - 2.5, - "modifierType:ModifierType.MoneyRewardModifierType.extra.moderate", - RewardId.BIG_NUGGET, - ); - allRewards[RewardId.RELIC_GOLD] = () => - new AddMoneyReward( - "modifierType:ModifierType.RELIC_GOLD", - "relic_gold", - 10, - "modifierType:ModifierType.MoneyRewardModifierType.extra.large", - RewardId.RELIC_GOLD, - ); + [RewardId.NUGGET]: new AddMoneyReward( + "modifierType:ModifierType.NUGGET", + "nugget", + 1, + "modifierType:ModifierType.MoneyRewardModifierType.extra.small", + RewardId.NUGGET, + ), + [RewardId.BIG_NUGGET]: new AddMoneyReward( + "modifierType:ModifierType.BIG_NUGGET", + "big_nugget", + 2.5, + "modifierType:ModifierType.MoneyRewardModifierType.extra.moderate", + RewardId.BIG_NUGGET, + ), + [RewardId.RELIC_GOLD]: new AddMoneyReward( + "modifierType:ModifierType.RELIC_GOLD", + "relic_gold", + 10, + "modifierType:ModifierType.MoneyRewardModifierType.extra.large", + RewardId.RELIC_GOLD, + ), // Party-wide consumables - allRewards[RewardId.RARER_CANDY] = () => - new AllPokemonLevelIncrementReward("modifierType:ModifierType.RARER_CANDY", "rarer_candy"); - allRewards[RewardId.SACRED_ASH] = () => - new AllPokemonFullReviveReward("modifierType:ModifierType.SACRED_ASH", "sacred_ash"); + [RewardId.RARER_CANDY]: new AllPokemonLevelIncrementReward("modifierType:ModifierType.RARER_CANDY", "rarer_candy"), + [RewardId.SACRED_ASH]: new AllPokemonFullReviveReward("modifierType:ModifierType.SACRED_ASH", "sacred_ash"), // Pokemon consumables - allRewards[RewardId.RARE_CANDY] = () => - new PokemonLevelIncrementReward("modifierType:ModifierType.RARE_CANDY", "rare_candy"); + [RewardId.RARE_CANDY]: new PokemonLevelIncrementReward("modifierType:ModifierType.RARE_CANDY", "rare_candy"), - allRewards[RewardId.EVOLUTION_ITEM] = () => new EvolutionItemRewardGenerator(false, RewardId.EVOLUTION_ITEM); - allRewards[RewardId.RARE_EVOLUTION_ITEM] = () => new EvolutionItemRewardGenerator(true, RewardId.RARE_EVOLUTION_ITEM); + [RewardId.EVOLUTION_ITEM]: new EvolutionItemRewardGenerator(false, RewardId.EVOLUTION_ITEM), + [RewardId.RARE_EVOLUTION_ITEM]: new EvolutionItemRewardGenerator(true, RewardId.RARE_EVOLUTION_ITEM), - allRewards[RewardId.POTION] = () => - new PokemonHpRestoreReward("modifierType:ModifierType.POTION", "potion", RewardId.POTION, 20, 10); - allRewards[RewardId.SUPER_POTION] = () => - new PokemonHpRestoreReward("modifierType:ModifierType.SUPER_POTION", "super_potion", RewardId.SUPER_POTION, 50, 25); - allRewards[RewardId.HYPER_POTION] = () => - new PokemonHpRestoreReward( - "modifierType:ModifierType.HYPER_POTION", - "hyper_potion", - RewardId.HYPER_POTION, - 200, - 50, - ); - allRewards[RewardId.MAX_POTION] = () => - new PokemonHpRestoreReward("modifierType:ModifierType.MAX_POTION", "max_potion", RewardId.MAX_POTION, 0, 100); - allRewards[RewardId.FULL_RESTORE] = () => - new PokemonHpRestoreReward( - "modifierType:ModifierType.FULL_RESTORE", - "full_restore", - RewardId.FULL_RESTORE, - 0, - 100, - true, - ); + [RewardId.POTION]: new PokemonHpRestoreReward("modifierType:ModifierType.POTION", "potion", RewardId.POTION, 20, 10), + [RewardId.SUPER_POTION]: new PokemonHpRestoreReward( + "modifierType:ModifierType.SUPER_POTION", + "super_potion", + RewardId.SUPER_POTION, + 50, + 25, + ), + [RewardId.HYPER_POTION]: new PokemonHpRestoreReward( + "modifierType:ModifierType.HYPER_POTION", + "hyper_potion", + RewardId.HYPER_POTION, + 200, + 50, + ), + [RewardId.MAX_POTION]: new PokemonHpRestoreReward( + "modifierType:ModifierType.MAX_POTION", + "max_potion", + RewardId.MAX_POTION, + 0, + 100, + ), + [RewardId.FULL_RESTORE]: new PokemonHpRestoreReward( + "modifierType:ModifierType.FULL_RESTORE", + "full_restore", + RewardId.FULL_RESTORE, + 0, + 100, + true, + ), - allRewards[RewardId.REVIVE] = () => - new PokemonReviveReward("modifierType:ModifierType.REVIVE", "revive", RewardId.REVIVE, 50); - allRewards[RewardId.MAX_REVIVE] = () => - new PokemonReviveReward("modifierType:ModifierType.MAX_REVIVE", "max_revive", RewardId.MAX_REVIVE, 100); + [RewardId.REVIVE]: new PokemonReviveReward("modifierType:ModifierType.REVIVE", "revive", RewardId.REVIVE, 50), + [RewardId.MAX_REVIVE]: new PokemonReviveReward( + "modifierType:ModifierType.MAX_REVIVE", + "max_revive", + RewardId.MAX_REVIVE, + 100, + ), - allRewards[RewardId.FULL_HEAL] = () => - new PokemonStatusHealReward("modifierType:ModifierType.FULL_HEAL", "full_heal"); + [RewardId.FULL_HEAL]: new PokemonStatusHealReward("modifierType:ModifierType.FULL_HEAL", "full_heal"), - allRewards[RewardId.ETHER] = () => - new PokemonPpRestoreReward("modifierType:ModifierType.ETHER", "ether", RewardId.ETHER, 10); - allRewards[RewardId.MAX_ETHER] = () => - new PokemonPpRestoreReward("modifierType:ModifierType.MAX_ETHER", "max_ether", RewardId.MAX_ETHER, -1); + [RewardId.ETHER]: new PokemonPpRestoreReward("modifierType:ModifierType.ETHER", "ether", RewardId.ETHER, 10), + [RewardId.MAX_ETHER]: new PokemonPpRestoreReward( + "modifierType:ModifierType.MAX_ETHER", + "max_ether", + RewardId.MAX_ETHER, + -1, + ), - allRewards[RewardId.ELIXIR] = () => - new PokemonAllMovePpRestoreReward("modifierType:ModifierType.ELIXIR", "elixir", RewardId.ELIXIR, 10); - allRewards[RewardId.MAX_ELIXIR] = () => - new PokemonAllMovePpRestoreReward("modifierType:ModifierType.MAX_ELIXIR", "max_elixir", RewardId.MAX_ELIXIR, -1); + [RewardId.ELIXIR]: new PokemonAllMovePpRestoreReward( + "modifierType:ModifierType.ELIXIR", + "elixir", + RewardId.ELIXIR, + 10, + ), + [RewardId.MAX_ELIXIR]: new PokemonAllMovePpRestoreReward( + "modifierType:ModifierType.MAX_ELIXIR", + "max_elixir", + RewardId.MAX_ELIXIR, + -1, + ), - allRewards[RewardId.PP_UP] = () => - new PokemonPpUpReward("modifierType:ModifierType.PP_UP", "pp_up", RewardId.PP_UP, 1); - allRewards[RewardId.PP_MAX] = () => - new PokemonPpUpReward("modifierType:ModifierType.PP_MAX", "pp_max", RewardId.PP_MAX, 3); + [RewardId.PP_UP]: new PokemonPpUpReward("modifierType:ModifierType.PP_UP", "pp_up", RewardId.PP_UP, 1), + [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),*/ + [RewardId.MINT]: new MintRewardGenerator(), - allRewards[RewardId.MINT] = () => new MintRewardGenerator(); + [RewardId.TERA_SHARD]: new TeraTypeRewardGenerator(), - allRewards[RewardId.TERA_SHARD] = () => new TeraTypeRewardGenerator(); + [RewardId.TM_COMMON]: new TmRewardGenerator(RarityTier.COMMON), + [RewardId.TM_GREAT]: new TmRewardGenerator(RarityTier.GREAT), + [RewardId.TM_ULTRA]: new TmRewardGenerator(RarityTier.ULTRA), - allRewards[RewardId.TM_COMMON] = () => new TmRewardGenerator(RarityTier.COMMON); - allRewards[RewardId.TM_GREAT] = () => new TmRewardGenerator(RarityTier.GREAT); - allRewards[RewardId.TM_ULTRA] = () => new TmRewardGenerator(RarityTier.ULTRA); + [RewardId.MEMORY_MUSHROOM]: new RememberMoveReward("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom"), - allRewards[RewardId.MEMORY_MUSHROOM] = () => - new RememberMoveReward("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom"); - - allRewards[RewardId.DNA_SPLICERS] = () => - new FusePokemonReward("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers"); + [RewardId.DNA_SPLICERS]: new FusePokemonReward("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers"), // Form change items - allRewards[RewardId.FORM_CHANGE_ITEM] = () => new FormChangeItemRewardGenerator(false, RewardId.FORM_CHANGE_ITEM); - allRewards[RewardId.RARE_FORM_CHANGE_ITEM] = () => - new FormChangeItemRewardGenerator(true, RewardId.RARE_FORM_CHANGE_ITEM); + [RewardId.FORM_CHANGE_ITEM]: new FormChangeItemRewardGenerator(false, RewardId.FORM_CHANGE_ITEM), + [RewardId.RARE_FORM_CHANGE_ITEM]: new FormChangeItemRewardGenerator(true, RewardId.RARE_FORM_CHANGE_ITEM), // Held items - allRewards[RewardId.SPECIES_STAT_BOOSTER] = () => new SpeciesStatBoosterRewardGenerator(false); - allRewards[RewardId.RARE_SPECIES_STAT_BOOSTER] = () => new SpeciesStatBoosterRewardGenerator(true); + [RewardId.SPECIES_STAT_BOOSTER]: new SpeciesStatBoosterRewardGenerator(false), + [RewardId.RARE_SPECIES_STAT_BOOSTER]: new SpeciesStatBoosterRewardGenerator(true), - allRewards[RewardId.BASE_STAT_BOOSTER] = () => new BaseStatBoosterRewardGenerator(); + [RewardId.BASE_STAT_BOOSTER]: new BaseStatBoosterRewardGenerator(), - allRewards[RewardId.ATTACK_TYPE_BOOSTER] = () => new AttackTypeBoosterRewardGenerator(); + [RewardId.ATTACK_TYPE_BOOSTER]: new AttackTypeBoosterRewardGenerator(), - allRewards[RewardId.BERRY] = () => new BerryRewardGenerator(); - - // MINI_BLACK_HOLE] = () => new HeldItemReward(HeldItemId.MINI_BLACK_HOLE), + [RewardId.BERRY]: new BerryRewardGenerator(), // Trainer items - allRewards[RewardId.LURE] = () => new LapsingTrainerItemReward(TrainerItemId.LURE, RewardId.LURE); - allRewards[RewardId.SUPER_LURE] = () => new LapsingTrainerItemReward(TrainerItemId.SUPER_LURE, RewardId.SUPER_LURE); - allRewards[RewardId.MAX_LURE] = () => new LapsingTrainerItemReward(TrainerItemId.MAX_LURE, RewardId.MAX_LURE); + [RewardId.LURE]: new LapsingTrainerItemReward(TrainerItemId.LURE, RewardId.LURE), + [RewardId.SUPER_LURE]: new LapsingTrainerItemReward(TrainerItemId.SUPER_LURE, RewardId.SUPER_LURE), + [RewardId.MAX_LURE]: new LapsingTrainerItemReward(TrainerItemId.MAX_LURE, RewardId.MAX_LURE), - allRewards[RewardId.TEMP_STAT_STAGE_BOOSTER] = () => new TempStatStageBoosterRewardGenerator(); + [RewardId.TEMP_STAT_STAGE_BOOSTER]: new TempStatStageBoosterRewardGenerator(), - allRewards[RewardId.DIRE_HIT] = () => - new LapsingTrainerItemReward(TrainerItemId.DIRE_HIT, RewardId.TEMP_STAT_STAGE_BOOSTER); - // GOLDEN_POKEBALL] = () => new TrainerItemReward(TrainerItemId.GOLDEN_POKEBALL), -} + [RewardId.DIRE_HIT]: new LapsingTrainerItemReward(TrainerItemId.DIRE_HIT, RewardId.TEMP_STAT_STAGE_BOOSTER), +} as const satisfies { + [k in RewardId]: Reward | RewardGenerator; +}; + +export type allRewardsType = typeof allRewards; 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..65fd6a6079a 100644 --- a/src/items/reward-utils.ts +++ b/src/items/reward-utils.ts @@ -1,10 +1,9 @@ -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 { allRewards } from "#items/all-rewards"; +import type { RewardPoolId, RewardSpecs } from "#types/rewards"; import { heldItemRarities } from "./held-item-default-tiers"; import { HeldItemReward, @@ -33,22 +32,24 @@ export function isRememberMoveReward(reward: Reward): reward is RememberMoveRewa } /** - * Generates a Reward from a given function - * @param rewardFunc - * @param pregenArgs Can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. + * Dynamically generate a {@linkcode RewardOption} from a given RewardSpecs. + * @param specs - The {@linkcode RewardSpecs} 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 generateReward(rewardFunc: RewardFunc, pregenArgs?: any[]): Reward | null { - const reward = rewardFunc(); - return reward instanceof RewardGenerator ? reward.generateReward(globalScene.getPlayerParty(), pregenArgs) : reward; -} - -export function generateRewardOptionFromId( - id: RewardPoolId, +export function generateRewardOptionFromId( + specs: RewardSpecs, cost = 0, tierOverride?: RarityTier, upgradeCount = 0, - pregenArgs?: any[], ): RewardOption | null { + // Destructure specs into individual parameters + const pregenArgs = typeof specs === "object" ? specs.args : undefined; + const id: RewardPoolId = typeof specs === "object" ? specs.id : specs; + if (isHeldItemId(id)) { const reward = new HeldItemReward(id); const tier = tierOverride ?? heldItemRarities[id]; @@ -61,8 +62,8 @@ export function generateRewardOptionFromId( return new RewardOption(reward, upgradeCount, tier, cost); } - const rewardFunc = allRewards[id]; - const reward = generateReward(rewardFunc, pregenArgs); + const rewardFunc = allRewards[id] as Reward | RewardGenerator; + const reward = rewardFunc instanceof RewardGenerator ? rewardFunc.generateReward(pregenArgs) : rewardFunc; if (reward) { const tier = tierOverride ?? rewardRarities[id]; return new RewardOption(reward, upgradeCount, tier, cost); @@ -70,17 +71,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 a9536142805..1637f059edb 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,65 +32,71 @@ 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"; import type { Exact } from "#types/type-helpers"; import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#ui/party-ui-handler"; import { PartyUiHandler } from "#ui/party-ui-handler"; -import { formatMoney, NumberHolder, padInt, randSeedInt, randSeedItem } from "#utils/common"; +import { formatMoney, NumberHolder, padInt, randSeedInt, randSeedItem, toDmgValue } from "#utils/common"; 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 + */ +export 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 public id: RewardId; public localeKey: string; public iconImage: string; @@ -109,6 +115,7 @@ export abstract class Reward { return i18next.t(`${this.localeKey}.name`); } + // TODO: These should be getters getDescription(): string { return i18next.t(`${this.localeKey}.description`); } @@ -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 { @@ -370,6 +370,7 @@ export class HeldItemReward extends PokemonReward { } export class TrainerItemReward extends Reward { + // TODO: This should not be public public itemId: TrainerItemId; constructor(itemId: TrainerItemId, group?: string, soundName?: string) { super("", "", group, soundName); @@ -461,32 +462,47 @@ export class ChangeTeraTypeReward extends PokemonReward { pokemon.teraType = this.teraType; return true; } -} - +} // todo: denest +// TODO: Consider removing `revive` from the signature of PokemonHealPhase in the wake of this +// (was only used for revives) +/** + * Helper function to instantly restore a Pokemon's hp. + * @param pokemon - The {@linkcode Pokemon} being healed + * @param percentToRestore - The percentage of the Pokemon's {@linkcode Stat.HP | maximum HP} to heal + * @param pointsToRestore - A minimum amount of HP points to restore; default `0` + * @param healStatus - Whether to also heal status ailments; default `false` + * @param fainted - Whether to allow reviving fainted Pokemon; default `false`. + * If `true`, will also disable the effect of {@linkcode TrainerItemEffect.HEALING_BOOSTER | Healing Charms}. + * @returns Whether the healing succeeded + */ function restorePokemonHp( pokemon: Pokemon, percentToRestore: number, - pointsToRestore = 0, - healStatus = false, - fainted = false, + { + pointsToRestore = 0, + healStatus = false, + fainted = false, + }: { + pointsToRestore?: number; + healStatus?: boolean; + fainted?: boolean; + } = {}, ): boolean { - if (!pokemon.hp === fainted) { - if (fainted || healStatus) { - pokemon.resetStatus(true, true, false, false); - } - // Apply HealingCharm - let multiplier = 1; - if (!fainted) { - const hpRestoreMultiplier = new NumberHolder(1); - this.applyPlayerItems(TrainerItemEffect.HEALING_BOOSTER, { numberHolder: hpRestoreMultiplier }); - multiplier = hpRestoreMultiplier.value; - } - const restorePoints = Math.floor(pointsToRestore * multiplier); - const restorePercent = Math.floor(percentToRestore * 0.01 * multiplier * pokemon.getMaxHp()); - pokemon.heal(Math.max(restorePercent, restorePoints, 1)); - return true; + if (pokemon.isFainted() !== fainted) { + return false; } - return false; + if (fainted || healStatus) { + pokemon.resetStatus(true, true, false, false); + } + // Apply HealingCharm + const hpRestoreMultiplier = new NumberHolder(1); + if (!fainted) { + this.applyPlayerItems(TrainerItemEffect.HEALING_BOOSTER, { numberHolder: hpRestoreMultiplier }); + } + const restorePoints = toDmgValue(pointsToRestore * hpRestoreMultiplier.value); + const restorePercent = toDmgValue((percentToRestore / 100) * hpRestoreMultiplier.value * pokemon.getMaxHp()); + pokemon.heal(Math.max(restorePercent, restorePoints)); + return true; } export class PokemonHpRestoreReward extends PokemonReward { @@ -854,59 +870,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; } } @@ -1208,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()); } } @@ -1244,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)]); } } @@ -1265,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)); } } @@ -1508,22 +1518,6 @@ export class RewardOption { } } -// TODO: If necessary, add the rest of the modifier types here. -// For now, doing the minimal work until the modifier rework lands. -const RewardConstructorMap = Object.freeze({ - RewardGenerator, -}); - -/** - * Map of of modifier type strings to their constructor type - */ -export type RewardConstructorMap = typeof RewardConstructorMap; - -/** - * Map of modifier type strings to their instance type - */ -export type RewardInstanceMap = { - [K in keyof RewardConstructorMap]: InstanceType; -}; - -export type RewardString = keyof RewardConstructorMap; +export class NoneReward extends Reward { + override apply(): void {} +} diff --git a/src/phases/reward-phase.ts b/src/phases/reward-phase.ts index 033a022958f..6211c627374 100644 --- a/src/phases/reward-phase.ts +++ b/src/phases/reward-phase.ts @@ -1,7 +1,6 @@ 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"; export class RewardPhase extends BattlePhase { @@ -10,7 +9,7 @@ export class RewardPhase extends BattlePhase { public readonly phaseName: "RewardPhase" | "RibbonRewardPhase" | "GameOverRewardPhase" = "RewardPhase"; protected reward: Reward; - constructor(rewardFunc: RewardFunc) { + constructor(rewardFunc: Reward | RewardGenerator) { super(); this.reward = rewardFunc(); diff --git a/src/phases/ribbon-reward-phase.ts b/src/phases/ribbon-reward-phase.ts index 42e5bd826a3..03c2681f833 100644 --- a/src/phases/ribbon-reward-phase.ts +++ b/src/phases/ribbon-reward-phase.ts @@ -1,15 +1,15 @@ import { globalScene } from "#app/global-scene"; import type { PokemonSpecies } from "#data/pokemon-species"; import { UiMode } from "#enums/ui-mode"; +import type { Reward, RewardGenerator } from "#items/reward"; import { RewardPhase } from "#phases/reward-phase"; -import type { RewardFunc } from "#types/rewards"; import i18next from "i18next"; export class RibbonRewardPhase extends RewardPhase { public readonly phaseName = "RibbonRewardPhase"; private species: PokemonSpecies; - constructor(rewardFunc: RewardFunc, species: PokemonSpecies) { + constructor(rewardFunc: Reward | RewardGenerator, species: PokemonSpecies) { super(rewardFunc); this.species = species; diff --git a/src/phases/trainer-item-reward-phase.ts b/src/phases/trainer-item-reward-phase.ts index 033a022958f..86769636e3e 100644 --- a/src/phases/trainer-item-reward-phase.ts +++ b/src/phases/trainer-item-reward-phase.ts @@ -1,7 +1,6 @@ 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"; export class RewardPhase extends BattlePhase { @@ -10,10 +9,11 @@ export class RewardPhase extends BattlePhase { public readonly phaseName: "RewardPhase" | "RibbonRewardPhase" | "GameOverRewardPhase" = "RewardPhase"; protected reward: Reward; - constructor(rewardFunc: RewardFunc) { + constructor(rewardFunc: Reward | RewardGenerator) { super(); - this.reward = rewardFunc(); + const reward = rewardFunc(); + this.reward = reward instanceof RewardGenerator ? reward.generateReward() : reward; } start() { diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index 34223f5d6a1..1cdb8d6ebf9 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -5,8 +5,8 @@ import { Challenges } from "#enums/challenges"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; -import { TrainerItemId } from "#enums/trainer-item-id"; import { TextStyle } from "#enums/text-style"; +import { TrainerItemId } from "#enums/trainer-item-id"; import { WeatherType } from "#enums/weather-type"; import { addTextObject } from "#ui/text"; import type { nil } from "#utils/common"; diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 9adf6b7f9de..5d25b7f2367 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -5,8 +5,8 @@ import { Button } from "#enums/buttons"; import { Command } from "#enums/command"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; -import { TrainerItemId } from "#enums/trainer-item-id"; import { TextStyle } from "#enums/text-style"; +import { TrainerItemId } from "#enums/trainer-item-id"; import { UiMode } from "#enums/ui-mode"; import type { CommandPhase } from "#phases/command-phase"; import { PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; 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) { diff --git a/src/utils/common.ts b/src/utils/common.ts index 1730a1ce82b..84742609fb2 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -125,8 +125,8 @@ export function randItem(items: T[]): T { return items.length === 1 ? items[0] : items[randInt(items.length)]; } -export function randSeedItem(items: T[]): T { - return items.length === 1 ? items[0] : Phaser.Math.RND.pick(items); +export function randSeedItem(items: T[] | readonly T[]): T { + return items.length === 1 ? items[0] : Phaser.Math.RND.pick(items as T[]); } export function randSeedWeightedItem(items: T[]): T { diff --git a/test/phases/form-change-phase.test.ts b/test/phases/form-change-phase.test.ts index df0bd74f8ef..4322be461ef 100644 --- a/test/phases/form-change-phase.test.ts +++ b/test/phases/form-change-phase.test.ts @@ -56,4 +56,4 @@ export class ModifierHelper extends GameManagerHelper { private log(...params: any[]) { console.log("Modifiers:", ...params); } -} \ No newline at end of file +} diff --git a/test/test-utils/helpers/modifiers-helper.ts b/test/test-utils/helpers/modifiers-helper.ts index df0bd74f8ef..4322be461ef 100644 --- a/test/test-utils/helpers/modifiers-helper.ts +++ b/test/test-utils/helpers/modifiers-helper.ts @@ -56,4 +56,4 @@ export class ModifierHelper extends GameManagerHelper { private log(...params: any[]) { console.log("Modifiers:", ...params); } -} \ No newline at end of file +}