diff --git a/scripts/create-test/boilerplates/rewards/reward.ts b/scripts/create-test/boilerplates/rewards/reward.ts index 0a6473b1a29..516d2c51542 100644 --- a/scripts/create-test/boilerplates/rewards/reward.ts +++ b/scripts/create-test/boilerplates/rewards/reward.ts @@ -3,7 +3,7 @@ import { getHeldItemCategory, HeldItemCategoryId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { RewardId } from "#enums/reward-id"; import { SpeciesId } from "#enums/species-id"; -import { HeldItemReward } from "#items/reward"; +import { HeldItemReward } from "#items/rewards/held-item-reward"; import { GameManager } from "#test/test-utils/game-manager"; import { generateRewardForTest } from "#test/test-utils/reward-test-utils"; import Phaser from "phaser"; diff --git a/src/items/held-item-data-types.ts b/src/@types/held-item-data-types.ts similarity index 100% rename from src/items/held-item-data-types.ts rename to src/@types/held-item-data-types.ts diff --git a/src/items/held-item-parameter.ts b/src/@types/held-item-parameter.ts similarity index 100% rename from src/items/held-item-parameter.ts rename to src/@types/held-item-parameter.ts diff --git a/src/@types/trainer-funcs.ts b/src/@types/trainer-funcs.ts index a18b11c07f0..5e8211f7b69 100644 --- a/src/@types/trainer-funcs.ts +++ b/src/@types/trainer-funcs.ts @@ -1,9 +1,9 @@ import type { PartyMemberStrength } from "#enums/party-member-strength"; import type { SpeciesId } from "#enums/species-id"; import type { EnemyPokemon } from "#field/pokemon"; -import type { TrainerItemConfiguration } from "#items/trainer-item-data-types"; import type { TrainerConfig } from "#trainers/trainer-config"; import type { TrainerPartyTemplate } from "#trainers/trainer-party-template"; +import type { TrainerItemConfiguration } from "./trainer-item-data-types"; export type PartyTemplateFunc = () => TrainerPartyTemplate; export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon; diff --git a/src/items/trainer-item-data-types.ts b/src/@types/trainer-item-data-types.ts similarity index 100% rename from src/items/trainer-item-data-types.ts rename to src/@types/trainer-item-data-types.ts diff --git a/src/items/trainer-item-parameter.ts b/src/@types/trainer-item-parameter.ts similarity index 100% rename from src/items/trainer-item-parameter.ts rename to src/@types/trainer-item-parameter.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 8c575dfe50b..93b8d870a57 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -79,19 +79,11 @@ import { PokemonSpriteSparkleHandler } from "#field/pokemon-sprite-sparkle-handl import { Trainer } from "#field/trainer"; import { applyHeldItems } from "#items/all-held-items"; import { applyTrainerItems } from "#items/all-trainer-items"; -import type { HeldItemConfiguration } from "#items/held-item-data-types"; import { assignEnemyHeldItemsForWave, assignItemsFromConfiguration } from "#items/held-item-pool"; import type { MatchExact, Reward } from "#items/reward"; -import type { EnemyAttackStatusEffectChanceTrainerItem } from "#items/trainer-item"; -import { - isTrainerItemPool, - isTrainerItemSpecs, - type TrainerItemConfiguration, - type TrainerItemSaveData, -} from "#items/trainer-item-data-types"; import { TrainerItemManager } from "#items/trainer-item-manager"; -import type { TrainerItemEffectParamMap } from "#items/trainer-item-parameter"; import { getNewTrainerItemFromPool } from "#items/trainer-item-pool"; +import type { EnemyAttackStatusEffectChanceTrainerItem } from "#items/trainer-items/enemy-tokens"; import { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterSaveData } from "#mystery-encounters/mystery-encounter-save-data"; import { allMysteryEncounters, mysteryEncountersByBiome } from "#mystery-encounters/mystery-encounters"; @@ -110,7 +102,15 @@ import type { TrainerData } from "#system/trainer-data"; import type { Voucher } from "#system/voucher"; import { vouchers } from "#system/voucher"; import { trainerConfigs } from "#trainers/trainer-config"; +import type { HeldItemConfiguration } from "#types/held-item-data-types"; import type { Localizable } from "#types/locales"; +import { + isTrainerItemPool, + isTrainerItemSpecs, + type TrainerItemConfiguration, + type TrainerItemSaveData, +} from "#types/trainer-item-data-types"; +import type { TrainerItemEffectParamMap } from "#types/trainer-item-parameter"; import { AbilityBar } from "#ui/ability-bar"; import { ArenaFlyout } from "#ui/arena-flyout"; import { CandyBar } from "#ui/candy-bar"; diff --git a/src/items/held-item-default-tiers.ts b/src/data/items/held-item-default-tiers.ts similarity index 100% rename from src/items/held-item-default-tiers.ts rename to src/data/items/held-item-default-tiers.ts diff --git a/src/items/reward-defaults-tiers.ts b/src/data/items/reward-defaults-tiers.ts similarity index 100% rename from src/items/reward-defaults-tiers.ts rename to src/data/items/reward-defaults-tiers.ts diff --git a/src/items/trainer-item-default-tiers.ts b/src/data/items/trainer-item-default-tiers.ts similarity index 100% rename from src/items/trainer-item-default-tiers.ts rename to src/data/items/trainer-item-default-tiers.ts diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index dd5a84112dc..7e9a0bd27af 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -15,7 +15,6 @@ import { TrainerSlot } from "#enums/trainer-slot"; import type { MysteryEncounterSpriteConfig } from "#field/mystery-encounter-intro"; import type { Pokemon } from "#field/pokemon"; import { EnemyPokemon } from "#field/pokemon"; -import type { HeldItemConfiguration, HeldItemSpecs, PokemonItemMap } from "#items/held-item-data-types"; import { getPartyBerries } from "#items/item-utility"; import { PokemonMove } from "#moves/pokemon-move"; import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils"; @@ -31,6 +30,7 @@ import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; import { HeldItemRequirement } from "#mystery-encounters/mystery-encounter-requirements"; +import type { HeldItemConfiguration, HeldItemSpecs, PokemonItemMap } from "#types/held-item-data-types"; import { pickWeightedIndex, randInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 4929b040b38..b9a3a77e9a0 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -2,6 +2,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; import { EncounterBattleAnim } from "#data/battle-anims"; import { allAbilities } from "#data/data-lists"; +import { getHeldItemTier } from "#data/items/held-item-default-tiers"; import { CustomPokemonData } from "#data/pokemon-data"; import { AbilityId } from "#enums/ability-id"; import { BattlerIndex } from "#enums/battler-index"; @@ -21,7 +22,6 @@ import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon } from "#field/pokemon"; -import { getHeldItemTier } from "#items/held-item-default-tiers"; import { assignItemsFromConfiguration } from "#items/held-item-pool"; import { PokemonMove } from "#moves/pokemon-move"; import { showEncounterDialogue, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 807c64bef5c..2267a70c9af 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -7,13 +7,13 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { PokemonType } from "#enums/pokemon-type"; import { RewardId } from "#enums/reward-id"; import { SpeciesId } from "#enums/species-id"; -import type { HeldItemConfiguration } from "#items/held-item-data-types"; import type { EnemyPartyConfig, EnemyPokemonConfig } from "#mystery-encounters/encounter-phase-utils"; import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle } from "#mystery-encounters/encounter-phase-utils"; import { getRandomPlayerPokemon, getRandomSpeciesByStarterCost } from "#mystery-encounters/encounter-pokemon-utils"; import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; +import type { HeldItemConfiguration } from "#types/held-item-data-types"; import { isNullOrUndefined, randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index eb37280181b..f5db66faf71 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -8,9 +8,10 @@ import { RewardPoolType } from "#enums/reward-pool-type"; import { RarityTier } from "#enums/reward-tier"; import { TrainerItemId } from "#enums/trainer-item-id"; import type { Pokemon } from "#field/pokemon"; -import type { RewardOption, TrainerItemReward } from "#items/reward"; +import type { RewardOption } from "#items/reward"; import { generatePlayerRewardOptions, generateRewardPoolWeights, getRewardPoolForType } from "#items/reward-pool-utils"; import { isTmReward } from "#items/reward-utils"; +import type { TrainerItemReward } from "#items/rewards/trainer-item-reward"; import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 09256ea4003..6d27cf93e1f 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -3,6 +3,7 @@ import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; import { allHeldItems, allSpecies } from "#data/data-lists"; import { Gender, getGenderSymbol } from "#data/gender"; +import { getHeldItemTier } from "#data/items/held-item-default-tiers"; import { getNatureName } from "#data/nature"; import { getPokeballAtlasKey, getPokeballTintColor } from "#data/pokeball"; import type { PokemonSpecies } from "#data/pokemon-species"; @@ -21,7 +22,6 @@ import { TrainerType } from "#enums/trainer-type"; import { doShinySparkleAnim } from "#field/anims"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; import { EnemyPokemon } from "#field/pokemon"; -import { getHeldItemTier } from "#items/held-item-default-tiers"; import type { RewardOption } from "#items/reward"; import { generatePlayerRewardOptions, generateRewardPoolWeights, getRewardPoolForType } from "#items/reward-pool-utils"; import { isTmReward } from "#items/reward-utils"; diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 14397e3d3bb..422f76d7d58 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -11,7 +11,6 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PokeballType } from "#enums/pokeball"; import { Stat } from "#enums/stat"; import type { EnemyPokemon, Pokemon } from "#field/pokemon"; -import type { HeldItemSpecs } from "#items/held-item-data-types"; import { getPartyBerries } from "#items/item-utility"; import { PokemonMove } from "#moves/pokemon-move"; import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils"; @@ -34,6 +33,7 @@ import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encou import { HeldItemRequirement, MoveRequirement } from "#mystery-encounters/mystery-encounter-requirements"; import { CHARMING_MOVES } from "#mystery-encounters/requirement-groups"; import { PokemonData } from "#system/pokemon-data"; +import type { HeldItemSpecs } from "#types/held-item-data-types"; import { isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common"; /** the i18n namespace for the encounter */ diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index e0d8845aabe..6efb57bac4a 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -19,7 +19,6 @@ import { StatusEffect } from "#enums/status-effect"; import { TrainerItemEffect } from "#enums/trainer-item-effect"; import { TrainerType } from "#enums/trainer-type"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; -import type { HeldItemConfiguration } from "#items/held-item-data-types"; import { PokemonMove } from "#moves/pokemon-move"; import { showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig, EnemyPokemonConfig } from "#mystery-encounters/encounter-phase-utils"; @@ -40,6 +39,7 @@ import { achvs } from "#system/achv"; import { PokemonData } from "#system/pokemon-data"; import { trainerConfigs } from "#trainers/trainer-config"; import { TrainerPartyTemplate } from "#trainers/trainer-party-template"; +import type { HeldItemConfiguration } from "#types/held-item-data-types"; import { isNullOrUndefined, NumberHolder, randSeedInt, randSeedShuffle } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 6c606bc4a4a..49a7bd69aa5 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -20,7 +20,6 @@ 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 type { HeldItemConfiguration } from "#items/held-item-data-types"; import { PokemonMove } from "#moves/pokemon-move"; import { getIsInitialized, initI18n } from "#plugins/i18n"; import type { EvilTeam } from "#trainers/evil-admin-trainer-pools"; @@ -33,6 +32,7 @@ import { TrainerPartyTemplate, trainerPartyTemplates, } from "#trainers/trainer-party-template"; +import type { HeldItemConfiguration } from "#types/held-item-data-types"; import type { SilentReward } from "#types/rewards"; import type { GenAIFunc, diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index f26770d7ffe..67e601925d7 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -112,7 +112,6 @@ import { UiMode } from "#enums/ui-mode"; import { WeatherType } from "#enums/weather-type"; import { doShinySparkleAnim } from "#field/anims"; import { applyHeldItems } from "#items/all-held-items"; -import type { HeldItemConfiguration } from "#items/held-item-data-types"; import { HeldItemManager } from "#items/held-item-manager"; import { assignItemsFromConfiguration } from "#items/held-item-pool"; import { applyMoveAttrs } from "#moves/apply-attrs"; @@ -129,6 +128,7 @@ import { RibbonData } from "#system/ribbons/ribbon-data"; import { awardRibbonsToSpeciesLine } from "#system/ribbons/ribbon-methods"; import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#types/ability-types"; import type { DamageCalculationResult, DamageResult } from "#types/damage-result"; +import type { HeldItemConfiguration } from "#types/held-item-data-types"; import type { IllusionData } from "#types/illusion-data"; import type { TurnMove } from "#types/turn-move"; import { BattleInfo } from "#ui/battle-info"; diff --git a/src/field/trainer.ts b/src/field/trainer.ts index cd78fba024d..1b0ea0e5771 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -12,11 +12,11 @@ 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 type { TrainerItemConfiguration } from "#items/trainer-item-data-types"; import { getIsInitialized, initI18n } from "#plugins/i18n"; import type { TrainerConfig } from "#trainers/trainer-config"; import { trainerConfigs } from "#trainers/trainer-config"; import { TrainerPartyCompoundTemplate, type TrainerPartyTemplate } from "#trainers/trainer-party-template"; +import type { TrainerItemConfiguration } from "#types/trainer-item-data-types"; import { randSeedInt, randSeedItem } from "#utils/common"; import { getRandomLocaleEntry } from "#utils/i18n"; import { getPokemonSpecies } from "#utils/pokemon-utils"; diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index c0b6f5c5195..716d7d3d4a4 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -33,8 +33,8 @@ import { EvolutionStatBoostHeldItem, SpeciesStatBoostHeldItem } from "#items/sta import { SurviveChanceHeldItem } from "#items/survive-chance"; import { TurnEndHealHeldItem } from "#items/turn-end-heal"; import { TurnEndStatusHeldItem } from "#items/turn-end-status"; +import type { HeldItemEffectParamMap } from "#types/held-item-parameter"; import { getEnumValues } from "#utils/enums"; -import type { HeldItemEffectParamMap } from "./held-item-parameter"; export function initHeldItems() { for (const berry of getEnumValues(BerryType)) { diff --git a/src/items/all-rewards.ts b/src/items/all-rewards.ts index cfd208a8432..586b819c91c 100644 --- a/src/items/all-rewards.ts +++ b/src/items/all-rewards.ts @@ -3,40 +3,33 @@ import { RewardId } from "#enums/reward-id"; import { RarityTier } from "#enums/reward-tier"; import { TrainerItemId } from "#enums/trainer-item-id"; import { VoucherType } from "#system/voucher"; +import { EmptyReward, type Reward, type RewardGenerator } from "./reward"; +import { EvolutionItemRewardGenerator } from "./rewards/evolution-item"; +import { FormChangeItemRewardGenerator } from "./rewards/form-change"; +import { FusePokemonReward } from "./rewards/fuse"; import { - AddMoneyReward, - AddPokeballReward, - AddVoucherReward, - AllPokemonFullReviveReward, - AllPokemonLevelIncrementReward, AttackTypeBoosterRewardGenerator, BaseStatBoosterRewardGenerator, BerryRewardGenerator, - EvolutionItemRewardGenerator, - FormChangeItemRewardGenerator, - FusePokemonReward, - LapsingTrainerItemReward, - MintRewardGenerator, - NoneReward, - PokemonAllMovePpRestoreReward, - PokemonHpRestoreReward, - PokemonLevelIncrementReward, - PokemonPpRestoreReward, - PokemonPpUpReward, - PokemonReviveReward, - PokemonStatusHealReward, - RememberMoveReward, - type Reward, - type RewardGenerator, - SpeciesStatBoosterRewardGenerator, - TempStatStageBoosterRewardGenerator, - TeraTypeRewardGenerator, - TmRewardGenerator, -} from "./reward"; +} from "./rewards/held-item-reward"; +import { AllPokemonLevelIncrementReward, PokemonLevelIncrementReward } from "./rewards/level-increment"; +import { AddMoneyReward } from "./rewards/money"; +import { MintRewardGenerator } from "./rewards/nature-change"; +import { AddPokeballReward } from "./rewards/pokeball"; +import { PokemonAllMovePpRestoreReward, PokemonPpRestoreReward } from "./rewards/pp-restore"; +import { PokemonPpUpReward } from "./rewards/pp-up"; +import { RememberMoveReward } from "./rewards/remember-move"; +import { AllPokemonFullReviveReward, PokemonHpRestoreReward, PokemonReviveReward } from "./rewards/restore"; +import { SpeciesStatBoosterRewardGenerator } from "./rewards/species-stat-booster"; +import { PokemonStatusHealReward } from "./rewards/status-heal"; +import { TeraTypeRewardGenerator } from "./rewards/tera-type"; +import { TmRewardGenerator } from "./rewards/tm"; +import { LapsingTrainerItemReward, TempStatStageBoosterRewardGenerator } from "./rewards/trainer-item-reward"; +import { AddVoucherReward } from "./rewards/voucher"; // TODO: Move to `reward-utils.ts` and un-exportt export const allRewards = { - [RewardId.NONE]: new NoneReward(), + [RewardId.NONE]: new EmptyReward(), // Pokeball rewards [RewardId.POKEBALL]: new AddPokeballReward("pb", PokeballType.POKEBALL, 5, RewardId.POKEBALL), diff --git a/src/items/all-trainer-items.ts b/src/items/all-trainer-items.ts index 23fe6a4b089..4bf4b8815de 100644 --- a/src/items/all-trainer-items.ts +++ b/src/items/all-trainer-items.ts @@ -5,14 +5,6 @@ import type { TrainerItemEffect } from "#enums/trainer-item-effect"; import { TrainerItemId } from "#enums/trainer-item-id"; import { CriticalCatchChanceBoosterTrainerItem, - DoubleBattleChanceBoosterTrainerItem, - EnemyAttackStatusEffectChanceTrainerItem, - EnemyDamageBoosterTrainerItem, - EnemyDamageReducerTrainerItem, - EnemyEndureChanceTrainerItem, - EnemyFusionChanceTrainerItem, - EnemyStatusEffectHealChanceTrainerItem, - EnemyTurnHealTrainerItem, ExpBoosterTrainerItem, ExtraRewardTrainerItem, HealingBoosterTrainerItem, @@ -22,14 +14,26 @@ import { MoneyMultiplierTrainerItem, PreserveBerryTrainerItem, ShinyRateBoosterTrainerItem, + TrainerItem, +} from "#items/trainer-item"; +import type { TrainerItemEffectParamMap } from "#types/trainer-item-parameter"; +import type { TrainerItemManager } from "./trainer-item-manager"; +import { + EnemyAttackStatusEffectChanceTrainerItem, + EnemyDamageBoosterTrainerItem, + EnemyDamageReducerTrainerItem, + EnemyEndureChanceTrainerItem, + EnemyFusionChanceTrainerItem, + EnemyStatusEffectHealChanceTrainerItem, + EnemyTurnHealTrainerItem, +} from "./trainer-items/enemy-tokens"; +import { DoubleBattleChanceBoosterTrainerItem } from "./trainer-items/lure"; +import { TempAccuracyBoosterTrainerItem, TempCritBoosterTrainerItem, TempStatStageBoosterTrainerItem, - TrainerItem, tempStatToTrainerItem, -} from "#items/trainer-item"; -import type { TrainerItemManager } from "./trainer-item-manager"; -import type { TrainerItemEffectParamMap } from "./trainer-item-parameter"; +} from "./trainer-items/x-items"; export function initTrainerItems() { allTrainerItems[TrainerItemId.MAP] = new TrainerItem(TrainerItemId.MAP, 1); diff --git a/src/items/held-item-manager.ts b/src/items/held-item-manager.ts index 43a0e5725a4..1b191e4505d 100644 --- a/src/items/held-item-manager.ts +++ b/src/items/held-item-manager.ts @@ -13,7 +13,7 @@ import { type HeldItemSaveData, type HeldItemSpecs, isHeldItemSpecs, -} from "#items/held-item-data-types"; +} from "#types/held-item-data-types"; export class HeldItemManager { // TODO: There should be a way of making these private... diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index e9eca41fdd1..626b1ee0e4c 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -7,7 +7,7 @@ import { RarityTier } from "#enums/reward-tier"; import { PERMANENT_STATS } from "#enums/stat"; import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; import { attackTypeToHeldItem } from "#items/attack-type-booster"; -import { permanentStatToHeldItem } from "#items/base-stat-booster"; +import { permanentStatToHeldItem } from "#items/base-stat-multiply"; import { berryTypeToHeldItem } from "#items/berry"; import { type HeldItemConfiguration, @@ -19,7 +19,7 @@ import { isHeldItemCategoryEntry, isHeldItemPool, isHeldItemSpecs, -} from "#items/held-item-data-types"; +} from "#types/held-item-data-types"; import { coerceArray, isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common"; import { getEnumValues } from "#utils/enums"; diff --git a/src/items/held-item.ts b/src/items/held-item.ts index ebd70a6d580..5af66a180c1 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -3,9 +3,9 @@ import { globalScene } from "#app/global-scene"; import type { HeldItemEffect } from "#enums/held-item-effect"; import { type HeldItemId, HeldItemNames } from "#enums/held-item-id"; import type { Pokemon } from "#field/pokemon"; +import type { HeldItemEffectParamMap } from "#types/held-item-parameter"; import type { UniqueArray } from "#utils/common"; import i18next from "i18next"; -import type { HeldItemEffectParamMap } from "./held-item-parameter"; export abstract class HeldItemBase { public type: HeldItemId; diff --git a/src/items/held-items/accuracy-booster.ts b/src/items/held-items/accuracy-booster.ts index f70cbfd066c..6024fcb8a68 100644 --- a/src/items/held-items/accuracy-booster.ts +++ b/src/items/held-items/accuracy-booster.ts @@ -1,7 +1,7 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import { HeldItem } from "#items/held-item"; -import type { AccuracyBoostParams } from "#items/held-item-parameter"; +import type { AccuracyBoostParams } from "#types/held-item-parameter"; /** * @sealed diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index df6c321184f..076cb858d5e 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -2,7 +2,7 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId, HeldItemNames } from "#enums/held-item-id"; import { PokemonType } from "#enums/pokemon-type"; import { HeldItem } from "#items/held-item"; -import type { AttackTypeBoostParams } from "#items/held-item-parameter"; +import type { AttackTypeBoostParams } from "#types/held-item-parameter"; import i18next from "i18next"; interface AttackTypeToHeldItemMap { diff --git a/src/items/held-items/base-stat-add.ts b/src/items/held-items/base-stat-add.ts index 5e1c6487a34..6512d915f58 100644 --- a/src/items/held-items/base-stat-add.ts +++ b/src/items/held-items/base-stat-add.ts @@ -3,7 +3,7 @@ import type { HeldItemId } from "#enums/held-item-id"; import { Stat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; import { HeldItem } from "#items/held-item"; -import type { BaseStatParams } from "#items/held-item-parameter"; +import type { BaseStatParams } from "#types/held-item-parameter"; import i18next from "i18next"; /** diff --git a/src/items/held-items/base-stat-multiply.ts b/src/items/held-items/base-stat-multiply.ts index 8feecc3632d..206abaebc71 100644 --- a/src/items/held-items/base-stat-multiply.ts +++ b/src/items/held-items/base-stat-multiply.ts @@ -2,7 +2,7 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import { getStatKey, type PermanentStat, Stat } from "#enums/stat"; import { HeldItem } from "#items/held-item"; -import type { BaseStatParams } from "#items/held-item-parameter"; +import type { BaseStatParams } from "#types/held-item-parameter"; import i18next from "i18next"; type PermanentStatToHeldItemMap = { diff --git a/src/items/held-items/baton.ts b/src/items/held-items/baton.ts index 95e215eb1a6..821ba1f5827 100644 --- a/src/items/held-items/baton.ts +++ b/src/items/held-items/baton.ts @@ -1,6 +1,6 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItem } from "#items/held-item"; -import type { BatonParams } from "#items/held-item-parameter"; +import type { BatonParams } from "#types/held-item-parameter"; export class BatonHeldItem extends HeldItem<[typeof HeldItemEffect.BATON]> { public readonly effects = [HeldItemEffect.BATON] as const; @@ -9,7 +9,7 @@ export class BatonHeldItem extends HeldItem<[typeof HeldItemEffect.BATON]> { * Applies {@linkcode SwitchEffectTransferModifier} * @returns always `true` */ - apply(_effect: typeof HeldItemEffect.BATON, { pokemon }: BatonParams): true { + apply(_effect: typeof HeldItemEffect.BATON, _params: BatonParams): true { return true; } } diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index 52d46a71725..10c2daf1344 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -6,7 +6,7 @@ import { HeldItemId } from "#enums/held-item-id"; import { TrainerItemEffect } from "#enums/trainer-item-effect"; import { BerryUsedEvent } from "#events/battle-scene"; import { ConsumableHeldItem } from "#items/held-item"; -import type { BerryParams } from "#items/held-item-parameter"; +import type { BerryParams } from "#types/held-item-parameter"; import type { ObjectValues } from "#types/type-helpers"; import { BooleanHolder } from "#utils/common"; diff --git a/src/items/held-items/bypass-speed-chance.ts b/src/items/held-items/bypass-speed-chance.ts index 647f5f571f5..b096f793d06 100644 --- a/src/items/held-items/bypass-speed-chance.ts +++ b/src/items/held-items/bypass-speed-chance.ts @@ -3,7 +3,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { Command } from "#enums/command"; import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItem } from "#items/held-item"; -import type { BypassSpeedChanceParams } from "#items/held-item-parameter"; +import type { BypassSpeedChanceParams } from "#types/held-item-parameter"; import i18next from "i18next"; /** diff --git a/src/items/held-items/crit-booster.ts b/src/items/held-items/crit-booster.ts index 449f9eae487..01881f6f3d4 100644 --- a/src/items/held-items/crit-booster.ts +++ b/src/items/held-items/crit-booster.ts @@ -2,7 +2,7 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import { HeldItem } from "#items/held-item"; -import type { CritBoostParams } from "#items/held-item-parameter"; +import type { CritBoostParams } from "#types/held-item-parameter"; /** * Modifier used for held items that apply critical-hit stage boost(s). diff --git a/src/items/held-items/damage-money-reward.ts b/src/items/held-items/damage-money-reward.ts index 602d73e43f0..0c478da481e 100644 --- a/src/items/held-items/damage-money-reward.ts +++ b/src/items/held-items/damage-money-reward.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { HeldItemEffect } from "#enums/held-item-effect"; import { TrainerItemEffect } from "#enums/trainer-item-effect"; import { HeldItem } from "#items/held-item"; -import type { DamageMoneyRewardParams } from "#items/held-item-parameter"; +import type { DamageMoneyRewardParams } from "#types/held-item-parameter"; import { NumberHolder } from "#utils/common"; export class DamageMoneyRewardHeldItem extends HeldItem<[typeof HeldItemEffect.DAMAGE_MONEY_REWARD]> { diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts index 92dbe18db59..c80bfaca57d 100644 --- a/src/items/held-items/exp-booster.ts +++ b/src/items/held-items/exp-booster.ts @@ -1,7 +1,7 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import { HeldItem } from "#items/held-item"; -import type { ExpBoostParams } from "#items/held-item-parameter"; +import type { ExpBoostParams } from "#types/held-item-parameter"; import i18next from "i18next"; export class ExpBoosterHeldItem extends HeldItem<[typeof HeldItemEffect.EXP_BOOSTER]> { diff --git a/src/items/held-items/field-effect.ts b/src/items/held-items/field-effect.ts index 3b558f1de97..be921893021 100644 --- a/src/items/held-items/field-effect.ts +++ b/src/items/held-items/field-effect.ts @@ -1,6 +1,6 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItem } from "#items/held-item"; -import type { FieldEffectParams } from "#items/held-item-parameter"; +import type { FieldEffectParams } from "#types/held-item-parameter"; /** * Modifier used for held items, namely Mystical Rock, that extend the diff --git a/src/items/held-items/flinch-chance.ts b/src/items/held-items/flinch-chance.ts index f1a4139c4f9..bbbdae2e575 100644 --- a/src/items/held-items/flinch-chance.ts +++ b/src/items/held-items/flinch-chance.ts @@ -1,7 +1,7 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import { HeldItem } from "#items/held-item"; -import type { FlinchChanceParams } from "#items/held-item-parameter"; +import type { FlinchChanceParams } from "#types/held-item-parameter"; /** * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a diff --git a/src/items/held-items/friendship-booster.ts b/src/items/held-items/friendship-booster.ts index 7172d5f30d8..03971758d40 100644 --- a/src/items/held-items/friendship-booster.ts +++ b/src/items/held-items/friendship-booster.ts @@ -1,6 +1,6 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItem } from "#items/held-item"; -import type { FriendshipBoostParams } from "#items/held-item-parameter"; +import type { FriendshipBoostParams } from "#types/held-item-parameter"; import i18next from "i18next"; export class FriendshipBoosterHeldItem extends HeldItem<[typeof HeldItemEffect.FRIENDSHIP_BOOSTER]> { diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts index 3dadd7e51de..907740df4ac 100644 --- a/src/items/held-items/hit-heal.ts +++ b/src/items/held-items/hit-heal.ts @@ -2,8 +2,8 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItem } from "#items/held-item"; -import type { HitHealParams } from "#items/held-item-parameter"; import { PokemonHealPhase } from "#phases/pokemon-heal-phase"; +import type { HitHealParams } from "#types/held-item-parameter"; import { toDmgValue } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts index bc03662cc52..6b61ca2fc25 100644 --- a/src/items/held-items/instant-revive.ts +++ b/src/items/held-items/instant-revive.ts @@ -3,8 +3,8 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { HeldItemEffect } from "#enums/held-item-effect"; import { ConsumableHeldItem } from "#items/held-item"; -import type { InstantReviveParams } from "#items/held-item-parameter"; import { PokemonHealPhase } from "#phases/pokemon-heal-phase"; +import type { InstantReviveParams } from "#types/held-item-parameter"; import { toDmgValue } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index d92934297f7..fd0d74f40cd 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -5,7 +5,7 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import { Pokemon } from "#field/pokemon"; import { type EffectTuple, HeldItem } from "#items/held-item"; -import type { ItemStealParams } from "#items/held-item-parameter"; +import type { ItemStealParams } from "#types/held-item-parameter"; import { coerceArray, randSeedFloat } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/macho-brace.ts b/src/items/held-items/macho-brace.ts index f7b3422a404..a4007ef295a 100644 --- a/src/items/held-items/macho-brace.ts +++ b/src/items/held-items/macho-brace.ts @@ -1,7 +1,7 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import { Stat } from "#enums/stat"; import { HeldItem } from "#items/held-item"; -import type { StatBoostParams } from "#items/held-item-parameter"; +import type { StatBoostParams } from "#types/held-item-parameter"; import i18next from "i18next"; /** diff --git a/src/items/held-items/multi-hit.ts b/src/items/held-items/multi-hit.ts index e5d9705a736..7ffcfc7f684 100644 --- a/src/items/held-items/multi-hit.ts +++ b/src/items/held-items/multi-hit.ts @@ -2,7 +2,7 @@ import { allMoves } from "#data/data-lists"; import { HeldItemEffect } from "#enums/held-item-effect"; import type { Pokemon } from "#field/pokemon"; import { HeldItem } from "#items/held-item"; -import type { HeldItemEffectParamMap, MultiHitCountParams, MultiHitDamageParams } from "#items/held-item-parameter"; +import type { HeldItemEffectParamMap, MultiHitCountParams, MultiHitDamageParams } from "#types/held-item-parameter"; import type { NumberHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/nature-weight-booster.ts b/src/items/held-items/nature-weight-booster.ts index 046f0b42185..9ff6d7bc5cb 100644 --- a/src/items/held-items/nature-weight-booster.ts +++ b/src/items/held-items/nature-weight-booster.ts @@ -1,6 +1,6 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItem } from "#items/held-item"; -import type { NatureWeightBoostParams } from "#items/held-item-parameter"; +import type { NatureWeightBoostParams } from "#types/held-item-parameter"; export class NatureWeightBoosterHeldItem extends HeldItem<[typeof HeldItemEffect.NATURE_WEIGHT_BOOSTER]> { public readonly effects = [HeldItemEffect.NATURE_WEIGHT_BOOSTER] as const; diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index 4bb04379395..ba186dde334 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -3,7 +3,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { HeldItemEffect } from "#enums/held-item-effect"; import { BATTLE_STATS } from "#enums/stat"; import { ConsumableHeldItem } from "#items/held-item"; -import type { ResetNegativeStatStageParams } from "#items/held-item-parameter"; +import type { ResetNegativeStatStageParams } from "#types/held-item-parameter"; import i18next from "i18next"; /** diff --git a/src/items/held-items/stat-boost.ts b/src/items/held-items/stat-boost.ts index 6c6a34da527..ba63ebcf99b 100644 --- a/src/items/held-items/stat-boost.ts +++ b/src/items/held-items/stat-boost.ts @@ -5,7 +5,7 @@ import type { SpeciesId } from "#enums/species-id"; import type { Stat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; import { HeldItem } from "#items/held-item"; -import type { StatBoostParams } from "#items/held-item-parameter"; +import type { StatBoostParams } from "#types/held-item-parameter"; /** * Modifier used for held items that Applies {@linkcode Stat} boost(s) diff --git a/src/items/held-items/survive-chance.ts b/src/items/held-items/survive-chance.ts index 3049e828490..bfd17e3704e 100644 --- a/src/items/held-items/survive-chance.ts +++ b/src/items/held-items/survive-chance.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItem } from "#items/held-item"; -import type { SurviveChanceParams } from "#items/held-item-parameter"; +import type { SurviveChanceParams } from "#types/held-item-parameter"; import i18next from "i18next"; /** diff --git a/src/items/held-items/turn-end-heal.ts b/src/items/held-items/turn-end-heal.ts index 11697f9c705..d239e77ced5 100644 --- a/src/items/held-items/turn-end-heal.ts +++ b/src/items/held-items/turn-end-heal.ts @@ -2,8 +2,8 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItem } from "#items/held-item"; -import type { TurnEndHealParams } from "#items/held-item-parameter"; import { PokemonHealPhase } from "#phases/pokemon-heal-phase"; +import type { TurnEndHealParams } from "#types/held-item-parameter"; import { toDmgValue } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/turn-end-status.ts b/src/items/held-items/turn-end-status.ts index 7b39b52f32b..dac9b457755 100644 --- a/src/items/held-items/turn-end-status.ts +++ b/src/items/held-items/turn-end-status.ts @@ -2,7 +2,7 @@ import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import type { StatusEffect } from "#enums/status-effect"; import { HeldItem } from "#items/held-item"; -import type { TurnEndStatusParams } from "#items/held-item-parameter"; +import type { TurnEndStatusParams } from "#types/held-item-parameter"; /** * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a diff --git a/src/items/item-overrides.ts b/src/items/item-overrides.ts index 0c71be28b8f..d3234b470e0 100644 --- a/src/items/item-overrides.ts +++ b/src/items/item-overrides.ts @@ -1,9 +1,9 @@ import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; import type { Pokemon } from "#field/pokemon"; -import type { HeldItemConfiguration } from "#items/held-item-data-types"; import { assignItemsFromConfiguration } from "#items/held-item-pool"; -import type { TrainerItemConfiguration } from "#items/trainer-item-data-types"; +import type { HeldItemConfiguration } from "#types/held-item-data-types"; +import type { TrainerItemConfiguration } from "#types/trainer-item-data-types"; /** * Uses either `MODIFIER_OVERRIDE` in overrides.ts to set {@linkcode PersistentModifier}s for either: diff --git a/src/items/item-utility.ts b/src/items/item-utility.ts index 72b435ec15c..7cfb093b901 100644 --- a/src/items/item-utility.ts +++ b/src/items/item-utility.ts @@ -3,7 +3,7 @@ import { allHeldItems, allTrainerItems } from "#data/data-lists"; import { HeldItemCategoryId, type HeldItemId, isItemInCategory } from "#enums/held-item-id"; import type { TrainerItemId } from "#enums/trainer-item-id"; import type { Pokemon } from "#field/pokemon"; -import type { PokemonItemMap } from "./held-item-data-types"; +import type { PokemonItemMap } from "#types/held-item-data-types"; export const trainerItemSortFunc = (a: TrainerItemId, b: TrainerItemId): number => { const itemNameMatch = allTrainerItems[a].name.localeCompare(allTrainerItems[b].name); diff --git a/src/items/reward-utils.ts b/src/items/reward-utils.ts index 28c86aa3b82..82453507174 100644 --- a/src/items/reward-utils.ts +++ b/src/items/reward-utils.ts @@ -1,23 +1,17 @@ +import { heldItemRarities } from "#data/items/held-item-default-tiers"; +import { rewardRarities } from "#data/items/reward-defaults-tiers"; +import { trainerItemRarities } from "#data/items/trainer-item-default-tiers"; 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 { allRewards } from "#items/all-rewards"; import type { RewardPoolId, RewardSpecs } from "#types/rewards"; -import { heldItemRarities } from "./held-item-default-tiers"; -import { - HeldItemReward, - NoneReward, - type PokemonMoveReward, - type RememberMoveReward, - type Reward, - RewardGenerator, - RewardOption, - type TmReward, - TrainerItemReward, -} from "./reward"; -import { rewardRarities } from "./reward-defaults-tiers"; -import { trainerItemRarities } from "./trainer-item-default-tiers"; +import { EmptyReward, type PokemonMoveReward, type Reward, RewardGenerator, RewardOption } from "./reward"; +import { HeldItemReward } from "./rewards/held-item-reward"; +import type { RememberMoveReward } from "./rewards/remember-move"; +import type { TmReward } from "./rewards/tm"; +import { TrainerItemReward } from "./rewards/trainer-item-reward"; export function isTmReward(reward: Reward): reward is TmReward { return getRewardCategory(reward.id) === RewardCategoryId.TM; @@ -66,7 +60,7 @@ export function generateRewardOptionFromId( const tempReward = allRewards[id] as Reward | RewardGenerator; let reward = tempReward instanceof RewardGenerator ? tempReward.generateReward(pregenArgs) : tempReward; if (!reward) { - reward = new NoneReward(); + reward = new EmptyReward(); } const tier = tierOverride ?? rewardRarities[id]; return new RewardOption(reward, upgradeCount, tier, cost); diff --git a/src/items/reward.ts b/src/items/reward.ts index 0f549217524..1753929e03a 100644 --- a/src/items/reward.ts +++ b/src/items/reward.ts @@ -1,47 +1,8 @@ -import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; -import { globalScene } from "#app/global-scene"; -import { getPokemonNameWithAffix } from "#app/messages"; -import { EvolutionItem, FusionSpeciesFormEvolution, pokemonEvolutions } from "#balance/pokemon-evolutions"; -import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#balance/starters"; -import { tmPoolTiers, tmSpecies } from "#balance/tms"; -import { allHeldItems, allMoves, allTrainerItems } from "#data/data-lists"; -import { getLevelTotalExp } from "#data/exp"; -import { SpeciesFormChangeItemTrigger } from "#data/form-change-triggers"; -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 type { BerryType } from "#enums/berry-type"; -import { FormChangeItemId } from "#enums/form-change-item-id"; -import { HeldItemId } from "#enums/held-item-id"; -import { LearnMoveType } from "#enums/learn-move-type"; -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 type { RewardId } from "#enums/reward-id"; 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"; -import { Stat, TEMP_BATTLE_STATS } from "#enums/stat"; -import { TrainerItemEffect } from "#enums/trainer-item-effect"; -import { TrainerItemId } from "#enums/trainer-item-id"; -import type { PlayerPokemon, Pokemon } from "#field/pokemon"; -import { attackTypeToHeldItem } from "#items/attack-type-booster"; -import { permanentStatToHeldItem, statBoostItems } from "#items/base-stat-booster"; -import { berryTypeToHeldItem } from "#items/berry"; -import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#items/held-item-pool"; -import type { SpeciesStatBoosterItemId, SpeciesStatBoostHeldItem } from "#items/stat-booster"; -import { tempStatToTrainerItem } from "#items/trainer-item"; -import type { PokemonMove } from "#moves/pokemon-move"; -import { getVoucherTypeIcon, getVoucherTypeName, type VoucherType } from "#system/voucher"; +import type { PlayerPokemon } from "#field/pokemon"; 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, toDmgValue } from "#utils/common"; -import { getEnumKeys, getEnumValues } from "#utils/enums"; -import { toCamelCase } from "#utils/strings"; import i18next from "i18next"; /** @@ -153,140 +114,6 @@ export abstract class RewardGenerator { abstract generateReward(pregenArgs?: unknown): Reward | null; } -export class AddPokeballReward extends Reward { - private pokeballType: PokeballType; - private count: number; - - constructor(iconImage: string, pokeballType: PokeballType, count: number, id: RewardId) { - super("", iconImage, "pb", "se/pb_bounce_1"); - this.pokeballType = pokeballType; - this.count = count; - this.id = id; - } - - get name(): string { - return i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", { - modifierCount: this.count, - pokeballName: getPokeballName(this.pokeballType), - }); - } - - get description(): string { - return i18next.t("modifierType:ModifierType.AddPokeballModifierType.description", { - modifierCount: this.count, - pokeballName: getPokeballName(this.pokeballType), - catchRate: - getPokeballCatchMultiplier(this.pokeballType) > -1 - ? `${getPokeballCatchMultiplier(this.pokeballType)}x` - : "100%", - pokeballAmount: `${globalScene.pokeballCounts[this.pokeballType]}`, - }); - } - - /** - * Applies {@linkcode AddPokeballReward} - * @returns always `true` - */ - apply(): boolean { - const pokeballCounts = globalScene.pokeballCounts; - pokeballCounts[this.pokeballType] = Math.min( - pokeballCounts[this.pokeballType] + this.count, - MAX_PER_TYPE_POKEBALLS, - ); - - return true; - } -} - -export class AddVoucherReward extends Reward { - private voucherType: VoucherType; - private count: number; - - constructor(voucherType: VoucherType, count: number, id: RewardId) { - super("", getVoucherTypeIcon(voucherType), "voucher"); - this.count = count; - this.voucherType = voucherType; - this.id = id; - } - - get name(): string { - return i18next.t("modifierType:ModifierType.AddVoucherConsumableType.name", { - modifierCount: this.count, - voucherTypeName: getVoucherTypeName(this.voucherType), - }); - } - - get description(): string { - return i18next.t("modifierType:ModifierType.AddVoucherConsumableType.description", { - modifierCount: this.count, - voucherTypeName: getVoucherTypeName(this.voucherType), - }); - } - - /** - * Applies {@linkcode AddVoucherReward} - * @param battleScene {@linkcode BattleScene} - * @returns always `true` - */ - apply(): boolean { - const voucherCounts = globalScene.gameData.voucherCounts; - voucherCounts[this.voucherType] += this.count; - - return true; - } -} - -export class AddMoneyReward extends Reward { - private moneyMultiplier: number; - private moneyMultiplierDescriptorKey: string; - - constructor( - localeKey: string, - iconImage: string, - moneyMultiplier: number, - moneyMultiplierDescriptorKey: string, - id: RewardId, - ) { - super(localeKey, iconImage, "money", "se/buy"); - - this.moneyMultiplier = moneyMultiplier; - this.moneyMultiplierDescriptorKey = moneyMultiplierDescriptorKey; - this.id = id; - } - - get description(): string { - const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); - const formattedMoney = formatMoney(globalScene.moneyFormat, moneyAmount.value); - - return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", { - moneyMultiplier: i18next.t(this.moneyMultiplierDescriptorKey as any), - moneyAmount: formattedMoney, - }); - } - - /** - * Applies {@linkcode AddMoneyReward} - * @returns always `true` - */ - apply(): boolean { - const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - - globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); - - globalScene.addMoney(moneyAmount.value); - - for (const p of globalScene.getPlayerParty()) { - if (p.hasSpecies(SpeciesId.GIMMIGHOUL)) { - const factor = Math.min(Math.floor(this.moneyMultiplier), 3); - p.heldItemManager.add(HeldItemId.GIMMIGHOUL_EVO_TRACKER, factor); - } - } - - return true; - } -} - /** Rewards that are applied to individual Pokemon. */ export abstract class PokemonReward extends Reward { public selectFilter: PokemonSelectFilter | undefined; @@ -305,329 +132,6 @@ export abstract class PokemonReward extends Reward { abstract override apply(_params: PokemonRewardParams): void; } -export interface PokemonRewardParams { - pokemon: PlayerPokemon; -} - -export interface PokemonMoveRewardParams { - pokemon: PlayerPokemon; - moveIndex: number; -} - -export interface PokemonMoveRecallRewardParams { - pokemon: PlayerPokemon; - moveIndex: number; - cost?: number; -} - -export interface PokemonFusionRewardParams { - pokemon: PlayerPokemon; - pokemon2: PlayerPokemon; -} - -export class HeldItemReward extends PokemonReward { - public itemId: HeldItemId; - constructor(itemId: HeldItemId, group?: string, soundName?: string) { - super( - "", - "", - (pokemon: PlayerPokemon) => { - const hasItem = pokemon.heldItemManager.hasItem(this.itemId); - const maxStackCount = allHeldItems[this.itemId].getMaxStackCount(); - if (!maxStackCount) { - return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.inoperable", { - pokemonName: getPokemonNameWithAffix(pokemon), - }); - } - if (hasItem && pokemon.heldItemManager.getStack(this.itemId) === maxStackCount) { - return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.tooMany", { - pokemonName: getPokemonNameWithAffix(pokemon), - }); - } - return null; - }, - group, - soundName, - ); - this.itemId = itemId; - } - - get name(): string { - return allHeldItems[this.itemId].name; - } - - get description(): string { - return allHeldItems[this.itemId].description; - } - - get iconName(): string { - return allHeldItems[this.itemId].iconName; - } - - apply({ pokemon }: PokemonRewardParams): boolean { - return pokemon.heldItemManager.add(this.itemId); - } -} - -export class TrainerItemReward extends Reward { - // TODO: This should not be public - public itemId: TrainerItemId; - constructor(itemId: TrainerItemId, group?: string, soundName?: string) { - super("", "", group, soundName); - this.itemId = itemId; - } - - get name(): string { - return allTrainerItems[this.itemId].name; - } - - get description(): string { - return allTrainerItems[this.itemId].description; - } - - get iconName(): string { - return allTrainerItems[this.itemId].iconName; - } - - apply(): boolean { - return globalScene.trainerItems.add(this.itemId); - } -} - -export class LapsingTrainerItemReward extends TrainerItemReward { - apply(): boolean { - return globalScene.trainerItems.add(this.itemId, allTrainerItems[this.itemId].getMaxStackCount()); - } -} - -export class ChangeTeraTypeReward extends PokemonReward { - private teraType: PokemonType; - - constructor(teraType: PokemonType) { - super( - "", - `${PokemonType[teraType].toLowerCase()}_tera_shard`, - (pokemon: PlayerPokemon) => { - if ( - [pokemon.species.speciesId, pokemon.fusionSpecies?.speciesId].filter( - s => s === SpeciesId.TERAPAGOS || s === SpeciesId.OGERPON || s === SpeciesId.SHEDINJA, - ).length > 0 - ) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }, - "tera_shard", - ); - - this.teraType = teraType; - } - - get name(): string { - return i18next.t("modifierType:ModifierType.ChangeTeraTypeModifierType.name", { - teraType: i18next.t(`pokemonInfo:type.${toCamelCase(PokemonType[this.teraType])}`), - }); - } - - get description(): string { - return i18next.t("modifierType:ModifierType.ChangeTeraTypeModifierType.description", { - teraType: i18next.t(`pokemonInfo:type.${toCamelCase(PokemonType[this.teraType])}`), - }); - } - - /** - * Checks if {@linkcode TerrastalizeConsumable} should be applied - * @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item - * @returns `true` if the {@linkcode TerrastalizeConsumable} should be applied - */ - shouldApply({ pokemon }: PokemonRewardParams): boolean { - return ( - pokemon.teraType !== this.teraType && - ![SpeciesId.SHEDINJA, SpeciesId.OGERPON, SpeciesId.TERAPAGOS].some(s => pokemon.hasSpecies(s)) - ); - } - - /** - * Applies {@linkcode TerrastalizeConsumable} - * @param pokemon The {@linkcode PlayerPokemon} that consumes the item - * @returns `true` if hp was restored - */ - apply({ pokemon }: PokemonRewardParams): boolean { - 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?: number; - healStatus?: boolean; - fainted?: boolean; - } = {}, -): boolean { - if (pokemon.isFainted() !== fainted) { - 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 { - protected restorePoints: number; - protected restorePercent: number; - protected healStatus: boolean; - - constructor( - localeKey: string, - iconImage: string, - id: RewardId, - restorePoints: number, - restorePercent: number, - healStatus = false, - selectFilter?: PokemonSelectFilter, - group?: string, - ) { - super( - localeKey, - iconImage, - selectFilter || - ((pokemon: PlayerPokemon) => { - if ( - !pokemon.hp || - (pokemon.isFullHp() && (!this.healStatus || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED)))) - ) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }), - group || "potion", - ); - - this.restorePoints = restorePoints; - this.restorePercent = restorePercent; - this.healStatus = healStatus; - this.id = id; - } - - get description(): string { - return this.restorePoints - ? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.description", { - restorePoints: this.restorePoints, - restorePercent: this.restorePercent, - }) - : this.healStatus - ? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.extra.fullyWithStatus") - : i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.extra.fully"); - } - - apply({ pokemon }: PokemonRewardParams): boolean { - return restorePokemonHp(pokemon, this.restorePercent, { - pointsToRestore: this.restorePoints, - healStatus: this.healStatus, - }); - } -} - -export class PokemonReviveReward extends PokemonHpRestoreReward { - constructor(localeKey: string, iconImage: string, id: RewardId, restorePercent: number) { - super( - localeKey, - iconImage, - id, - 0, - restorePercent, - false, - (pokemon: PlayerPokemon) => { - if (!pokemon.isFainted()) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }, - "revive", - ); - - this.selectFilter = (pokemon: PlayerPokemon) => { - if (pokemon.hp) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }; - } - - get description(): string { - return i18next.t("modifierType:ModifierType.PokemonReviveModifierType.description", { - restorePercent: this.restorePercent, - }); - } - - apply({ pokemon }: PokemonRewardParams): boolean { - return restorePokemonHp(pokemon, this.restorePercent, { fainted: true }); - } -} - -export class AllPokemonFullReviveReward extends Reward { - constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, "modifierType:ModifierType.AllPokemonFullReviveModifierType"); - this.id = RewardId.SACRED_ASH; - } - - apply(): boolean { - for (const pokemon of globalScene.getPlayerParty()) { - restorePokemonHp(pokemon, 100, { fainted: true }); - } - - return true; - } -} - -export class PokemonStatusHealReward extends PokemonReward { - constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, (pokemon: PlayerPokemon) => { - if (!pokemon.hp || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED))) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }); - this.id = RewardId.FULL_HEAL; - } - - get description(): string { - return i18next.t("modifierType:ModifierType.PokemonStatusHealModifierType.description"); - } - - apply({ pokemon }: PokemonRewardParams): boolean { - pokemon.resetStatus(true, true, false, false); - return true; - } -} - export abstract class PokemonMoveReward extends PokemonReward { public moveSelectFilter: PokemonMoveSelectFilter | undefined; @@ -649,857 +153,24 @@ export abstract class PokemonMoveReward extends PokemonReward { } } -export class PokemonPpRestoreReward extends PokemonMoveReward { - protected restorePoints: number; - - constructor(localeKey: string, iconImage: string, id: RewardId, restorePoints: number) { - super( - localeKey, - iconImage, - id, - (_pokemon: PlayerPokemon) => { - return null; - }, - (pokemonMove: PokemonMove) => { - if (!pokemonMove.ppUsed) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }, - "ether", - ); - - this.restorePoints = restorePoints; - } - - get description(): string { - return this.restorePoints > -1 - ? i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.description", { - restorePoints: this.restorePoints, - }) - : i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.extra.fully"); - } - - /** - * Applies {@linkcode PokemonPpRestoreConsumable} - * @param playerPokemon The {@linkcode PlayerPokemon} that should get move pp restored - * @returns always `true` - */ - apply({ pokemon, moveIndex }: PokemonMoveRewardParams): boolean { - const move = pokemon.getMoveset()[moveIndex]; - - if (move) { - move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; - } - - return true; - } +export interface PokemonRewardParams { + pokemon: PlayerPokemon; } -export class PokemonAllMovePpRestoreReward extends PokemonReward { - protected restorePoints: number; - - constructor(localeKey: string, iconImage: string, id: RewardId, restorePoints: number) { - super( - localeKey, - iconImage, - (pokemon: PlayerPokemon) => { - if (!pokemon.getMoveset().filter(m => m.ppUsed).length) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }, - "elixir", - ); - - this.restorePoints = restorePoints; - this.id = id; - } - - get description(): string { - return this.restorePoints > -1 - ? i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.description", { - restorePoints: this.restorePoints, - }) - : i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.extra.fully"); - } - - /** - * Applies {@linkcode PokemonAllMovePpRestoreConsumable} - * @param playerPokemon The {@linkcode PlayerPokemon} that should get all move pp restored - * @returns always `true` - */ - apply({ pokemon }: PokemonRewardParams): boolean { - for (const move of pokemon.getMoveset()) { - if (move) { - move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; - } - } - - return true; - } +export interface PokemonMoveRewardParams { + pokemon: PlayerPokemon; + moveIndex: number; } -export class PokemonPpUpReward extends PokemonMoveReward { - protected upPoints: number; - - constructor(localeKey: string, iconImage: string, id: RewardId, upPoints: number) { - super( - localeKey, - iconImage, - id, - (_pokemon: PlayerPokemon) => { - return null; - }, - (pokemonMove: PokemonMove) => { - if (pokemonMove.getMove().pp < 5 || pokemonMove.ppUp >= 3 || pokemonMove.maxPpOverride) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }, - "ppUp", - ); - - this.upPoints = upPoints; - } - - get description(): string { - return i18next.t("modifierType:ModifierType.PokemonPpUpModifierType.description", { upPoints: this.upPoints }); - } - - /** - * Applies {@linkcode PokemonPpUpConsumable} - * @param playerPokemon The {@linkcode PlayerPokemon} that gets a pp up on move-slot {@linkcode moveIndex} - * @returns - */ - apply({ pokemon, moveIndex }: PokemonMoveRewardParams): boolean { - const move = pokemon.getMoveset()[moveIndex]; - - if (move && !move.maxPpOverride) { - move.ppUp = Math.min(move.ppUp + this.upPoints, 3); - } - - return true; - } +export interface PokemonMoveRecallRewardParams { + pokemon: PlayerPokemon; + moveIndex: number; + cost?: number; } -export class PokemonNatureChangeReward extends PokemonReward { - protected nature: Nature; - - constructor(nature: Nature) { - super( - "", - `mint_${ - getEnumKeys(Stat) - .find(s => getNatureStatMultiplier(nature, Stat[s]) > 1) - ?.toLowerCase() || "neutral" - }`, - (pokemon: PlayerPokemon) => { - if (pokemon.getNature() === this.nature) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }, - "mint", - ); - - this.nature = nature; - this.id = RewardId.MINT; - } - - get name(): string { - return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.name", { - natureName: getNatureName(this.nature), - }); - } - - get description(): string { - return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.description", { - natureName: getNatureName(this.nature, true, true, true), - }); - } - - /** - * Applies {@linkcode PokemonNatureChangeConsumable} - * @param playerPokemon {@linkcode PlayerPokemon} to apply the {@linkcode Nature} change to - * @returns - */ - apply({ pokemon }: PokemonRewardParams): boolean { - pokemon.setCustomNature(this.nature); - globalScene.gameData.unlockSpeciesNature(pokemon.species, this.nature); - - return true; - } -} - -export class RememberMoveReward extends PokemonReward { - constructor(localeKey: string, iconImage: string, group?: string) { - super( - localeKey, - iconImage, - (pokemon: PlayerPokemon) => { - if (!pokemon.getLearnableLevelMoves().length) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }, - group, - ); - this.id = RewardId.MEMORY_MUSHROOM; - } - - /** - * Applies {@linkcode RememberMoveConsumable} - * @param playerPokemon The {@linkcode PlayerPokemon} that should remember the move - * @returns always `true` - */ - apply({ pokemon, moveIndex, cost }: PokemonMoveRecallRewardParams): boolean { - globalScene.phaseManager.unshiftNew( - "LearnMovePhase", - globalScene.getPlayerParty().indexOf(pokemon as PlayerPokemon), - pokemon.getLearnableLevelMoves()[moveIndex], - LearnMoveType.MEMORY, - cost, - ); - - return true; - } -} - -export class BerryRewardGenerator extends RewardGenerator { - override generateReward(pregenArgs?: BerryType): HeldItemReward { - if (pregenArgs !== undefined) { - const item = berryTypeToHeldItem[pregenArgs]; - return new HeldItemReward(item); - } - const item = getNewBerryHeldItem(); - return new HeldItemReward(item); - } -} - -export class MintRewardGenerator extends RewardGenerator { - override generateReward(pregenArgs?: Nature) { - if (pregenArgs !== undefined) { - return new PokemonNatureChangeReward(pregenArgs); - } - return new PokemonNatureChangeReward(randSeedItem(getEnumValues(Nature))); - } -} - -export class TeraTypeRewardGenerator extends RewardGenerator { - 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; - } - 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; - } -} - -export class AttackTypeBoosterReward extends HeldItemReward { - public moveType: PokemonType; - public boostPercent: number; - - constructor(moveType: PokemonType, boostPercent: number) { - const itemId = attackTypeToHeldItem[moveType]; - super(itemId); - this.moveType = moveType; - this.boostPercent = boostPercent; - } -} - -function incrementLevelWithCandy(pokemon: Pokemon): boolean { - const levelCount = new NumberHolder(1); - globalScene.applyPlayerItems(TrainerItemEffect.LEVEL_INCREMENT_BOOSTER, { numberHolder: levelCount }); - - pokemon.level += levelCount.value; - if (pokemon.level <= globalScene.getMaxExpLevel(true)) { - pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate); - pokemon.levelExp = 0; - } - - if (pokemon.isPlayer()) { - pokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY); - - globalScene.phaseManager.unshiftNew( - "LevelUpPhase", - globalScene.getPlayerParty().indexOf(pokemon), - pokemon.level - levelCount.value, - pokemon.level, - ); - } - return true; -} - -export class PokemonLevelIncrementReward extends PokemonReward { - constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, (_pokemon: PlayerPokemon) => null); - this.id = RewardId.RARE_CANDY; - } - - get description(): string { - let levels = 1; - const candyJarStack = globalScene.trainerItems.getStack(TrainerItemId.CANDY_JAR); - levels += candyJarStack; - return i18next.t("modifierType:ModifierType.PokemonLevelIncrementModifierType.description", { levels }); - } - - /** - * Applies {@linkcode PokemonLevelIncrementConsumable} - * @param playerPokemon The {@linkcode PlayerPokemon} that should get levels incremented - * @param levelCount The amount of levels to increment - * @returns always `true` - */ - apply({ pokemon }: PokemonRewardParams): boolean { - return incrementLevelWithCandy(pokemon); - } -} - -export class AllPokemonLevelIncrementReward extends Reward { - id = RewardId.RARER_CANDY; - - get description(): string { - let levels = 1; - const candyJarStack = globalScene.trainerItems.getStack(TrainerItemId.CANDY_JAR); - levels += candyJarStack; - return i18next.t("modifierType:ModifierType.AllPokemonLevelIncrementModifierType.description", { levels }); - } - - apply(): boolean { - for (const pokemon of globalScene.getPlayerParty()) { - incrementLevelWithCandy(pokemon); - } - - return true; - } -} - -export class BaseStatBoosterReward extends HeldItemReward { - private stat: PermanentStat; - private key: string; - - constructor(stat: PermanentStat) { - const key = statBoostItems[stat]; - const itemId = permanentStatToHeldItem[stat]; - super(itemId); - - this.stat = stat; - this.key = key; - } -} - -export class TmReward extends PokemonReward { - public moveId: MoveId; - - constructor(moveId: MoveId) { - super( - "", - `tm_${PokemonType[allMoves[moveId].type].toLowerCase()}`, - (pokemon: PlayerPokemon) => { - if ( - pokemon.compatibleTms.indexOf(moveId) === -1 || - pokemon.getMoveset().filter(m => m.moveId === moveId).length - ) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }, - "tm", - ); - - this.moveId = moveId; - } - - get name(): string { - return i18next.t("modifierType:ModifierType.TmModifierType.name", { - moveId: padInt(Object.keys(tmSpecies).indexOf(this.moveId.toString()) + 1, 3), - moveName: allMoves[this.moveId].name, - }); - } - - get description(): string { - return i18next.t( - globalScene.enableMoveInfo - ? "modifierType:ModifierType.TmModifierTypeWithInfo.description" - : "modifierType:ModifierType.TmModifierType.description", - { moveName: allMoves[this.moveId].name }, - ); - } - - /** - * Applies {@linkcode TmConsumable} - * @param playerPokemon The {@linkcode PlayerPokemon} that should learn the TM - * @returns always `true` - */ - apply({ pokemon }: PokemonRewardParams): boolean { - globalScene.phaseManager.unshiftNew( - "LearnMovePhase", - globalScene.getPlayerParty().indexOf(pokemon), - this.moveId, - LearnMoveType.TM, - ); - - return true; - } -} - -export class EvolutionItemReward extends PokemonReward { - public evolutionItem: EvolutionItem; - - constructor(evolutionItem: EvolutionItem) { - super("", EvolutionItem[evolutionItem].toLowerCase(), (pokemon: PlayerPokemon) => { - if ( - pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && - pokemonEvolutions[pokemon.species.speciesId].filter(e => e.validate(pokemon, false, this.evolutionItem)) - .length && - pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX - ) { - return null; - } - if ( - pokemon.isFusion() && - pokemon.fusionSpecies && - pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && - pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(e => e.validate(pokemon, true, this.evolutionItem)) - .length && - pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX - ) { - return null; - } - - return PartyUiHandler.NoEffectMessage; - }); - - this.evolutionItem = evolutionItem; - } - - get name(): string { - return i18next.t(`modifierType:EvolutionItem.${EvolutionItem[this.evolutionItem]}`); - } - - get description(): string { - return i18next.t("modifierType:ModifierType.EvolutionItemModifierType.description"); - } - - /** - * Applies {@linkcode EvolutionItemConsumable} - * @param playerPokemon The {@linkcode PlayerPokemon} that should evolve via item - * @returns `true` if the evolution was successful - */ - apply({ pokemon }: PokemonRewardParams): boolean { - let matchingEvolution = pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) - ? pokemonEvolutions[pokemon.species.speciesId].find( - e => e.evoItem === this.evolutionItem && e.validate(pokemon, false, e.item!), - ) - : null; - - if (!matchingEvolution && pokemon.isFusion()) { - matchingEvolution = pokemonEvolutions[pokemon.fusionSpecies!.speciesId].find( - e => e.evoItem === this.evolutionItem && e.validate(pokemon, true, e.item!), - ); - if (matchingEvolution) { - matchingEvolution = new FusionSpeciesFormEvolution(pokemon.species.speciesId, matchingEvolution); - } - } - - if (matchingEvolution) { - globalScene.phaseManager.unshiftNew("EvolutionPhase", pokemon, matchingEvolution, pokemon.level - 1); - return true; - } - - return false; - } -} - -/** - * Class that represents form changing items - */ -export class FormChangeItemReward extends PokemonReward { - public formChangeItem: FormChangeItemId; - - constructor(formChangeItem: FormChangeItemId) { - super("", allHeldItems[formChangeItem].iconName, (pokemon: PlayerPokemon) => { - // Make sure the Pokemon has alternate forms - if ( - pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) && - // Get all form changes for this species with an item trigger, including any compound triggers - pokemonFormChanges[pokemon.species.speciesId] - .filter( - fc => fc.trigger.hasTriggerType(SpeciesFormChangeItemTrigger) && fc.preFormKey === pokemon.getFormKey(), - ) - // Returns true if any form changes match this item - .flatMap(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger) - .flatMap(fc => fc.item) - .includes(this.formChangeItem) - ) { - return null; - } - - return PartyUiHandler.NoEffectMessage; - }); - - this.formChangeItem = formChangeItem; - this.id = RewardId.FORM_CHANGE_ITEM; - } - - get name(): string { - return allHeldItems[this.formChangeItem].name; - } - - get description(): string { - return allHeldItems[this.formChangeItem].description; - } - - apply({ pokemon }: PokemonRewardParams): boolean { - if (pokemon.heldItemManager.hasItem(this.formChangeItem)) { - return false; - } - - pokemon.heldItemManager.add(this.formChangeItem); - pokemon.heldItemManager.toggleActive(this.formChangeItem); - - // TODO: revise logic of this trigger based on active/inactive item - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger); - - globalScene.updateItems(true); - - return true; - } -} - -export class FusePokemonReward extends PokemonReward { - constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, (pokemon: PlayerPokemon) => { - if (pokemon.isFusion()) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }); - this.id = RewardId.DNA_SPLICERS; - } - - get description(): string { - return i18next.t("modifierType:ModifierType.FusePokemonModifierType.description"); - } - - /** - * Applies {@linkcode FusePokemonConsumable} - * @param playerPokemon {@linkcode PlayerPokemon} that should be fused - * @param playerPokemon2 {@linkcode PlayerPokemon} that should be fused with {@linkcode playerPokemon} - * @returns always Promise - */ - apply({ pokemon, pokemon2 }: PokemonFusionRewardParams): boolean { - pokemon.fuse(pokemon2); - return true; - } -} - -export class AttackTypeBoosterRewardGenerator extends RewardGenerator { - override generateReward(pregenArgs?: PokemonType) { - if (pregenArgs !== undefined) { - return new AttackTypeBoosterReward(pregenArgs, TYPE_BOOST_ITEM_BOOST_PERCENT); - } - - const item = getNewAttackTypeBoosterHeldItem(globalScene.getPlayerParty()); - - return item ? new HeldItemReward(item) : null; - } -} - -export class BaseStatBoosterRewardGenerator extends RewardGenerator { - override generateReward(pregenArgs?: PermanentStat) { - if (pregenArgs !== undefined) { - return new BaseStatBoosterReward(pregenArgs); - } - return new HeldItemReward(getNewVitaminHeldItem()); - } -} - -export class TempStatStageBoosterRewardGenerator extends RewardGenerator { - public static readonly items: Record = { - [Stat.ATK]: "x_attack", - [Stat.DEF]: "x_defense", - [Stat.SPATK]: "x_sp_atk", - [Stat.SPDEF]: "x_sp_def", - [Stat.SPD]: "x_speed", - [Stat.ACC]: "x_accuracy", - }; - - override generateReward(pregenArgs?: TempBattleStat) { - return new LapsingTrainerItemReward(tempStatToTrainerItem[pregenArgs ?? randSeedItem(TEMP_BATTLE_STATS)]); - } -} - -/** - * Consumable type generator for {@linkcode SpeciesStatBoosterReward}, which - * encapsulates the logic for weighting the most useful held item from - * the current list of {@linkcode items}. - * @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(); - 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 = 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); - - 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; - - // 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]++; - } - } - } - } - - // 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; - - 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; - } -} - -export class TmRewardGenerator extends RewardGenerator { - private tier: RarityTier; - constructor(tier: RarityTier) { - 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), - ); - }); - 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 { - private rare: boolean; - constructor(rare: boolean) { - super(); - this.rare = rare; - } - - override generateReward(pregenArgs?: EvolutionItem) { - if (pregenArgs !== undefined) { - return new EvolutionItemReward(pregenArgs); - } - - const party = globalScene.getPlayerParty(); - - 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 { - private isRareFormChangeItem: boolean; - - constructor(isRareFormChangeItem: boolean) { - super(); - this.isRareFormChangeItem = isRareFormChangeItem; - } - - override generateReward(pregenArgs?: FormChangeItemId) { - 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.hasItem(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 FormChangeItemId.ULTRANECROZIUM_Z: - foundULTRA_Z = true; - break; - case FormChangeItemId.N_LUNARIZER: - foundN_LUNA = true; - break; - case FormChangeItemId.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 !== FormChangeItemId.ULTRANECROZIUM_Z, - ); - } else { - console.log("DID NOT FIND "); - } - } - 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; - } - - return new FormChangeItemReward(randSeedItem(formChangeItemPool)); - } +export interface PokemonFusionRewardParams { + pokemon: PlayerPokemon; + pokemon2: PlayerPokemon; } export class RewardOption { @@ -1516,7 +187,7 @@ export class RewardOption { } } -export class NoneReward extends Reward { +export class EmptyReward extends Reward { constructor() { super("", ""); } diff --git a/src/items/rewards/evolution-item.ts b/src/items/rewards/evolution-item.ts new file mode 100644 index 00000000000..805a88875fb --- /dev/null +++ b/src/items/rewards/evolution-item.ts @@ -0,0 +1,135 @@ +import { globalScene } from "#app/global-scene"; +import { EvolutionItem, FusionSpeciesFormEvolution, pokemonEvolutions } from "#balance/pokemon-evolutions"; +import { SpeciesFormKey } from "#enums/species-form-key"; +import { SpeciesId } from "#enums/species-id"; +import type { PlayerPokemon } from "#field/pokemon"; +import { PokemonReward, type PokemonRewardParams, RewardGenerator } from "#items/reward"; +import { PartyUiHandler } from "#ui/party-ui-handler"; +import { randSeedItem } from "#utils/common"; +import i18next from "i18next"; + +export class EvolutionItemReward extends PokemonReward { + public evolutionItem: EvolutionItem; + + constructor(evolutionItem: EvolutionItem) { + super("", EvolutionItem[evolutionItem].toLowerCase(), (pokemon: PlayerPokemon) => { + if ( + pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && + pokemonEvolutions[pokemon.species.speciesId].filter(e => e.validate(pokemon, false, this.evolutionItem)) + .length && + pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX + ) { + return null; + } + if ( + pokemon.isFusion() && + pokemon.fusionSpecies && + pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && + pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(e => e.validate(pokemon, true, this.evolutionItem)) + .length && + pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX + ) { + return null; + } + + return PartyUiHandler.NoEffectMessage; + }); + + this.evolutionItem = evolutionItem; + } + + get name(): string { + return i18next.t(`modifierType:EvolutionItem.${EvolutionItem[this.evolutionItem]}`); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.EvolutionItemModifierType.description"); + } + + /** + * Applies {@linkcode EvolutionItemConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that should evolve via item + * @returns `true` if the evolution was successful + */ + apply({ pokemon }: PokemonRewardParams): boolean { + let matchingEvolution = pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) + ? pokemonEvolutions[pokemon.species.speciesId].find( + e => e.evoItem === this.evolutionItem && e.validate(pokemon, false, e.item!), + ) + : null; + + if (!matchingEvolution && pokemon.isFusion()) { + matchingEvolution = pokemonEvolutions[pokemon.fusionSpecies!.speciesId].find( + e => e.evoItem === this.evolutionItem && e.validate(pokemon, true, e.item!), + ); + if (matchingEvolution) { + matchingEvolution = new FusionSpeciesFormEvolution(pokemon.species.speciesId, matchingEvolution); + } + } + + if (matchingEvolution) { + globalScene.phaseManager.unshiftNew("EvolutionPhase", pokemon, matchingEvolution, pokemon.level - 1); + return true; + } + + return false; + } +} + +export class EvolutionItemRewardGenerator extends RewardGenerator { + private rare: boolean; + constructor(rare: boolean) { + super(); + this.rare = rare; + } + + override generateReward(pregenArgs?: EvolutionItem) { + if (pregenArgs !== undefined) { + return new EvolutionItemReward(pregenArgs); + } + + const party = globalScene.getPlayerParty(); + + 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)); + } +} diff --git a/src/items/rewards/form-change.ts b/src/items/rewards/form-change.ts new file mode 100644 index 00000000000..bee56e9ecee --- /dev/null +++ b/src/items/rewards/form-change.ts @@ -0,0 +1,152 @@ +import { globalScene } from "#app/global-scene"; +import { allHeldItems } from "#data/data-lists"; +import { SpeciesFormChangeItemTrigger } from "#data/form-change-triggers"; +import { pokemonFormChanges, SpeciesFormChangeCondition } from "#data/pokemon-forms"; +import { FormChangeItemId } from "#enums/form-change-item-id"; +import { RewardId } from "#enums/reward-id"; +import { SpeciesFormKey } from "#enums/species-form-key"; +import { SpeciesId } from "#enums/species-id"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import type { PlayerPokemon } from "#field/pokemon"; +import { PokemonReward, type PokemonRewardParams, RewardGenerator } from "#items/reward"; +import { PartyUiHandler } from "#ui/party-ui-handler"; +import { randSeedItem } from "#utils/common"; + +/** + * Class that represents form changing items + */ +export class FormChangeItemReward extends PokemonReward { + public formChangeItem: FormChangeItemId; + + constructor(formChangeItem: FormChangeItemId) { + super("", allHeldItems[formChangeItem].iconName, (pokemon: PlayerPokemon) => { + // Make sure the Pokemon has alternate forms + if ( + pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) && + // Get all form changes for this species with an item trigger, including any compound triggers + pokemonFormChanges[pokemon.species.speciesId] + .filter( + fc => fc.trigger.hasTriggerType(SpeciesFormChangeItemTrigger) && fc.preFormKey === pokemon.getFormKey(), + ) + // Returns true if any form changes match this item + .flatMap(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger) + .flatMap(fc => fc.item) + .includes(this.formChangeItem) + ) { + return null; + } + + return PartyUiHandler.NoEffectMessage; + }); + + this.formChangeItem = formChangeItem; + this.id = RewardId.FORM_CHANGE_ITEM; + } + + get name(): string { + return allHeldItems[this.formChangeItem].name; + } + + get description(): string { + return allHeldItems[this.formChangeItem].description; + } + + apply({ pokemon }: PokemonRewardParams): boolean { + if (pokemon.heldItemManager.hasItem(this.formChangeItem)) { + return false; + } + + pokemon.heldItemManager.add(this.formChangeItem); + pokemon.heldItemManager.toggleActive(this.formChangeItem); + + // TODO: revise logic of this trigger based on active/inactive item + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger); + + globalScene.updateItems(true); + + return true; + } +} + +export class FormChangeItemRewardGenerator extends RewardGenerator { + private isRareFormChangeItem: boolean; + + constructor(isRareFormChangeItem: boolean) { + super(); + this.isRareFormChangeItem = isRareFormChangeItem; + } + + override generateReward(pregenArgs?: FormChangeItemId) { + 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.hasItem(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 FormChangeItemId.ULTRANECROZIUM_Z: + foundULTRA_Z = true; + break; + case FormChangeItemId.N_LUNARIZER: + foundN_LUNA = true; + break; + case FormChangeItemId.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 !== FormChangeItemId.ULTRANECROZIUM_Z, + ); + } else { + console.log("DID NOT FIND "); + } + } + 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; + } + + return new FormChangeItemReward(randSeedItem(formChangeItemPool)); + } +} diff --git a/src/items/rewards/fuse.ts b/src/items/rewards/fuse.ts new file mode 100644 index 00000000000..0e803071821 --- /dev/null +++ b/src/items/rewards/fuse.ts @@ -0,0 +1,32 @@ +import { RewardId } from "#enums/reward-id"; +import type { PlayerPokemon } from "#field/pokemon"; +import { type PokemonFusionRewardParams, PokemonReward } from "#items/reward"; +import { PartyUiHandler } from "#ui/party-ui-handler"; +import i18next from "i18next"; + +export class FusePokemonReward extends PokemonReward { + constructor(localeKey: string, iconImage: string) { + super(localeKey, iconImage, (pokemon: PlayerPokemon) => { + if (pokemon.isFusion()) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }); + this.id = RewardId.DNA_SPLICERS; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.FusePokemonModifierType.description"); + } + + /** + * Applies {@linkcode FusePokemonConsumable} + * @param playerPokemon {@linkcode PlayerPokemon} that should be fused + * @param playerPokemon2 {@linkcode PlayerPokemon} that should be fused with {@linkcode playerPokemon} + * @returns always Promise + */ + apply({ pokemon, pokemon2 }: PokemonFusionRewardParams): boolean { + pokemon.fuse(pokemon2); + return true; + } +} diff --git a/src/items/rewards/held-item-reward.ts b/src/items/rewards/held-item-reward.ts new file mode 100644 index 00000000000..6eb3516d643 --- /dev/null +++ b/src/items/rewards/held-item-reward.ts @@ -0,0 +1,92 @@ +import { globalScene } from "#app/global-scene"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { allHeldItems } from "#data/data-lists"; +import type { BerryType } from "#enums/berry-type"; +import type { HeldItemId } from "#enums/held-item-id"; +import type { PokemonType } from "#enums/pokemon-type"; +import type { PermanentStat } from "#enums/stat"; +import type { PlayerPokemon } from "#field/pokemon"; +import { attackTypeToHeldItem } from "#items/attack-type-booster"; +import { permanentStatToHeldItem } from "#items/base-stat-multiply"; +import { berryTypeToHeldItem } from "#items/berry"; +import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#items/held-item-pool"; +import { PokemonReward, type PokemonRewardParams, RewardGenerator } from "#items/reward"; +import i18next from "i18next"; + +export class HeldItemReward extends PokemonReward { + public itemId: HeldItemId; + constructor(itemId: HeldItemId, group?: string, soundName?: string) { + super( + "", + "", + (pokemon: PlayerPokemon) => { + const hasItem = pokemon.heldItemManager.hasItem(this.itemId); + const maxStackCount = allHeldItems[this.itemId].getMaxStackCount(); + if (!maxStackCount) { + return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.inoperable", { + pokemonName: getPokemonNameWithAffix(pokemon), + }); + } + if (hasItem && pokemon.heldItemManager.getStack(this.itemId) === maxStackCount) { + return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.tooMany", { + pokemonName: getPokemonNameWithAffix(pokemon), + }); + } + return null; + }, + group, + soundName, + ); + this.itemId = itemId; + } + + get name(): string { + return allHeldItems[this.itemId].name; + } + + get description(): string { + return allHeldItems[this.itemId].description; + } + + get iconName(): string { + return allHeldItems[this.itemId].iconName; + } + + apply({ pokemon }: PokemonRewardParams): boolean { + return pokemon.heldItemManager.add(this.itemId); + } +} + +export class BerryRewardGenerator extends RewardGenerator { + override generateReward(pregenArgs?: BerryType): HeldItemReward { + if (pregenArgs !== undefined) { + const item = berryTypeToHeldItem[pregenArgs]; + return new HeldItemReward(item); + } + const item = getNewBerryHeldItem(); + return new HeldItemReward(item); + } +} + +export class AttackTypeBoosterRewardGenerator extends RewardGenerator { + override generateReward(pregenArgs?: PokemonType) { + if (pregenArgs !== undefined) { + const item = attackTypeToHeldItem[pregenArgs]; + return new HeldItemReward(item); + } + + const item = getNewAttackTypeBoosterHeldItem(globalScene.getPlayerParty()); + + return item ? new HeldItemReward(item) : null; + } +} + +export class BaseStatBoosterRewardGenerator extends RewardGenerator { + override generateReward(pregenArgs?: PermanentStat) { + if (pregenArgs !== undefined) { + const item = permanentStatToHeldItem[pregenArgs]; + return new HeldItemReward(item); + } + return new HeldItemReward(getNewVitaminHeldItem()); + } +} diff --git a/src/items/rewards/level-increment.ts b/src/items/rewards/level-increment.ts new file mode 100644 index 00000000000..3c55f245bdb --- /dev/null +++ b/src/items/rewards/level-increment.ts @@ -0,0 +1,76 @@ +import { globalScene } from "#app/global-scene"; +import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#balance/starters"; +import { getLevelTotalExp } from "#data/exp"; +import { RewardId } from "#enums/reward-id"; +import { TrainerItemEffect } from "#enums/trainer-item-effect"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import type { PlayerPokemon, Pokemon } from "#field/pokemon"; +import { PokemonReward, type PokemonRewardParams, Reward } from "#items/reward"; +import { NumberHolder } from "#utils/common"; +import i18next from "i18next"; + +function incrementLevelWithCandy(pokemon: Pokemon): boolean { + const levelCount = new NumberHolder(1); + globalScene.applyPlayerItems(TrainerItemEffect.LEVEL_INCREMENT_BOOSTER, { numberHolder: levelCount }); + + pokemon.level += levelCount.value; + if (pokemon.level <= globalScene.getMaxExpLevel(true)) { + pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate); + pokemon.levelExp = 0; + } + + if (pokemon.isPlayer()) { + pokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY); + + globalScene.phaseManager.unshiftNew( + "LevelUpPhase", + globalScene.getPlayerParty().indexOf(pokemon), + pokemon.level - levelCount.value, + pokemon.level, + ); + } + return true; +} + +export class PokemonLevelIncrementReward extends PokemonReward { + constructor(localeKey: string, iconImage: string) { + super(localeKey, iconImage, (_pokemon: PlayerPokemon) => null); + this.id = RewardId.RARE_CANDY; + } + + get description(): string { + let levels = 1; + const candyJarStack = globalScene.trainerItems.getStack(TrainerItemId.CANDY_JAR); + levels += candyJarStack; + return i18next.t("modifierType:ModifierType.PokemonLevelIncrementModifierType.description", { levels }); + } + + /** + * Applies {@linkcode PokemonLevelIncrementConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that should get levels incremented + * @param levelCount The amount of levels to increment + * @returns always `true` + */ + apply({ pokemon }: PokemonRewardParams): boolean { + return incrementLevelWithCandy(pokemon); + } +} + +export class AllPokemonLevelIncrementReward extends Reward { + id = RewardId.RARER_CANDY; + + get description(): string { + let levels = 1; + const candyJarStack = globalScene.trainerItems.getStack(TrainerItemId.CANDY_JAR); + levels += candyJarStack; + return i18next.t("modifierType:ModifierType.AllPokemonLevelIncrementModifierType.description", { levels }); + } + + apply(): boolean { + for (const pokemon of globalScene.getPlayerParty()) { + incrementLevelWithCandy(pokemon); + } + + return true; + } +} diff --git a/src/items/rewards/money.ts b/src/items/rewards/money.ts new file mode 100644 index 00000000000..08060612209 --- /dev/null +++ b/src/items/rewards/money.ts @@ -0,0 +1,59 @@ +import { globalScene } from "#app/global-scene"; +import { HeldItemId } from "#enums/held-item-id"; +import type { RewardId } from "#enums/reward-id"; +import { SpeciesId } from "#enums/species-id"; +import { TrainerItemEffect } from "#enums/trainer-item-effect"; +import { Reward } from "#items/reward"; +import { formatMoney, NumberHolder } from "#utils/common"; +import i18next from "i18next"; + +export class AddMoneyReward extends Reward { + private moneyMultiplier: number; + private moneyMultiplierDescriptorKey: string; + + constructor( + localeKey: string, + iconImage: string, + moneyMultiplier: number, + moneyMultiplierDescriptorKey: string, + id: RewardId, + ) { + super(localeKey, iconImage, "money", "se/buy"); + + this.moneyMultiplier = moneyMultiplier; + this.moneyMultiplierDescriptorKey = moneyMultiplierDescriptorKey; + this.id = id; + } + + get description(): string { + const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); + globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); + const formattedMoney = formatMoney(globalScene.moneyFormat, moneyAmount.value); + + return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", { + moneyMultiplier: i18next.t(this.moneyMultiplierDescriptorKey as any), + moneyAmount: formattedMoney, + }); + } + + /** + * Applies {@linkcode AddMoneyReward} + * @returns always `true` + */ + apply(): boolean { + const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); + + globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); + + globalScene.addMoney(moneyAmount.value); + + for (const p of globalScene.getPlayerParty()) { + if (p.hasSpecies(SpeciesId.GIMMIGHOUL)) { + const factor = Math.min(Math.floor(this.moneyMultiplier), 3); + p.heldItemManager.add(HeldItemId.GIMMIGHOUL_EVO_TRACKER, factor); + } + } + + return true; + } +} diff --git a/src/items/rewards/nature-change.ts b/src/items/rewards/nature-change.ts new file mode 100644 index 00000000000..92aee1bffec --- /dev/null +++ b/src/items/rewards/nature-change.ts @@ -0,0 +1,69 @@ +import { globalScene } from "#app/global-scene"; +import { getNatureName, getNatureStatMultiplier } from "#data/nature"; +import { Nature } from "#enums/nature"; +import { RewardId } from "#enums/reward-id"; +import { Stat } from "#enums/stat"; +import type { PlayerPokemon } from "#field/pokemon"; +import { PokemonReward, type PokemonRewardParams, RewardGenerator } from "#items/reward"; +import { PartyUiHandler } from "#ui/party-ui-handler"; +import { randSeedItem } from "#utils/common"; +import { getEnumKeys, getEnumValues } from "#utils/enums"; +import i18next from "i18next"; + +export class PokemonNatureChangeReward extends PokemonReward { + protected nature: Nature; + + constructor(nature: Nature) { + super( + "", + `mint_${ + getEnumKeys(Stat) + .find(s => getNatureStatMultiplier(nature, Stat[s]) > 1) + ?.toLowerCase() || "neutral" + }`, + (pokemon: PlayerPokemon) => { + if (pokemon.getNature() === this.nature) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "mint", + ); + + this.nature = nature; + this.id = RewardId.MINT; + } + + get name(): string { + return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.name", { + natureName: getNatureName(this.nature), + }); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.description", { + natureName: getNatureName(this.nature, true, true, true), + }); + } + + /** + * Applies {@linkcode PokemonNatureChangeConsumable} + * @param playerPokemon {@linkcode PlayerPokemon} to apply the {@linkcode Nature} change to + * @returns + */ + apply({ pokemon }: PokemonRewardParams): boolean { + pokemon.setCustomNature(this.nature); + globalScene.gameData.unlockSpeciesNature(pokemon.species, this.nature); + + return true; + } +} + +export class MintRewardGenerator extends RewardGenerator { + override generateReward(pregenArgs?: Nature) { + if (pregenArgs !== undefined) { + return new PokemonNatureChangeReward(pregenArgs); + } + return new PokemonNatureChangeReward(randSeedItem(getEnumValues(Nature))); + } +} diff --git a/src/items/rewards/pokeball.ts b/src/items/rewards/pokeball.ts new file mode 100644 index 00000000000..5f38af0e7f2 --- /dev/null +++ b/src/items/rewards/pokeball.ts @@ -0,0 +1,51 @@ +import { globalScene } from "#app/global-scene"; +import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#data/pokeball"; +import type { PokeballType } from "#enums/pokeball"; +import type { RewardId } from "#enums/reward-id"; +import { Reward } from "#items/reward"; +import i18next from "i18next"; + +export class AddPokeballReward extends Reward { + private pokeballType: PokeballType; + private count: number; + + constructor(iconImage: string, pokeballType: PokeballType, count: number, id: RewardId) { + super("", iconImage, "pb", "se/pb_bounce_1"); + this.pokeballType = pokeballType; + this.count = count; + this.id = id; + } + + get name(): string { + return i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", { + modifierCount: this.count, + pokeballName: getPokeballName(this.pokeballType), + }); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.AddPokeballModifierType.description", { + modifierCount: this.count, + pokeballName: getPokeballName(this.pokeballType), + catchRate: + getPokeballCatchMultiplier(this.pokeballType) > -1 + ? `${getPokeballCatchMultiplier(this.pokeballType)}x` + : "100%", + pokeballAmount: `${globalScene.pokeballCounts[this.pokeballType]}`, + }); + } + + /** + * Applies {@linkcode AddPokeballReward} + * @returns always `true` + */ + apply(): boolean { + const pokeballCounts = globalScene.pokeballCounts; + pokeballCounts[this.pokeballType] = Math.min( + pokeballCounts[this.pokeballType] + this.count, + MAX_PER_TYPE_POKEBALLS, + ); + + return true; + } +} diff --git a/src/items/rewards/pp-restore.ts b/src/items/rewards/pp-restore.ts new file mode 100644 index 00000000000..a92eaabeaf3 --- /dev/null +++ b/src/items/rewards/pp-restore.ts @@ -0,0 +1,102 @@ +import type { RewardId } from "#enums/reward-id"; +import type { PlayerPokemon } from "#field/pokemon"; +import { + PokemonMoveReward, + type PokemonMoveRewardParams, + PokemonReward, + type PokemonRewardParams, +} from "#items/reward"; +import type { PokemonMove } from "#moves/pokemon-move"; +import { PartyUiHandler } from "#ui/party-ui-handler"; +import i18next from "i18next"; + +export class PokemonPpRestoreReward extends PokemonMoveReward { + protected restorePoints: number; + + constructor(localeKey: string, iconImage: string, id: RewardId, restorePoints: number) { + super( + localeKey, + iconImage, + id, + (_pokemon: PlayerPokemon) => { + return null; + }, + (pokemonMove: PokemonMove) => { + if (!pokemonMove.ppUsed) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "ether", + ); + + this.restorePoints = restorePoints; + } + + get description(): string { + return this.restorePoints > -1 + ? i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.description", { + restorePoints: this.restorePoints, + }) + : i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.extra.fully"); + } + + /** + * Applies {@linkcode PokemonPpRestoreConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that should get move pp restored + * @returns always `true` + */ + apply({ pokemon, moveIndex }: PokemonMoveRewardParams): boolean { + const move = pokemon.getMoveset()[moveIndex]; + + if (move) { + move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; + } + + return true; + } +} + +export class PokemonAllMovePpRestoreReward extends PokemonReward { + protected restorePoints: number; + + constructor(localeKey: string, iconImage: string, id: RewardId, restorePoints: number) { + super( + localeKey, + iconImage, + (pokemon: PlayerPokemon) => { + if (!pokemon.getMoveset().filter(m => m.ppUsed).length) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "elixir", + ); + + this.restorePoints = restorePoints; + this.id = id; + } + + get description(): string { + return this.restorePoints > -1 + ? i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.description", { + restorePoints: this.restorePoints, + }) + : i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.extra.fully"); + } + + /** + * Applies {@linkcode PokemonAllMovePpRestoreConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that should get all move pp restored + * @returns always `true` + */ + apply({ pokemon }: PokemonRewardParams): boolean { + for (const move of pokemon.getMoveset()) { + if (move) { + move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; + } + } + + return true; + } +} diff --git a/src/items/rewards/pp-up.ts b/src/items/rewards/pp-up.ts new file mode 100644 index 00000000000..f15262df6b8 --- /dev/null +++ b/src/items/rewards/pp-up.ts @@ -0,0 +1,49 @@ +import type { RewardId } from "#enums/reward-id"; +import type { PlayerPokemon } from "#field/pokemon"; +import { PokemonMoveReward, type PokemonMoveRewardParams } from "#items/reward"; +import type { PokemonMove } from "#moves/pokemon-move"; +import { PartyUiHandler } from "#ui/party-ui-handler"; +import i18next from "i18next"; + +export class PokemonPpUpReward extends PokemonMoveReward { + protected upPoints: number; + + constructor(localeKey: string, iconImage: string, id: RewardId, upPoints: number) { + super( + localeKey, + iconImage, + id, + (_pokemon: PlayerPokemon) => { + return null; + }, + (pokemonMove: PokemonMove) => { + if (pokemonMove.getMove().pp < 5 || pokemonMove.ppUp >= 3 || pokemonMove.maxPpOverride) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "ppUp", + ); + + this.upPoints = upPoints; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonPpUpModifierType.description", { upPoints: this.upPoints }); + } + + /** + * Applies {@linkcode PokemonPpUpConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that gets a pp up on move-slot {@linkcode moveIndex} + * @returns + */ + apply({ pokemon, moveIndex }: PokemonMoveRewardParams): boolean { + const move = pokemon.getMoveset()[moveIndex]; + + if (move && !move.maxPpOverride) { + move.ppUp = Math.min(move.ppUp + this.upPoints, 3); + } + + return true; + } +} diff --git a/src/items/rewards/remember-move.ts b/src/items/rewards/remember-move.ts new file mode 100644 index 00000000000..28be3d69206 --- /dev/null +++ b/src/items/rewards/remember-move.ts @@ -0,0 +1,40 @@ +import { globalScene } from "#app/global-scene"; +import { LearnMoveType } from "#enums/learn-move-type"; +import { RewardId } from "#enums/reward-id"; +import type { PlayerPokemon } from "#field/pokemon"; +import { type PokemonMoveRecallRewardParams, PokemonReward } from "#items/reward"; +import { PartyUiHandler } from "#ui/party-ui-handler"; + +export class RememberMoveReward extends PokemonReward { + constructor(localeKey: string, iconImage: string, group?: string) { + super( + localeKey, + iconImage, + (pokemon: PlayerPokemon) => { + if (!pokemon.getLearnableLevelMoves().length) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + group, + ); + this.id = RewardId.MEMORY_MUSHROOM; + } + + /** + * Applies {@linkcode RememberMoveConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that should remember the move + * @returns always `true` + */ + apply({ pokemon, moveIndex, cost }: PokemonMoveRecallRewardParams): boolean { + globalScene.phaseManager.unshiftNew( + "LearnMovePhase", + globalScene.getPlayerParty().indexOf(pokemon as PlayerPokemon), + pokemon.getLearnableLevelMoves()[moveIndex], + LearnMoveType.MEMORY, + cost, + ); + + return true; + } +} diff --git a/src/items/rewards/restore.ts b/src/items/rewards/restore.ts new file mode 100644 index 00000000000..fbc59e2aade --- /dev/null +++ b/src/items/rewards/restore.ts @@ -0,0 +1,160 @@ +// TODO: Consider removing `revive` from the signature of PokemonHealPhase in the wake of this +// (was only used for revives) + +import { globalScene } from "#app/global-scene"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { RewardId } from "#enums/reward-id"; +import { TrainerItemEffect } from "#enums/trainer-item-effect"; +import type { PlayerPokemon, Pokemon } from "#field/pokemon"; +import { PokemonReward, type PokemonRewardParams, Reward } from "#items/reward"; +import { PartyUiHandler, type PokemonSelectFilter } from "#ui/party-ui-handler"; +import { NumberHolder, toDmgValue } from "#utils/common"; +import i18next from "i18next"; + +/** + * 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?: number; + healStatus?: boolean; + fainted?: boolean; + } = {}, +): boolean { + if (pokemon.isFainted() !== fainted) { + 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 { + protected restorePoints: number; + protected restorePercent: number; + protected healStatus: boolean; + + constructor( + localeKey: string, + iconImage: string, + id: RewardId, + restorePoints: number, + restorePercent: number, + healStatus = false, + selectFilter?: PokemonSelectFilter, + group?: string, + ) { + super( + localeKey, + iconImage, + selectFilter || + ((pokemon: PlayerPokemon) => { + if ( + !pokemon.hp || + (pokemon.isFullHp() && (!this.healStatus || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED)))) + ) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }), + group || "potion", + ); + + this.restorePoints = restorePoints; + this.restorePercent = restorePercent; + this.healStatus = healStatus; + this.id = id; + } + + get description(): string { + return this.restorePoints + ? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.description", { + restorePoints: this.restorePoints, + restorePercent: this.restorePercent, + }) + : this.healStatus + ? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.extra.fullyWithStatus") + : i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.extra.fully"); + } + + apply({ pokemon }: PokemonRewardParams): boolean { + return restorePokemonHp(pokemon, this.restorePercent, { + pointsToRestore: this.restorePoints, + healStatus: this.healStatus, + }); + } +} + +export class PokemonReviveReward extends PokemonHpRestoreReward { + constructor(localeKey: string, iconImage: string, id: RewardId, restorePercent: number) { + super( + localeKey, + iconImage, + id, + 0, + restorePercent, + false, + (pokemon: PlayerPokemon) => { + if (!pokemon.isFainted()) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "revive", + ); + + this.selectFilter = (pokemon: PlayerPokemon) => { + if (pokemon.hp) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonReviveModifierType.description", { + restorePercent: this.restorePercent, + }); + } + + apply({ pokemon }: PokemonRewardParams): boolean { + return restorePokemonHp(pokemon, this.restorePercent, { fainted: true }); + } +} + +export class AllPokemonFullReviveReward extends Reward { + constructor(localeKey: string, iconImage: string) { + super(localeKey, iconImage, "modifierType:ModifierType.AllPokemonFullReviveModifierType"); + this.id = RewardId.SACRED_ASH; + } + + apply(): boolean { + for (const pokemon of globalScene.getPlayerParty()) { + restorePokemonHp(pokemon, 100, { fainted: true }); + } + + return true; + } +} diff --git a/src/items/rewards/species-stat-booster.ts b/src/items/rewards/species-stat-booster.ts new file mode 100644 index 00000000000..610895c25e9 --- /dev/null +++ b/src/items/rewards/species-stat-booster.ts @@ -0,0 +1,83 @@ +import { globalScene } from "#app/global-scene"; +import { allHeldItems } from "#data/data-lists"; +import { HeldItemId } from "#enums/held-item-id"; +import { SpeciesId } from "#enums/species-id"; +import { RewardGenerator } from "#items/reward"; +import type { SpeciesStatBoosterItemId, SpeciesStatBoostHeldItem } from "#items/stat-boost"; +import { randSeedInt } from "#utils/common"; +import { HeldItemReward } from "./held-item-reward"; + +/** + * Consumable type generator for {@linkcode SpeciesStatBoosterReward}, which + * encapsulates the logic for weighting the most useful held item from + * the current list of {@linkcode items}. + * @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(); + 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 = 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); + + 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; + + // 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]++; + } + } + } + } + + // 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; + + 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; + } +} diff --git a/src/items/rewards/status-heal.ts b/src/items/rewards/status-heal.ts new file mode 100644 index 00000000000..e2e30342cc4 --- /dev/null +++ b/src/items/rewards/status-heal.ts @@ -0,0 +1,27 @@ +import { BattlerTagType } from "#enums/battler-tag-type"; +import { RewardId } from "#enums/reward-id"; +import type { PlayerPokemon } from "#field/pokemon"; +import { PokemonReward, type PokemonRewardParams } from "#items/reward"; +import { PartyUiHandler } from "#ui/party-ui-handler"; +import i18next from "i18next"; + +export class PokemonStatusHealReward extends PokemonReward { + constructor(localeKey: string, iconImage: string) { + super(localeKey, iconImage, (pokemon: PlayerPokemon) => { + if (!pokemon.hp || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED))) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }); + this.id = RewardId.FULL_HEAL; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonStatusHealModifierType.description"); + } + + apply({ pokemon }: PokemonRewardParams): boolean { + pokemon.resetStatus(true, true, false, false); + return true; + } +} diff --git a/src/items/rewards/tera-type.ts b/src/items/rewards/tera-type.ts new file mode 100644 index 00000000000..b105c21d09f --- /dev/null +++ b/src/items/rewards/tera-type.ts @@ -0,0 +1,102 @@ +import { globalScene } from "#app/global-scene"; +import { PokemonType } from "#enums/pokemon-type"; +import { SpeciesId } from "#enums/species-id"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import type { PlayerPokemon } from "#field/pokemon"; +import { PokemonReward, type PokemonRewardParams, RewardGenerator } from "#items/reward"; +import { PartyUiHandler } from "#ui/party-ui-handler"; +import { randSeedInt, randSeedItem } from "#utils/common"; +import { getEnumValues } from "#utils/enums"; +import { toCamelCase } from "#utils/strings"; +import i18next from "i18next"; + +export class ChangeTeraTypeReward extends PokemonReward { + private teraType: PokemonType; + + constructor(teraType: PokemonType) { + super( + "", + `${PokemonType[teraType].toLowerCase()}_tera_shard`, + (pokemon: PlayerPokemon) => { + if ( + [pokemon.species.speciesId, pokemon.fusionSpecies?.speciesId].filter( + s => s === SpeciesId.TERAPAGOS || s === SpeciesId.OGERPON || s === SpeciesId.SHEDINJA, + ).length > 0 + ) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "tera_shard", + ); + + this.teraType = teraType; + } + + get name(): string { + return i18next.t("modifierType:ModifierType.ChangeTeraTypeModifierType.name", { + teraType: i18next.t(`pokemonInfo:type.${toCamelCase(PokemonType[this.teraType])}`), + }); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.ChangeTeraTypeModifierType.description", { + teraType: i18next.t(`pokemonInfo:type.${toCamelCase(PokemonType[this.teraType])}`), + }); + } + + /** + * Checks if {@linkcode TerrastalizeConsumable} should be applied + * @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item + * @returns `true` if the {@linkcode TerrastalizeConsumable} should be applied + */ + shouldApply({ pokemon }: PokemonRewardParams): boolean { + return ( + pokemon.teraType !== this.teraType && + ![SpeciesId.SHEDINJA, SpeciesId.OGERPON, SpeciesId.TERAPAGOS].some(s => pokemon.hasSpecies(s)) + ); + } + + /** + * Applies {@linkcode TerrastalizeConsumable} + * @param pokemon The {@linkcode PlayerPokemon} that consumes the item + * @returns `true` if hp was restored + */ + apply({ pokemon }: PokemonRewardParams): boolean { + pokemon.teraType = this.teraType; + return true; + } +} // todo: denest + +export class TeraTypeRewardGenerator extends RewardGenerator { + 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; + } + 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; + } +} diff --git a/src/items/rewards/tm.ts b/src/items/rewards/tm.ts new file mode 100644 index 00000000000..b7fc45b5136 --- /dev/null +++ b/src/items/rewards/tm.ts @@ -0,0 +1,100 @@ +import { globalScene } from "#app/global-scene"; +import { tmPoolTiers, tmSpecies } from "#balance/tms"; +import { allMoves } from "#data/data-lists"; +import { LearnMoveType } from "#enums/learn-move-type"; +import type { MoveId } from "#enums/move-id"; +import { PokemonType } from "#enums/pokemon-type"; +import type { RarityTier } from "#enums/reward-tier"; +import type { PlayerPokemon } from "#field/pokemon"; +import { PokemonReward, type PokemonRewardParams, RewardGenerator } from "#items/reward"; +import { PartyUiHandler } from "#ui/party-ui-handler"; +import { padInt, randSeedItem } from "#utils/common"; +import i18next from "i18next"; + +export class TmReward extends PokemonReward { + public moveId: MoveId; + + constructor(moveId: MoveId) { + super( + "", + `tm_${PokemonType[allMoves[moveId].type].toLowerCase()}`, + (pokemon: PlayerPokemon) => { + if ( + pokemon.compatibleTms.indexOf(moveId) === -1 || + pokemon.getMoveset().filter(m => m.moveId === moveId).length + ) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "tm", + ); + + this.moveId = moveId; + } + + get name(): string { + return i18next.t("modifierType:ModifierType.TmModifierType.name", { + moveId: padInt(Object.keys(tmSpecies).indexOf(this.moveId.toString()) + 1, 3), + moveName: allMoves[this.moveId].name, + }); + } + + get description(): string { + return i18next.t( + globalScene.enableMoveInfo + ? "modifierType:ModifierType.TmModifierTypeWithInfo.description" + : "modifierType:ModifierType.TmModifierType.description", + { moveName: allMoves[this.moveId].name }, + ); + } + + /** + * Applies {@linkcode TmConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that should learn the TM + * @returns always `true` + */ + apply({ pokemon }: PokemonRewardParams): boolean { + globalScene.phaseManager.unshiftNew( + "LearnMovePhase", + globalScene.getPlayerParty().indexOf(pokemon), + this.moveId, + LearnMoveType.TM, + ); + + return true; + } +} + +export class TmRewardGenerator extends RewardGenerator { + private tier: RarityTier; + constructor(tier: RarityTier) { + 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), + ); + }); + 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); + } +} diff --git a/src/items/rewards/trainer-item-reward.ts b/src/items/rewards/trainer-item-reward.ts new file mode 100644 index 00000000000..2a126764585 --- /dev/null +++ b/src/items/rewards/trainer-item-reward.ts @@ -0,0 +1,53 @@ +import { globalScene } from "#app/global-scene"; +import { allTrainerItems } from "#data/data-lists"; +import { Stat, TEMP_BATTLE_STATS, type TempBattleStat } from "#enums/stat"; +import type { TrainerItemId } from "#enums/trainer-item-id"; +import { Reward, RewardGenerator } from "#items/reward"; +import { tempStatToTrainerItem } from "#items/trainer-items/x-items"; +import { randSeedItem } from "#utils/common"; + +export class TrainerItemReward extends Reward { + // TODO: This should not be public + public itemId: TrainerItemId; + constructor(itemId: TrainerItemId, group?: string) { + super("", "", group, "se/restore"); + this.itemId = itemId; + } + + get name(): string { + return allTrainerItems[this.itemId].name; + } + + get description(): string { + return allTrainerItems[this.itemId].description; + } + + get iconName(): string { + return allTrainerItems[this.itemId].iconName; + } + + apply(): boolean { + return globalScene.trainerItems.add(this.itemId); + } +} + +export class LapsingTrainerItemReward extends TrainerItemReward { + apply(): boolean { + return globalScene.trainerItems.add(this.itemId, allTrainerItems[this.itemId].getMaxStackCount()); + } +} + +export class TempStatStageBoosterRewardGenerator extends RewardGenerator { + public static readonly items: Record = { + [Stat.ATK]: "x_attack", + [Stat.DEF]: "x_defense", + [Stat.SPATK]: "x_sp_atk", + [Stat.SPDEF]: "x_sp_def", + [Stat.SPD]: "x_speed", + [Stat.ACC]: "x_accuracy", + }; + + override generateReward(pregenArgs?: TempBattleStat) { + return new LapsingTrainerItemReward(tempStatToTrainerItem[pregenArgs ?? randSeedItem(TEMP_BATTLE_STATS)]); + } +} diff --git a/src/items/rewards/voucher.ts b/src/items/rewards/voucher.ts new file mode 100644 index 00000000000..68b285fac8f --- /dev/null +++ b/src/items/rewards/voucher.ts @@ -0,0 +1,43 @@ +import { globalScene } from "#app/global-scene"; +import type { RewardId } from "#enums/reward-id"; +import { Reward } from "#items/reward"; +import { getVoucherTypeIcon, getVoucherTypeName, type VoucherType } from "#system/voucher"; +import i18next from "i18next"; + +export class AddVoucherReward extends Reward { + private voucherType: VoucherType; + private count: number; + + constructor(voucherType: VoucherType, count: number, id: RewardId) { + super("", getVoucherTypeIcon(voucherType), "voucher"); + this.count = count; + this.voucherType = voucherType; + this.id = id; + } + + get name(): string { + return i18next.t("modifierType:ModifierType.AddVoucherConsumableType.name", { + modifierCount: this.count, + voucherTypeName: getVoucherTypeName(this.voucherType), + }); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.AddVoucherConsumableType.description", { + modifierCount: this.count, + voucherTypeName: getVoucherTypeName(this.voucherType), + }); + } + + /** + * Applies {@linkcode AddVoucherReward} + * @param battleScene {@linkcode BattleScene} + * @returns always `true` + */ + apply(): boolean { + const voucherCounts = globalScene.gameData.voucherCounts; + voucherCounts[this.voucherType] += this.count; + + return true; + } +} diff --git a/src/items/trainer-item-manager.ts b/src/items/trainer-item-manager.ts index 6579df69ca5..895f80a6b25 100644 --- a/src/items/trainer-item-manager.ts +++ b/src/items/trainer-item-manager.ts @@ -6,7 +6,7 @@ import { type TrainerItemDataMap, type TrainerItemSaveData, type TrainerItemSpecs, -} from "#items/trainer-item-data-types"; +} from "#types/trainer-item-data-types"; import { getTypedEntries, getTypedKeys } from "#utils/common"; export class TrainerItemManager { diff --git a/src/items/trainer-item.ts b/src/items/trainer-item.ts index d247361e4fb..9e43ee7eb9b 100644 --- a/src/items/trainer-item.ts +++ b/src/items/trainer-item.ts @@ -1,22 +1,12 @@ import { globalScene } from "#app/global-scene"; -import { getPokemonNameWithAffix } from "#app/messages"; -import { getStatusEffectDescriptor, getStatusEffectHealText } from "#data/status-effect"; -import { BattlerTagType } from "#enums/battler-tag-type"; -import { getStatKey, Stat, type TempBattleStat } from "#enums/stat"; -import { StatusEffect } from "#enums/status-effect"; import { TextStyle } from "#enums/text-style"; import { TrainerItemEffect } from "#enums/trainer-item-effect"; -import { TrainerItemId, TrainerItemNames } from "#enums/trainer-item-id"; +import { type TrainerItemId, TrainerItemNames } from "#enums/trainer-item-id"; import type { TrainerItemManager } from "#items/trainer-item-manager"; +import type { NumberHolderParams, PreserveBerryParams } from "#types/trainer-item-parameter"; import { addTextObject } from "#ui/text"; -import { hslToHex, randSeedFloat, toDmgValue } from "#utils/common"; +import { hslToHex } from "#utils/common"; import i18next from "i18next"; -import type { - BooleanHolderParams, - NumberHolderParams, - PokemonParams, - PreserveBerryParams, -} from "./trainer-item-parameter"; export class TrainerItem { // public pokemonId: number; @@ -25,9 +15,6 @@ export class TrainerItem { public isLapsing = false; public effects: TrainerItemEffect[] = []; - //TODO: If this is actually never changed by any subclass, perhaps it should not be here - public soundName = "se/restore"; - constructor(type: TrainerItemId, maxStackCount = 1) { this.type = type; this.maxStackCount = maxStackCount; @@ -83,6 +70,35 @@ export class TrainerItem { } } +export class LapsingTrainerItem extends TrainerItem { + isLapsing = true; + + createIcon(battleCount: number): Phaser.GameObjects.Container { + const container = globalScene.add.container(0, 0); + + const item = globalScene.add.sprite(0, 12, "items").setFrame(this.iconName).setOrigin(0, 0.5); + container.add(item); + + // Linear interpolation on hue + const hue = Math.floor(120 * (battleCount / this.getMaxStackCount()) + 5); + + // Generates the color hex code with a constant saturation and lightness but varying hue + const typeHex = hslToHex(hue, 0.5, 0.9); + const strokeHex = hslToHex(hue, 0.7, 0.3); + + const battleCountText = addTextObject(27, 0, battleCount.toString(), TextStyle.PARTY, { + fontSize: "66px", + color: typeHex, + }); + battleCountText.setShadow(0, 0); + battleCountText.setStroke(strokeHex, 16); + battleCountText.setOrigin(1, 0); + container.add(battleCountText); + + return container; + } +} + // Candy Jar export class LevelIncrementBoosterTrainerItem extends TrainerItem { public effects: TrainerItemEffect[] = [TrainerItemEffect.LEVEL_INCREMENT_BOOSTER]; @@ -211,326 +227,3 @@ export class HealShopCostTrainerItem extends TrainerItem { moneyCost.value = Math.floor(moneyCost.value * this.shopMultiplier); } } - -export class LapsingTrainerItem extends TrainerItem { - isLapsing = true; - - createIcon(battleCount: number): Phaser.GameObjects.Container { - const container = globalScene.add.container(0, 0); - - const item = globalScene.add.sprite(0, 12, "items").setFrame(this.iconName).setOrigin(0, 0.5); - container.add(item); - - // Linear interpolation on hue - const hue = Math.floor(120 * (battleCount / this.getMaxStackCount()) + 5); - - // Generates the color hex code with a constant saturation and lightness but varying hue - const typeHex = hslToHex(hue, 0.5, 0.9); - const strokeHex = hslToHex(hue, 0.7, 0.3); - - const battleCountText = addTextObject(27, 0, battleCount.toString(), TextStyle.PARTY, { - fontSize: "66px", - color: typeHex, - }); - battleCountText.setShadow(0, 0); - battleCountText.setStroke(strokeHex, 16); - battleCountText.setOrigin(1, 0); - container.add(battleCountText); - - return container; - } -} - -export class DoubleBattleChanceBoosterTrainerItem extends LapsingTrainerItem { - public effects: TrainerItemEffect[] = [TrainerItemEffect.DOUBLE_BATTLE_CHANCE_BOOSTER]; - - get description(): string { - return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", { - battleCount: this.getMaxStackCount(), - }); - } - - apply(_manager: TrainerItemManager, params: NumberHolderParams) { - const doubleBattleChance = params.numberHolder; - // This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using randSeedInt - // A double battle will initiate if the generated number is 0 - doubleBattleChance.value /= 4; - } -} - -type TempStatToTrainerItemMap = { - [key in TempBattleStat]: TrainerItemId; -}; - -export const tempStatToTrainerItem: TempStatToTrainerItemMap = { - [Stat.ATK]: TrainerItemId.X_ATTACK, - [Stat.DEF]: TrainerItemId.X_DEFENSE, - [Stat.SPATK]: TrainerItemId.X_SP_ATK, - [Stat.SPDEF]: TrainerItemId.X_SP_DEF, - [Stat.SPD]: TrainerItemId.X_SPEED, - [Stat.ACC]: TrainerItemId.X_ACCURACY, -}; - -export class TempStatStageBoosterTrainerItem extends LapsingTrainerItem { - public effects: TrainerItemEffect[] = [TrainerItemEffect.TEMP_STAT_STAGE_BOOSTER]; - private stat: TempBattleStat; - - constructor(type: TrainerItemId, stat: TempBattleStat, stackCount?: number) { - super(type, stackCount); - - this.stat = stat; - } - - get name(): string { - return i18next.t(`modifierType:TempStatStageBoosterItem.${TrainerItemNames[this.type]?.toLowerCase()}`); - } - - get description(): string { - console.log(); - return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { - stat: i18next.t(getStatKey(this.stat)), - amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.percentage"), - }); - } - - apply(_manager: TrainerItemManager, params: NumberHolderParams) { - const statLevel = params.numberHolder; - const boost = 0.3; - statLevel.value += boost; - } -} - -export class TempAccuracyBoosterTrainerItem extends LapsingTrainerItem { - public effects: TrainerItemEffect[] = [TrainerItemEffect.TEMP_ACCURACY_BOOSTER]; - - get name(): string { - return i18next.t(`modifierType:TempStatStageBoosterItem.${TrainerItemNames[this.type]?.toLowerCase()}`); - } - - get description(): string { - console.log(); - return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { - stat: i18next.t(getStatKey(Stat.ACC)), - amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.percentage"), - }); - } - - apply(_manager: TrainerItemManager, params: NumberHolderParams) { - const statLevel = params.numberHolder; - const boost = 1; - statLevel.value += boost; - } -} - -export class TempCritBoosterTrainerItem extends LapsingTrainerItem { - public effects: TrainerItemEffect[] = [TrainerItemEffect.TEMP_CRIT_BOOSTER]; - - get description(): string { - return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { - stat: i18next.t("modifierType:ModifierType.DIRE_HIT.extra.raises"), - amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.stage"), - }); - } - - apply(_manager: TrainerItemManager, params: NumberHolderParams) { - const critLevel = params.numberHolder; - critLevel.value++; - } -} - -export class EnemyDamageBoosterTrainerItem extends TrainerItem { - public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_DAMAGE_BOOSTER]; - public damageBoost = 1.05; - - get iconName(): string { - return "wl_item_drop"; - } - - apply(manager: TrainerItemManager, params: NumberHolderParams): boolean { - const stack = manager.getStack(this.type); - const multiplier = params.numberHolder; - - multiplier.value = toDmgValue(multiplier.value * Math.pow(this.damageBoost, stack)); - - return true; - } - - getMaxStackCount(): number { - return 999; - } -} - -export class EnemyDamageReducerTrainerItem extends TrainerItem { - public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_DAMAGE_REDUCER]; - public damageReduction = 0.975; - - get iconName(): string { - return "wl_guard_spec"; - } - - apply(manager: TrainerItemManager, params: NumberHolderParams): boolean { - const stack = manager.getStack(this.type); - const multiplier = params.numberHolder; - - multiplier.value = toDmgValue(multiplier.value * Math.pow(this.damageReduction, stack)); - - return true; - } - - getMaxStackCount(): number { - return globalScene.currentBattle.waveIndex < 2000 ? 99 : 999; - } -} - -export class EnemyTurnHealTrainerItem extends TrainerItem { - public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_HEAL]; - public healPercent = 2; - - get iconName(): string { - return "wl_potion"; - } - - apply(manager: TrainerItemManager, params: PokemonParams): boolean { - const stack = manager.getStack(this.type); - const enemyPokemon = params.pokemon; - - if (!enemyPokemon.isFullHp()) { - globalScene.phaseManager.unshiftNew( - "PokemonHealPhase", - enemyPokemon.getBattlerIndex(), - Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * stack, 1), - i18next.t("modifier:enemyTurnHealApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon), - }), - true, - false, - false, - false, - true, - ); - return true; - } - - return false; - } -} - -export class EnemyAttackStatusEffectChanceTrainerItem extends TrainerItem { - public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_ATTACK_STATUS_CHANCE]; - public effect: StatusEffect; - - constructor(type: TrainerItemId, effect: StatusEffect, stackCount?: number) { - super(type, stackCount); - - this.effect = effect; - } - - get iconName(): string { - if (this.effect === StatusEffect.POISON) { - return "wl_antidote"; - } - if (this.effect === StatusEffect.PARALYSIS) { - return "wl_paralyze_heal"; - } - if (this.effect === StatusEffect.BURN) { - return "wl_burn_heal"; - } - return ""; - } - - get description(): string { - return i18next.t("modifierType:ModifierType.EnemyAttackStatusEffectChanceModifierType.description", { - chancePercent: this.getChance() * 100, - statusEffect: getStatusEffectDescriptor(this.effect), - }); - } - - apply(manager: TrainerItemManager, params: PokemonParams): boolean { - const stack = manager.getStack(this.type); - const enemyPokemon = params.pokemon; - const chance = this.getChance(); - - if (randSeedFloat() <= chance * stack) { - return enemyPokemon.trySetStatus(this.effect); - } - - return false; - } - - getChance(): number { - return 0.025 * (this.effect === StatusEffect.BURN || this.effect === StatusEffect.POISON ? 2 : 1); - } -} - -export class EnemyStatusEffectHealChanceTrainerItem extends TrainerItem { - public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_STATUS_HEAL_CHANCE]; - public chance = 0.025; - - get iconName(): string { - return "wl_full_heal"; - } - - apply(manager: TrainerItemManager, params: PokemonParams): boolean { - const stack = manager.getStack(this.type); - const enemyPokemon = params.pokemon; - - if (!enemyPokemon.status || randSeedFloat() > this.chance * stack) { - return false; - } - - globalScene.phaseManager.queueMessage( - getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)), - ); - enemyPokemon.resetStatus(); - enemyPokemon.updateInfo(); - return true; - } -} - -export class EnemyEndureChanceTrainerItem extends TrainerItem { - public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_ENDURE_CHANCE]; - public chance = 2; - - get iconName(): string { - return "wl_reset_urge"; - } - - get description(): string { - return i18next.t("modifierType:ModifierType.EnemyEndureChanceModifierType.description", { - chancePercent: this.chance, - }); - } - - apply(manager: TrainerItemManager, params: PokemonParams): boolean { - const stack = manager.getStack(this.type); - const target = params.pokemon; - - if (target.waveData.endured || target.randBattleSeedInt(100) >= this.chance * stack) { - return false; - } - - target.addTag(BattlerTagType.ENDURE_TOKEN, 1); - - target.waveData.endured = true; - - return true; - } -} - -export class EnemyFusionChanceTrainerItem extends TrainerItem { - public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_FUSED_CHANCE]; - public chance = 0.01; - - get iconName(): string { - return "wl_custom_spliced"; - } - - apply(manager: TrainerItemManager, params: BooleanHolderParams) { - const stack = manager.getStack(this.type); - const isFusion = params.booleanHolder; - if (randSeedFloat() > this.chance * stack) { - return false; - } - isFusion.value = true; - } -} diff --git a/src/items/trainer-items/enemy-tokens.ts b/src/items/trainer-items/enemy-tokens.ts new file mode 100644 index 00000000000..42b7cb63848 --- /dev/null +++ b/src/items/trainer-items/enemy-tokens.ts @@ -0,0 +1,209 @@ +import { globalScene } from "#app/global-scene"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { getStatusEffectDescriptor, getStatusEffectHealText } from "#data/status-effect"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { StatusEffect } from "#enums/status-effect"; +import { TrainerItemEffect } from "#enums/trainer-item-effect"; +import type { TrainerItemId } from "#enums/trainer-item-id"; +import { TrainerItem } from "#items/trainer-item"; +import type { TrainerItemManager } from "#items/trainer-item-manager"; +import type { BooleanHolderParams, NumberHolderParams, PokemonParams } from "#types/trainer-item-parameter"; +import { randSeedFloat, toDmgValue } from "#utils/common"; +import i18next from "i18next"; + +export class EnemyDamageBoosterTrainerItem extends TrainerItem { + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_DAMAGE_BOOSTER]; + public damageBoost = 1.05; + + get iconName(): string { + return "wl_item_drop"; + } + + apply(manager: TrainerItemManager, params: NumberHolderParams): boolean { + const stack = manager.getStack(this.type); + const multiplier = params.numberHolder; + + multiplier.value = toDmgValue(multiplier.value * Math.pow(this.damageBoost, stack)); + + return true; + } + + getMaxStackCount(): number { + return 999; + } +} + +export class EnemyDamageReducerTrainerItem extends TrainerItem { + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_DAMAGE_REDUCER]; + public damageReduction = 0.975; + + get iconName(): string { + return "wl_guard_spec"; + } + + apply(manager: TrainerItemManager, params: NumberHolderParams): boolean { + const stack = manager.getStack(this.type); + const multiplier = params.numberHolder; + + multiplier.value = toDmgValue(multiplier.value * Math.pow(this.damageReduction, stack)); + + return true; + } + + getMaxStackCount(): number { + return globalScene.currentBattle.waveIndex < 2000 ? 99 : 999; + } +} + +export class EnemyTurnHealTrainerItem extends TrainerItem { + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_HEAL]; + public healPercent = 2; + + get iconName(): string { + return "wl_potion"; + } + + apply(manager: TrainerItemManager, params: PokemonParams): boolean { + const stack = manager.getStack(this.type); + const enemyPokemon = params.pokemon; + + if (!enemyPokemon.isFullHp()) { + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + enemyPokemon.getBattlerIndex(), + Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * stack, 1), + i18next.t("modifier:enemyTurnHealApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon), + }), + true, + false, + false, + false, + true, + ); + return true; + } + + return false; + } +} + +export class EnemyAttackStatusEffectChanceTrainerItem extends TrainerItem { + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_ATTACK_STATUS_CHANCE]; + public effect: StatusEffect; + + constructor(type: TrainerItemId, effect: StatusEffect, stackCount?: number) { + super(type, stackCount); + + this.effect = effect; + } + + get iconName(): string { + if (this.effect === StatusEffect.POISON) { + return "wl_antidote"; + } + if (this.effect === StatusEffect.PARALYSIS) { + return "wl_paralyze_heal"; + } + if (this.effect === StatusEffect.BURN) { + return "wl_burn_heal"; + } + return ""; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.EnemyAttackStatusEffectChanceModifierType.description", { + chancePercent: this.getChance() * 100, + statusEffect: getStatusEffectDescriptor(this.effect), + }); + } + + apply(manager: TrainerItemManager, params: PokemonParams): boolean { + const stack = manager.getStack(this.type); + const enemyPokemon = params.pokemon; + const chance = this.getChance(); + + if (randSeedFloat() <= chance * stack) { + return enemyPokemon.trySetStatus(this.effect); + } + + return false; + } + + getChance(): number { + return 0.025 * (this.effect === StatusEffect.BURN || this.effect === StatusEffect.POISON ? 2 : 1); + } +} + +export class EnemyStatusEffectHealChanceTrainerItem extends TrainerItem { + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_STATUS_HEAL_CHANCE]; + public chance = 0.025; + + get iconName(): string { + return "wl_full_heal"; + } + + apply(manager: TrainerItemManager, params: PokemonParams): boolean { + const stack = manager.getStack(this.type); + const enemyPokemon = params.pokemon; + + if (!enemyPokemon.status || randSeedFloat() > this.chance * stack) { + return false; + } + + globalScene.phaseManager.queueMessage( + getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)), + ); + enemyPokemon.resetStatus(); + enemyPokemon.updateInfo(); + return true; + } +} + +export class EnemyEndureChanceTrainerItem extends TrainerItem { + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_ENDURE_CHANCE]; + public chance = 2; + + get iconName(): string { + return "wl_reset_urge"; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.EnemyEndureChanceModifierType.description", { + chancePercent: this.chance, + }); + } + + apply(manager: TrainerItemManager, params: PokemonParams): boolean { + const stack = manager.getStack(this.type); + const target = params.pokemon; + + if (target.waveData.endured || target.randBattleSeedInt(100) >= this.chance * stack) { + return false; + } + + target.addTag(BattlerTagType.ENDURE_TOKEN, 1); + + target.waveData.endured = true; + + return true; + } +} + +export class EnemyFusionChanceTrainerItem extends TrainerItem { + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_FUSED_CHANCE]; + public chance = 0.01; + + get iconName(): string { + return "wl_custom_spliced"; + } + + apply(manager: TrainerItemManager, params: BooleanHolderParams) { + const stack = manager.getStack(this.type); + const isFusion = params.booleanHolder; + if (randSeedFloat() > this.chance * stack) { + return false; + } + isFusion.value = true; + } +} diff --git a/src/items/trainer-items/lure.ts b/src/items/trainer-items/lure.ts new file mode 100644 index 00000000000..e9e6f3e8a34 --- /dev/null +++ b/src/items/trainer-items/lure.ts @@ -0,0 +1,22 @@ +import { TrainerItemEffect } from "#enums/trainer-item-effect"; +import { LapsingTrainerItem } from "#items/trainer-item"; +import type { TrainerItemManager } from "#items/trainer-item-manager"; +import type { NumberHolderParams } from "#types/trainer-item-parameter"; +import i18next from "i18next"; + +export class DoubleBattleChanceBoosterTrainerItem extends LapsingTrainerItem { + public effects: TrainerItemEffect[] = [TrainerItemEffect.DOUBLE_BATTLE_CHANCE_BOOSTER]; + + get description(): string { + return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", { + battleCount: this.getMaxStackCount(), + }); + } + + apply(_manager: TrainerItemManager, params: NumberHolderParams) { + const doubleBattleChance = params.numberHolder; + // This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using randSeedInt + // A double battle will initiate if the generated number is 0 + doubleBattleChance.value /= 4; + } +} diff --git a/src/items/trainer-items/x-items.ts b/src/items/trainer-items/x-items.ts new file mode 100644 index 00000000000..eecfd210db9 --- /dev/null +++ b/src/items/trainer-items/x-items.ts @@ -0,0 +1,87 @@ +import { getStatKey, Stat, type TempBattleStat } from "#enums/stat"; +import { TrainerItemEffect } from "#enums/trainer-item-effect"; +import { TrainerItemId, TrainerItemNames } from "#enums/trainer-item-id"; +import { LapsingTrainerItem } from "#items/trainer-item"; +import type { TrainerItemManager } from "#items/trainer-item-manager"; +import type { NumberHolderParams } from "#types/trainer-item-parameter"; +import i18next from "i18next"; + +type TempStatToTrainerItemMap = { + [key in TempBattleStat]: TrainerItemId; +}; + +export const tempStatToTrainerItem: TempStatToTrainerItemMap = { + [Stat.ATK]: TrainerItemId.X_ATTACK, + [Stat.DEF]: TrainerItemId.X_DEFENSE, + [Stat.SPATK]: TrainerItemId.X_SP_ATK, + [Stat.SPDEF]: TrainerItemId.X_SP_DEF, + [Stat.SPD]: TrainerItemId.X_SPEED, + [Stat.ACC]: TrainerItemId.X_ACCURACY, +}; + +export class TempStatStageBoosterTrainerItem extends LapsingTrainerItem { + public effects: TrainerItemEffect[] = [TrainerItemEffect.TEMP_STAT_STAGE_BOOSTER]; + private stat: TempBattleStat; + + constructor(type: TrainerItemId, stat: TempBattleStat, stackCount?: number) { + super(type, stackCount); + + this.stat = stat; + } + + get name(): string { + return i18next.t(`modifierType:TempStatStageBoosterItem.${TrainerItemNames[this.type]?.toLowerCase()}`); + } + + get description(): string { + console.log(); + return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { + stat: i18next.t(getStatKey(this.stat)), + amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.percentage"), + }); + } + + apply(_manager: TrainerItemManager, params: NumberHolderParams) { + const statLevel = params.numberHolder; + const boost = 0.3; + statLevel.value += boost; + } +} + +export class TempAccuracyBoosterTrainerItem extends LapsingTrainerItem { + public effects: TrainerItemEffect[] = [TrainerItemEffect.TEMP_ACCURACY_BOOSTER]; + + get name(): string { + return i18next.t(`modifierType:TempStatStageBoosterItem.${TrainerItemNames[this.type]?.toLowerCase()}`); + } + + get description(): string { + console.log(); + return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { + stat: i18next.t(getStatKey(Stat.ACC)), + amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.percentage"), + }); + } + + apply(_manager: TrainerItemManager, params: NumberHolderParams) { + const statLevel = params.numberHolder; + const boost = 1; + statLevel.value += boost; + } +} + +export class TempCritBoosterTrainerItem extends LapsingTrainerItem { + public effects: TrainerItemEffect[] = [TrainerItemEffect.TEMP_CRIT_BOOSTER]; + + get description(): string { + return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { + stat: i18next.t("modifierType:ModifierType.DIRE_HIT.extra.raises"), + amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.stage"), + }); + } + + apply(_manager: TrainerItemManager, params: NumberHolderParams) { + const critLevel = params.numberHolder; + critLevel.value++; + } +} diff --git a/src/overrides.ts b/src/overrides.ts index cd97a30f656..b061dc87a01 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -21,10 +21,10 @@ import { TrainerType } from "#enums/trainer-type"; import { Unlockables } from "#enums/unlockables"; import { VariantTier } from "#enums/variant-tier"; import { WeatherType } from "#enums/weather-type"; -import { HeldItemConfiguration } from "#items/held-item-data-types"; -import { TrainerItemConfiguration } from "#items/trainer-item-data-types"; import { Variant } from "#sprites/variant"; +import { HeldItemConfiguration } from "#types/held-item-data-types"; import { RewardSpecs } from "#types/rewards"; +import { TrainerItemConfiguration } from "#types/trainer-item-data-types"; /** * This comment block exists to prevent IDEs from automatically removing unused imports diff --git a/src/phases/select-reward-phase.ts b/src/phases/select-reward-phase.ts index be752fb4539..9d486d68f7b 100644 --- a/src/phases/select-reward-phase.ts +++ b/src/phases/select-reward-phase.ts @@ -5,14 +5,15 @@ import { RewardPoolType } from "#enums/reward-pool-type"; import type { RarityTier } from "#enums/reward-tier"; import { TrainerItemEffect } from "#enums/trainer-item-effect"; import { UiMode } from "#enums/ui-mode"; -import type { - PokemonMoveRecallRewardParams, - PokemonMoveRewardParams, - PokemonRewardParams, - Reward, - RewardOption, +import { + type PokemonMoveRecallRewardParams, + type PokemonMoveReward, + type PokemonMoveRewardParams, + PokemonReward, + type PokemonRewardParams, + type Reward, + type RewardOption, } from "#items/reward"; -import { FusePokemonReward, type PokemonMoveReward, PokemonReward, RememberMoveReward, TmReward } from "#items/reward"; import { type CustomRewardSettings, generatePlayerRewardOptions, @@ -20,6 +21,9 @@ import { getRewardPoolForType, } from "#items/reward-pool-utils"; import { getPlayerShopRewardOptionsForWave, isMoveReward, isRememberMoveReward, isTmReward } from "#items/reward-utils"; +import { FusePokemonReward } from "#items/rewards/fuse"; +import { RememberMoveReward } from "#items/rewards/remember-move"; +import { TmReward } from "#items/rewards/tm"; import { BattlePhase } from "#phases/battle-phase"; import { PartyOption, PartyUiHandler, PartyUiMode, type PokemonMoveSelectFilter } from "#ui/party-ui-handler"; import { type RewardSelectUiHandler, SHOP_OPTIONS_ROW_LIMIT } from "#ui/reward-select-ui-handler"; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index ec950f83687..b6c5db9a454 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -36,7 +36,6 @@ import { Unlockables } from "#enums/unlockables"; import { WeatherType } from "#enums/weather-type"; import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#events/arena"; import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; -import type { TrainerItemConfiguration, TrainerItemSaveData } from "#items/trainer-item-data-types"; import { MysteryEncounterSaveData } from "#mystery-encounters/mystery-encounter-save-data"; import type { Variant } from "#sprites/variant"; import { achvs } from "#system/achv"; @@ -59,6 +58,7 @@ import { import { VoucherType, vouchers } from "#system/voucher"; import { trainerConfigs } from "#trainers/trainer-config"; import type { DexData, DexEntry } from "#types/dex-data"; +import type { TrainerItemConfiguration, TrainerItemSaveData } from "#types/trainer-item-data-types"; import { RUN_HISTORY_LIMIT } from "#ui/run-history-ui-handler"; import { applyChallenges } from "#utils/challenge-utils"; import { executeIf, fixedInt, isLocal, NumberHolder, randInt, randSeedItem } from "#utils/common"; diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 055c0eb8172..3f60d091a5c 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -11,10 +11,10 @@ import type { PokemonType } from "#enums/pokemon-type"; import type { SpeciesId } from "#enums/species-id"; import { TrainerSlot } from "#enums/trainer-slot"; import { EnemyPokemon, Pokemon } from "#field/pokemon"; -import type { HeldItemSaveData } from "#items/held-item-data-types"; import { saveDataToConfig } from "#items/held-item-pool"; import { PokemonMove } from "#moves/pokemon-move"; import type { Variant } from "#sprites/variant"; +import type { HeldItemSaveData } from "#types/held-item-data-types"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; export class PokemonData {