mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-06 07:29:30 +02:00
Added type safety to reward generators
This commit is contained in:
parent
dac9e202a0
commit
6077cfff17
127
src/@types/all-reward-type.ts
Normal file
127
src/@types/all-reward-type.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import type { RewardId } from "#enums/reward-id";
|
||||
import type {
|
||||
AddMoneyReward,
|
||||
AddPokeballReward,
|
||||
AddVoucherReward,
|
||||
AllPokemonFullReviveReward,
|
||||
AllPokemonLevelIncrementReward,
|
||||
AttackTypeBoosterRewardGenerator,
|
||||
BaseStatBoosterRewardGenerator,
|
||||
BerryRewardGenerator,
|
||||
EvolutionItemRewardGenerator,
|
||||
FormChangeItemRewardGenerator,
|
||||
FusePokemonReward,
|
||||
LapsingTrainerItemReward,
|
||||
MintRewardGenerator,
|
||||
PokemonAllMovePpRestoreReward,
|
||||
PokemonHpRestoreReward,
|
||||
PokemonLevelIncrementReward,
|
||||
PokemonPpRestoreReward,
|
||||
PokemonPpUpReward,
|
||||
PokemonReviveReward,
|
||||
PokemonStatusHealReward,
|
||||
RememberMoveReward,
|
||||
SpeciesStatBoosterRewardGenerator,
|
||||
TempStatStageBoosterRewardGenerator,
|
||||
TeraTypeRewardGenerator,
|
||||
TmRewardGenerator,
|
||||
} from "#items/reward";
|
||||
|
||||
/**
|
||||
* The type of the `allRewards` const object.
|
||||
* @todo Make `allRewards` a const object and replace all references to this with `typeof allRewards`
|
||||
*/
|
||||
export type allRewardsType = {
|
||||
// Pokeball rewards
|
||||
[RewardId.POKEBALL]: () => AddPokeballReward;
|
||||
[RewardId.GREAT_BALL]: () => AddPokeballReward;
|
||||
[RewardId.ULTRA_BALL]: () => AddPokeballReward;
|
||||
[RewardId.ROGUE_BALL]: () => AddPokeballReward;
|
||||
[RewardId.MASTER_BALL]: () => AddPokeballReward;
|
||||
|
||||
// Voucher rewards
|
||||
[RewardId.VOUCHER]: () => AddVoucherReward;
|
||||
[RewardId.VOUCHER_PLUS]: () => AddVoucherReward;
|
||||
[RewardId.VOUCHER_PREMIUM]: () => AddVoucherReward;
|
||||
|
||||
// Money rewards
|
||||
[RewardId.NUGGET]: () => AddMoneyReward;
|
||||
[RewardId.BIG_NUGGET]: () => AddMoneyReward;
|
||||
[RewardId.RELIC_GOLD]: () => AddMoneyReward;
|
||||
|
||||
// Party-wide consumables
|
||||
[RewardId.RARER_CANDY]: () => AllPokemonLevelIncrementReward;
|
||||
[RewardId.SACRED_ASH]: () => AllPokemonFullReviveReward;
|
||||
|
||||
// Pokemon consumables
|
||||
[RewardId.RARE_CANDY]: () => PokemonLevelIncrementReward;
|
||||
|
||||
[RewardId.EVOLUTION_ITEM]: () => EvolutionItemRewardGenerator;
|
||||
[RewardId.RARE_EVOLUTION_ITEM]: () => EvolutionItemRewardGenerator;
|
||||
|
||||
[RewardId.POTION]: () => PokemonHpRestoreReward;
|
||||
[RewardId.SUPER_POTION]: () => PokemonHpRestoreReward;
|
||||
[RewardId.HYPER_POTION]: () => PokemonHpRestoreReward;
|
||||
[RewardId.MAX_POTION]: () => PokemonHpRestoreReward;
|
||||
[RewardId.FULL_RESTORE]: () => PokemonHpRestoreReward;
|
||||
|
||||
[RewardId.REVIVE]: () => PokemonReviveReward;
|
||||
[RewardId.MAX_REVIVE]: () => PokemonReviveReward;
|
||||
|
||||
[RewardId.FULL_HEAL]: () => PokemonStatusHealReward;
|
||||
|
||||
[RewardId.ETHER]: () => PokemonPpRestoreReward;
|
||||
[RewardId.MAX_ETHER]: () => PokemonPpRestoreReward;
|
||||
|
||||
[RewardId.ELIXIR]: () => PokemonAllMovePpRestoreReward;
|
||||
[RewardId.MAX_ELIXIR]: () => PokemonAllMovePpRestoreReward;
|
||||
|
||||
[RewardId.PP_UP]: () => PokemonPpUpReward;
|
||||
[RewardId.PP_MAX]: () => PokemonPpUpReward;
|
||||
|
||||
/*
|
||||
[RewardId.REPEL]: () => DoubleBattleChanceBoosterReward,
|
||||
[RewardId.SUPER_REPEL]: () => DoubleBattleChanceBoosterReward,
|
||||
[RewardId.MAX_REPEL]: () => DoubleBattleChanceBoosterReward,
|
||||
*/
|
||||
|
||||
[RewardId.MINT]: () => MintRewardGenerator;
|
||||
|
||||
[RewardId.TERA_SHARD]: () => TeraTypeRewardGenerator;
|
||||
|
||||
[RewardId.TM_COMMON]: () => TmRewardGenerator;
|
||||
[RewardId.TM_GREAT]: () => TmRewardGenerator;
|
||||
[RewardId.TM_ULTRA]: () => TmRewardGenerator;
|
||||
|
||||
[RewardId.MEMORY_MUSHROOM]: () => RememberMoveReward;
|
||||
|
||||
[RewardId.DNA_SPLICERS]: () => FusePokemonReward;
|
||||
|
||||
// Form change items
|
||||
[RewardId.FORM_CHANGE_ITEM]: () => FormChangeItemRewardGenerator;
|
||||
[RewardId.RARE_FORM_CHANGE_ITEM]: () => FormChangeItemRewardGenerator;
|
||||
|
||||
// Held items
|
||||
|
||||
[RewardId.SPECIES_STAT_BOOSTER]: () => SpeciesStatBoosterRewardGenerator;
|
||||
[RewardId.RARE_SPECIES_STAT_BOOSTER]: () => SpeciesStatBoosterRewardGenerator;
|
||||
|
||||
[RewardId.BASE_STAT_BOOSTER]: () => BaseStatBoosterRewardGenerator;
|
||||
|
||||
[RewardId.ATTACK_TYPE_BOOSTER]: () => AttackTypeBoosterRewardGenerator;
|
||||
|
||||
[RewardId.BERRY]: () => BerryRewardGenerator;
|
||||
|
||||
// [RewardId.MINI_BLACK_HOLE]: () => HeldItemReward,
|
||||
|
||||
// Trainer items
|
||||
|
||||
[RewardId.LURE]: () => LapsingTrainerItemReward;
|
||||
[RewardId.SUPER_LURE]: () => LapsingTrainerItemReward;
|
||||
[RewardId.MAX_LURE]: () => LapsingTrainerItemReward;
|
||||
|
||||
[RewardId.TEMP_STAT_STAGE_BOOSTER]: () => TempStatStageBoosterRewardGenerator;
|
||||
|
||||
[RewardId.DIRE_HIT]: () => LapsingTrainerItemReward;
|
||||
// [RewardId.GOLDEN_POKEBALL]: () => TrainerItemReward,
|
||||
};
|
@ -3,19 +3,36 @@ import type { RewardId } from "#enums/reward-id";
|
||||
import type { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { Reward, RewardGenerator } from "#items/reward";
|
||||
import type { allRewardsType } from "#types/all-reward-type";
|
||||
|
||||
export type RewardFunc = () => Reward | RewardGenerator;
|
||||
// TODO: Remove party from arguments can be accessed from `globalScene`
|
||||
export type WeightedRewardWeightFunc = (party: Pokemon[], rerollCount?: number) => number;
|
||||
|
||||
export type RewardPoolId = RewardId | HeldItemId | TrainerItemId;
|
||||
|
||||
export type RewardGeneratorSpecs = {
|
||||
id: RewardId;
|
||||
args: RewardGeneratorArgs;
|
||||
type allRewardsInstanceMap = {
|
||||
[k in keyof allRewardsType as ReturnType<allRewardsType[k]> extends RewardGenerator ? k : never]: ReturnType<
|
||||
allRewardsType[k]
|
||||
>;
|
||||
};
|
||||
// TODO: fix this with correctly typed args for different RewardIds
|
||||
|
||||
export type RewardSpecs = RewardPoolId | RewardGeneratorSpecs;
|
||||
export type RewardGeneratorArgMap = {
|
||||
[k in keyof allRewardsInstanceMap]: Exclude<Parameters<allRewardsInstanceMap[k]["generateReward"]>[0], undefined>;
|
||||
};
|
||||
|
||||
/** Union type containing all `RewardId`s corresponding to valid {@linkcode RewardGenerator}s. */
|
||||
export type RewardGeneratorId = keyof allRewardsInstanceMap;
|
||||
|
||||
// TODO: SOrt out which types can and cannot be exported
|
||||
export type RewardGeneratorSpecs<T extends RewardGeneratorId = RewardGeneratorId> = {
|
||||
id: T;
|
||||
args: RewardGeneratorArgMap[T];
|
||||
};
|
||||
|
||||
export type RewardSpecs<T extends RewardPoolId = RewardPoolId> = T extends RewardGeneratorId
|
||||
? T | RewardGeneratorSpecs<T>
|
||||
: T;
|
||||
|
||||
export type RewardPoolEntry = {
|
||||
id: RewardPoolId;
|
||||
|
@ -1,12 +1,11 @@
|
||||
import type { Ability } from "#abilities/ability";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { RewardId } from "#enums/reward-id";
|
||||
import type { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import type { HeldItem } from "#items/held-item";
|
||||
import type { TrainerItem } from "#items/trainer-item";
|
||||
import type { Move } from "#moves/move";
|
||||
import type { RewardFunc } from "#types/rewards";
|
||||
import type { allRewardsType } from "#types/all-reward-type";
|
||||
|
||||
export const allAbilities: Ability[] = [];
|
||||
export const allMoves: Move[] = [];
|
||||
@ -14,4 +13,5 @@ export const allSpecies: PokemonSpecies[] = [];
|
||||
|
||||
export const allHeldItems: Record<HeldItemId, HeldItem> = {};
|
||||
export const allTrainerItems: Record<TrainerItemId, TrainerItem> = {};
|
||||
export const allRewards: Record<RewardId, RewardFunc> = {};
|
||||
// TODO: Consider moving into `all-rewards.ts` as a const object - files should not be importing this
|
||||
export const allRewards: allRewardsType = {};
|
||||
|
@ -132,7 +132,8 @@ export function initRewards() {
|
||||
allRewards[RewardId.PP_MAX] = () =>
|
||||
new PokemonPpUpReward("modifierType:ModifierType.PP_MAX", "pp_max", RewardId.PP_MAX, 3);
|
||||
|
||||
/*REPEL] = () => new DoubleBattleChanceBoosterReward('Repel', 5),
|
||||
/*
|
||||
REPEL] = () => new DoubleBattleChanceBoosterReward('Repel', 5),
|
||||
SUPER_REPEL] = () => new DoubleBattleChanceBoosterReward('Super Repel', 10),
|
||||
MAX_REPEL] = () => new DoubleBattleChanceBoosterReward('Max Repel', 25),*/
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { allRewards } from "#data/data-lists";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import { getRewardCategory, RewardCategoryId, RewardId } from "#enums/reward-id";
|
||||
import type { RarityTier } from "#enums/reward-tier";
|
||||
import type { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import type { RewardFunc, RewardPoolId, RewardSpecs } from "#types/rewards";
|
||||
import type {
|
||||
RewardFunc,
|
||||
RewardGeneratorArgMap,
|
||||
RewardGeneratorId,
|
||||
RewardGeneratorSpecs,
|
||||
RewardPoolId,
|
||||
} from "#types/rewards";
|
||||
import { heldItemRarities } from "./held-item-default-tiers";
|
||||
import {
|
||||
HeldItemReward,
|
||||
@ -33,22 +38,73 @@ export function isRememberMoveReward(reward: Reward): reward is RememberMoveRewa
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Reward from a given function
|
||||
* Generates a Reward from a given function.
|
||||
* @param rewardFunc
|
||||
*/
|
||||
function generateReward(rewardFunc: () => Reward): Reward | null;
|
||||
/**
|
||||
* Generates a Reward from a given function
|
||||
* @param generator
|
||||
* @param pregenArgs Can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc.
|
||||
*/
|
||||
export function generateReward(rewardFunc: RewardFunc, pregenArgs?: any[]): Reward | null {
|
||||
function generateReward<T extends RewardGenerator>(
|
||||
generator: () => T,
|
||||
pregenArgs?: Parameters<T["generateReward"]>[0],
|
||||
): Reward | null;
|
||||
function generateReward(rewardFunc: RewardFunc, pregenArgs?: any[]): Reward | null {
|
||||
const reward = rewardFunc();
|
||||
return reward instanceof RewardGenerator ? reward.generateReward(globalScene.getPlayerParty(), pregenArgs) : reward;
|
||||
return reward instanceof RewardGenerator ? reward.generateReward(pregenArgs) : reward;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically generate a {@linkcode RewardOption} from a given ID.
|
||||
* @param specs - The {@linkcode RewardGeneratorSpecs} used to generate the reward
|
||||
* @param cost - The monetary cost of selecting the option; default `0`
|
||||
* @param tierOverride - An optional {@linkcode RarityTier} to override the option's rarity
|
||||
* @param upgradeCount - The number of tier upgrades having occurred; default `0`
|
||||
* @returns The generated {@linkcode RewardOption}, or `null` if no reward could be generated
|
||||
* @todo Remove `null` from signature eventually
|
||||
*/
|
||||
export function generateRewardOptionFromId<T extends RewardGeneratorId>(
|
||||
specs: RewardGeneratorSpecs<T>,
|
||||
cost?: number,
|
||||
tierOverride?: RarityTier,
|
||||
upgradeCount?: number,
|
||||
): RewardOption | null;
|
||||
/**
|
||||
* Dynamically generate a {@linkcode RewardOption} from a given ID.
|
||||
* @param id - The {@linkcode GeneratorRewardId} to generate a reward for
|
||||
* @param cost - The monetary cost of selecting the option; default `0`
|
||||
* @param tierOverride - An optional {@linkcode RarityTier} to override the option's rarity
|
||||
* @param upgradeCount - The number of tier upgrades having occurred; default `0`
|
||||
* @param pregenArgs - Optional arguments used to seed the generator.
|
||||
* @returns The generated {@linkcode RewardOption}, or `null` if no reward could be generated
|
||||
*/
|
||||
export function generateRewardOptionFromId<T extends RewardGeneratorId>(
|
||||
id: T,
|
||||
cost?: number,
|
||||
tierOverride?: RarityTier,
|
||||
upgradeCount?: number,
|
||||
pregenArgs?: RewardGeneratorArgMap[T],
|
||||
): RewardOption | null;
|
||||
export function generateRewardOptionFromId(
|
||||
id: RewardPoolId,
|
||||
id: Exclude<RewardPoolId, RewardGeneratorId>,
|
||||
cost?: number,
|
||||
tierOverride?: RarityTier,
|
||||
upgradeCount?: number,
|
||||
): RewardOption | null;
|
||||
export function generateRewardOptionFromId(
|
||||
id: RewardGeneratorSpecs | RewardPoolId,
|
||||
cost = 0,
|
||||
tierOverride?: RarityTier,
|
||||
upgradeCount = 0,
|
||||
pregenArgs?: any[],
|
||||
pregenArgs?: unknown,
|
||||
): RewardOption | null {
|
||||
// Destructure specs into objects
|
||||
if (typeof id === "object") {
|
||||
({ id, args: pregenArgs } = id);
|
||||
}
|
||||
|
||||
if (isHeldItemId(id)) {
|
||||
const reward = new HeldItemReward(id);
|
||||
const tier = tierOverride ?? heldItemRarities[id];
|
||||
@ -61,6 +117,7 @@ export function generateRewardOptionFromId(
|
||||
return new RewardOption(reward, upgradeCount, tier, cost);
|
||||
}
|
||||
|
||||
// TODO: This narrows to `any`
|
||||
const rewardFunc = allRewards[id];
|
||||
const reward = generateReward(rewardFunc, pregenArgs);
|
||||
if (reward) {
|
||||
@ -70,17 +127,6 @@ export function generateRewardOptionFromId(
|
||||
return null;
|
||||
}
|
||||
|
||||
export function generateRewardOptionFromSpecs(
|
||||
specs: RewardSpecs,
|
||||
cost = 0,
|
||||
overrideTier?: RarityTier,
|
||||
): RewardOption | null {
|
||||
if (typeof specs === "number") {
|
||||
return generateRewardOptionFromId(specs, cost, overrideTier);
|
||||
}
|
||||
return generateRewardOptionFromId(specs.id, cost, overrideTier, 0, specs.args);
|
||||
}
|
||||
|
||||
export function getPlayerShopRewardOptionsForWave(waveIndex: number, baseCost: number): RewardOption[] {
|
||||
if (!(waveIndex % 10)) {
|
||||
return [];
|
||||
|
@ -11,16 +11,16 @@ import { getNatureName, getNatureStatMultiplier } from "#data/nature";
|
||||
import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#data/pokeball";
|
||||
import { pokemonFormChanges, SpeciesFormChangeCondition } from "#data/pokemon-forms";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import { FormChangeItem } from "#enums/form-change-item";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { LearnMoveType } from "#enums/learn-move-type";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import { Nature } from "#enums/nature";
|
||||
import type { PokeballType } from "#enums/pokeball";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import type { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import type { PermanentStat, TempBattleStat } from "#enums/stat";
|
||||
@ -32,7 +32,7 @@ import { permanentStatToHeldItem, statBoostItems } from "#items/base-stat-booste
|
||||
import { berryTypeToHeldItem } from "#items/berry";
|
||||
import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#items/held-item-pool";
|
||||
import { formChangeItemName } from "#items/item-utility";
|
||||
import { SPECIES_STAT_BOOSTER_ITEMS, type SpeciesStatBoostHeldItem } from "#items/stat-booster";
|
||||
import type { SpeciesStatBoosterItemId, SpeciesStatBoostHeldItem } from "#items/stat-booster";
|
||||
import { TrainerItemEffect, tempStatToTrainerItem } from "#items/trainer-item";
|
||||
import type { PokemonMove } from "#moves/pokemon-move";
|
||||
import { getVoucherTypeIcon, getVoucherTypeName, type VoucherType } from "#system/voucher";
|
||||
@ -43,51 +43,58 @@ import { formatMoney, NumberHolder, padInt, randSeedInt, randSeedItem } from "#u
|
||||
import { getEnumKeys, getEnumValues } from "#utils/enums";
|
||||
import i18next from "i18next";
|
||||
|
||||
/*
|
||||
The term "Reward" refers to items the player can access in the post-battle screen (although
|
||||
they may be used in other places of the code as well).
|
||||
/**
|
||||
* @module
|
||||
* The term "Reward" refers to items the player can access in the post-battle screen (although
|
||||
* they may be used in other places of the code as well).
|
||||
|
||||
Examples include (but are not limited to):
|
||||
- Potions and other healing items
|
||||
- Held items and trainer items
|
||||
- Money items such as nugget and ancient relic
|
||||
* Examples include (but are not limited to):
|
||||
* - Potions and other healing items
|
||||
* - Held items and trainer items
|
||||
* - Money items such as nugget and ancient relic
|
||||
|
||||
Rewards have a basic structure with a name, description, and icon. These are used to display
|
||||
the reward in the reward select screen. All rewards have an .apply() method, which applies the
|
||||
effect, for example:
|
||||
- Apply healing to a pokemon
|
||||
- Assign a held item to a pokemon, or a trainer item to the player
|
||||
- Add money
|
||||
* Rewards have a basic structure with a name, description, and icon. These are used to display
|
||||
* the reward in the reward select screen. All rewards have an .apply() method, which applies the
|
||||
* effect, for example:
|
||||
* - Apply healing to a pokemon
|
||||
* - Assign a held item to a pokemon, or a trainer item to the player
|
||||
* - Add money
|
||||
|
||||
Some rewards, once clicked, simply have their effect---these are Rewards that add money, pokéball,
|
||||
vouchers, or global effect such as Sacred Ash.
|
||||
Most rewards require extra parameters. They are divided into subclasses depending on the parameters
|
||||
that they need, in particular:
|
||||
- PokemonReward requires to pass a Pokemon (to apply healing, assign item...)
|
||||
- PokemonMoveReward requires to pass a Pokemon and a move (for Elixir, or PP Up)
|
||||
Plus some edge cases for Memory Mushroom and DNA Splicers.
|
||||
* Some rewards, once clicked, simply have their effect---these are Rewards that add money, pokéball,
|
||||
* vouchers, or global effect such as Sacred Ash.
|
||||
* Most rewards require extra parameters. They are divided into subclasses depending on the parameters
|
||||
* that they need, in particular:
|
||||
* - PokemonReward requires to pass a Pokemon (to apply healing, assign item...)
|
||||
* - PokemonMoveReward requires to pass a Pokemon and a move (for Elixir, or PP Up)
|
||||
* Plus some edge cases for Memory Mushroom and DNA Splicers.
|
||||
|
||||
The parameters to be passed are generated by the .applyReward() function in SelectRewardPhase.
|
||||
This function takes care of opening the party screen and letting the player select a party pokemon,
|
||||
a move, etc. depending on what is required. Once the parameters are generated, instead of calling
|
||||
.apply() directly, we call the .applyReward() method in BattleScene, which also plays the sound.
|
||||
[This method could perhaps be removed].
|
||||
* The parameters to be passed are generated by the .applyReward() function in {@linkcode SelectRewardPhase}.
|
||||
* This function takes care of opening the party screen and letting the player select a party pokemon,
|
||||
* a move, etc. depending on what is required. Once the parameters are generated, instead of calling
|
||||
* .apply() directly, we call the .applyReward() method in BattleScene, which also plays the sound.
|
||||
* [This method could perhaps be removed].
|
||||
|
||||
Rewards are assigned RewardId, and there are also RewardCategoryId. For example, TM is a RewardCategoryId,
|
||||
while CommonTM, RareTM etc are RewardIds. There is _not_ a RewardId for _each_ move. Similarly,
|
||||
some specific categories of held items are assigned their own RewardId, but they all fall under a single
|
||||
RewardCategoryId.
|
||||
* Rewards are assigned RewardId, and there are also RewardCategoryId. For example, TM is a RewardCategoryId,
|
||||
* while CommonTM, RareTM etc are RewardIds. There is _not_ a RewardId for _each_ move. Similarly,
|
||||
* some specific categories of held items are assigned their own RewardId, but they all fall under a single
|
||||
* RewardCategoryId.
|
||||
|
||||
rewardInitObj plays a similar role to allHeldItems, except instead of containing all possible reward
|
||||
instances, it instead contains functions that generate those rewards. Here, the keys used are strings
|
||||
rather than RewardId, the difference exists because here we want to distinguish unique held items
|
||||
for example. The entries of rewardInitObj are used in the RewardPool.
|
||||
* rewardInitObj plays a similar role to allHeldItems, except instead of containing all possible reward
|
||||
* instances, it instead contains functions that generate those rewards. Here, the keys used are strings
|
||||
* rather than RewardId, the difference exists because here we want to distinguish unique held items
|
||||
* for example. The entries of rewardInitObj are used in the RewardPool.
|
||||
|
||||
There are some more derived classes, in particular:
|
||||
RewardGenerator, which creates Reward instances from a certain group (e.g. TMs, nature mints, or berries);
|
||||
and RewardOption, which is displayed during the select reward phase at the end of each encounter.
|
||||
* There are some more derived classes, in particular:
|
||||
* RewardGenerator, which creates Reward instances from a certain group (e.g. TMs, nature mints, or berries);
|
||||
* and RewardOption, which is displayed during the select reward phase at the end of each encounter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Type helper to exactly match objects and nothing else.
|
||||
* @todo merge with `Exact` later on
|
||||
*/
|
||||
type MatchExact<T> = 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
|
||||
@ -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 {
|
||||
@ -440,6 +440,7 @@ export class ChangeTeraTypeReward extends PokemonReward {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: What is this for?
|
||||
getPregenArgs(): any[] {
|
||||
return [this.teraType];
|
||||
}
|
||||
@ -467,6 +468,9 @@ export class ChangeTeraTypeReward extends PokemonReward {
|
||||
}
|
||||
}
|
||||
|
||||
// todo: denest
|
||||
// TODO: Consider removing `revive` from the signature of PokemonHealPhase in the wake of this
|
||||
// (was only used for revives)
|
||||
function restorePokemonHp(
|
||||
pokemon: Pokemon,
|
||||
percentToRestore: number,
|
||||
@ -858,59 +862,55 @@ export class RememberMoveReward extends PokemonReward {
|
||||
}
|
||||
|
||||
export class BerryRewardGenerator extends RewardGenerator {
|
||||
constructor() {
|
||||
super((_party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) {
|
||||
const item = berryTypeToHeldItem[pregenArgs[0] as BerryType];
|
||||
return new HeldItemReward(item);
|
||||
}
|
||||
const item = getNewBerryHeldItem();
|
||||
override generateReward(pregenArgs?: BerryType): HeldItemReward {
|
||||
if (pregenArgs !== undefined) {
|
||||
const item = berryTypeToHeldItem[pregenArgs];
|
||||
return new HeldItemReward(item);
|
||||
});
|
||||
this.id = RewardId.BERRY;
|
||||
}
|
||||
const item = getNewBerryHeldItem();
|
||||
return new HeldItemReward(item);
|
||||
}
|
||||
}
|
||||
|
||||
export class MintRewardGenerator extends RewardGenerator {
|
||||
constructor() {
|
||||
super((_party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in Nature) {
|
||||
return new PokemonNatureChangeReward(pregenArgs[0] as Nature);
|
||||
}
|
||||
return new PokemonNatureChangeReward(randSeedItem(getEnumValues(Nature)));
|
||||
});
|
||||
this.id = RewardId.MINT;
|
||||
override generateReward(pregenArgs?: Nature) {
|
||||
if (pregenArgs !== undefined) {
|
||||
return new PokemonNatureChangeReward(pregenArgs);
|
||||
}
|
||||
return new PokemonNatureChangeReward(randSeedItem(getEnumValues(Nature)));
|
||||
}
|
||||
}
|
||||
|
||||
export class TeraTypeRewardGenerator extends RewardGenerator {
|
||||
constructor() {
|
||||
super((party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) {
|
||||
return new ChangeTeraTypeReward(pregenArgs[0] as PokemonType);
|
||||
override generateReward(pregenArgs?: PokemonType) {
|
||||
if (pregenArgs !== undefined) {
|
||||
return new ChangeTeraTypeReward(pregenArgs[0]);
|
||||
}
|
||||
if (!globalScene.trainerItems.hasItem(TrainerItemId.TERA_ORB)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const shardType = this.getTeraType();
|
||||
return new ChangeTeraTypeReward(shardType);
|
||||
}
|
||||
|
||||
private getTeraType(): PokemonType {
|
||||
// If all party members have a given Tera Type, omit it from the pool
|
||||
const excludedType = globalScene.getPlayerParty().reduce((prevType, p) => {
|
||||
if (
|
||||
// Ignore Pokemon with fixed Tera Types
|
||||
p.hasSpecies(SpeciesId.TERAPAGOS) ||
|
||||
p.hasSpecies(SpeciesId.OGERPON) ||
|
||||
p.hasSpecies(SpeciesId.SHEDINJA)
|
||||
) {
|
||||
return prevType;
|
||||
}
|
||||
if (!globalScene.trainerItems.hasItem(TrainerItemId.TERA_ORB)) {
|
||||
return null;
|
||||
}
|
||||
const teraTypes: PokemonType[] = [];
|
||||
for (const p of party) {
|
||||
if (
|
||||
!(p.hasSpecies(SpeciesId.TERAPAGOS) || p.hasSpecies(SpeciesId.OGERPON) || p.hasSpecies(SpeciesId.SHEDINJA))
|
||||
) {
|
||||
teraTypes.push(p.teraType);
|
||||
}
|
||||
}
|
||||
let excludedType = PokemonType.UNKNOWN;
|
||||
if (teraTypes.length > 0 && teraTypes.filter(t => t === teraTypes[0]).length === teraTypes.length) {
|
||||
excludedType = teraTypes[0];
|
||||
}
|
||||
let shardType = randSeedInt(64) ? (randSeedInt(18) as PokemonType) : PokemonType.STELLAR;
|
||||
while (shardType === excludedType) {
|
||||
shardType = randSeedInt(64) ? (randSeedInt(18) as PokemonType) : PokemonType.STELLAR;
|
||||
}
|
||||
return new ChangeTeraTypeReward(shardType);
|
||||
});
|
||||
this.id = RewardId.TERA_SHARD;
|
||||
return prevType === p.teraType ? prevType : PokemonType.UNKNOWN;
|
||||
}, PokemonType.UNKNOWN);
|
||||
|
||||
const validTypes = getEnumValues(PokemonType).filter(t => t !== excludedType);
|
||||
// 1/64 chance for tera stellar
|
||||
return randSeedInt(64) ? randSeedItem(validTypes) : PokemonType.STELLAR;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1220,29 +1220,23 @@ export class FusePokemonReward extends PokemonReward {
|
||||
}
|
||||
|
||||
export class AttackTypeBoosterRewardGenerator extends RewardGenerator {
|
||||
constructor() {
|
||||
super((party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) {
|
||||
return new AttackTypeBoosterReward(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT);
|
||||
}
|
||||
override generateReward(pregenArgs?: PokemonType) {
|
||||
if (pregenArgs !== undefined) {
|
||||
return new AttackTypeBoosterReward(pregenArgs, TYPE_BOOST_ITEM_BOOST_PERCENT);
|
||||
}
|
||||
|
||||
const item = getNewAttackTypeBoosterHeldItem(party);
|
||||
const item = getNewAttackTypeBoosterHeldItem(globalScene.getPlayerParty());
|
||||
|
||||
return item ? new HeldItemReward(item) : null;
|
||||
});
|
||||
this.id = RewardId.ATTACK_TYPE_BOOSTER;
|
||||
return item ? new HeldItemReward(item) : null;
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseStatBoosterRewardGenerator extends RewardGenerator {
|
||||
constructor() {
|
||||
super((_party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs) {
|
||||
return new BaseStatBoosterReward(pregenArgs[0]);
|
||||
}
|
||||
return new HeldItemReward(getNewVitaminHeldItem());
|
||||
});
|
||||
this.id = RewardId.BASE_STAT_BOOSTER;
|
||||
override generateReward(pregenArgs?: PermanentStat) {
|
||||
if (pregenArgs !== undefined) {
|
||||
return new BaseStatBoosterReward(pregenArgs);
|
||||
}
|
||||
return new HeldItemReward(getNewVitaminHeldItem());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1256,15 +1250,8 @@ export class TempStatStageBoosterRewardGenerator extends RewardGenerator {
|
||||
[Stat.ACC]: "x_accuracy",
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super((_party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs && pregenArgs.length === 1 && TEMP_BATTLE_STATS.includes(pregenArgs[0])) {
|
||||
return new LapsingTrainerItemReward(tempStatToTrainerItem[pregenArgs[0]]);
|
||||
}
|
||||
const randStat: TempBattleStat = randSeedInt(Stat.ACC, Stat.ATK);
|
||||
return new LapsingTrainerItemReward(tempStatToTrainerItem[randStat]);
|
||||
});
|
||||
this.id = RewardId.TEMP_STAT_STAGE_BOOSTER;
|
||||
override generateReward(pregenArgs?: TempBattleStat) {
|
||||
return new LapsingTrainerItemReward(tempStatToTrainerItem[pregenArgs ?? randSeedItem(TEMP_BATTLE_STATS)]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1277,232 +1264,243 @@ export class TempStatStageBoosterRewardGenerator extends RewardGenerator {
|
||||
export class SpeciesStatBoosterRewardGenerator extends RewardGenerator {
|
||||
/** Object comprised of the currently available species-based stat boosting held items */
|
||||
|
||||
private rare: boolean;
|
||||
constructor(rare: boolean) {
|
||||
super((party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in SPECIES_STAT_BOOSTER_ITEMS) {
|
||||
return new HeldItemReward(pregenArgs[0] as HeldItemId);
|
||||
}
|
||||
super();
|
||||
this.rare = rare;
|
||||
}
|
||||
override generateReward(pregenArgs?: SpeciesStatBoosterItemId) {
|
||||
if (pregenArgs !== undefined) {
|
||||
return new HeldItemReward(pregenArgs);
|
||||
}
|
||||
|
||||
// Get a pool of items based on the rarity.
|
||||
const tierItems = rare
|
||||
? [HeldItemId.LIGHT_BALL, HeldItemId.THICK_CLUB, HeldItemId.METAL_POWDER, HeldItemId.QUICK_POWDER]
|
||||
: [HeldItemId.DEEP_SEA_SCALE, HeldItemId.DEEP_SEA_TOOTH];
|
||||
// Get a pool of items based on the rarity.
|
||||
const tierItems = this.rare
|
||||
? [HeldItemId.LIGHT_BALL, HeldItemId.THICK_CLUB, HeldItemId.METAL_POWDER, HeldItemId.QUICK_POWDER]
|
||||
: [HeldItemId.DEEP_SEA_SCALE, HeldItemId.DEEP_SEA_TOOTH];
|
||||
|
||||
const weights = new Array(tierItems.length).fill(0);
|
||||
const weights = new Array(tierItems.length).fill(0);
|
||||
|
||||
for (const p of party) {
|
||||
const speciesId = p.getSpeciesForm(true).speciesId;
|
||||
const fusionSpeciesId = p.isFusion() ? p.getFusionSpeciesForm(true).speciesId : null;
|
||||
// TODO: Use commented boolean when Fling is implemented
|
||||
const hasFling = false; /* p.getMoveset(true).some(m => m.moveId === MoveId.FLING) */
|
||||
for (const p of globalScene.getPlayerParty()) {
|
||||
const speciesId = p.getSpeciesForm(true).speciesId;
|
||||
const fusionSpeciesId = p.isFusion() ? p.getFusionSpeciesForm(true).speciesId : null;
|
||||
// TODO: Use commented boolean when Fling is implemented
|
||||
const hasFling = false; /* p.getMoveset(true).some(m => m.moveId === MoveId.FLING) */
|
||||
|
||||
for (const i in tierItems) {
|
||||
const checkedSpecies = (allHeldItems[tierItems[i]] as SpeciesStatBoostHeldItem).species;
|
||||
for (const i in tierItems) {
|
||||
const checkedSpecies = (allHeldItems[tierItems[i]] as SpeciesStatBoostHeldItem).species;
|
||||
|
||||
// If party member already has the item being weighted currently, skip to the next item
|
||||
const hasItem = p.heldItemManager.hasItem(tierItems[i]);
|
||||
// If party member already has the item being weighted currently, skip to the next item
|
||||
const hasItem = p.heldItemManager.hasItem(tierItems[i]);
|
||||
|
||||
if (!hasItem) {
|
||||
if (checkedSpecies.includes(speciesId) || (!!fusionSpeciesId && checkedSpecies.includes(fusionSpeciesId))) {
|
||||
// Add weight if party member has a matching species or, if applicable, a matching fusion species
|
||||
weights[i]++;
|
||||
} else if (checkedSpecies.includes(SpeciesId.PIKACHU) && hasFling) {
|
||||
// Add weight to Light Ball if party member has Fling
|
||||
weights[i]++;
|
||||
}
|
||||
if (!hasItem) {
|
||||
if (checkedSpecies.includes(speciesId) || (!!fusionSpeciesId && checkedSpecies.includes(fusionSpeciesId))) {
|
||||
// Add weight if party member has a matching species or, if applicable, a matching fusion species
|
||||
weights[i]++;
|
||||
} else if (checkedSpecies.includes(SpeciesId.PIKACHU) && hasFling) {
|
||||
// Add weight to Light Ball if party member has Fling
|
||||
weights[i]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Replace this with a helper function
|
||||
let totalWeight = 0;
|
||||
for (const weight of weights) {
|
||||
totalWeight += weight;
|
||||
}
|
||||
// TODO: Replace this with a helper function
|
||||
let totalWeight = 0;
|
||||
for (const weight of weights) {
|
||||
totalWeight += weight;
|
||||
}
|
||||
|
||||
if (totalWeight !== 0) {
|
||||
const randInt = randSeedInt(totalWeight, 1);
|
||||
let weight = 0;
|
||||
if (totalWeight !== 0) {
|
||||
const randInt = randSeedInt(totalWeight, 1);
|
||||
let weight = 0;
|
||||
|
||||
for (const i in weights) {
|
||||
if (weights[i] !== 0) {
|
||||
const curWeight = weight + weights[i];
|
||||
if (randInt <= weight + weights[i]) {
|
||||
return new HeldItemReward(tierItems[i]);
|
||||
}
|
||||
weight = curWeight;
|
||||
for (const i in weights) {
|
||||
if (weights[i] !== 0) {
|
||||
const curWeight = weight + weights[i];
|
||||
if (randInt <= weight + weights[i]) {
|
||||
return new HeldItemReward(tierItems[i]);
|
||||
}
|
||||
weight = curWeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
this.id = rare ? RewardId.SPECIES_STAT_BOOSTER : RewardId.RARE_SPECIES_STAT_BOOSTER;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class TmRewardGenerator extends RewardGenerator {
|
||||
private tier: RarityTier;
|
||||
constructor(tier: RarityTier) {
|
||||
super((party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in MoveId) {
|
||||
return new TmReward(pregenArgs[0] as MoveId);
|
||||
}
|
||||
const partyMemberCompatibleTms = party.map(p => {
|
||||
const previousLevelMoves = p.getLearnableLevelMoves();
|
||||
return (p as PlayerPokemon).compatibleTms.filter(
|
||||
tm => !p.moveset.find(m => m.moveId === tm) && !previousLevelMoves.find(lm => lm === tm),
|
||||
);
|
||||
});
|
||||
const tierUniqueCompatibleTms = partyMemberCompatibleTms
|
||||
.flat()
|
||||
.filter(tm => tmPoolTiers[tm] === tier)
|
||||
.filter(tm => !allMoves[tm].name.endsWith(" (N)"))
|
||||
.filter((tm, i, array) => array.indexOf(tm) === i);
|
||||
if (!tierUniqueCompatibleTms.length) {
|
||||
return null;
|
||||
}
|
||||
// TODO: should this use `randSeedItem`?
|
||||
const randTmIndex = randSeedInt(tierUniqueCompatibleTms.length);
|
||||
return new TmReward(tierUniqueCompatibleTms[randTmIndex]);
|
||||
super();
|
||||
this.tier = tier;
|
||||
}
|
||||
|
||||
override generateReward(pregenArgs?: MoveId) {
|
||||
if (pregenArgs !== undefined) {
|
||||
return new TmReward(pregenArgs);
|
||||
}
|
||||
|
||||
const party = globalScene.getPlayerParty();
|
||||
const partyMemberCompatibleTms = party.map(p => {
|
||||
const previousLevelMoves = p.getLearnableLevelMoves();
|
||||
return (p as PlayerPokemon).compatibleTms.filter(
|
||||
tm => !p.moveset.find(m => m.moveId === tm) && !previousLevelMoves.find(lm => lm === tm),
|
||||
);
|
||||
});
|
||||
this.id =
|
||||
tier === RarityTier.COMMON
|
||||
? RewardId.TM_COMMON
|
||||
: tier === RarityTier.GREAT
|
||||
? RewardId.TM_GREAT
|
||||
: RewardId.TM_ULTRA;
|
||||
const tierUniqueCompatibleTms = partyMemberCompatibleTms
|
||||
.flat()
|
||||
.filter(tm => tmPoolTiers[tm] === this.tier)
|
||||
.filter(tm => !allMoves[tm].name.endsWith(" (N)"))
|
||||
.filter((tm, i, array) => array.indexOf(tm) === i);
|
||||
if (!tierUniqueCompatibleTms.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const randTmIndex = randSeedItem(tierUniqueCompatibleTms);
|
||||
return new TmReward(randTmIndex);
|
||||
}
|
||||
}
|
||||
|
||||
export class EvolutionItemRewardGenerator extends RewardGenerator {
|
||||
constructor(rare: boolean, id: RewardId) {
|
||||
super((party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in EvolutionItem) {
|
||||
return new EvolutionItemReward(pregenArgs[0] as EvolutionItem);
|
||||
}
|
||||
private rare: boolean;
|
||||
constructor(rare: boolean) {
|
||||
super();
|
||||
this.rare = rare;
|
||||
}
|
||||
|
||||
const evolutionItemPool = [
|
||||
party
|
||||
.filter(
|
||||
p =>
|
||||
pokemonEvolutions.hasOwnProperty(p.species.speciesId) &&
|
||||
(!p.pauseEvolutions ||
|
||||
p.species.speciesId === SpeciesId.SLOWPOKE ||
|
||||
p.species.speciesId === SpeciesId.EEVEE ||
|
||||
p.species.speciesId === SpeciesId.KIRLIA ||
|
||||
p.species.speciesId === SpeciesId.SNORUNT),
|
||||
)
|
||||
.flatMap(p => {
|
||||
const evolutions = pokemonEvolutions[p.species.speciesId];
|
||||
return evolutions.filter(e => e.isValidItemEvolution(p));
|
||||
}),
|
||||
party
|
||||
.filter(
|
||||
p =>
|
||||
p.isFusion() &&
|
||||
p.fusionSpecies &&
|
||||
pokemonEvolutions.hasOwnProperty(p.fusionSpecies.speciesId) &&
|
||||
(!p.pauseEvolutions ||
|
||||
p.fusionSpecies.speciesId === SpeciesId.SLOWPOKE ||
|
||||
p.fusionSpecies.speciesId === SpeciesId.EEVEE ||
|
||||
p.fusionSpecies.speciesId === SpeciesId.KIRLIA ||
|
||||
p.fusionSpecies.speciesId === SpeciesId.SNORUNT),
|
||||
)
|
||||
.flatMap(p => {
|
||||
const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId];
|
||||
return evolutions.filter(e => e.isValidItemEvolution(p, true));
|
||||
}),
|
||||
]
|
||||
.flat()
|
||||
.flatMap(e => e.evoItem)
|
||||
.filter(i => !!i && i > 50 === rare);
|
||||
override generateReward(pregenArgs?: EvolutionItem) {
|
||||
if (pregenArgs !== undefined) {
|
||||
return new EvolutionItemReward(pregenArgs);
|
||||
}
|
||||
|
||||
if (!evolutionItemPool.length) {
|
||||
return null;
|
||||
}
|
||||
const party = globalScene.getPlayerParty();
|
||||
|
||||
// TODO: should this use `randSeedItem`?
|
||||
return new EvolutionItemReward(evolutionItemPool[randSeedInt(evolutionItemPool.length)]!); // TODO: is the bang correct?
|
||||
});
|
||||
this.id = id;
|
||||
const evolutionItemPool = [
|
||||
party
|
||||
.filter(
|
||||
p =>
|
||||
pokemonEvolutions.hasOwnProperty(p.species.speciesId) &&
|
||||
(!p.pauseEvolutions ||
|
||||
p.species.speciesId === SpeciesId.SLOWPOKE ||
|
||||
p.species.speciesId === SpeciesId.EEVEE ||
|
||||
p.species.speciesId === SpeciesId.KIRLIA ||
|
||||
p.species.speciesId === SpeciesId.SNORUNT),
|
||||
)
|
||||
.flatMap(p => {
|
||||
const evolutions = pokemonEvolutions[p.species.speciesId];
|
||||
return evolutions.filter(e => e.isValidItemEvolution(p));
|
||||
}),
|
||||
party
|
||||
.filter(
|
||||
p =>
|
||||
p.isFusion() &&
|
||||
p.fusionSpecies &&
|
||||
pokemonEvolutions.hasOwnProperty(p.fusionSpecies.speciesId) &&
|
||||
(!p.pauseEvolutions ||
|
||||
p.fusionSpecies.speciesId === SpeciesId.SLOWPOKE ||
|
||||
p.fusionSpecies.speciesId === SpeciesId.EEVEE ||
|
||||
p.fusionSpecies.speciesId === SpeciesId.KIRLIA ||
|
||||
p.fusionSpecies.speciesId === SpeciesId.SNORUNT),
|
||||
)
|
||||
.flatMap(p => {
|
||||
const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId];
|
||||
return evolutions.filter(e => e.isValidItemEvolution(p, true));
|
||||
}),
|
||||
]
|
||||
.flat()
|
||||
.flatMap(e => e.evoItem)
|
||||
.filter(i => !!i && i > 50 === this.rare);
|
||||
|
||||
if (!evolutionItemPool.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new EvolutionItemReward(randSeedItem(evolutionItemPool));
|
||||
}
|
||||
}
|
||||
|
||||
export class FormChangeItemRewardGenerator extends RewardGenerator {
|
||||
constructor(isRareFormChangeItem: boolean, id: RewardId) {
|
||||
super((party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in FormChangeItem) {
|
||||
return new FormChangeItemReward(pregenArgs[0] as FormChangeItem);
|
||||
}
|
||||
private isRareFormChangeItem: boolean;
|
||||
|
||||
const formChangeItemPool = [
|
||||
...new Set(
|
||||
party
|
||||
.filter(p => pokemonFormChanges.hasOwnProperty(p.species.speciesId))
|
||||
.flatMap(p => {
|
||||
const formChanges = pokemonFormChanges[p.species.speciesId];
|
||||
let formChangeItemTriggers = formChanges
|
||||
.filter(
|
||||
fc =>
|
||||
((fc.formKey.indexOf(SpeciesFormKey.MEGA) === -1 &&
|
||||
fc.formKey.indexOf(SpeciesFormKey.PRIMAL) === -1) ||
|
||||
globalScene.trainerItems.hasItem(TrainerItemId.MEGA_BRACELET)) &&
|
||||
((fc.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) === -1 &&
|
||||
fc.formKey.indexOf(SpeciesFormKey.ETERNAMAX) === -1) ||
|
||||
globalScene.trainerItems.hasItem(TrainerItemId.DYNAMAX_BAND)) &&
|
||||
(!fc.conditions.length ||
|
||||
fc.conditions.filter(cond => cond instanceof SpeciesFormChangeCondition && cond.predicate(p))
|
||||
.length) &&
|
||||
fc.preFormKey === p.getFormKey(),
|
||||
)
|
||||
.map(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger)
|
||||
.filter(t => t?.active && !p.heldItemManager.hasFormChangeItem(t.item));
|
||||
constructor(isRareFormChangeItem: boolean) {
|
||||
super();
|
||||
this.isRareFormChangeItem = isRareFormChangeItem;
|
||||
}
|
||||
|
||||
if (p.species.speciesId === SpeciesId.NECROZMA) {
|
||||
// technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break...
|
||||
let foundULTRA_Z = false,
|
||||
foundN_LUNA = false,
|
||||
foundN_SOLAR = false;
|
||||
formChangeItemTriggers.forEach((fc, _i) => {
|
||||
console.log("Checking ", fc.item);
|
||||
switch (fc.item) {
|
||||
case FormChangeItem.ULTRANECROZIUM_Z:
|
||||
foundULTRA_Z = true;
|
||||
break;
|
||||
case FormChangeItem.N_LUNARIZER:
|
||||
foundN_LUNA = true;
|
||||
break;
|
||||
case FormChangeItem.N_SOLARIZER:
|
||||
foundN_SOLAR = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (foundULTRA_Z && foundN_LUNA && foundN_SOLAR) {
|
||||
// all three items are present -> user hasn't acquired any of the N_*ARIZERs -> block ULTRANECROZIUM_Z acquisition.
|
||||
formChangeItemTriggers = formChangeItemTriggers.filter(
|
||||
fc => fc.item !== FormChangeItem.ULTRANECROZIUM_Z,
|
||||
);
|
||||
} else {
|
||||
console.log("DID NOT FIND ");
|
||||
override generateReward(pregenArgs?: FormChangeItem) {
|
||||
if (pregenArgs !== undefined) {
|
||||
return new FormChangeItemReward(pregenArgs);
|
||||
}
|
||||
const party = globalScene.getPlayerParty();
|
||||
|
||||
// TODO: REFACTOR THIS FUCKERY PLEASE
|
||||
const formChangeItemPool = [
|
||||
...new Set(
|
||||
party
|
||||
.filter(p => pokemonFormChanges.hasOwnProperty(p.species.speciesId))
|
||||
.flatMap(p => {
|
||||
const formChanges = pokemonFormChanges[p.species.speciesId];
|
||||
let formChangeItemTriggers = formChanges
|
||||
.filter(
|
||||
fc =>
|
||||
((fc.formKey.indexOf(SpeciesFormKey.MEGA) === -1 &&
|
||||
fc.formKey.indexOf(SpeciesFormKey.PRIMAL) === -1) ||
|
||||
globalScene.trainerItems.hasItem(TrainerItemId.MEGA_BRACELET)) &&
|
||||
((fc.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) === -1 &&
|
||||
fc.formKey.indexOf(SpeciesFormKey.ETERNAMAX) === -1) ||
|
||||
globalScene.trainerItems.hasItem(TrainerItemId.DYNAMAX_BAND)) &&
|
||||
(!fc.conditions.length ||
|
||||
fc.conditions.filter(cond => cond instanceof SpeciesFormChangeCondition && cond.predicate(p))
|
||||
.length) &&
|
||||
fc.preFormKey === p.getFormKey(),
|
||||
)
|
||||
.map(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger)
|
||||
.filter(t => t?.active && !p.heldItemManager.hasFormChangeItem(t.item));
|
||||
|
||||
if (p.species.speciesId === SpeciesId.NECROZMA) {
|
||||
// technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break...
|
||||
let foundULTRA_Z = false,
|
||||
foundN_LUNA = false,
|
||||
foundN_SOLAR = false;
|
||||
formChangeItemTriggers.forEach((fc, _i) => {
|
||||
console.log("Checking ", fc.item);
|
||||
switch (fc.item) {
|
||||
case FormChangeItem.ULTRANECROZIUM_Z:
|
||||
foundULTRA_Z = true;
|
||||
break;
|
||||
case FormChangeItem.N_LUNARIZER:
|
||||
foundN_LUNA = true;
|
||||
break;
|
||||
case FormChangeItem.N_SOLARIZER:
|
||||
foundN_SOLAR = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (foundULTRA_Z && foundN_LUNA && foundN_SOLAR) {
|
||||
// all three items are present -> user hasn't acquired any of the N_*ARIZERs -> block ULTRANECROZIUM_Z acquisition.
|
||||
formChangeItemTriggers = formChangeItemTriggers.filter(
|
||||
fc => fc.item !== FormChangeItem.ULTRANECROZIUM_Z,
|
||||
);
|
||||
} else {
|
||||
console.log("DID NOT FIND ");
|
||||
}
|
||||
return formChangeItemTriggers;
|
||||
}),
|
||||
),
|
||||
]
|
||||
.flat()
|
||||
.flatMap(fc => fc.item)
|
||||
.filter(i => (i && i < 100) === isRareFormChangeItem);
|
||||
// convert it into a set to remove duplicate values, which can appear when the same species with a potential form change is in the party.
|
||||
}
|
||||
return formChangeItemTriggers;
|
||||
}),
|
||||
),
|
||||
]
|
||||
.flat()
|
||||
.flatMap(fc => fc.item)
|
||||
.filter(i => (i && i < 100) === this.isRareFormChangeItem);
|
||||
// convert it into a set to remove duplicate values, which can appear when the same species with a potential form change is in the party.
|
||||
|
||||
if (!formChangeItemPool.length) {
|
||||
return null;
|
||||
}
|
||||
if (!formChangeItemPool.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: should this use `randSeedItem`?
|
||||
return new FormChangeItemReward(formChangeItemPool[randSeedInt(formChangeItemPool.length)]);
|
||||
});
|
||||
this.id = id;
|
||||
return new FormChangeItemReward(randSeedItem(formChangeItemPool));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { Reward } from "#items/reward";
|
||||
import { type Reward, RewardGenerator } from "#items/reward";
|
||||
import { BattlePhase } from "#phases/battle-phase";
|
||||
import type { RewardFunc } from "#types/rewards";
|
||||
import i18next from "i18next";
|
||||
@ -13,7 +13,8 @@ export class RewardPhase extends BattlePhase {
|
||||
constructor(rewardFunc: RewardFunc) {
|
||||
super();
|
||||
|
||||
this.reward = rewardFunc();
|
||||
const reward = rewardFunc();
|
||||
this.reward = reward instanceof RewardGenerator ? reward.generateReward() : reward;
|
||||
}
|
||||
|
||||
start() {
|
||||
|
@ -125,7 +125,9 @@ export function randItem<T>(items: T[]): T {
|
||||
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 {
|
||||
// TODO: Resolve this later
|
||||
// @ts-expect-error - phaser is dumb af
|
||||
return items.length === 1 ? items[0] : Phaser.Math.RND.pick(items);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user