Reward type safety (#6211)

* Added type safety to reward generators

* Removed duplicate class member so biome won't yell at meeeeeee

* Update all-reward-type.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Update all-rewards.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Moved `allRewards` to a const object; general cleanup

* Added `noneReward`

* Removed `getPregenArgs`

* Resolved kev's commnet

* Expunged `RewardFunc`

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
Bertie690 2025-08-07 13:58:53 -04:00 committed by GitHub
parent acc12318d7
commit 2aca187c66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 602 additions and 570 deletions

View File

@ -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);
});
});

View File

@ -2,20 +2,34 @@ import type { HeldItemId } from "#enums/held-item-id";
import type { RewardId } from "#enums/reward-id"; import type { RewardId } from "#enums/reward-id";
import type { TrainerItemId } from "#enums/trainer-item-id"; import type { TrainerItemId } from "#enums/trainer-item-id";
import type { Pokemon } from "#field/pokemon"; 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 WeightedRewardWeightFunc = (party: Pokemon[], rerollCount?: number) => number;
export type RewardPoolId = RewardId | HeldItemId | TrainerItemId; export type RewardPoolId = RewardId | HeldItemId | TrainerItemId;
export type RewardGeneratorSpecs = { type allRewardGenerators = {
id: RewardId; [k in keyof allRewardsType as allRewardsType[k] extends RewardGenerator ? k : never]: allRewardsType[k];
args: RewardGeneratorArgs;
}; };
// TODO: fix this with correctly typed args for different RewardIds
export type RewardSpecs = RewardPoolId | RewardGeneratorSpecs; type RewardGeneratorArgMap = {
[k in keyof allRewardGenerators]: Exclude<Parameters<allRewardGenerators[k]["generateReward"]>[0], undefined>;
};
/** Union type containing all {@linkcode RewardId}s corresponding to valid {@linkcode RewardGenerator}s. */
type RewardGeneratorId = keyof allRewardGenerators;
type RewardGeneratorSpecs<T extends RewardGeneratorId = RewardGeneratorId> = {
id: T;
args: RewardGeneratorArgMap[T];
};
/** Union type used to specify fixed rewards used in generation. */
export type RewardSpecs<T extends RewardPoolId = RewardPoolId> = T extends RewardGeneratorId
? T | RewardGeneratorSpecs<T>
: T;
export type RewardPoolEntry = { export type RewardPoolEntry = {
id: RewardPoolId; id: RewardPoolId;

View File

@ -69,8 +69,8 @@ import { HeldItemPoolType, RewardPoolType } from "#enums/reward-pool-type";
import { ShopCursorTarget } from "#enums/shop-cursor-target"; import { ShopCursorTarget } from "#enums/shop-cursor-target";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { TrainerItemId } from "#enums/trainer-item-id";
import { TextStyle } from "#enums/text-style"; import { TextStyle } from "#enums/text-style";
import { TrainerItemId } from "#enums/trainer-item-id";
import type { TrainerSlot } from "#enums/trainer-slot"; import type { TrainerSlot } from "#enums/trainer-slot";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { TrainerVariant } from "#enums/trainer-variant"; 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 ApplyTrainerItemsParams, applyTrainerItems } from "#items/apply-trainer-items";
import type { HeldItemConfiguration } from "#items/held-item-data-types"; import type { HeldItemConfiguration } from "#items/held-item-data-types";
import { assignEnemyHeldItemsForWave, assignItemsFromConfiguration } from "#items/held-item-pool"; 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 { getRewardPoolForType } from "#items/reward-pool-utils";
import { type EnemyAttackStatusEffectChanceTrainerItem, TrainerItemEffect } from "#items/trainer-item"; import { type EnemyAttackStatusEffectChanceTrainerItem, TrainerItemEffect } from "#items/trainer-item";
import { import {
@ -2636,7 +2636,11 @@ export class BattleScene extends SceneBase {
applyTrainerItems(effect, this.trainerItems, params); applyTrainerItems(effect, this.trainerItems, params);
} }
applyReward<T extends Reward>(reward: T, params: Parameters<T["apply"]>[0], playSound?: boolean): boolean { applyReward<T extends Reward>(
reward: T,
params: MatchExact<Parameters<T["apply"]>[0]>,
playSound?: boolean,
): boolean {
const soundName = reward.soundName; const soundName = reward.soundName;
if (playSound && !this.sound.get(soundName)) { if (playSound && !this.sound.get(soundName)) {
@ -2780,7 +2784,6 @@ export class BattleScene extends SceneBase {
}); });
} }
// TODO @Wlowscha: Fix this // TODO @Wlowscha: Fix this
/** /**
* Attempt to discard one or more copies of a held item. * Attempt to discard one or more copies of a held item.
@ -2799,23 +2802,6 @@ export class BattleScene extends SceneBase {
return this.removeModifier(itemModifier); 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 { canTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, transferQuantity = 1): boolean {
const mod = itemModifier.clone() as PokemonHeldItemModifier; const mod = itemModifier.clone() as PokemonHeldItemModifier;

View File

@ -1,12 +1,10 @@
import type { Ability } from "#abilities/ability"; import type { Ability } from "#abilities/ability";
import type { PokemonSpecies } from "#data/pokemon-species"; import type { PokemonSpecies } from "#data/pokemon-species";
import type { HeldItemId } from "#enums/held-item-id"; 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 { TrainerItemId } from "#enums/trainer-item-id";
import type { HeldItem } from "#items/held-item"; import type { HeldItem } from "#items/held-item";
import type { TrainerItem } from "#items/trainer-item"; import type { TrainerItem } from "#items/trainer-item";
import type { Move } from "#moves/move"; import type { Move } from "#moves/move";
import type { RewardFunc } from "#types/rewards";
export const allAbilities: Ability[] = []; export const allAbilities: Ability[] = [];
export const allMoves: Move[] = []; export const allMoves: Move[] = [];
@ -14,4 +12,3 @@ export const allSpecies: PokemonSpecies[] = [];
export const allHeldItems: Record<HeldItemId, HeldItem> = {}; export const allHeldItems: Record<HeldItemId, HeldItem> = {};
export const allTrainerItems: Record<TrainerItemId, TrainerItem> = {}; export const allTrainerItems: Record<TrainerItemId, TrainerItem> = {};
export const allRewards: Record<RewardId, RewardFunc> = {};

View File

@ -3,13 +3,12 @@ import { globalScene } from "#app/global-scene";
import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions";
import { signatureSpecies } from "#balance/signature-species"; import { signatureSpecies } from "#balance/signature-species";
import { tmSpecies } from "#balance/tms"; import { tmSpecies } from "#balance/tms";
import { allRewards } from "#data/data-lists";
import { doubleBattleDialogue } from "#data/double-battle-dialogue"; import { doubleBattleDialogue } from "#data/double-battle-dialogue";
import { Gender } from "#data/gender"; import { Gender } from "#data/gender";
import type { PokemonSpecies, PokemonSpeciesFilter } from "#data/pokemon-species"; import type { PokemonSpecies, PokemonSpeciesFilter } from "#data/pokemon-species";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-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 { PokeballType } from "#enums/pokeball";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
@ -19,6 +18,8 @@ import { TrainerSlot } from "#enums/trainer-slot";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { TrainerVariant } from "#enums/trainer-variant"; import { TrainerVariant } from "#enums/trainer-variant";
import type { EnemyPokemon } from "#field/pokemon"; 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 { PokemonMove } from "#moves/pokemon-move";
import { getIsInitialized, initI18n } from "#plugins/i18n"; import { getIsInitialized, initI18n } from "#plugins/i18n";
import type { EvilTeam } from "#trainers/evil-admin-trainer-pools"; import type { EvilTeam } from "#trainers/evil-admin-trainer-pools";
@ -28,10 +29,9 @@ import {
getGymLeaderPartyTemplate, getGymLeaderPartyTemplate,
getWavePartyTemplate, getWavePartyTemplate,
TrainerPartyCompoundTemplate, TrainerPartyCompoundTemplate,
TrainerPartyTemplate, type TrainerPartyTemplate,
trainerPartyTemplates, trainerPartyTemplates,
} from "#trainers/trainer-party-template"; } from "#trainers/trainer-party-template";
import type { RewardFunc } from "#types/rewards";
import type { import type {
GenAIFunc, GenAIFunc,
GenTrainerItemsFunc, GenTrainerItemsFunc,
@ -109,7 +109,7 @@ export class TrainerConfig {
public victoryBgm: string; public victoryBgm: string;
public genTrainerItemsFunc: GenTrainerItemsFunc; public genTrainerItemsFunc: GenTrainerItemsFunc;
public genAIFuncs: GenAIFunc[] = []; public genAIFuncs: GenAIFunc[] = [];
public rewardFuncs: RewardFunc[] = []; public rewardFuncs: (Reward | RewardGenerator)[] = [];
public partyTemplates: TrainerPartyTemplate[]; public partyTemplates: TrainerPartyTemplate[];
public partyTemplateFunc: PartyTemplateFunc; public partyTemplateFunc: PartyTemplateFunc;
public partyMemberFuncs: PartyMemberFuncs = {}; public partyMemberFuncs: PartyMemberFuncs = {};
@ -501,7 +501,7 @@ export class TrainerConfig {
return this; return this;
} }
setRewardFuncs(...rewardFuncs: (() => RewardFunc)[]): TrainerConfig { setRewardFuncs(...rewardFuncs: (Reward | RewardGenerator)[]): TrainerConfig {
this.rewardFuncs = rewardFuncs.map(func => () => { this.rewardFuncs = rewardFuncs.map(func => () => {
const rewardFunc = func(); const rewardFunc = func();
const reward = rewardFunc(); const reward = rewardFunc();

View File

@ -52,19 +52,17 @@ export const RewardId = {
MEMORY_MUSHROOM: 0x2B03, MEMORY_MUSHROOM: 0x2B03,
DNA_SPLICERS: 0x2B04, DNA_SPLICERS: 0x2B04,
HELD_ITEM: 0x2C01, SPECIES_STAT_BOOSTER: 0x2C01,
SPECIES_STAT_BOOSTER: 0x2C02, RARE_SPECIES_STAT_BOOSTER: 0x2C02,
RARE_SPECIES_STAT_BOOSTER: 0x2C03, BASE_STAT_BOOSTER: 0x2C03,
BASE_STAT_BOOSTER: 0x2C04, ATTACK_TYPE_BOOSTER: 0x2C04,
ATTACK_TYPE_BOOSTER: 0x2C05, BERRY: 0x2C05,
BERRY: 0x2C06,
TRAINER_ITEM: 0x2D01, TEMP_STAT_STAGE_BOOSTER: 0x2D01,
TEMP_STAT_STAGE_BOOSTER: 0x2D02, DIRE_HIT: 0x2D02,
DIRE_HIT: 0x2D03, LURE: 0x2D03,
LURE: 0x2D04, SUPER_LURE: 0x2D04,
SUPER_LURE: 0x2D05, MAX_LURE: 0x2D05,
MAX_LURE: 0x2D06,
FORM_CHANGE_ITEM: 0x2E01, FORM_CHANGE_ITEM: 0x2E01,
RARE_FORM_CHANGE_ITEM: 0x2E02, RARE_FORM_CHANGE_ITEM: 0x2E02,

View File

@ -7,7 +7,6 @@ import { initChallenges } from "#data/challenge";
import { initTrainerTypeDialogue } from "#data/dialogue"; import { initTrainerTypeDialogue } from "#data/dialogue";
import { initPokemonForms } from "#data/pokemon-forms"; import { initPokemonForms } from "#data/pokemon-forms";
import { initHeldItems } from "#items/all-held-items"; import { initHeldItems } from "#items/all-held-items";
import { initRewards } from "#items/all-rewards";
import { initTrainerItems } from "#items/all-trainer-items"; import { initTrainerItems } from "#items/all-trainer-items";
import { initHeldItemPools } from "#items/init-held-item-pools"; import { initHeldItemPools } from "#items/init-held-item-pools";
import { initRewardPools } from "#items/init-reward-pools"; import { initRewardPools } from "#items/init-reward-pools";
@ -45,6 +44,5 @@ function initItems() {
initHeldItemPools(); initHeldItemPools();
initTrainerItems(); initTrainerItems();
initTrainerItemPools(); initTrainerItemPools();
initRewards();
initRewardPools(); initRewardPools();
} }

View File

@ -1,4 +1,3 @@
import { allRewards } from "#data/data-lists";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { RewardId } from "#enums/reward-id"; import { RewardId } from "#enums/reward-id";
import { RarityTier } from "#enums/reward-tier"; import { RarityTier } from "#enums/reward-tier";
@ -18,6 +17,7 @@ import {
FusePokemonReward, FusePokemonReward,
LapsingTrainerItemReward, LapsingTrainerItemReward,
MintRewardGenerator, MintRewardGenerator,
NoneReward,
PokemonAllMovePpRestoreReward, PokemonAllMovePpRestoreReward,
PokemonHpRestoreReward, PokemonHpRestoreReward,
PokemonLevelIncrementReward, PokemonLevelIncrementReward,
@ -26,157 +26,166 @@ import {
PokemonReviveReward, PokemonReviveReward,
PokemonStatusHealReward, PokemonStatusHealReward,
RememberMoveReward, RememberMoveReward,
type Reward,
type RewardGenerator,
SpeciesStatBoosterRewardGenerator, SpeciesStatBoosterRewardGenerator,
TempStatStageBoosterRewardGenerator, TempStatStageBoosterRewardGenerator,
TeraTypeRewardGenerator, TeraTypeRewardGenerator,
TmRewardGenerator, TmRewardGenerator,
} from "./reward"; } from "./reward";
export function initRewards() { // TODO: Move to `reward-utils.ts` and un-exportt
export const allRewards = {
[RewardId.NONE]: new NoneReward(),
// Pokeball rewards // Pokeball rewards
allRewards[RewardId.POKEBALL] = () => new AddPokeballReward("pb", PokeballType.POKEBALL, 5, RewardId.POKEBALL); [RewardId.POKEBALL]: new AddPokeballReward("pb", PokeballType.POKEBALL, 5, RewardId.POKEBALL),
allRewards[RewardId.GREAT_BALL] = () => new AddPokeballReward("gb", PokeballType.GREAT_BALL, 5, RewardId.GREAT_BALL); [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); [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); [RewardId.ROGUE_BALL]: new AddPokeballReward("rb", PokeballType.ROGUE_BALL, 5, RewardId.ROGUE_BALL),
allRewards[RewardId.MASTER_BALL] = () => [RewardId.MASTER_BALL]: new AddPokeballReward("mb", PokeballType.MASTER_BALL, 1, RewardId.MASTER_BALL),
new AddPokeballReward("mb", PokeballType.MASTER_BALL, 1, RewardId.MASTER_BALL);
// Voucher rewards // Voucher rewards
allRewards[RewardId.VOUCHER] = () => new AddVoucherReward(VoucherType.REGULAR, 1, RewardId.VOUCHER); [RewardId.VOUCHER]: new AddVoucherReward(VoucherType.REGULAR, 1, RewardId.VOUCHER),
allRewards[RewardId.VOUCHER_PLUS] = () => new AddVoucherReward(VoucherType.PLUS, 1, RewardId.VOUCHER_PLUS); [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_PREMIUM]: new AddVoucherReward(VoucherType.PREMIUM, 1, RewardId.VOUCHER_PREMIUM),
// Money rewards // Money rewards
allRewards[RewardId.NUGGET] = () => [RewardId.NUGGET]: new AddMoneyReward(
new AddMoneyReward(
"modifierType:ModifierType.NUGGET", "modifierType:ModifierType.NUGGET",
"nugget", "nugget",
1, 1,
"modifierType:ModifierType.MoneyRewardModifierType.extra.small", "modifierType:ModifierType.MoneyRewardModifierType.extra.small",
RewardId.NUGGET, RewardId.NUGGET,
); ),
allRewards[RewardId.BIG_NUGGET] = () => [RewardId.BIG_NUGGET]: new AddMoneyReward(
new AddMoneyReward(
"modifierType:ModifierType.BIG_NUGGET", "modifierType:ModifierType.BIG_NUGGET",
"big_nugget", "big_nugget",
2.5, 2.5,
"modifierType:ModifierType.MoneyRewardModifierType.extra.moderate", "modifierType:ModifierType.MoneyRewardModifierType.extra.moderate",
RewardId.BIG_NUGGET, RewardId.BIG_NUGGET,
); ),
allRewards[RewardId.RELIC_GOLD] = () => [RewardId.RELIC_GOLD]: new AddMoneyReward(
new AddMoneyReward(
"modifierType:ModifierType.RELIC_GOLD", "modifierType:ModifierType.RELIC_GOLD",
"relic_gold", "relic_gold",
10, 10,
"modifierType:ModifierType.MoneyRewardModifierType.extra.large", "modifierType:ModifierType.MoneyRewardModifierType.extra.large",
RewardId.RELIC_GOLD, RewardId.RELIC_GOLD,
); ),
// Party-wide consumables // Party-wide consumables
allRewards[RewardId.RARER_CANDY] = () => [RewardId.RARER_CANDY]: new AllPokemonLevelIncrementReward("modifierType:ModifierType.RARER_CANDY", "rarer_candy"),
new AllPokemonLevelIncrementReward("modifierType:ModifierType.RARER_CANDY", "rarer_candy"); [RewardId.SACRED_ASH]: new AllPokemonFullReviveReward("modifierType:ModifierType.SACRED_ASH", "sacred_ash"),
allRewards[RewardId.SACRED_ASH] = () =>
new AllPokemonFullReviveReward("modifierType:ModifierType.SACRED_ASH", "sacred_ash");
// Pokemon consumables // Pokemon consumables
allRewards[RewardId.RARE_CANDY] = () => [RewardId.RARE_CANDY]: new PokemonLevelIncrementReward("modifierType:ModifierType.RARE_CANDY", "rare_candy"),
new PokemonLevelIncrementReward("modifierType:ModifierType.RARE_CANDY", "rare_candy");
allRewards[RewardId.EVOLUTION_ITEM] = () => new EvolutionItemRewardGenerator(false, RewardId.EVOLUTION_ITEM); [RewardId.EVOLUTION_ITEM]: new EvolutionItemRewardGenerator(false, RewardId.EVOLUTION_ITEM),
allRewards[RewardId.RARE_EVOLUTION_ITEM] = () => new EvolutionItemRewardGenerator(true, RewardId.RARE_EVOLUTION_ITEM); [RewardId.RARE_EVOLUTION_ITEM]: new EvolutionItemRewardGenerator(true, RewardId.RARE_EVOLUTION_ITEM),
allRewards[RewardId.POTION] = () => [RewardId.POTION]: new PokemonHpRestoreReward("modifierType:ModifierType.POTION", "potion", RewardId.POTION, 20, 10),
new PokemonHpRestoreReward("modifierType:ModifierType.POTION", "potion", RewardId.POTION, 20, 10); [RewardId.SUPER_POTION]: new PokemonHpRestoreReward(
allRewards[RewardId.SUPER_POTION] = () => "modifierType:ModifierType.SUPER_POTION",
new PokemonHpRestoreReward("modifierType:ModifierType.SUPER_POTION", "super_potion", RewardId.SUPER_POTION, 50, 25); "super_potion",
allRewards[RewardId.HYPER_POTION] = () => RewardId.SUPER_POTION,
new PokemonHpRestoreReward( 50,
25,
),
[RewardId.HYPER_POTION]: new PokemonHpRestoreReward(
"modifierType:ModifierType.HYPER_POTION", "modifierType:ModifierType.HYPER_POTION",
"hyper_potion", "hyper_potion",
RewardId.HYPER_POTION, RewardId.HYPER_POTION,
200, 200,
50, 50,
); ),
allRewards[RewardId.MAX_POTION] = () => [RewardId.MAX_POTION]: new PokemonHpRestoreReward(
new PokemonHpRestoreReward("modifierType:ModifierType.MAX_POTION", "max_potion", RewardId.MAX_POTION, 0, 100); "modifierType:ModifierType.MAX_POTION",
allRewards[RewardId.FULL_RESTORE] = () => "max_potion",
new PokemonHpRestoreReward( RewardId.MAX_POTION,
0,
100,
),
[RewardId.FULL_RESTORE]: new PokemonHpRestoreReward(
"modifierType:ModifierType.FULL_RESTORE", "modifierType:ModifierType.FULL_RESTORE",
"full_restore", "full_restore",
RewardId.FULL_RESTORE, RewardId.FULL_RESTORE,
0, 0,
100, 100,
true, true,
); ),
allRewards[RewardId.REVIVE] = () => [RewardId.REVIVE]: new PokemonReviveReward("modifierType:ModifierType.REVIVE", "revive", RewardId.REVIVE, 50),
new PokemonReviveReward("modifierType:ModifierType.REVIVE", "revive", RewardId.REVIVE, 50); [RewardId.MAX_REVIVE]: new PokemonReviveReward(
allRewards[RewardId.MAX_REVIVE] = () => "modifierType:ModifierType.MAX_REVIVE",
new PokemonReviveReward("modifierType:ModifierType.MAX_REVIVE", "max_revive", RewardId.MAX_REVIVE, 100); "max_revive",
RewardId.MAX_REVIVE,
100,
),
allRewards[RewardId.FULL_HEAL] = () => [RewardId.FULL_HEAL]: new PokemonStatusHealReward("modifierType:ModifierType.FULL_HEAL", "full_heal"),
new PokemonStatusHealReward("modifierType:ModifierType.FULL_HEAL", "full_heal");
allRewards[RewardId.ETHER] = () => [RewardId.ETHER]: new PokemonPpRestoreReward("modifierType:ModifierType.ETHER", "ether", RewardId.ETHER, 10),
new PokemonPpRestoreReward("modifierType:ModifierType.ETHER", "ether", RewardId.ETHER, 10); [RewardId.MAX_ETHER]: new PokemonPpRestoreReward(
allRewards[RewardId.MAX_ETHER] = () => "modifierType:ModifierType.MAX_ETHER",
new PokemonPpRestoreReward("modifierType:ModifierType.MAX_ETHER", "max_ether", RewardId.MAX_ETHER, -1); "max_ether",
RewardId.MAX_ETHER,
-1,
),
allRewards[RewardId.ELIXIR] = () => [RewardId.ELIXIR]: new PokemonAllMovePpRestoreReward(
new PokemonAllMovePpRestoreReward("modifierType:ModifierType.ELIXIR", "elixir", RewardId.ELIXIR, 10); "modifierType:ModifierType.ELIXIR",
allRewards[RewardId.MAX_ELIXIR] = () => "elixir",
new PokemonAllMovePpRestoreReward("modifierType:ModifierType.MAX_ELIXIR", "max_elixir", RewardId.MAX_ELIXIR, -1); RewardId.ELIXIR,
10,
),
[RewardId.MAX_ELIXIR]: new PokemonAllMovePpRestoreReward(
"modifierType:ModifierType.MAX_ELIXIR",
"max_elixir",
RewardId.MAX_ELIXIR,
-1,
),
allRewards[RewardId.PP_UP] = () => [RewardId.PP_UP]: new PokemonPpUpReward("modifierType:ModifierType.PP_UP", "pp_up", RewardId.PP_UP, 1),
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),
allRewards[RewardId.PP_MAX] = () =>
new PokemonPpUpReward("modifierType:ModifierType.PP_MAX", "pp_max", RewardId.PP_MAX, 3);
/*REPEL] = () => new DoubleBattleChanceBoosterReward('Repel', 5), [RewardId.MINT]: new MintRewardGenerator(),
SUPER_REPEL] = () => new DoubleBattleChanceBoosterReward('Super Repel', 10),
MAX_REPEL] = () => new DoubleBattleChanceBoosterReward('Max Repel', 25),*/
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); [RewardId.MEMORY_MUSHROOM]: new RememberMoveReward("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom"),
allRewards[RewardId.TM_GREAT] = () => new TmRewardGenerator(RarityTier.GREAT);
allRewards[RewardId.TM_ULTRA] = () => new TmRewardGenerator(RarityTier.ULTRA);
allRewards[RewardId.MEMORY_MUSHROOM] = () => [RewardId.DNA_SPLICERS]: new FusePokemonReward("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers"),
new RememberMoveReward("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom");
allRewards[RewardId.DNA_SPLICERS] = () =>
new FusePokemonReward("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers");
// Form change items // Form change items
allRewards[RewardId.FORM_CHANGE_ITEM] = () => new FormChangeItemRewardGenerator(false, RewardId.FORM_CHANGE_ITEM); [RewardId.FORM_CHANGE_ITEM]: new FormChangeItemRewardGenerator(false, RewardId.FORM_CHANGE_ITEM),
allRewards[RewardId.RARE_FORM_CHANGE_ITEM] = () => [RewardId.RARE_FORM_CHANGE_ITEM]: new FormChangeItemRewardGenerator(true, RewardId.RARE_FORM_CHANGE_ITEM),
new FormChangeItemRewardGenerator(true, RewardId.RARE_FORM_CHANGE_ITEM);
// Held items // Held items
allRewards[RewardId.SPECIES_STAT_BOOSTER] = () => new SpeciesStatBoosterRewardGenerator(false); [RewardId.SPECIES_STAT_BOOSTER]: new SpeciesStatBoosterRewardGenerator(false),
allRewards[RewardId.RARE_SPECIES_STAT_BOOSTER] = () => new SpeciesStatBoosterRewardGenerator(true); [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(); [RewardId.BERRY]: new BerryRewardGenerator(),
// MINI_BLACK_HOLE] = () => new HeldItemReward(HeldItemId.MINI_BLACK_HOLE),
// Trainer items // Trainer items
allRewards[RewardId.LURE] = () => new LapsingTrainerItemReward(TrainerItemId.LURE, RewardId.LURE); [RewardId.LURE]: new LapsingTrainerItemReward(TrainerItemId.LURE, RewardId.LURE),
allRewards[RewardId.SUPER_LURE] = () => new LapsingTrainerItemReward(TrainerItemId.SUPER_LURE, RewardId.SUPER_LURE); [RewardId.SUPER_LURE]: new LapsingTrainerItemReward(TrainerItemId.SUPER_LURE, RewardId.SUPER_LURE),
allRewards[RewardId.MAX_LURE] = () => new LapsingTrainerItemReward(TrainerItemId.MAX_LURE, RewardId.MAX_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] = () => [RewardId.DIRE_HIT]: new LapsingTrainerItemReward(TrainerItemId.DIRE_HIT, RewardId.TEMP_STAT_STAGE_BOOSTER),
new LapsingTrainerItemReward(TrainerItemId.DIRE_HIT, RewardId.TEMP_STAT_STAGE_BOOSTER); } as const satisfies {
// GOLDEN_POKEBALL] = () => new TrainerItemReward(TrainerItemId.GOLDEN_POKEBALL), [k in RewardId]: Reward | RewardGenerator;
} };
export type allRewardsType = typeof allRewards;

