mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-10 01:19:29 +02:00
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:
parent
acc12318d7
commit
2aca187c66
47
scripts/create-test/boilerplates/reward.ts
Normal file
47
scripts/create-test/boilerplates/reward.ts
Normal 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);
|
||||
});
|
||||
});
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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> = {};
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,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 [];
|
||||
|
@ -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 {}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -56,4 +56,4 @@ export class ModifierHelper extends GameManagerHelper {
|
||||
private log(...params: any[]) {
|
||||
console.log("Modifiers:", ...params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,4 +56,4 @@ export class ModifierHelper extends GameManagerHelper {
|
||||
private log(...params: any[]) {
|
||||
console.log("Modifiers:", ...params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user