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 { 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<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 = {
id: RewardPoolId;

View File

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

View File

@ -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<HeldItemId, HeldItem> = {};
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 { 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();

View File

@ -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,

View File

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

View File

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

View File

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

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 { 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<T extends RewardPoolId>(
specs: RewardSpecs<T>,
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 [];

View File

@ -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> = T extends object ? Exact<T> : 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<Parameters<this["apply"]>[0]>): boolean {
// TODO: This is erroring on stuff with `undefined`
shouldApply(_params: MatchExact<Parameters<this["apply"]>[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<T extends Reward> = (party: Pokemon[], pregenArgs?: any[]) => T | null;
export abstract class RewardGenerator<T extends Reward = Reward> {
private genRewardFunc: RewardGeneratorFunc<T>;
public id: RewardId;
constructor(genRewardFunc: RewardGeneratorFunc<T>) {
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<RewardConstructorMap[K]>;
};
export type RewardString = keyof RewardConstructorMap;
export class NoneReward extends Reward {
override apply(): void {}
}

View File

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

View File

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

View File

@ -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() {

View File

@ -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";

View File

@ -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";

View File

@ -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) {

View File

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

View File

@ -56,4 +56,4 @@ export class ModifierHelper extends GameManagerHelper {
private log(...params: any[]) {
console.log("Modifiers:", ...params);
}
}
}

View File

@ -56,4 +56,4 @@ export class ModifierHelper extends GameManagerHelper {
private log(...params: any[]) {
console.log("Modifiers:", ...params);
}
}
}