View File

@ -8,7 +8,7 @@ import { isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common
import { getPartyLuckValue } from "#utils/party"; import { getPartyLuckValue } from "#utils/party";
import type { RewardOption } from "./reward"; import type { RewardOption } from "./reward";
import { rewardPool, rewardPoolWeights } from "./reward-pools"; 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. 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) { if (customRewardSettings?.guaranteedRewardSpecs && customRewardSettings.guaranteedRewardSpecs.length > 0) {
for (const specs of customRewardSettings.guaranteedRewardSpecs) { for (const specs of customRewardSettings.guaranteedRewardSpecs) {
const rewardOption = generateRewardOptionFromSpecs(specs); const rewardOption = generateRewardOptionFromId(specs);
if (rewardOption) { if (rewardOption) {
options.push(rewardOption); options.push(rewardOption);
} }
@ -287,13 +287,12 @@ function getNewRewardOption(
* Replaces the {@linkcode Reward} of the entries within {@linkcode options} with any * 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. * up to the smallest amount of entries between {@linkcode options} and the override array.
* @param options Array of naturally rolled {@linkcode RewardOption}s * @param options Array of naturally rolled {@linkcode RewardOption}s
* @param party Array of the player's current party
*/ */
export function overridePlayerRewardOptions(options: RewardOption[]) { export function overridePlayerRewardOptions(options: RewardOption[]) {
const minLength = Math.min(options.length, Overrides.REWARD_OVERRIDE.length); const minLength = Math.min(options.length, Overrides.REWARD_OVERRIDE.length);
for (let i = 0; i < minLength; i++) { for (let i = 0; i < minLength; i++) {
const specs: RewardSpecs = Overrides.REWARD_OVERRIDE[i]; const specs: RewardSpecs = Overrides.REWARD_OVERRIDE[i];
const rewardOption = generateRewardOptionFromSpecs(specs); const rewardOption = generateRewardOptionFromId(specs);
if (rewardOption) { if (rewardOption) {
options[i] = rewardOption; options[i] = rewardOption;
} }

View File

@ -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 type { HeldItemId } from "#enums/held-item-id";
import { getRewardCategory, RewardCategoryId, RewardId } from "#enums/reward-id"; import { getRewardCategory, RewardCategoryId, RewardId } from "#enums/reward-id";
import type { RarityTier } from "#enums/reward-tier"; import type { RarityTier } from "#enums/reward-tier";
import type { TrainerItemId } from "#enums/trainer-item-id"; 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 { heldItemRarities } from "./held-item-default-tiers";
import { import {
HeldItemReward, HeldItemReward,
@ -33,22 +32,24 @@ export function isRememberMoveReward(reward: Reward): reward is RememberMoveRewa
} }
/** /**
* Generates a Reward from a given function * Dynamically generate a {@linkcode RewardOption} from a given RewardSpecs.
* @param rewardFunc * @param specs - The {@linkcode RewardSpecs} used to generate the reward
* @param pregenArgs Can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. * @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 { export function generateRewardOptionFromId<T extends RewardPoolId>(
const reward = rewardFunc(); specs: RewardSpecs<T>,
return reward instanceof RewardGenerator ? reward.generateReward(globalScene.getPlayerParty(), pregenArgs) : reward;
}
export function generateRewardOptionFromId(
id: RewardPoolId,
cost = 0, cost = 0,
tierOverride?: RarityTier, tierOverride?: RarityTier,
upgradeCount = 0, upgradeCount = 0,
pregenArgs?: any[],
): RewardOption | null { ): 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)) { if (isHeldItemId(id)) {
const reward = new HeldItemReward(id); const reward = new HeldItemReward(id);
const tier = tierOverride ?? heldItemRarities[id]; const tier = tierOverride ?? heldItemRarities[id];
@ -61,8 +62,8 @@ export function generateRewardOptionFromId(
return new RewardOption(reward, upgradeCount, tier, cost); return new RewardOption(reward, upgradeCount, tier, cost);
} }
const rewardFunc = allRewards[id]; const rewardFunc = allRewards[id] as Reward | RewardGenerator;
const reward = generateReward(rewardFunc, pregenArgs); const reward = rewardFunc instanceof RewardGenerator ? rewardFunc.generateReward(pregenArgs) : rewardFunc;
if (reward) { if (reward) {
const tier = tierOverride ?? rewardRarities[id]; const tier = tierOverride ?? rewardRarities[id];
return new RewardOption(reward, upgradeCount, tier, cost); return new RewardOption(reward, upgradeCount, tier, cost);
@ -70,17 +71,6 @@ export function generateRewardOptionFromId(
return null; 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[] { export function getPlayerShopRewardOptionsForWave(waveIndex: number, baseCost: number): RewardOption[] {
if (!(waveIndex % 10)) { if (!(waveIndex % 10)) {
return []; return [];

View File

@ -11,16 +11,16 @@ import { getNatureName, getNatureStatMultiplier } from "#data/nature";
import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#data/pokeball"; import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#data/pokeball";
import { pokemonFormChanges, SpeciesFormChangeCondition } from "#data/pokemon-forms"; import { pokemonFormChanges, SpeciesFormChangeCondition } from "#data/pokemon-forms";
import { BattlerTagType } from "#enums/battler-tag-type"; 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 { FormChangeItem } from "#enums/form-change-item";
import { HeldItemId } from "#enums/held-item-id"; import { HeldItemId } from "#enums/held-item-id";
import { LearnMoveType } from "#enums/learn-move-type"; 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 { Nature } from "#enums/nature";
import type { PokeballType } from "#enums/pokeball"; import type { PokeballType } from "#enums/pokeball";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { RewardId } from "#enums/reward-id"; 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 { SpeciesFormKey } from "#enums/species-form-key";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import type { PermanentStat, TempBattleStat } from "#enums/stat"; 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 { berryTypeToHeldItem } from "#items/berry";
import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#items/held-item-pool"; import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#items/held-item-pool";
import { formChangeItemName } from "#items/item-utility"; 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 { TrainerItemEffect, tempStatToTrainerItem } from "#items/trainer-item";
import type { PokemonMove } from "#moves/pokemon-move"; import type { PokemonMove } from "#moves/pokemon-move";
import { getVoucherTypeIcon, getVoucherTypeName, type VoucherType } from "#system/voucher"; import { getVoucherTypeIcon, getVoucherTypeName, type VoucherType } from "#system/voucher";
import type { Exact } from "#types/type-helpers"; import type { Exact } from "#types/type-helpers";
import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#ui/party-ui-handler"; import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#ui/party-ui-handler";
import { PartyUiHandler } 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 { getEnumKeys, getEnumValues } from "#utils/enums";
import i18next from "i18next"; import i18next from "i18next";
/* /**
The term "Reward" refers to items the player can access in the post-battle screen (although * @module
they may be used in other places of the code as well). * 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): * Examples include (but are not limited to):
- Potions and other healing items * - Potions and other healing items
- Held items and trainer items * - Held items and trainer items
- Money items such as nugget and ancient relic * - Money items such as nugget and ancient relic
Rewards have a basic structure with a name, description, and icon. These are used to display * 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 * the reward in the reward select screen. All rewards have an .apply() method, which applies the
effect, for example: * effect, for example:
- Apply healing to a pokemon * - Apply healing to a pokemon
- Assign a held item to a pokemon, or a trainer item to the player * - Assign a held item to a pokemon, or a trainer item to the player
- Add money * - Add money
Some rewards, once clicked, simply have their effect---these are Rewards that add money, pokéball, * Some rewards, once clicked, simply have their effect---these are Rewards that add money, pokéball,
vouchers, or global effect such as Sacred Ash. * vouchers, or global effect such as Sacred Ash.
Most rewards require extra parameters. They are divided into subclasses depending on the parameters * Most rewards require extra parameters. They are divided into subclasses depending on the parameters
that they need, in particular: * that they need, in particular:
- PokemonReward requires to pass a Pokemon (to apply healing, assign item...) * - 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) * - PokemonMoveReward requires to pass a Pokemon and a move (for Elixir, or PP Up)
Plus some edge cases for Memory Mushroom and DNA Splicers. * Plus some edge cases for Memory Mushroom and DNA Splicers.
The parameters to be passed are generated by the .applyReward() function in SelectRewardPhase. * 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, * 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 * 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. * .apply() directly, we call the .applyReward() method in BattleScene, which also plays the sound.
[This method could perhaps be removed]. * [This method could perhaps be removed].
Rewards are assigned RewardId, and there are also RewardCategoryId. For example, TM is a 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, * 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 * some specific categories of held items are assigned their own RewardId, but they all fall under a single
RewardCategoryId. * RewardCategoryId.
rewardInitObj plays a similar role to allHeldItems, except instead of containing all possible reward * 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 * 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 * 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. * for example. The entries of rewardInitObj are used in the RewardPool.
There are some more derived classes, in particular: * There are some more derived classes, in particular:
RewardGenerator, which creates Reward instances from a certain group (e.g. TMs, nature mints, or berries); * 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. * 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> = T extends object ? Exact<T> : T;
export abstract class Reward { 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: 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 id: RewardId;
public localeKey: string; public localeKey: string;
public iconImage: string; public iconImage: string;
@ -109,6 +115,7 @@ export abstract class Reward {
return i18next.t(`${this.localeKey}.name`); return i18next.t(`${this.localeKey}.name`);
} }
// TODO: These should be getters
getDescription(): string { getDescription(): string {
return i18next.t(`${this.localeKey}.description`); return i18next.t(`${this.localeKey}.description`);
} }
@ -121,8 +128,8 @@ export abstract class Reward {
/** /**
* Check whether this reward should be applied. * Check whether this reward should be applied.
*/ */
// TODO: This is erroring on stuff of typ // TODO: This is erroring on stuff with `undefined`
shouldApply(_params: Exact<Parameters<this["apply"]>[0]>): boolean { shouldApply(_params: MatchExact<Parameters<this["apply"]>[0]>): boolean {
return true; return true;
} }
@ -131,25 +138,18 @@ export abstract class Reward {
abstract apply(_params?: unknown): void; abstract apply(_params?: unknown): void;
} }
// TODO: Can this return null? /**
// TODO: Make this generic based on T * A {@linkcode RewardGenerator} represents a dynamic generator for a given type of reward.
type RewardGeneratorFunc<T extends Reward> = (party: Pokemon[], pregenArgs?: any[]) => T | null; * These can be customized by lieu of {@linkcode generateReward} to alter the generation result.
*/
export abstract class RewardGenerator<T extends Reward = Reward> { export abstract class RewardGenerator {
private genRewardFunc: RewardGeneratorFunc<T>; /**
public id: RewardId; * Dynamically generate a new reward.
* @param pregenArgs - An optional argument taken by super classes to customize the reward generated.
constructor(genRewardFunc: RewardGeneratorFunc<T>) { * @returns The generated reward, or `null` if none are able to be produced
this.genRewardFunc = genRewardFunc; */
} // TODO: Remove null from signature in favor of adding a condition or similar (reduces bangs needed)
abstract generateReward(pregenArgs?: unknown): Reward | null;
generateReward(party: Pokemon[], pregenArgs?: any[]) {
const ret = this.genRewardFunc(party, pregenArgs);
if (ret && this.id) {
ret.id = this.id;
}
return ret;
}
} }
export class AddPokeballReward extends Reward { export class AddPokeballReward extends Reward {
@ -370,6 +370,7 @@ export class HeldItemReward extends PokemonReward {
} }
export class TrainerItemReward extends Reward { export class TrainerItemReward extends Reward {
// TODO: This should not be public
public itemId: TrainerItemId; public itemId: TrainerItemId;
constructor(itemId: TrainerItemId, group?: string, soundName?: string) { constructor(itemId: TrainerItemId, group?: string, soundName?: string) {
super("", "", group, soundName); super("", "", group, soundName);
@ -461,33 +462,48 @@ export class ChangeTeraTypeReward extends PokemonReward {
pokemon.teraType = this.teraType; pokemon.teraType = this.teraType;
return true; 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( function restorePokemonHp(
pokemon: Pokemon, pokemon: Pokemon,
percentToRestore: number, percentToRestore: number,
{
pointsToRestore = 0, pointsToRestore = 0,
healStatus = false, healStatus = false,
fainted = false, fainted = false,
}: {
pointsToRestore?: number;
healStatus?: boolean;
fainted?: boolean;
} = {},
): boolean { ): boolean {
if (!pokemon.hp === fainted) { if (pokemon.isFainted() !== fainted) {
return false;
}
if (fainted || healStatus) { if (fainted || healStatus) {
pokemon.resetStatus(true, true, false, false); pokemon.resetStatus(true, true, false, false);
} }
// Apply HealingCharm // Apply HealingCharm
let multiplier = 1;
if (!fainted) {
const hpRestoreMultiplier = new NumberHolder(1); const hpRestoreMultiplier = new NumberHolder(1);
if (!fainted) {
this.applyPlayerItems(TrainerItemEffect.HEALING_BOOSTER, { numberHolder: hpRestoreMultiplier }); this.applyPlayerItems(TrainerItemEffect.HEALING_BOOSTER, { numberHolder: hpRestoreMultiplier });
multiplier = hpRestoreMultiplier.value;
} }
const restorePoints = Math.floor(pointsToRestore * multiplier); const restorePoints = toDmgValue(pointsToRestore * hpRestoreMultiplier.value);
const restorePercent = Math.floor(percentToRestore * 0.01 * multiplier * pokemon.getMaxHp()); const restorePercent = toDmgValue((percentToRestore / 100) * hpRestoreMultiplier.value * pokemon.getMaxHp());
pokemon.heal(Math.max(restorePercent, restorePoints, 1)); pokemon.heal(Math.max(restorePercent, restorePoints));
return true; return true;
} }
return false;
}
export class PokemonHpRestoreReward extends PokemonReward { export class PokemonHpRestoreReward extends PokemonReward {
protected restorePoints: number; protected restorePoints: number;
@ -854,59 +870,55 @@ export class RememberMoveReward extends PokemonReward {
} }
export class BerryRewardGenerator extends RewardGenerator { export class BerryRewardGenerator extends RewardGenerator {
constructor() { override generateReward(pregenArgs?: BerryType): HeldItemReward {
super((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs !== undefined) {
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { const item = berryTypeToHeldItem[pregenArgs];
const item = berryTypeToHeldItem[pregenArgs[0] as BerryType];
return new HeldItemReward(item); return new HeldItemReward(item);
} }
const item = getNewBerryHeldItem(); const item = getNewBerryHeldItem();
return new HeldItemReward(item); return new HeldItemReward(item);
});
this.id = RewardId.BERRY;
} }
} }
export class MintRewardGenerator extends RewardGenerator { export class MintRewardGenerator extends RewardGenerator {
constructor() { override generateReward(pregenArgs?: Nature) {
super((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs !== undefined) {
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in Nature) { return new PokemonNatureChangeReward(pregenArgs);
return new PokemonNatureChangeReward(pregenArgs[0] as Nature);
} }
return new PokemonNatureChangeReward(randSeedItem(getEnumValues(Nature))); return new PokemonNatureChangeReward(randSeedItem(getEnumValues(Nature)));
});
this.id = RewardId.MINT;
} }
} }
export class TeraTypeRewardGenerator extends RewardGenerator { export class TeraTypeRewardGenerator extends RewardGenerator {
constructor() { override generateReward(pregenArgs?: PokemonType) {
super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs !== undefined) {
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { return new ChangeTeraTypeReward(pregenArgs[0]);
return new ChangeTeraTypeReward(pregenArgs[0] as PokemonType);
} }
if (!globalScene.trainerItems.hasItem(TrainerItemId.TERA_ORB)) { if (!globalScene.trainerItems.hasItem(TrainerItemId.TERA_ORB)) {
return null; return null;
} }
const teraTypes: PokemonType[] = [];
for (const p of party) { const shardType = this.getTeraType();
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); return new ChangeTeraTypeReward(shardType);
}); }
this.id = RewardId.TERA_SHARD;
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;
}
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 { export class AttackTypeBoosterRewardGenerator extends RewardGenerator {
constructor() { override generateReward(pregenArgs?: PokemonType) {
super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs !== undefined) {
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { return new AttackTypeBoosterReward(pregenArgs, TYPE_BOOST_ITEM_BOOST_PERCENT);
return new AttackTypeBoosterReward(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT);
} }
const item = getNewAttackTypeBoosterHeldItem(party); const item = getNewAttackTypeBoosterHeldItem(globalScene.getPlayerParty());
return item ? new HeldItemReward(item) : null; return item ? new HeldItemReward(item) : null;
});
this.id = RewardId.ATTACK_TYPE_BOOSTER;
} }
} }
export class BaseStatBoosterRewardGenerator extends RewardGenerator { export class BaseStatBoosterRewardGenerator extends RewardGenerator {
constructor() { override generateReward(pregenArgs?: PermanentStat) {
super((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs !== undefined) {
if (pregenArgs) { return new BaseStatBoosterReward(pregenArgs);
return new BaseStatBoosterReward(pregenArgs[0]);
} }
return new HeldItemReward(getNewVitaminHeldItem()); return new HeldItemReward(getNewVitaminHeldItem());
});
this.id = RewardId.BASE_STAT_BOOSTER;
} }
} }
@ -1244,15 +1250,8 @@ export class TempStatStageBoosterRewardGenerator extends RewardGenerator {
[Stat.ACC]: "x_accuracy", [Stat.ACC]: "x_accuracy",
}; };
constructor() { override generateReward(pregenArgs?: TempBattleStat) {
super((_party: Pokemon[], pregenArgs?: any[]) => { return new LapsingTrainerItemReward(tempStatToTrainerItem[pregenArgs ?? randSeedItem(TEMP_BATTLE_STATS)]);
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;
} }
} }
@ -1265,20 +1264,24 @@ export class TempStatStageBoosterRewardGenerator extends RewardGenerator {
export class SpeciesStatBoosterRewardGenerator extends RewardGenerator { export class SpeciesStatBoosterRewardGenerator extends RewardGenerator {
/** Object comprised of the currently available species-based stat boosting held items */ /** Object comprised of the currently available species-based stat boosting held items */
private rare: boolean;
constructor(rare: boolean) { constructor(rare: boolean) {
super((party: Pokemon[], pregenArgs?: any[]) => { super();
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in SPECIES_STAT_BOOSTER_ITEMS) { this.rare = rare;
return new HeldItemReward(pregenArgs[0] as HeldItemId); }
override generateReward(pregenArgs?: SpeciesStatBoosterItemId) {
if (pregenArgs !== undefined) {
return new HeldItemReward(pregenArgs);
} }
// Get a pool of items based on the rarity. // Get a pool of items based on the rarity.
const tierItems = rare const tierItems = this.rare
? [HeldItemId.LIGHT_BALL, HeldItemId.THICK_CLUB, HeldItemId.METAL_POWDER, HeldItemId.QUICK_POWDER] ? [HeldItemId.LIGHT_BALL, HeldItemId.THICK_CLUB, HeldItemId.METAL_POWDER, HeldItemId.QUICK_POWDER]
: [HeldItemId.DEEP_SEA_SCALE, HeldItemId.DEEP_SEA_TOOTH]; : [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) { for (const p of globalScene.getPlayerParty()) {
const speciesId = p.getSpeciesForm(true).speciesId; const speciesId = p.getSpeciesForm(true).speciesId;
const fusionSpeciesId = p.isFusion() ? p.getFusionSpeciesForm(true).speciesId : null; const fusionSpeciesId = p.isFusion() ? p.getFusionSpeciesForm(true).speciesId : null;
// TODO: Use commented boolean when Fling is implemented // TODO: Use commented boolean when Fling is implemented
@ -1324,17 +1327,22 @@ export class SpeciesStatBoosterRewardGenerator extends RewardGenerator {
} }
return null; return null;
});
this.id = rare ? RewardId.SPECIES_STAT_BOOSTER : RewardId.RARE_SPECIES_STAT_BOOSTER;
} }
} }
export class TmRewardGenerator extends RewardGenerator { export class TmRewardGenerator extends RewardGenerator {
private tier: RarityTier;
constructor(tier: RarityTier) { constructor(tier: RarityTier) {
super((party: Pokemon[], pregenArgs?: any[]) => { super();
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in MoveId) { this.tier = tier;
return new TmReward(pregenArgs[0] as MoveId);
} }
override generateReward(pregenArgs?: MoveId) {
if (pregenArgs !== undefined) {
return new TmReward(pregenArgs);
}
const party = globalScene.getPlayerParty();
const partyMemberCompatibleTms = party.map(p => { const partyMemberCompatibleTms = party.map(p => {
const previousLevelMoves = p.getLearnableLevelMoves(); const previousLevelMoves = p.getLearnableLevelMoves();
return (p as PlayerPokemon).compatibleTms.filter( return (p as PlayerPokemon).compatibleTms.filter(
@ -1343,32 +1351,32 @@ export class TmRewardGenerator extends RewardGenerator {
}); });
const tierUniqueCompatibleTms = partyMemberCompatibleTms const tierUniqueCompatibleTms = partyMemberCompatibleTms
.flat() .flat()
.filter(tm => tmPoolTiers[tm] === tier) .filter(tm => tmPoolTiers[tm] === this.tier)
.filter(tm => !allMoves[tm].name.endsWith(" (N)")) .filter(tm => !allMoves[tm].name.endsWith(" (N)"))
.filter((tm, i, array) => array.indexOf(tm) === i); .filter((tm, i, array) => array.indexOf(tm) === i);
if (!tierUniqueCompatibleTms.length) { if (!tierUniqueCompatibleTms.length) {
return null; return null;
} }
// TODO: should this use `randSeedItem`?
const randTmIndex = randSeedInt(tierUniqueCompatibleTms.length); const randTmIndex = randSeedItem(tierUniqueCompatibleTms);
return new TmReward(tierUniqueCompatibleTms[randTmIndex]); return new TmReward(randTmIndex);
});
this.id =
tier === RarityTier.COMMON
? RewardId.TM_COMMON
: tier === RarityTier.GREAT
? RewardId.TM_GREAT
: RewardId.TM_ULTRA;
} }
} }
export class EvolutionItemRewardGenerator extends RewardGenerator { export class EvolutionItemRewardGenerator extends RewardGenerator {
constructor(rare: boolean, id: RewardId) { private rare: boolean;
super((party: Pokemon[], pregenArgs?: any[]) => { constructor(rare: boolean) {
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in EvolutionItem) { super();
return new EvolutionItemReward(pregenArgs[0] as EvolutionItem); this.rare = rare;
} }
override generateReward(pregenArgs?: EvolutionItem) {
if (pregenArgs !== undefined) {
return new EvolutionItemReward(pregenArgs);
}
const party = globalScene.getPlayerParty();
const evolutionItemPool = [ const evolutionItemPool = [
party party
.filter( .filter(
@ -1403,26 +1411,31 @@ export class EvolutionItemRewardGenerator extends RewardGenerator {
] ]
.flat() .flat()
.flatMap(e => e.evoItem) .flatMap(e => e.evoItem)
.filter(i => !!i && i > 50 === rare); .filter(i => !!i && i > 50 === this.rare);
if (!evolutionItemPool.length) { if (!evolutionItemPool.length) {
return null; return null;
} }
// TODO: should this use `randSeedItem`? return new EvolutionItemReward(randSeedItem(evolutionItemPool));
return new EvolutionItemReward(evolutionItemPool[randSeedInt(evolutionItemPool.length)]!); // TODO: is the bang correct?
});
this.id = id;
} }
} }
export class FormChangeItemRewardGenerator extends RewardGenerator { export class FormChangeItemRewardGenerator extends RewardGenerator {
constructor(isRareFormChangeItem: boolean, id: RewardId) { private isRareFormChangeItem: boolean;
super((party: Pokemon[], pregenArgs?: any[]) => {
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in FormChangeItem) { constructor(isRareFormChangeItem: boolean) {
return new FormChangeItemReward(pregenArgs[0] as FormChangeItem); super();
this.isRareFormChangeItem = isRareFormChangeItem;
} }
override generateReward(pregenArgs?: FormChangeItem) {
if (pregenArgs !== undefined) {
return new FormChangeItemReward(pregenArgs);
}
const party = globalScene.getPlayerParty();
// TODO: REFACTOR THIS FUCKERY PLEASE
const formChangeItemPool = [ const formChangeItemPool = [
...new Set( ...new Set(
party party
@ -1480,17 +1493,14 @@ export class FormChangeItemRewardGenerator extends RewardGenerator {
] ]
.flat() .flat()
.flatMap(fc => fc.item) .flatMap(fc => fc.item)
.filter(i => (i && i < 100) === isRareFormChangeItem); .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. // 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) { if (!formChangeItemPool.length) {
return null; return null;
} }
// TODO: should this use `randSeedItem`? return new FormChangeItemReward(randSeedItem(formChangeItemPool));
return new FormChangeItemReward(formChangeItemPool[randSeedInt(formChangeItemPool.length)]);
});
this.id = id;
} }
} }
@ -1508,22 +1518,6 @@ export class RewardOption {
} }
} }
// TODO: If necessary, add the rest of the modifier types here. export class NoneReward extends Reward {
// For now, doing the minimal work until the modifier rework lands. override apply(): void {}
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<RewardConstructorMap[K]>;
};
export type RewardString = keyof RewardConstructorMap;

View File

@ -1,7 +1,6 @@
import { globalScene } from "#app/global-scene"; 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 { BattlePhase } from "#phases/battle-phase";
import type { RewardFunc } from "#types/rewards";
import i18next from "i18next"; import i18next from "i18next";
export class RewardPhase extends BattlePhase { export class RewardPhase extends BattlePhase {
@ -10,7 +9,7 @@ export class RewardPhase extends BattlePhase {
public readonly phaseName: "RewardPhase" | "RibbonRewardPhase" | "GameOverRewardPhase" = "RewardPhase"; public readonly phaseName: "RewardPhase" | "RibbonRewardPhase" | "GameOverRewardPhase" = "RewardPhase";
protected reward: Reward; protected reward: Reward;
constructor(rewardFunc: RewardFunc) { constructor(rewardFunc: Reward | RewardGenerator) {
super(); super();
this.reward = rewardFunc(); this.reward = rewardFunc();

View File

@ -1,15 +1,15 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { PokemonSpecies } from "#data/pokemon-species"; import type { PokemonSpecies } from "#data/pokemon-species";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import type { Reward, RewardGenerator } from "#items/reward";
import { RewardPhase } from "#phases/reward-phase"; import { RewardPhase } from "#phases/reward-phase";
import type { RewardFunc } from "#types/rewards";
import i18next from "i18next"; import i18next from "i18next";
export class RibbonRewardPhase extends RewardPhase { export class RibbonRewardPhase extends RewardPhase {
public readonly phaseName = "RibbonRewardPhase"; public readonly phaseName = "RibbonRewardPhase";
private species: PokemonSpecies; private species: PokemonSpecies;
constructor(rewardFunc: RewardFunc, species: PokemonSpecies) { constructor(rewardFunc: Reward | RewardGenerator, species: PokemonSpecies) {
super(rewardFunc); super(rewardFunc);
this.species = species; this.species = species;

View File

@ -1,7 +1,6 @@
import { globalScene } from "#app/global-scene"; 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 { BattlePhase } from "#phases/battle-phase";
import type { RewardFunc } from "#types/rewards";
import i18next from "i18next"; import i18next from "i18next";
export class RewardPhase extends BattlePhase { export class RewardPhase extends BattlePhase {
@ -10,10 +9,11 @@ export class RewardPhase extends BattlePhase {
public readonly phaseName: "RewardPhase" | "RibbonRewardPhase" | "GameOverRewardPhase" = "RewardPhase"; public readonly phaseName: "RewardPhase" | "RibbonRewardPhase" | "GameOverRewardPhase" = "RewardPhase";
protected reward: Reward; protected reward: Reward;
constructor(rewardFunc: RewardFunc) { constructor(rewardFunc: Reward | RewardGenerator) {
super(); super();
this.reward = rewardFunc(); const reward = rewardFunc();
this.reward = reward instanceof RewardGenerator ? reward.generateReward() : reward;
} }
start() { start() {

View File

@ -5,8 +5,8 @@ import { Challenges } from "#enums/challenges";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { TrainerItemId } from "#enums/trainer-item-id";
import { TextStyle } from "#enums/text-style"; import { TextStyle } from "#enums/text-style";
import { TrainerItemId } from "#enums/trainer-item-id";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { addTextObject } from "#ui/text"; import { addTextObject } from "#ui/text";
import type { nil } from "#utils/common"; import type { nil } from "#utils/common";

View File

@ -5,8 +5,8 @@ import { Button } from "#enums/buttons";
import { Command } from "#enums/command"; import { Command } from "#enums/command";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { TrainerItemId } from "#enums/trainer-item-id";
import { TextStyle } from "#enums/text-style"; import { TextStyle } from "#enums/text-style";
import { TrainerItemId } from "#enums/trainer-item-id";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import type { CommandPhase } from "#phases/command-phase"; import type { CommandPhase } from "#phases/command-phase";
import { PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; import { PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler";

View File

@ -2160,6 +2160,7 @@ class PartyDiscardModeButton extends Phaser.GameObjects.Container {
* @remarks * @remarks
* This will also reveal the button if it is currently hidden. * This will also reveal the button if it is currently hidden.
*/ */
// TODO: Reminder to fix
public toggleIcon(partyMode: PartyUiMode.MODIFIER_TRANSFER | PartyUiMode.DISCARD): void { public toggleIcon(partyMode: PartyUiMode.MODIFIER_TRANSFER | PartyUiMode.DISCARD): void {
this.setActive(true).setVisible(true); this.setActive(true).setVisible(true);
switch (partyMode) { switch (partyMode) {

View File

@ -125,8 +125,8 @@ export function randItem<T>(items: T[]): T {
return items.length === 1 ? items[0] : items[randInt(items.length)]; return items.length === 1 ? items[0] : items[randInt(items.length)];
} }
export function randSeedItem<T>(items: T[]): T { export function randSeedItem<T>(items: T[] | readonly T[]): T {
return items.length === 1 ? items[0] : Phaser.Math.RND.pick(items); return items.length === 1 ? items[0] : Phaser.Math.RND.pick(items as T[]);
} }
export function randSeedWeightedItem<T>(items: T[]): T { export function randSeedWeightedItem<T>(items: T[]): T {