mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-06 07:29:30 +02:00
Merge dac9e202a0
into 5bfcb1d379
This commit is contained in:
commit
3bcd3b816d
@ -183,7 +183,7 @@ input:-internal-autofill-selected {
|
||||
/* Show #apadStats only in battle and shop */
|
||||
#touchControls:not([data-ui-mode="COMMAND"]):not([data-ui-mode="FIGHT"]):not(
|
||||
[data-ui-mode="BALL"]
|
||||
):not([data-ui-mode="TARGET_SELECT"]):not([data-ui-mode="MODIFIER_SELECT"])
|
||||
):not([data-ui-mode="TARGET_SELECT"]):not([data-ui-mode="REWARD_SELECT"])
|
||||
#apadStats {
|
||||
display: none;
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
|
||||
export interface HeldModifierConfig {
|
||||
modifier: PokemonHeldItemModifierType | PokemonHeldItemModifier;
|
||||
stackCount?: number;
|
||||
isTransferable?: boolean;
|
||||
}
|
@ -24,15 +24,15 @@ export interface AbilityTranslationEntries {
|
||||
[key: string]: AbilityTranslationEntry;
|
||||
}
|
||||
|
||||
export interface ModifierTypeTranslationEntry {
|
||||
export interface RewardTranslationEntry {
|
||||
name?: string;
|
||||
description?: string;
|
||||
extra?: SimpleTranslationEntries;
|
||||
}
|
||||
|
||||
export interface ModifierTypeTranslationEntries {
|
||||
ModifierType: { [key: string]: ModifierTypeTranslationEntry };
|
||||
SpeciesBoosterItem: { [key: string]: ModifierTypeTranslationEntry };
|
||||
export interface RewardTranslationEntries {
|
||||
Reward: { [key: string]: RewardTranslationEntry };
|
||||
SpeciesBoosterItem: { [key: string]: RewardTranslationEntry };
|
||||
AttackTypeBoosterItem: SimpleTranslationEntries;
|
||||
TempStatStageBoosterItem: SimpleTranslationEntries;
|
||||
BaseStatBoosterItem: SimpleTranslationEntries;
|
||||
|
@ -1,32 +0,0 @@
|
||||
// Intentionally re-exports `ModifierConstructorMap` from `modifier.ts`
|
||||
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { ModifierConstructorMap } from "#modifiers/modifier";
|
||||
import type { ModifierType, WeightedModifierType } from "#modifiers/modifier-type";
|
||||
import type { ObjectValues } from "#types/type-helpers";
|
||||
|
||||
export type ModifierTypeFunc = () => ModifierType;
|
||||
export type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number;
|
||||
|
||||
export type { ModifierConstructorMap } from "#modifiers/modifier";
|
||||
|
||||
/**
|
||||
* Map of modifier names to their respective instance types
|
||||
*/
|
||||
export type ModifierInstanceMap = {
|
||||
[K in keyof ModifierConstructorMap]: InstanceType<ModifierConstructorMap[K]>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Union type of all modifier constructors.
|
||||
*/
|
||||
export type ModifierClass = ObjectValues<ModifierConstructorMap>;
|
||||
|
||||
/**
|
||||
* Union type of all modifier names as strings.
|
||||
*/
|
||||
export type ModifierString = keyof ModifierConstructorMap;
|
||||
|
||||
export type ModifierPool = {
|
||||
[tier: string]: WeightedModifierType[];
|
||||
};
|
32
src/@types/rewards.ts
Normal file
32
src/@types/rewards.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { RewardId } from "#enums/reward-id";
|
||||
import type { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { Reward, RewardGenerator } from "#items/reward";
|
||||
|
||||
export type RewardFunc = () => Reward | RewardGenerator;
|
||||
export type WeightedRewardWeightFunc = (party: Pokemon[], rerollCount?: number) => number;
|
||||
|
||||
export type RewardPoolId = RewardId | HeldItemId | TrainerItemId;
|
||||
|
||||
export type RewardGeneratorSpecs = {
|
||||
id: RewardId;
|
||||
args: RewardGeneratorArgs;
|
||||
};
|
||||
// TODO: fix this with correctly typed args for different RewardIds
|
||||
|
||||
export type RewardSpecs = RewardPoolId | RewardGeneratorSpecs;
|
||||
|
||||
export type RewardPoolEntry = {
|
||||
id: RewardPoolId;
|
||||
weight: number | WeightedRewardWeightFunc;
|
||||
maxWeight?: number;
|
||||
};
|
||||
|
||||
export type RewardPool = {
|
||||
[tier: string]: RewardPoolEntry[];
|
||||
};
|
||||
|
||||
export interface RewardPoolWeights {
|
||||
[tier: string]: number[];
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
import type { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
import type { EnemyPokemon } from "#field/pokemon";
|
||||
import type { PersistentModifier } from "#modifiers/modifier";
|
||||
import type { TrainerItemConfiguration } from "#items/trainer-item-data-types";
|
||||
import type { TrainerConfig } from "#trainers/trainer-config";
|
||||
import type { TrainerPartyTemplate } from "#trainers/trainer-party-template";
|
||||
|
||||
export type PartyTemplateFunc = () => TrainerPartyTemplate;
|
||||
export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon;
|
||||
export type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[];
|
||||
export type GenTrainerItemsFunc = (party: EnemyPokemon[]) => TrainerItemConfiguration;
|
||||
export type GenAIFunc = (party: EnemyPokemon[]) => void;
|
||||
|
||||
export interface TrainerTierPools {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ import { BattleSpec } from "#enums/battle-spec";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import type { Command } from "#enums/command";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
@ -15,8 +16,8 @@ import { TrainerType } from "#enums/trainer-type";
|
||||
import { TrainerVariant } from "#enums/trainer-variant";
|
||||
import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { Trainer } from "#field/trainer";
|
||||
import { MoneyMultiplierModifier, type PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import type { CustomModifierSettings } from "#modifiers/modifier-type";
|
||||
import type { CustomRewardSettings } from "#items/reward-pool-utils";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import i18next from "#plugins/i18n";
|
||||
import { MusicPreference } from "#system/settings";
|
||||
@ -68,7 +69,7 @@ export class Battle {
|
||||
public turnCommands: TurnCommands;
|
||||
public playerParticipantIds: Set<number> = new Set<number>();
|
||||
public battleScore = 0;
|
||||
public postBattleLoot: PokemonHeldItemModifier[] = [];
|
||||
public postBattleLoot: HeldItemId[] = [];
|
||||
public escapeAttempts = 0;
|
||||
public lastMove: MoveId;
|
||||
public battleSeed: string = randomString(16, true);
|
||||
@ -173,24 +174,12 @@ export class Battle {
|
||||
}
|
||||
|
||||
addPostBattleLoot(enemyPokemon: EnemyPokemon): void {
|
||||
this.postBattleLoot.push(
|
||||
...globalScene
|
||||
.findModifiers(
|
||||
m => m.is("PokemonHeldItemModifier") && m.pokemonId === enemyPokemon.id && m.isTransferable,
|
||||
false,
|
||||
)
|
||||
.map(i => {
|
||||
const ret = i as PokemonHeldItemModifier;
|
||||
//@ts-expect-error - this is awful to fix/change
|
||||
ret.pokemonId = null;
|
||||
return ret;
|
||||
}),
|
||||
);
|
||||
this.postBattleLoot.push(...enemyPokemon.getHeldItems());
|
||||
}
|
||||
|
||||
pickUpScatteredMoney(): void {
|
||||
const moneyAmount = new NumberHolder(globalScene.currentBattle.moneyScattered);
|
||||
globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount });
|
||||
|
||||
if (globalScene.arena.getTag(ArenaTagType.HAPPY_HOUR)) {
|
||||
moneyAmount.value *= 2;
|
||||
@ -491,7 +480,7 @@ export class FixedBattleConfig {
|
||||
public getTrainer: GetTrainerFunc;
|
||||
public getEnemyParty: GetEnemyPartyFunc;
|
||||
public seedOffsetWaveIndex: number;
|
||||
public customModifierRewardSettings?: CustomModifierSettings;
|
||||
public customRewardSettings?: CustomRewardSettings;
|
||||
|
||||
setBattleType(battleType: BattleType): FixedBattleConfig {
|
||||
this.battleType = battleType;
|
||||
@ -518,8 +507,8 @@ export class FixedBattleConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
setCustomModifierRewards(customModifierRewardSettings: CustomModifierSettings) {
|
||||
this.customModifierRewardSettings = customModifierRewardSettings;
|
||||
setCustomRewards(customRewardSettings: CustomRewardSettings) {
|
||||
this.customRewardSettings = customRewardSettings;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import type { ArenaTrapTag, SuppressAbilitiesTag } from "#data/arena-tag";
|
||||
import type { BattlerTag } from "#data/battler-tags";
|
||||
import { GroundedTag } from "#data/battler-tags";
|
||||
import { getBerryEffectFunc } from "#data/berry";
|
||||
import { allAbilities, allMoves } from "#data/data-lists";
|
||||
import { allAbilities, allHeldItems, allMoves } from "#data/data-lists";
|
||||
import { SpeciesFormChangeAbilityTrigger, SpeciesFormChangeWeatherTrigger } from "#data/form-change-triggers";
|
||||
import { Gender } from "#data/gender";
|
||||
import { getPokeballName } from "#data/pokeball";
|
||||
@ -28,6 +28,7 @@ import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import { Command } from "#enums/command";
|
||||
import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { CommonAnim } from "#enums/move-anims-common";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
@ -46,8 +47,7 @@ import { SwitchType } from "#enums/switch-type";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { BerryUsedEvent } from "#events/battle-scene";
|
||||
import type { EnemyPokemon, Pokemon } from "#field/pokemon";
|
||||
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import { BerryModifierType } from "#modifiers/modifier-type";
|
||||
import { type BerryHeldItem, berryTypeToHeldItem } from "#items/berry";
|
||||
import { applyMoveAttrs } from "#moves/apply-attrs";
|
||||
import { noAbilityTypeOverrideMoves } from "#moves/invalid-moves";
|
||||
import type { Move } from "#moves/move";
|
||||
@ -2137,7 +2137,7 @@ export abstract class PostAttackAbAttr extends AbAttr {
|
||||
|
||||
export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
|
||||
private stealCondition: PokemonAttackCondition | null;
|
||||
private stolenItem?: PokemonHeldItemModifier;
|
||||
private stolenItem?: HeldItemId;
|
||||
|
||||
constructor(stealCondition?: PokemonAttackCondition) {
|
||||
super();
|
||||
@ -2156,11 +2156,11 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
|
||||
hitResult < HitResult.NO_EFFECT &&
|
||||
(!this.stealCondition || this.stealCondition(pokemon, opponent, move))
|
||||
) {
|
||||
const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable);
|
||||
const heldItems = opponent.heldItemManager.getTransferableHeldItems();
|
||||
if (heldItems.length) {
|
||||
// Ensure that the stolen item in testing is the same as when the effect is applied
|
||||
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
|
||||
if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) {
|
||||
if (globalScene.canTransferHeldItem(this.stolenItem, opponent, pokemon)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -2170,28 +2170,21 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
|
||||
}
|
||||
|
||||
override apply({ opponent, pokemon }: PostMoveInteractionAbAttrParams): void {
|
||||
const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable);
|
||||
const heldItems = opponent.heldItemManager.getTransferableHeldItems();
|
||||
if (!this.stolenItem) {
|
||||
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
|
||||
}
|
||||
if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) {
|
||||
if (globalScene.tryTransferHeldItem(this.stolenItem, opponent, pokemon, false)) {
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("abilityTriggers:postAttackStealHeldItem", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
defenderName: opponent.name,
|
||||
stolenItemType: this.stolenItem.type.name,
|
||||
stolenItemType: allHeldItems[this.stolenItem].name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
this.stolenItem = undefined;
|
||||
}
|
||||
|
||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||
return globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id,
|
||||
target.isPlayer(),
|
||||
) as PokemonHeldItemModifier[];
|
||||
}
|
||||
}
|
||||
|
||||
export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
|
||||
@ -2282,7 +2275,7 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
|
||||
|
||||
export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
||||
private condition?: PokemonDefendCondition;
|
||||
private stolenItem?: PokemonHeldItemModifier;
|
||||
private stolenItem?: HeldItemId;
|
||||
|
||||
constructor(condition?: PokemonDefendCondition) {
|
||||
super();
|
||||
@ -2292,10 +2285,10 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
||||
|
||||
override canApply({ simulated, pokemon, opponent, move, hitResult }: PostMoveInteractionAbAttrParams): boolean {
|
||||
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, opponent, move))) {
|
||||
const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable);
|
||||
const heldItems = opponent.heldItemManager.getTransferableHeldItems();
|
||||
if (heldItems.length) {
|
||||
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
|
||||
if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) {
|
||||
if (globalScene.canTransferHeldItem(this.stolenItem, opponent, pokemon)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -2304,28 +2297,21 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
||||
}
|
||||
|
||||
override apply({ pokemon, opponent }: PostMoveInteractionAbAttrParams): void {
|
||||
const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable);
|
||||
const heldItems = opponent.heldItemManager.getTransferableHeldItems();
|
||||
if (!this.stolenItem) {
|
||||
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
|
||||
}
|
||||
if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) {
|
||||
if (globalScene.tryTransferHeldItem(this.stolenItem, opponent, pokemon, false)) {
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("abilityTriggers:postDefendStealHeldItem", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
attackerName: opponent.name,
|
||||
stolenItemType: this.stolenItem.type.name,
|
||||
stolenItemType: allHeldItems[this.stolenItem].name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
this.stolenItem = undefined;
|
||||
}
|
||||
|
||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||
return globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id,
|
||||
target.isPlayer(),
|
||||
) as PokemonHeldItemModifier[];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4661,10 +4647,14 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
||||
override canApply({ pokemon }: AbAttrBaseParams): boolean {
|
||||
// Ensure we have at least 1 recoverable berry (at least 1 berry in berriesEaten is not capped)
|
||||
const cappedBerries = new Set(
|
||||
globalScene
|
||||
.getModifiers(BerryModifier, pokemon.isPlayer())
|
||||
.filter(bm => bm.pokemonId === pokemon.id && bm.getCountUnderMax() < 1)
|
||||
.map(bm => bm.berryType),
|
||||
pokemon
|
||||
.getHeldItems()
|
||||
.filter(
|
||||
bm =>
|
||||
isItemInCategory(bm, HeldItemCategoryId.BERRY) &&
|
||||
pokemon.heldItemManager.getStack(bm) < allHeldItems[bm].maxStackCount,
|
||||
)
|
||||
.map(bm => (allHeldItems[bm] as BerryHeldItem).berryType),
|
||||
);
|
||||
|
||||
this.berriesUnderCap = pokemon.battleData.berriesEaten.filter(bt => !cappedBerries.has(bt));
|
||||
@ -4694,30 +4684,15 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
||||
const randomIdx = randSeedInt(this.berriesUnderCap.length);
|
||||
const chosenBerryType = this.berriesUnderCap[randomIdx];
|
||||
pokemon.battleData.berriesEaten.splice(randomIdx, 1); // Remove berry from memory
|
||||
const chosenBerry = new BerryModifierType(chosenBerryType);
|
||||
const chosenBerry = berryTypeToHeldItem[chosenBerryType];
|
||||
|
||||
// Add the randomly chosen berry or update the existing one
|
||||
const berryModifier = globalScene.findModifier(
|
||||
m => m instanceof BerryModifier && m.berryType === chosenBerryType && m.pokemonId === pokemon.id,
|
||||
pokemon.isPlayer(),
|
||||
) as BerryModifier | undefined;
|
||||
pokemon.heldItemManager.add(chosenBerry);
|
||||
|
||||
if (berryModifier) {
|
||||
berryModifier.stackCount++;
|
||||
} else {
|
||||
const newBerry = new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1);
|
||||
if (pokemon.isPlayer()) {
|
||||
globalScene.addModifier(newBerry);
|
||||
} else {
|
||||
globalScene.addEnemyModifier(newBerry);
|
||||
}
|
||||
}
|
||||
|
||||
globalScene.updateModifiers(pokemon.isPlayer());
|
||||
globalScene.updateItems(pokemon.isPlayer());
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("abilityTriggers:postTurnLootCreateEatenBerry", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
berryName: chosenBerry.name,
|
||||
berryName: allHeldItems[chosenBerry].name,
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
@ -4751,8 +4726,7 @@ export class CudChewConsumeBerryAbAttr extends AbAttr {
|
||||
// This doesn't count as "eating" a berry (for unnerve/stuff cheeks/unburden) as no item is consumed.
|
||||
for (const berryType of pokemon.summonData.berriesEatenLast) {
|
||||
getBerryEffectFunc(berryType)(pokemon);
|
||||
const bMod = new BerryModifier(new BerryModifierType(berryType), pokemon.id, berryType, 1);
|
||||
globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(bMod)); // trigger message
|
||||
globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(pokemon, berryType)); // trigger message
|
||||
}
|
||||
|
||||
// uncomment to make cheek pouch work with cud chew
|
||||
@ -5343,13 +5317,13 @@ export abstract class PostBattleAbAttr extends AbAttr {
|
||||
}
|
||||
|
||||
export class PostBattleLootAbAttr extends PostBattleAbAttr {
|
||||
private randItem?: PokemonHeldItemModifier;
|
||||
private randItem?: HeldItemId;
|
||||
|
||||
override canApply({ simulated, victory, pokemon }: PostBattleAbAttrParams): boolean {
|
||||
const postBattleLoot = globalScene.currentBattle.postBattleLoot;
|
||||
if (!simulated && postBattleLoot.length && victory) {
|
||||
this.randItem = randSeedItem(postBattleLoot);
|
||||
return globalScene.canTransferHeldItemModifier(this.randItem, pokemon, 1);
|
||||
return pokemon.heldItemManager.getStack(this.randItem) < allHeldItems[this.randItem].maxStackCount;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -5360,12 +5334,12 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr {
|
||||
this.randItem = randSeedItem(postBattleLoot);
|
||||
}
|
||||
|
||||
if (globalScene.tryTransferHeldItemModifier(this.randItem, pokemon, true, 1, true, undefined, false)) {
|
||||
if (pokemon.heldItemManager.add(this.randItem)) {
|
||||
postBattleLoot.splice(postBattleLoot.indexOf(this.randItem), 1);
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("abilityTriggers:postBattleLoot", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
itemName: this.randItem.type.name,
|
||||
itemName: allHeldItems[this.randItem].name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -6312,8 +6286,6 @@ class ForceSwitchOutHelper {
|
||||
}
|
||||
|
||||
if (!allyPokemon?.isActive(true)) {
|
||||
globalScene.clearEnemyHeldItemModifiers();
|
||||
|
||||
if (switchOutTarget.hp) {
|
||||
globalScene.phaseManager.pushNew("BattleEndPhase", false);
|
||||
|
||||
@ -6397,11 +6369,9 @@ class ForceSwitchOutHelper {
|
||||
* @returns The amount of health recovered by Shell Bell.
|
||||
*/
|
||||
function calculateShellBellRecovery(pokemon: Pokemon): number {
|
||||
const shellBellModifier = pokemon.getHeldItems().find(m => m instanceof HitHealModifier);
|
||||
if (shellBellModifier) {
|
||||
return toDmgValue(pokemon.turnData.totalDamageDealt / 8) * shellBellModifier.stackCount;
|
||||
}
|
||||
return 0;
|
||||
// Returns 0 if no Shell Bell is present
|
||||
const shellBellStack = pokemon.heldItemManager.getStack(HeldItemId.SHELL_BELL);
|
||||
return toDmgValue(pokemon.turnData.totalDamageDealt / 8) * shellBellStack;
|
||||
}
|
||||
|
||||
export interface PostDamageAbAttrParams extends AbAttrBaseParams {
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { speciesStarterCosts } from "#balance/starters";
|
||||
import { allMoves } from "#data/data-lists";
|
||||
import { allHeldItems, allMoves } from "#data/data-lists";
|
||||
import { Gender, getGenderSymbol } from "#data/gender";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
@ -12,7 +13,6 @@ import { SpeciesId } from "#enums/species-id";
|
||||
import { TimeOfDay } from "#enums/time-of-day";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { SpeciesStatBoosterItem, SpeciesStatBoosterModifierType } from "#modifiers/modifier-type";
|
||||
import { coerceArray, isNullOrUndefined, randSeedInt } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import i18next from "i18next";
|
||||
@ -99,7 +99,7 @@ const EvoCondKey = {
|
||||
SPECIES_CAUGHT: 12,
|
||||
GENDER: 13,
|
||||
NATURE: 14,
|
||||
HELD_ITEM: 15, // Currently checks only for species stat booster items
|
||||
HELD_ITEM: 15,
|
||||
} as const;
|
||||
|
||||
type EvolutionConditionData =
|
||||
@ -110,7 +110,7 @@ type EvolutionConditionData =
|
||||
{key: typeof EvoCondKey.GENDER, gender: Gender} |
|
||||
{key: typeof EvoCondKey.MOVE_TYPE | typeof EvoCondKey.PARTY_TYPE, pkmnType: PokemonType} |
|
||||
{key: typeof EvoCondKey.SPECIES_CAUGHT, speciesCaught: SpeciesId} |
|
||||
{key: typeof EvoCondKey.HELD_ITEM, itemKey: SpeciesStatBoosterItem} |
|
||||
{key: typeof EvoCondKey.HELD_ITEM, itemKey: HeldItemId} |
|
||||
{key: typeof EvoCondKey.NATURE, nature: Nature[]} |
|
||||
{key: typeof EvoCondKey.WEATHER, weather: WeatherType[]} |
|
||||
{key: typeof EvoCondKey.TYROGUE, move: TyrogueMove} |
|
||||
@ -177,10 +177,7 @@ export class SpeciesEvolutionCondition {
|
||||
case EvoCondKey.PARTY_TYPE:
|
||||
return globalScene.getPlayerParty().some(p => p.getTypes(false, false, true).includes(cond.pkmnType))
|
||||
case EvoCondKey.EVO_TREASURE_TRACKER:
|
||||
return pokemon.getHeldItems().some(m =>
|
||||
m.is("EvoTrackerModifier") &&
|
||||
m.getStackCount() + pokemon.getPersistentTreasureCount() >= cond.value
|
||||
);
|
||||
return allHeldItems[HeldItemId.GIMMIGHOUL_EVO_TRACKER].getStackCount(pokemon) >= cond.value;
|
||||
case EvoCondKey.GENDER:
|
||||
return pokemon.gender === cond.gender;
|
||||
case EvoCondKey.SHEDINJA: // Shedinja cannot be evolved into directly
|
||||
@ -201,7 +198,7 @@ export class SpeciesEvolutionCondition {
|
||||
case EvoCondKey.SPECIES_CAUGHT:
|
||||
return !!globalScene.gameData.dexData[cond.speciesCaught].caughtAttr;
|
||||
case EvoCondKey.HELD_ITEM:
|
||||
return pokemon.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === cond.itemKey)
|
||||
return pokemon.heldItemManager.hasItem(cond.itemKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1765,8 +1762,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
|
||||
new SpeciesEvolution(SpeciesId.DUSKNOIR, 1, EvolutionItem.REAPER_CLOTH, null, SpeciesWildEvolutionDelay.VERY_LONG)
|
||||
],
|
||||
[SpeciesId.CLAMPERL]: [
|
||||
new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: "DEEP_SEA_TOOTH"}, SpeciesWildEvolutionDelay.VERY_LONG),
|
||||
new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: "DEEP_SEA_SCALE"}, SpeciesWildEvolutionDelay.VERY_LONG)
|
||||
new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: HeldItemId.DEEP_SEA_TOOTH}, SpeciesWildEvolutionDelay.VERY_LONG),
|
||||
new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: HeldItemId.DEEP_SEA_SCALE}, SpeciesWildEvolutionDelay.VERY_LONG)
|
||||
],
|
||||
[SpeciesId.BOLDORE]: [
|
||||
new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
|
||||
@ -68591,324 +68591,324 @@ function transposeTmSpecies(): SpeciesTmMoves {
|
||||
export const speciesTmMoves: SpeciesTmMoves = transposeTmSpecies();
|
||||
|
||||
interface TmPoolTiers {
|
||||
[key: number]: ModifierTier
|
||||
[key: number]: RarityTier
|
||||
}
|
||||
|
||||
export const tmPoolTiers: TmPoolTiers = {
|
||||
[MoveId.MEGA_PUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.PAY_DAY]: ModifierTier.ULTRA,
|
||||
[MoveId.FIRE_PUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.ICE_PUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.THUNDER_PUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.SWORDS_DANCE]: ModifierTier.COMMON,
|
||||
[MoveId.CUT]: ModifierTier.COMMON,
|
||||
[MoveId.FLY]: ModifierTier.COMMON,
|
||||
[MoveId.MEGA_KICK]: ModifierTier.GREAT,
|
||||
[MoveId.BODY_SLAM]: ModifierTier.GREAT,
|
||||
[MoveId.TAKE_DOWN]: ModifierTier.GREAT,
|
||||
[MoveId.DOUBLE_EDGE]: ModifierTier.ULTRA,
|
||||
[MoveId.PIN_MISSILE]: ModifierTier.COMMON,
|
||||
[MoveId.ROAR]: ModifierTier.COMMON,
|
||||
[MoveId.FLAMETHROWER]: ModifierTier.ULTRA,
|
||||
[MoveId.HYDRO_PUMP]: ModifierTier.ULTRA,
|
||||
[MoveId.SURF]: ModifierTier.ULTRA,
|
||||
[MoveId.ICE_BEAM]: ModifierTier.ULTRA,
|
||||
[MoveId.BLIZZARD]: ModifierTier.ULTRA,
|
||||
[MoveId.PSYBEAM]: ModifierTier.GREAT,
|
||||
[MoveId.HYPER_BEAM]: ModifierTier.ULTRA,
|
||||
[MoveId.LOW_KICK]: ModifierTier.COMMON,
|
||||
[MoveId.COUNTER]: ModifierTier.COMMON,
|
||||
[MoveId.STRENGTH]: ModifierTier.GREAT,
|
||||
[MoveId.SOLAR_BEAM]: ModifierTier.ULTRA,
|
||||
[MoveId.FIRE_SPIN]: ModifierTier.COMMON,
|
||||
[MoveId.THUNDERBOLT]: ModifierTier.ULTRA,
|
||||
[MoveId.THUNDER_WAVE]: ModifierTier.COMMON,
|
||||
[MoveId.THUNDER]: ModifierTier.ULTRA,
|
||||
[MoveId.EARTHQUAKE]: ModifierTier.ULTRA,
|
||||
[MoveId.DIG]: ModifierTier.GREAT,
|
||||
[MoveId.TOXIC]: ModifierTier.GREAT,
|
||||
[MoveId.PSYCHIC]: ModifierTier.ULTRA,
|
||||
[MoveId.AGILITY]: ModifierTier.COMMON,
|
||||
[MoveId.NIGHT_SHADE]: ModifierTier.COMMON,
|
||||
[MoveId.SCREECH]: ModifierTier.COMMON,
|
||||
[MoveId.DOUBLE_TEAM]: ModifierTier.COMMON,
|
||||
[MoveId.CONFUSE_RAY]: ModifierTier.COMMON,
|
||||
[MoveId.LIGHT_SCREEN]: ModifierTier.COMMON,
|
||||
[MoveId.HAZE]: ModifierTier.COMMON,
|
||||
[MoveId.REFLECT]: ModifierTier.COMMON,
|
||||
[MoveId.FOCUS_ENERGY]: ModifierTier.COMMON,
|
||||
[MoveId.METRONOME]: ModifierTier.COMMON,
|
||||
[MoveId.SELF_DESTRUCT]: ModifierTier.GREAT,
|
||||
[MoveId.FIRE_BLAST]: ModifierTier.ULTRA,
|
||||
[MoveId.WATERFALL]: ModifierTier.GREAT,
|
||||
[MoveId.SWIFT]: ModifierTier.COMMON,
|
||||
[MoveId.AMNESIA]: ModifierTier.COMMON,
|
||||
[MoveId.DREAM_EATER]: ModifierTier.GREAT,
|
||||
[MoveId.LEECH_LIFE]: ModifierTier.ULTRA,
|
||||
[MoveId.FLASH]: ModifierTier.COMMON,
|
||||
[MoveId.EXPLOSION]: ModifierTier.GREAT,
|
||||
[MoveId.REST]: ModifierTier.COMMON,
|
||||
[MoveId.ROCK_SLIDE]: ModifierTier.GREAT,
|
||||
[MoveId.TRI_ATTACK]: ModifierTier.ULTRA,
|
||||
[MoveId.SUPER_FANG]: ModifierTier.COMMON,
|
||||
[MoveId.SUBSTITUTE]: ModifierTier.COMMON,
|
||||
[MoveId.THIEF]: ModifierTier.GREAT,
|
||||
[MoveId.SNORE]: ModifierTier.COMMON,
|
||||
[MoveId.CURSE]: ModifierTier.COMMON,
|
||||
[MoveId.REVERSAL]: ModifierTier.COMMON,
|
||||
[MoveId.SPITE]: ModifierTier.COMMON,
|
||||
[MoveId.PROTECT]: ModifierTier.COMMON,
|
||||
[MoveId.SCARY_FACE]: ModifierTier.COMMON,
|
||||
[MoveId.SLUDGE_BOMB]: ModifierTier.GREAT,
|
||||
[MoveId.MUD_SLAP]: ModifierTier.COMMON,
|
||||
[MoveId.SPIKES]: ModifierTier.COMMON,
|
||||
[MoveId.ICY_WIND]: ModifierTier.GREAT,
|
||||
[MoveId.OUTRAGE]: ModifierTier.ULTRA,
|
||||
[MoveId.SANDSTORM]: ModifierTier.COMMON,
|
||||
[MoveId.GIGA_DRAIN]: ModifierTier.ULTRA,
|
||||
[MoveId.ENDURE]: ModifierTier.COMMON,
|
||||
[MoveId.CHARM]: ModifierTier.COMMON,
|
||||
[MoveId.FALSE_SWIPE]: ModifierTier.COMMON,
|
||||
[MoveId.SWAGGER]: ModifierTier.COMMON,
|
||||
[MoveId.STEEL_WING]: ModifierTier.GREAT,
|
||||
[MoveId.ATTRACT]: ModifierTier.COMMON,
|
||||
[MoveId.SLEEP_TALK]: ModifierTier.COMMON,
|
||||
[MoveId.HEAL_BELL]: ModifierTier.COMMON,
|
||||
[MoveId.RETURN]: ModifierTier.ULTRA,
|
||||
[MoveId.FRUSTRATION]: ModifierTier.COMMON,
|
||||
[MoveId.SAFEGUARD]: ModifierTier.COMMON,
|
||||
[MoveId.PAIN_SPLIT]: ModifierTier.COMMON,
|
||||
[MoveId.MEGAHORN]: ModifierTier.ULTRA,
|
||||
[MoveId.BATON_PASS]: ModifierTier.COMMON,
|
||||
[MoveId.ENCORE]: ModifierTier.COMMON,
|
||||
[MoveId.IRON_TAIL]: ModifierTier.GREAT,
|
||||
[MoveId.METAL_CLAW]: ModifierTier.COMMON,
|
||||
[MoveId.SYNTHESIS]: ModifierTier.GREAT,
|
||||
[MoveId.HIDDEN_POWER]: ModifierTier.GREAT,
|
||||
[MoveId.RAIN_DANCE]: ModifierTier.COMMON,
|
||||
[MoveId.SUNNY_DAY]: ModifierTier.COMMON,
|
||||
[MoveId.CRUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.PSYCH_UP]: ModifierTier.COMMON,
|
||||
[MoveId.SHADOW_BALL]: ModifierTier.ULTRA,
|
||||
[MoveId.FUTURE_SIGHT]: ModifierTier.GREAT,
|
||||
[MoveId.ROCK_SMASH]: ModifierTier.COMMON,
|
||||
[MoveId.WHIRLPOOL]: ModifierTier.COMMON,
|
||||
[MoveId.BEAT_UP]: ModifierTier.COMMON,
|
||||
[MoveId.UPROAR]: ModifierTier.GREAT,
|
||||
[MoveId.HEAT_WAVE]: ModifierTier.ULTRA,
|
||||
[MoveId.HAIL]: ModifierTier.COMMON,
|
||||
[MoveId.TORMENT]: ModifierTier.COMMON,
|
||||
[MoveId.WILL_O_WISP]: ModifierTier.COMMON,
|
||||
[MoveId.FACADE]: ModifierTier.GREAT,
|
||||
[MoveId.FOCUS_PUNCH]: ModifierTier.COMMON,
|
||||
[MoveId.NATURE_POWER]: ModifierTier.COMMON,
|
||||
[MoveId.CHARGE]: ModifierTier.COMMON,
|
||||
[MoveId.TAUNT]: ModifierTier.COMMON,
|
||||
[MoveId.HELPING_HAND]: ModifierTier.COMMON,
|
||||
[MoveId.TRICK]: ModifierTier.COMMON,
|
||||
[MoveId.SUPERPOWER]: ModifierTier.ULTRA,
|
||||
[MoveId.RECYCLE]: ModifierTier.COMMON,
|
||||
[MoveId.REVENGE]: ModifierTier.GREAT,
|
||||
[MoveId.BRICK_BREAK]: ModifierTier.GREAT,
|
||||
[MoveId.KNOCK_OFF]: ModifierTier.GREAT,
|
||||
[MoveId.ENDEAVOR]: ModifierTier.COMMON,
|
||||
[MoveId.SKILL_SWAP]: ModifierTier.COMMON,
|
||||
[MoveId.IMPRISON]: ModifierTier.COMMON,
|
||||
[MoveId.SECRET_POWER]: ModifierTier.COMMON,
|
||||
[MoveId.DIVE]: ModifierTier.GREAT,
|
||||
[MoveId.FEATHER_DANCE]: ModifierTier.COMMON,
|
||||
[MoveId.BLAZE_KICK]: ModifierTier.GREAT,
|
||||
[MoveId.HYPER_VOICE]: ModifierTier.ULTRA,
|
||||
[MoveId.BLAST_BURN]: ModifierTier.ULTRA,
|
||||
[MoveId.HYDRO_CANNON]: ModifierTier.ULTRA,
|
||||
[MoveId.WEATHER_BALL]: ModifierTier.COMMON,
|
||||
[MoveId.FAKE_TEARS]: ModifierTier.COMMON,
|
||||
[MoveId.AIR_CUTTER]: ModifierTier.GREAT,
|
||||
[MoveId.OVERHEAT]: ModifierTier.ULTRA,
|
||||
[MoveId.ROCK_TOMB]: ModifierTier.GREAT,
|
||||
[MoveId.METAL_SOUND]: ModifierTier.COMMON,
|
||||
[MoveId.COSMIC_POWER]: ModifierTier.COMMON,
|
||||
[MoveId.SIGNAL_BEAM]: ModifierTier.GREAT,
|
||||
[MoveId.SAND_TOMB]: ModifierTier.COMMON,
|
||||
[MoveId.MUDDY_WATER]: ModifierTier.GREAT,
|
||||
[MoveId.BULLET_SEED]: ModifierTier.GREAT,
|
||||
[MoveId.AERIAL_ACE]: ModifierTier.GREAT,
|
||||
[MoveId.ICICLE_SPEAR]: ModifierTier.GREAT,
|
||||
[MoveId.IRON_DEFENSE]: ModifierTier.GREAT,
|
||||
[MoveId.DRAGON_CLAW]: ModifierTier.ULTRA,
|
||||
[MoveId.FRENZY_PLANT]: ModifierTier.ULTRA,
|
||||
[MoveId.BULK_UP]: ModifierTier.COMMON,
|
||||
[MoveId.BOUNCE]: ModifierTier.GREAT,
|
||||
[MoveId.MUD_SHOT]: ModifierTier.GREAT,
|
||||
[MoveId.POISON_TAIL]: ModifierTier.GREAT,
|
||||
[MoveId.COVET]: ModifierTier.GREAT,
|
||||
[MoveId.MAGICAL_LEAF]: ModifierTier.GREAT,
|
||||
[MoveId.CALM_MIND]: ModifierTier.GREAT,
|
||||
[MoveId.LEAF_BLADE]: ModifierTier.ULTRA,
|
||||
[MoveId.DRAGON_DANCE]: ModifierTier.GREAT,
|
||||
[MoveId.ROCK_BLAST]: ModifierTier.GREAT,
|
||||
[MoveId.WATER_PULSE]: ModifierTier.GREAT,
|
||||
[MoveId.ROOST]: ModifierTier.GREAT,
|
||||
[MoveId.GRAVITY]: ModifierTier.COMMON,
|
||||
[MoveId.GYRO_BALL]: ModifierTier.COMMON,
|
||||
[MoveId.BRINE]: ModifierTier.GREAT,
|
||||
[MoveId.PLUCK]: ModifierTier.GREAT,
|
||||
[MoveId.TAILWIND]: ModifierTier.GREAT,
|
||||
[MoveId.U_TURN]: ModifierTier.GREAT,
|
||||
[MoveId.CLOSE_COMBAT]: ModifierTier.ULTRA,
|
||||
[MoveId.PAYBACK]: ModifierTier.COMMON,
|
||||
[MoveId.ASSURANCE]: ModifierTier.COMMON,
|
||||
[MoveId.EMBARGO]: ModifierTier.COMMON,
|
||||
[MoveId.FLING]: ModifierTier.COMMON,
|
||||
[MoveId.GASTRO_ACID]: ModifierTier.GREAT,
|
||||
[MoveId.POWER_SWAP]: ModifierTier.COMMON,
|
||||
[MoveId.GUARD_SWAP]: ModifierTier.COMMON,
|
||||
[MoveId.WORRY_SEED]: ModifierTier.GREAT,
|
||||
[MoveId.TOXIC_SPIKES]: ModifierTier.GREAT,
|
||||
[MoveId.FLARE_BLITZ]: ModifierTier.ULTRA,
|
||||
[MoveId.AURA_SPHERE]: ModifierTier.GREAT,
|
||||
[MoveId.ROCK_POLISH]: ModifierTier.COMMON,
|
||||
[MoveId.POISON_JAB]: ModifierTier.GREAT,
|
||||
[MoveId.DARK_PULSE]: ModifierTier.GREAT,
|
||||
[MoveId.AQUA_TAIL]: ModifierTier.GREAT,
|
||||
[MoveId.SEED_BOMB]: ModifierTier.GREAT,
|
||||
[MoveId.AIR_SLASH]: ModifierTier.GREAT,
|
||||
[MoveId.X_SCISSOR]: ModifierTier.GREAT,
|
||||
[MoveId.BUG_BUZZ]: ModifierTier.GREAT,
|
||||
[MoveId.DRAGON_PULSE]: ModifierTier.GREAT,
|
||||
[MoveId.POWER_GEM]: ModifierTier.GREAT,
|
||||
[MoveId.DRAIN_PUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.VACUUM_WAVE]: ModifierTier.COMMON,
|
||||
[MoveId.FOCUS_BLAST]: ModifierTier.GREAT,
|
||||
[MoveId.ENERGY_BALL]: ModifierTier.GREAT,
|
||||
[MoveId.BRAVE_BIRD]: ModifierTier.ULTRA,
|
||||
[MoveId.EARTH_POWER]: ModifierTier.ULTRA,
|
||||
[MoveId.GIGA_IMPACT]: ModifierTier.GREAT,
|
||||
[MoveId.NASTY_PLOT]: ModifierTier.COMMON,
|
||||
[MoveId.AVALANCHE]: ModifierTier.GREAT,
|
||||
[MoveId.SHADOW_CLAW]: ModifierTier.GREAT,
|
||||
[MoveId.THUNDER_FANG]: ModifierTier.GREAT,
|
||||
[MoveId.ICE_FANG]: ModifierTier.GREAT,
|
||||
[MoveId.FIRE_FANG]: ModifierTier.GREAT,
|
||||
[MoveId.PSYCHO_CUT]: ModifierTier.GREAT,
|
||||
[MoveId.ZEN_HEADBUTT]: ModifierTier.GREAT,
|
||||
[MoveId.FLASH_CANNON]: ModifierTier.GREAT,
|
||||
[MoveId.ROCK_CLIMB]: ModifierTier.GREAT,
|
||||
[MoveId.DEFOG]: ModifierTier.COMMON,
|
||||
[MoveId.TRICK_ROOM]: ModifierTier.COMMON,
|
||||
[MoveId.DRACO_METEOR]: ModifierTier.ULTRA,
|
||||
[MoveId.LEAF_STORM]: ModifierTier.ULTRA,
|
||||
[MoveId.POWER_WHIP]: ModifierTier.ULTRA,
|
||||
[MoveId.CROSS_POISON]: ModifierTier.GREAT,
|
||||
[MoveId.GUNK_SHOT]: ModifierTier.ULTRA,
|
||||
[MoveId.IRON_HEAD]: ModifierTier.GREAT,
|
||||
[MoveId.STONE_EDGE]: ModifierTier.ULTRA,
|
||||
[MoveId.STEALTH_ROCK]: ModifierTier.COMMON,
|
||||
[MoveId.GRASS_KNOT]: ModifierTier.ULTRA,
|
||||
[MoveId.BUG_BITE]: ModifierTier.GREAT,
|
||||
[MoveId.CHARGE_BEAM]: ModifierTier.GREAT,
|
||||
[MoveId.HONE_CLAWS]: ModifierTier.COMMON,
|
||||
[MoveId.WONDER_ROOM]: ModifierTier.COMMON,
|
||||
[MoveId.PSYSHOCK]: ModifierTier.GREAT,
|
||||
[MoveId.VENOSHOCK]: ModifierTier.GREAT,
|
||||
[MoveId.MAGIC_ROOM]: ModifierTier.COMMON,
|
||||
[MoveId.SMACK_DOWN]: ModifierTier.COMMON,
|
||||
[MoveId.SLUDGE_WAVE]: ModifierTier.GREAT,
|
||||
[MoveId.HEAVY_SLAM]: ModifierTier.GREAT,
|
||||
[MoveId.ELECTRO_BALL]: ModifierTier.GREAT,
|
||||
[MoveId.FLAME_CHARGE]: ModifierTier.GREAT,
|
||||
[MoveId.LOW_SWEEP]: ModifierTier.GREAT,
|
||||
[MoveId.ACID_SPRAY]: ModifierTier.COMMON,
|
||||
[MoveId.FOUL_PLAY]: ModifierTier.ULTRA,
|
||||
[MoveId.ROUND]: ModifierTier.COMMON,
|
||||
[MoveId.ECHOED_VOICE]: ModifierTier.COMMON,
|
||||
[MoveId.STORED_POWER]: ModifierTier.COMMON,
|
||||
[MoveId.ALLY_SWITCH]: ModifierTier.COMMON,
|
||||
[MoveId.SCALD]: ModifierTier.GREAT,
|
||||
[MoveId.HEX]: ModifierTier.GREAT,
|
||||
[MoveId.SKY_DROP]: ModifierTier.GREAT,
|
||||
[MoveId.INCINERATE]: ModifierTier.GREAT,
|
||||
[MoveId.QUASH]: ModifierTier.COMMON,
|
||||
[MoveId.ACROBATICS]: ModifierTier.GREAT,
|
||||
[MoveId.RETALIATE]: ModifierTier.GREAT,
|
||||
[MoveId.WATER_PLEDGE]: ModifierTier.GREAT,
|
||||
[MoveId.FIRE_PLEDGE]: ModifierTier.GREAT,
|
||||
[MoveId.GRASS_PLEDGE]: ModifierTier.GREAT,
|
||||
[MoveId.VOLT_SWITCH]: ModifierTier.GREAT,
|
||||
[MoveId.STRUGGLE_BUG]: ModifierTier.COMMON,
|
||||
[MoveId.BULLDOZE]: ModifierTier.GREAT,
|
||||
[MoveId.FROST_BREATH]: ModifierTier.GREAT,
|
||||
[MoveId.DRAGON_TAIL]: ModifierTier.GREAT,
|
||||
[MoveId.WORK_UP]: ModifierTier.COMMON,
|
||||
[MoveId.ELECTROWEB]: ModifierTier.GREAT,
|
||||
[MoveId.WILD_CHARGE]: ModifierTier.GREAT,
|
||||
[MoveId.DRILL_RUN]: ModifierTier.GREAT,
|
||||
[MoveId.RAZOR_SHELL]: ModifierTier.GREAT,
|
||||
[MoveId.HEAT_CRASH]: ModifierTier.GREAT,
|
||||
[MoveId.TAIL_SLAP]: ModifierTier.GREAT,
|
||||
[MoveId.HURRICANE]: ModifierTier.ULTRA,
|
||||
[MoveId.SNARL]: ModifierTier.COMMON,
|
||||
[MoveId.PHANTOM_FORCE]: ModifierTier.ULTRA,
|
||||
[MoveId.PETAL_BLIZZARD]: ModifierTier.GREAT,
|
||||
[MoveId.DISARMING_VOICE]: ModifierTier.GREAT,
|
||||
[MoveId.DRAINING_KISS]: ModifierTier.GREAT,
|
||||
[MoveId.GRASSY_TERRAIN]: ModifierTier.COMMON,
|
||||
[MoveId.MISTY_TERRAIN]: ModifierTier.COMMON,
|
||||
[MoveId.PLAY_ROUGH]: ModifierTier.GREAT,
|
||||
[MoveId.CONFIDE]: ModifierTier.COMMON,
|
||||
[MoveId.MYSTICAL_FIRE]: ModifierTier.GREAT,
|
||||
[MoveId.EERIE_IMPULSE]: ModifierTier.COMMON,
|
||||
[MoveId.VENOM_DRENCH]: ModifierTier.COMMON,
|
||||
[MoveId.ELECTRIC_TERRAIN]: ModifierTier.COMMON,
|
||||
[MoveId.DAZZLING_GLEAM]: ModifierTier.ULTRA,
|
||||
[MoveId.INFESTATION]: ModifierTier.COMMON,
|
||||
[MoveId.POWER_UP_PUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.DARKEST_LARIAT]: ModifierTier.GREAT,
|
||||
[MoveId.HIGH_HORSEPOWER]: ModifierTier.ULTRA,
|
||||
[MoveId.SOLAR_BLADE]: ModifierTier.GREAT,
|
||||
[MoveId.THROAT_CHOP]: ModifierTier.GREAT,
|
||||
[MoveId.POLLEN_PUFF]: ModifierTier.GREAT,
|
||||
[MoveId.PSYCHIC_TERRAIN]: ModifierTier.COMMON,
|
||||
[MoveId.LUNGE]: ModifierTier.GREAT,
|
||||
[MoveId.SPEED_SWAP]: ModifierTier.COMMON,
|
||||
[MoveId.SMART_STRIKE]: ModifierTier.GREAT,
|
||||
[MoveId.BRUTAL_SWING]: ModifierTier.GREAT,
|
||||
[MoveId.AURORA_VEIL]: ModifierTier.COMMON,
|
||||
[MoveId.PSYCHIC_FANGS]: ModifierTier.GREAT,
|
||||
[MoveId.STOMPING_TANTRUM]: ModifierTier.GREAT,
|
||||
[MoveId.LIQUIDATION]: ModifierTier.ULTRA,
|
||||
[MoveId.BODY_PRESS]: ModifierTier.ULTRA,
|
||||
[MoveId.BREAKING_SWIPE]: ModifierTier.GREAT,
|
||||
[MoveId.STEEL_BEAM]: ModifierTier.ULTRA,
|
||||
[MoveId.EXPANDING_FORCE]: ModifierTier.GREAT,
|
||||
[MoveId.STEEL_ROLLER]: ModifierTier.COMMON,
|
||||
[MoveId.SCALE_SHOT]: ModifierTier.ULTRA,
|
||||
[MoveId.METEOR_BEAM]: ModifierTier.GREAT,
|
||||
[MoveId.MISTY_EXPLOSION]: ModifierTier.COMMON,
|
||||
[MoveId.GRASSY_GLIDE]: ModifierTier.COMMON,
|
||||
[MoveId.RISING_VOLTAGE]: ModifierTier.COMMON,
|
||||
[MoveId.TERRAIN_PULSE]: ModifierTier.COMMON,
|
||||
[MoveId.SKITTER_SMACK]: ModifierTier.GREAT,
|
||||
[MoveId.BURNING_JEALOUSY]: ModifierTier.GREAT,
|
||||
[MoveId.LASH_OUT]: ModifierTier.GREAT,
|
||||
[MoveId.POLTERGEIST]: ModifierTier.ULTRA,
|
||||
[MoveId.CORROSIVE_GAS]: ModifierTier.COMMON,
|
||||
[MoveId.COACHING]: ModifierTier.COMMON,
|
||||
[MoveId.FLIP_TURN]: ModifierTier.COMMON,
|
||||
[MoveId.TRIPLE_AXEL]: ModifierTier.COMMON,
|
||||
[MoveId.DUAL_WINGBEAT]: ModifierTier.COMMON,
|
||||
[MoveId.SCORCHING_SANDS]: ModifierTier.GREAT,
|
||||
[MoveId.TERA_BLAST]: ModifierTier.GREAT,
|
||||
[MoveId.ICE_SPINNER]: ModifierTier.GREAT,
|
||||
[MoveId.SNOWSCAPE]: ModifierTier.COMMON,
|
||||
[MoveId.POUNCE]: ModifierTier.COMMON,
|
||||
[MoveId.TRAILBLAZE]: ModifierTier.COMMON,
|
||||
[MoveId.CHILLING_WATER]: ModifierTier.COMMON,
|
||||
[MoveId.HARD_PRESS]: ModifierTier.GREAT,
|
||||
[MoveId.DRAGON_CHEER]: ModifierTier.COMMON,
|
||||
[MoveId.ALLURING_VOICE]: ModifierTier.GREAT,
|
||||
[MoveId.TEMPER_FLARE]: ModifierTier.GREAT,
|
||||
[MoveId.SUPERCELL_SLAM]: ModifierTier.GREAT,
|
||||
[MoveId.PSYCHIC_NOISE]: ModifierTier.GREAT,
|
||||
[MoveId.UPPER_HAND]: ModifierTier.COMMON,
|
||||
[MoveId.MEGA_PUNCH]: RarityTier.GREAT,
|
||||
[MoveId.PAY_DAY]: RarityTier.ULTRA,
|
||||
[MoveId.FIRE_PUNCH]: RarityTier.GREAT,
|
||||
[MoveId.ICE_PUNCH]: RarityTier.GREAT,
|
||||
[MoveId.THUNDER_PUNCH]: RarityTier.GREAT,
|
||||
[MoveId.SWORDS_DANCE]: RarityTier.COMMON,
|
||||
[MoveId.CUT]: RarityTier.COMMON,
|
||||
[MoveId.FLY]: RarityTier.COMMON,
|
||||
[MoveId.MEGA_KICK]: RarityTier.GREAT,
|
||||
[MoveId.BODY_SLAM]: RarityTier.GREAT,
|
||||
[MoveId.TAKE_DOWN]: RarityTier.GREAT,
|
||||
[MoveId.DOUBLE_EDGE]: RarityTier.ULTRA,
|
||||
[MoveId.PIN_MISSILE]: RarityTier.COMMON,
|
||||
[MoveId.ROAR]: RarityTier.COMMON,
|
||||
[MoveId.FLAMETHROWER]: RarityTier.ULTRA,
|
||||
[MoveId.HYDRO_PUMP]: RarityTier.ULTRA,
|
||||
[MoveId.SURF]: RarityTier.ULTRA,
|
||||
[MoveId.ICE_BEAM]: RarityTier.ULTRA,
|
||||
[MoveId.BLIZZARD]: RarityTier.ULTRA,
|
||||
[MoveId.PSYBEAM]: RarityTier.GREAT,
|
||||
[MoveId.HYPER_BEAM]: RarityTier.ULTRA,
|
||||
[MoveId.LOW_KICK]: RarityTier.COMMON,
|
||||
[MoveId.COUNTER]: RarityTier.COMMON,
|
||||
[MoveId.STRENGTH]: RarityTier.GREAT,
|
||||
[MoveId.SOLAR_BEAM]: RarityTier.ULTRA,
|
||||
[MoveId.FIRE_SPIN]: RarityTier.COMMON,
|
||||
[MoveId.THUNDERBOLT]: RarityTier.ULTRA,
|
||||
[MoveId.THUNDER_WAVE]: RarityTier.COMMON,
|
||||
[MoveId.THUNDER]: RarityTier.ULTRA,
|
||||
[MoveId.EARTHQUAKE]: RarityTier.ULTRA,
|
||||
[MoveId.DIG]: RarityTier.GREAT,
|
||||
[MoveId.TOXIC]: RarityTier.GREAT,
|
||||
[MoveId.PSYCHIC]: RarityTier.ULTRA,
|
||||
[MoveId.AGILITY]: RarityTier.COMMON,
|
||||
[MoveId.NIGHT_SHADE]: RarityTier.COMMON,
|
||||
[MoveId.SCREECH]: RarityTier.COMMON,
|
||||
[MoveId.DOUBLE_TEAM]: RarityTier.COMMON,
|
||||
[MoveId.CONFUSE_RAY]: RarityTier.COMMON,
|
||||
[MoveId.LIGHT_SCREEN]: RarityTier.COMMON,
|
||||
[MoveId.HAZE]: RarityTier.COMMON,
|
||||
[MoveId.REFLECT]: RarityTier.COMMON,
|
||||
[MoveId.FOCUS_ENERGY]: RarityTier.COMMON,
|
||||
[MoveId.METRONOME]: RarityTier.COMMON,
|
||||
[MoveId.SELF_DESTRUCT]: RarityTier.GREAT,
|
||||
[MoveId.FIRE_BLAST]: RarityTier.ULTRA,
|
||||
[MoveId.WATERFALL]: RarityTier.GREAT,
|
||||
[MoveId.SWIFT]: RarityTier.COMMON,
|
||||
[MoveId.AMNESIA]: RarityTier.COMMON,
|
||||
[MoveId.DREAM_EATER]: RarityTier.GREAT,
|
||||
[MoveId.LEECH_LIFE]: RarityTier.ULTRA,
|
||||
[MoveId.FLASH]: RarityTier.COMMON,
|
||||
[MoveId.EXPLOSION]: RarityTier.GREAT,
|
||||
[MoveId.REST]: RarityTier.COMMON,
|
||||
[MoveId.ROCK_SLIDE]: RarityTier.GREAT,
|
||||
[MoveId.TRI_ATTACK]: RarityTier.ULTRA,
|
||||
[MoveId.SUPER_FANG]: RarityTier.COMMON,
|
||||
[MoveId.SUBSTITUTE]: RarityTier.COMMON,
|
||||
[MoveId.THIEF]: RarityTier.GREAT,
|
||||
[MoveId.SNORE]: RarityTier.COMMON,
|
||||
[MoveId.CURSE]: RarityTier.COMMON,
|
||||
[MoveId.REVERSAL]: RarityTier.COMMON,
|
||||
[MoveId.SPITE]: RarityTier.COMMON,
|
||||
[MoveId.PROTECT]: RarityTier.COMMON,
|
||||
[MoveId.SCARY_FACE]: RarityTier.COMMON,
|
||||
[MoveId.SLUDGE_BOMB]: RarityTier.GREAT,
|
||||
[MoveId.MUD_SLAP]: RarityTier.COMMON,
|
||||
[MoveId.SPIKES]: RarityTier.COMMON,
|
||||
[MoveId.ICY_WIND]: RarityTier.GREAT,
|
||||
[MoveId.OUTRAGE]: RarityTier.ULTRA,
|
||||
[MoveId.SANDSTORM]: RarityTier.COMMON,
|
||||
[MoveId.GIGA_DRAIN]: RarityTier.ULTRA,
|
||||
[MoveId.ENDURE]: RarityTier.COMMON,
|
||||
[MoveId.CHARM]: RarityTier.COMMON,
|
||||
[MoveId.FALSE_SWIPE]: RarityTier.COMMON,
|
||||
[MoveId.SWAGGER]: RarityTier.COMMON,
|
||||
[MoveId.STEEL_WING]: RarityTier.GREAT,
|
||||
[MoveId.ATTRACT]: RarityTier.COMMON,
|
||||
[MoveId.SLEEP_TALK]: RarityTier.COMMON,
|
||||
[MoveId.HEAL_BELL]: RarityTier.COMMON,
|
||||
[MoveId.RETURN]: RarityTier.ULTRA,
|
||||
[MoveId.FRUSTRATION]: RarityTier.COMMON,
|
||||
[MoveId.SAFEGUARD]: RarityTier.COMMON,
|
||||
[MoveId.PAIN_SPLIT]: RarityTier.COMMON,
|
||||
[MoveId.MEGAHORN]: RarityTier.ULTRA,
|
||||
[MoveId.BATON_PASS]: RarityTier.COMMON,
|
||||
[MoveId.ENCORE]: RarityTier.COMMON,
|
||||
[MoveId.IRON_TAIL]: RarityTier.GREAT,
|
||||
[MoveId.METAL_CLAW]: RarityTier.COMMON,
|
||||
[MoveId.SYNTHESIS]: RarityTier.GREAT,
|
||||
[MoveId.HIDDEN_POWER]: RarityTier.GREAT,
|
||||
[MoveId.RAIN_DANCE]: RarityTier.COMMON,
|
||||
[MoveId.SUNNY_DAY]: RarityTier.COMMON,
|
||||
[MoveId.CRUNCH]: RarityTier.GREAT,
|
||||
[MoveId.PSYCH_UP]: RarityTier.COMMON,
|
||||
[MoveId.SHADOW_BALL]: RarityTier.ULTRA,
|
||||
[MoveId.FUTURE_SIGHT]: RarityTier.GREAT,
|
||||
[MoveId.ROCK_SMASH]: RarityTier.COMMON,
|
||||
[MoveId.WHIRLPOOL]: RarityTier.COMMON,
|
||||
[MoveId.BEAT_UP]: RarityTier.COMMON,
|
||||
[MoveId.UPROAR]: RarityTier.GREAT,
|
||||
[MoveId.HEAT_WAVE]: RarityTier.ULTRA,
|
||||
[MoveId.HAIL]: RarityTier.COMMON,
|
||||
[MoveId.TORMENT]: RarityTier.COMMON,
|
||||
[MoveId.WILL_O_WISP]: RarityTier.COMMON,
|
||||
[MoveId.FACADE]: RarityTier.GREAT,
|
||||
[MoveId.FOCUS_PUNCH]: RarityTier.COMMON,
|
||||
[MoveId.NATURE_POWER]: RarityTier.COMMON,
|
||||
[MoveId.CHARGE]: RarityTier.COMMON,
|
||||
[MoveId.TAUNT]: RarityTier.COMMON,
|
||||
[MoveId.HELPING_HAND]: RarityTier.COMMON,
|
||||
[MoveId.TRICK]: RarityTier.COMMON,
|
||||
[MoveId.SUPERPOWER]: RarityTier.ULTRA,
|
||||
[MoveId.RECYCLE]: RarityTier.COMMON,
|
||||
[MoveId.REVENGE]: RarityTier.GREAT,
|
||||
[MoveId.BRICK_BREAK]: RarityTier.GREAT,
|
||||
[MoveId.KNOCK_OFF]: RarityTier.GREAT,
|
||||
[MoveId.ENDEAVOR]: RarityTier.COMMON,
|
||||
[MoveId.SKILL_SWAP]: RarityTier.COMMON,
|
||||
[MoveId.IMPRISON]: RarityTier.COMMON,
|
||||
[MoveId.SECRET_POWER]: RarityTier.COMMON,
|
||||
[MoveId.DIVE]: RarityTier.GREAT,
|
||||
[MoveId.FEATHER_DANCE]: RarityTier.COMMON,
|
||||
[MoveId.BLAZE_KICK]: RarityTier.GREAT,
|
||||
[MoveId.HYPER_VOICE]: RarityTier.ULTRA,
|
||||
[MoveId.BLAST_BURN]: RarityTier.ULTRA,
|
||||
[MoveId.HYDRO_CANNON]: RarityTier.ULTRA,
|
||||
[MoveId.WEATHER_BALL]: RarityTier.COMMON,
|
||||
[MoveId.FAKE_TEARS]: RarityTier.COMMON,
|
||||
[MoveId.AIR_CUTTER]: RarityTier.GREAT,
|
||||
[MoveId.OVERHEAT]: RarityTier.ULTRA,
|
||||
[MoveId.ROCK_TOMB]: RarityTier.GREAT,
|
||||
[MoveId.METAL_SOUND]: RarityTier.COMMON,
|
||||
[MoveId.COSMIC_POWER]: RarityTier.COMMON,
|
||||
[MoveId.SIGNAL_BEAM]: RarityTier.GREAT,
|
||||
[MoveId.SAND_TOMB]: RarityTier.COMMON,
|
||||
[MoveId.MUDDY_WATER]: RarityTier.GREAT,
|
||||
[MoveId.BULLET_SEED]: RarityTier.GREAT,
|
||||
[MoveId.AERIAL_ACE]: RarityTier.GREAT,
|
||||
[MoveId.ICICLE_SPEAR]: RarityTier.GREAT,
|
||||
[MoveId.IRON_DEFENSE]: RarityTier.GREAT,
|
||||
[MoveId.DRAGON_CLAW]: RarityTier.ULTRA,
|
||||
[MoveId.FRENZY_PLANT]: RarityTier.ULTRA,
|
||||
[MoveId.BULK_UP]: RarityTier.COMMON,
|
||||
[MoveId.BOUNCE]: RarityTier.GREAT,
|
||||
[MoveId.MUD_SHOT]: RarityTier.GREAT,
|
||||
[MoveId.POISON_TAIL]: RarityTier.GREAT,
|
||||
[MoveId.COVET]: RarityTier.GREAT,
|
||||
[MoveId.MAGICAL_LEAF]: RarityTier.GREAT,
|
||||
[MoveId.CALM_MIND]: RarityTier.GREAT,
|
||||
[MoveId.LEAF_BLADE]: RarityTier.ULTRA,
|
||||
[MoveId.DRAGON_DANCE]: RarityTier.GREAT,
|
||||
[MoveId.ROCK_BLAST]: RarityTier.GREAT,
|
||||
[MoveId.WATER_PULSE]: RarityTier.GREAT,
|
||||
[MoveId.ROOST]: RarityTier.GREAT,
|
||||
[MoveId.GRAVITY]: RarityTier.COMMON,
|
||||
[MoveId.GYRO_BALL]: RarityTier.COMMON,
|
||||
[MoveId.BRINE]: RarityTier.GREAT,
|
||||
[MoveId.PLUCK]: RarityTier.GREAT,
|
||||
[MoveId.TAILWIND]: RarityTier.GREAT,
|
||||
[MoveId.U_TURN]: RarityTier.GREAT,
|
||||
[MoveId.CLOSE_COMBAT]: RarityTier.ULTRA,
|
||||
[MoveId.PAYBACK]: RarityTier.COMMON,
|
||||
[MoveId.ASSURANCE]: RarityTier.COMMON,
|
||||
[MoveId.EMBARGO]: RarityTier.COMMON,
|
||||
[MoveId.FLING]: RarityTier.COMMON,
|
||||
[MoveId.GASTRO_ACID]: RarityTier.GREAT,
|
||||
[MoveId.POWER_SWAP]: RarityTier.COMMON,
|
||||
[MoveId.GUARD_SWAP]: RarityTier.COMMON,
|
||||
[MoveId.WORRY_SEED]: RarityTier.GREAT,
|
||||
[MoveId.TOXIC_SPIKES]: RarityTier.GREAT,
|
||||
[MoveId.FLARE_BLITZ]: RarityTier.ULTRA,
|
||||
[MoveId.AURA_SPHERE]: RarityTier.GREAT,
|
||||
[MoveId.ROCK_POLISH]: RarityTier.COMMON,
|
||||
[MoveId.POISON_JAB]: RarityTier.GREAT,
|
||||
[MoveId.DARK_PULSE]: RarityTier.GREAT,
|
||||
[MoveId.AQUA_TAIL]: RarityTier.GREAT,
|
||||
[MoveId.SEED_BOMB]: RarityTier.GREAT,
|
||||
[MoveId.AIR_SLASH]: RarityTier.GREAT,
|
||||
[MoveId.X_SCISSOR]: RarityTier.GREAT,
|
||||
[MoveId.BUG_BUZZ]: RarityTier.GREAT,
|
||||
[MoveId.DRAGON_PULSE]: RarityTier.GREAT,
|
||||
[MoveId.POWER_GEM]: RarityTier.GREAT,
|
||||
[MoveId.DRAIN_PUNCH]: RarityTier.GREAT,
|
||||
[MoveId.VACUUM_WAVE]: RarityTier.COMMON,
|
||||
[MoveId.FOCUS_BLAST]: RarityTier.GREAT,
|
||||
[MoveId.ENERGY_BALL]: RarityTier.GREAT,
|
||||
[MoveId.BRAVE_BIRD]: RarityTier.ULTRA,
|
||||
[MoveId.EARTH_POWER]: RarityTier.ULTRA,
|
||||
[MoveId.GIGA_IMPACT]: RarityTier.GREAT,
|
||||
[MoveId.NASTY_PLOT]: RarityTier.COMMON,
|
||||
[MoveId.AVALANCHE]: RarityTier.GREAT,
|
||||
[MoveId.SHADOW_CLAW]: RarityTier.GREAT,
|
||||
[MoveId.THUNDER_FANG]: RarityTier.GREAT,
|
||||
[MoveId.ICE_FANG]: RarityTier.GREAT,
|
||||
[MoveId.FIRE_FANG]: RarityTier.GREAT,
|
||||
[MoveId.PSYCHO_CUT]: RarityTier.GREAT,
|
||||
[MoveId.ZEN_HEADBUTT]: RarityTier.GREAT,
|
||||
[MoveId.FLASH_CANNON]: RarityTier.GREAT,
|
||||
[MoveId.ROCK_CLIMB]: RarityTier.GREAT,
|
||||
[MoveId.DEFOG]: RarityTier.COMMON,
|
||||
[MoveId.TRICK_ROOM]: RarityTier.COMMON,
|
||||
[MoveId.DRACO_METEOR]: RarityTier.ULTRA,
|
||||
[MoveId.LEAF_STORM]: RarityTier.ULTRA,
|
||||
[MoveId.POWER_WHIP]: RarityTier.ULTRA,
|
||||
[MoveId.CROSS_POISON]: RarityTier.GREAT,
|
||||
[MoveId.GUNK_SHOT]: RarityTier.ULTRA,
|
||||
[MoveId.IRON_HEAD]: RarityTier.GREAT,
|
||||
[MoveId.STONE_EDGE]: RarityTier.ULTRA,
|
||||
[MoveId.STEALTH_ROCK]: RarityTier.COMMON,
|
||||
[MoveId.GRASS_KNOT]: RarityTier.ULTRA,
|
||||
[MoveId.BUG_BITE]: RarityTier.GREAT,
|
||||
[MoveId.CHARGE_BEAM]: RarityTier.GREAT,
|
||||
[MoveId.HONE_CLAWS]: RarityTier.COMMON,
|
||||
[MoveId.WONDER_ROOM]: RarityTier.COMMON,
|
||||
[MoveId.PSYSHOCK]: RarityTier.GREAT,
|
||||
[MoveId.VENOSHOCK]: RarityTier.GREAT,
|
||||
[MoveId.MAGIC_ROOM]: RarityTier.COMMON,
|
||||
[MoveId.SMACK_DOWN]: RarityTier.COMMON,
|
||||
[MoveId.SLUDGE_WAVE]: RarityTier.GREAT,
|
||||
[MoveId.HEAVY_SLAM]: RarityTier.GREAT,
|
||||
[MoveId.ELECTRO_BALL]: RarityTier.GREAT,
|
||||
[MoveId.FLAME_CHARGE]: RarityTier.GREAT,
|
||||
[MoveId.LOW_SWEEP]: RarityTier.GREAT,
|
||||
[MoveId.ACID_SPRAY]: RarityTier.COMMON,
|
||||
[MoveId.FOUL_PLAY]: RarityTier.ULTRA,
|
||||
[MoveId.ROUND]: RarityTier.COMMON,
|
||||
[MoveId.ECHOED_VOICE]: RarityTier.COMMON,
|
||||
[MoveId.STORED_POWER]: RarityTier.COMMON,
|
||||
[MoveId.ALLY_SWITCH]: RarityTier.COMMON,
|
||||
[MoveId.SCALD]: RarityTier.GREAT,
|
||||
[MoveId.HEX]: RarityTier.GREAT,
|
||||
[MoveId.SKY_DROP]: RarityTier.GREAT,
|
||||
[MoveId.INCINERATE]: RarityTier.GREAT,
|
||||
[MoveId.QUASH]: RarityTier.COMMON,
|
||||
[MoveId.ACROBATICS]: RarityTier.GREAT,
|
||||
[MoveId.RETALIATE]: RarityTier.GREAT,
|
||||
[MoveId.WATER_PLEDGE]: RarityTier.GREAT,
|
||||
[MoveId.FIRE_PLEDGE]: RarityTier.GREAT,
|
||||
[MoveId.GRASS_PLEDGE]: RarityTier.GREAT,
|
||||
[MoveId.VOLT_SWITCH]: RarityTier.GREAT,
|
||||
[MoveId.STRUGGLE_BUG]: RarityTier.COMMON,
|
||||
[MoveId.BULLDOZE]: RarityTier.GREAT,
|
||||
[MoveId.FROST_BREATH]: RarityTier.GREAT,
|
||||
[MoveId.DRAGON_TAIL]: RarityTier.GREAT,
|
||||
[MoveId.WORK_UP]: RarityTier.COMMON,
|
||||
[MoveId.ELECTROWEB]: RarityTier.GREAT,
|
||||
[MoveId.WILD_CHARGE]: RarityTier.GREAT,
|
||||
[MoveId.DRILL_RUN]: RarityTier.GREAT,
|
||||
[MoveId.RAZOR_SHELL]: RarityTier.GREAT,
|
||||
[MoveId.HEAT_CRASH]: RarityTier.GREAT,
|
||||
[MoveId.TAIL_SLAP]: RarityTier.GREAT,
|
||||
[MoveId.HURRICANE]: RarityTier.ULTRA,
|
||||
[MoveId.SNARL]: RarityTier.COMMON,
|
||||
[MoveId.PHANTOM_FORCE]: RarityTier.ULTRA,
|
||||
[MoveId.PETAL_BLIZZARD]: RarityTier.GREAT,
|
||||
[MoveId.DISARMING_VOICE]: RarityTier.GREAT,
|
||||
[MoveId.DRAINING_KISS]: RarityTier.GREAT,
|
||||
[MoveId.GRASSY_TERRAIN]: RarityTier.COMMON,
|
||||
[MoveId.MISTY_TERRAIN]: RarityTier.COMMON,
|
||||
[MoveId.PLAY_ROUGH]: RarityTier.GREAT,
|
||||
[MoveId.CONFIDE]: RarityTier.COMMON,
|
||||
[MoveId.MYSTICAL_FIRE]: RarityTier.GREAT,
|
||||
[MoveId.EERIE_IMPULSE]: RarityTier.COMMON,
|
||||
[MoveId.VENOM_DRENCH]: RarityTier.COMMON,
|
||||
[MoveId.ELECTRIC_TERRAIN]: RarityTier.COMMON,
|
||||
[MoveId.DAZZLING_GLEAM]: RarityTier.ULTRA,
|
||||
[MoveId.INFESTATION]: RarityTier.COMMON,
|
||||
[MoveId.POWER_UP_PUNCH]: RarityTier.GREAT,
|
||||
[MoveId.DARKEST_LARIAT]: RarityTier.GREAT,
|
||||
[MoveId.HIGH_HORSEPOWER]: RarityTier.ULTRA,
|
||||
[MoveId.SOLAR_BLADE]: RarityTier.GREAT,
|
||||
[MoveId.THROAT_CHOP]: RarityTier.GREAT,
|
||||
[MoveId.POLLEN_PUFF]: RarityTier.GREAT,
|
||||
[MoveId.PSYCHIC_TERRAIN]: RarityTier.COMMON,
|
||||
[MoveId.LUNGE]: RarityTier.GREAT,
|
||||
[MoveId.SPEED_SWAP]: RarityTier.COMMON,
|
||||
[MoveId.SMART_STRIKE]: RarityTier.GREAT,
|
||||
[MoveId.BRUTAL_SWING]: RarityTier.GREAT,
|
||||
[MoveId.AURORA_VEIL]: RarityTier.COMMON,
|
||||
[MoveId.PSYCHIC_FANGS]: RarityTier.GREAT,
|
||||
[MoveId.STOMPING_TANTRUM]: RarityTier.GREAT,
|
||||
[MoveId.LIQUIDATION]: RarityTier.ULTRA,
|
||||
[MoveId.BODY_PRESS]: RarityTier.ULTRA,
|
||||
[MoveId.BREAKING_SWIPE]: RarityTier.GREAT,
|
||||
[MoveId.STEEL_BEAM]: RarityTier.ULTRA,
|
||||
[MoveId.EXPANDING_FORCE]: RarityTier.GREAT,
|
||||
[MoveId.STEEL_ROLLER]: RarityTier.COMMON,
|
||||
[MoveId.SCALE_SHOT]: RarityTier.ULTRA,
|
||||
[MoveId.METEOR_BEAM]: RarityTier.GREAT,
|
||||
[MoveId.MISTY_EXPLOSION]: RarityTier.COMMON,
|
||||
[MoveId.GRASSY_GLIDE]: RarityTier.COMMON,
|
||||
[MoveId.RISING_VOLTAGE]: RarityTier.COMMON,
|
||||
[MoveId.TERRAIN_PULSE]: RarityTier.COMMON,
|
||||
[MoveId.SKITTER_SMACK]: RarityTier.GREAT,
|
||||
[MoveId.BURNING_JEALOUSY]: RarityTier.GREAT,
|
||||
[MoveId.LASH_OUT]: RarityTier.GREAT,
|
||||
[MoveId.POLTERGEIST]: RarityTier.ULTRA,
|
||||
[MoveId.CORROSIVE_GAS]: RarityTier.COMMON,
|
||||
[MoveId.COACHING]: RarityTier.COMMON,
|
||||
[MoveId.FLIP_TURN]: RarityTier.COMMON,
|
||||
[MoveId.TRIPLE_AXEL]: RarityTier.COMMON,
|
||||
[MoveId.DUAL_WINGBEAT]: RarityTier.COMMON,
|
||||
[MoveId.SCORCHING_SANDS]: RarityTier.GREAT,
|
||||
[MoveId.TERA_BLAST]: RarityTier.GREAT,
|
||||
[MoveId.ICE_SPINNER]: RarityTier.GREAT,
|
||||
[MoveId.SNOWSCAPE]: RarityTier.COMMON,
|
||||
[MoveId.POUNCE]: RarityTier.COMMON,
|
||||
[MoveId.TRAILBLAZE]: RarityTier.COMMON,
|
||||
[MoveId.CHILLING_WATER]: RarityTier.COMMON,
|
||||
[MoveId.HARD_PRESS]: RarityTier.GREAT,
|
||||
[MoveId.DRAGON_CHEER]: RarityTier.COMMON,
|
||||
[MoveId.ALLURING_VOICE]: RarityTier.GREAT,
|
||||
[MoveId.TEMPER_FLARE]: RarityTier.GREAT,
|
||||
[MoveId.SUPERCELL_SLAM]: RarityTier.GREAT,
|
||||
[MoveId.PSYCHIC_NOISE]: RarityTier.GREAT,
|
||||
[MoveId.UPPER_HAND]: RarityTier.COMMON,
|
||||
};
|
||||
|
@ -11,11 +11,11 @@ import { ChallengeType } from "#enums/challenge-type";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { TypeColor, TypeShadow } from "#enums/color";
|
||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { MoveSourceType } from "#enums/move-source-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { TrainerVariant } from "#enums/trainer-variant";
|
||||
@ -453,13 +453,13 @@ export class SingleGenerationChallenge extends Challenge {
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
});
|
||||
@ -470,14 +470,14 @@ export class SingleGenerationChallenge extends Challenge {
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
});
|
||||
|
@ -1,11 +1,17 @@
|
||||
import type { Ability } from "#abilities/ability";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import type { ModifierTypes } from "#modifiers/modifier-type";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { RewardId } from "#enums/reward-id";
|
||||
import type { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import type { HeldItem } from "#items/held-item";
|
||||
import type { TrainerItem } from "#items/trainer-item";
|
||||
import type { Move } from "#moves/move";
|
||||
import type { RewardFunc } from "#types/rewards";
|
||||
|
||||
export const allAbilities: Ability[] = [];
|
||||
export const allMoves: Move[] = [];
|
||||
export const allSpecies: PokemonSpecies[] = [];
|
||||
|
||||
// TODO: Figure out what this is used for and provide an appropriate tsdoc comment
|
||||
export const modifierTypes = {} as ModifierTypes;
|
||||
export const allHeldItems: Record<HeldItemId, HeldItem> = {};
|
||||
export const allTrainerItems: Record<TrainerItemId, TrainerItem> = {};
|
||||
export const allRewards: Record<RewardId, RewardFunc> = {};
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
} from "#data/battler-tags";
|
||||
import { getBerryEffectFunc } from "#data/berry";
|
||||
import { applyChallenges } from "#data/challenge";
|
||||
import { allAbilities, allMoves } from "#data/data-lists";
|
||||
import { allAbilities, allHeldItems, allMoves } from "#data/data-lists";
|
||||
import { SpeciesFormChangeRevertWeatherFormTrigger } from "#data/form-change-triggers";
|
||||
import { DelayedAttackTag } from "#data/positional-tags/positional-tag";
|
||||
import {
|
||||
@ -43,8 +43,8 @@ import { BiomeId } from "#enums/biome-id";
|
||||
import { ChallengeType } from "#enums/challenge-type";
|
||||
import { Command } from "#enums/command";
|
||||
import { FieldPosition } from "#enums/field-position";
|
||||
import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { ChargeAnim } from "#enums/move-anims-common";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
@ -69,14 +69,10 @@ import { SwitchType } from "#enums/switch-type";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { MoveUsedEvent } from "#events/battle-scene";
|
||||
import type { EnemyPokemon, Pokemon } from "#field/pokemon";
|
||||
import {
|
||||
AttackTypeBoosterModifier,
|
||||
BerryModifier,
|
||||
PokemonHeldItemModifier,
|
||||
PokemonMoveAccuracyBoosterModifier,
|
||||
PokemonMultiHitModifier,
|
||||
PreserveBerryModifier,
|
||||
} from "#modifiers/modifier";
|
||||
import { applyHeldItems } from "#items/all-held-items";
|
||||
import { BerryHeldItem, berryTypeToHeldItem } from "#items/berry";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { applyMoveAttrs } from "#moves/apply-attrs";
|
||||
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSketchMoves, invalidSleepTalkMoves } from "#moves/invalid-moves";
|
||||
import { frenzyMissFunc, getMoveTargets } from "#moves/move-utils";
|
||||
@ -779,7 +775,7 @@ export abstract class Move implements Localizable {
|
||||
const isOhko = this.hasAttr("OneHitKOAccuracyAttr");
|
||||
|
||||
if (!isOhko) {
|
||||
globalScene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy);
|
||||
applyHeldItems(HeldItemEffect.ACCURACY_BOOSTER, { pokemon: user, moveAccuracy: moveAccuracy });
|
||||
}
|
||||
|
||||
if (globalScene.arena.weather?.weatherType === WeatherType.FOG) {
|
||||
@ -833,9 +829,15 @@ export abstract class Move implements Localizable {
|
||||
}
|
||||
|
||||
// Non-priority, single-hit moves of the user's Tera Type are always a bare minimum of 60 power
|
||||
|
||||
const sourceTeraType = source.getTeraType();
|
||||
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
|
||||
if (
|
||||
source.isTerastallized
|
||||
&& sourceTeraType === this.type
|
||||
&& power.value < 60
|
||||
&& this.priority <= 0
|
||||
&& !this.hasAttr("MultiHitAttr")
|
||||
&& !source.heldItemManager.hasItem(HeldItemId.MULTI_LENS)
|
||||
) {
|
||||
power.value = 60;
|
||||
}
|
||||
|
||||
@ -865,7 +867,11 @@ export abstract class Move implements Localizable {
|
||||
|
||||
if (!this.hasAttr("TypelessAttr")) {
|
||||
globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power);
|
||||
globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, typeChangeHolder.value, power);
|
||||
applyHeldItems(HeldItemEffect.ATTACK_TYPE_BOOST, {
|
||||
pokemon: source,
|
||||
moveType: typeChangeHolder.value,
|
||||
movePower: power,
|
||||
});
|
||||
}
|
||||
|
||||
if (source.getTag(HelpingHandTag)) {
|
||||
@ -928,7 +934,7 @@ export abstract class Move implements Localizable {
|
||||
* Returns `true` if this move can be given additional strikes
|
||||
* by enhancing effects.
|
||||
* Currently used for {@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(Ability) | Parental Bond}
|
||||
* and {@linkcode PokemonMultiHitModifier | Multi-Lens}.
|
||||
* and {@linkcode MultiHitHeldItem | Multi-Lens}.
|
||||
* @param user The {@linkcode Pokemon} using the move
|
||||
* @param restrictSpread `true` if the enhancing effect
|
||||
* should not affect multi-target moves (default `false`)
|
||||
@ -1577,7 +1583,7 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr {
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
// first, determine if the hit is coming from multi lens or not
|
||||
const lensCount = user.getHeldItems().find(i => i instanceof PokemonMultiHitModifier)?.getStackCount() ?? 0;
|
||||
const lensCount = user.heldItemManager.getStack(HeldItemId.MULTI_LENS);
|
||||
if (lensCount <= 0) {
|
||||
// no multi lenses; we can just halve the target's hp and call it a day
|
||||
(args[0] as NumberHolder).value = toDmgValue(target.hp / 2);
|
||||
@ -2636,35 +2642,33 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable);
|
||||
const heldItems = target.heldItemManager.getTransferableHeldItems();
|
||||
if (!heldItems.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
|
||||
const highestItemTier = heldItems.map((m) => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct?
|
||||
const tierHeldItems = heldItems.filter((m) => m.type.getOrInferTier(poolType) === highestItemTier);
|
||||
const stolenItem = tierHeldItems[user.randBattleSeedInt(tierHeldItems.length)];
|
||||
if (!globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) {
|
||||
const stolenItem = heldItems[user.randBattleSeedInt(heldItems.length)];
|
||||
|
||||
if (!globalScene.tryTransferHeldItem(stolenItem, target, user, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name }));
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stoleItem",
|
||||
{ pokemonName: getPokemonNameWithAffix(user),
|
||||
targetName: getPokemonNameWithAffix(target),
|
||||
itemName: allHeldItems[stolenItem].name
|
||||
}
|
||||
));
|
||||
return true;
|
||||
}
|
||||
|
||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||
return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
const heldItems = target.heldItemManager.getTransferableHeldItems();
|
||||
return heldItems.length ? 5 : 0;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
const heldItems = target.heldItemManager.getTransferableHeldItems();
|
||||
return heldItems.length ? -5 : 0;
|
||||
}
|
||||
}
|
||||
@ -2710,10 +2714,10 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
|
||||
// Considers entire transferrable item pool by default (Knock Off).
|
||||
// Otherwise only consider berries (Incinerate).
|
||||
let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable);
|
||||
let heldItems = target.heldItemManager.getTransferableHeldItems();
|
||||
|
||||
if (this.berriesOnly) {
|
||||
heldItems = heldItems.filter(m => m instanceof BerryModifier && m.pokemonId === target.id, target.isPlayer());
|
||||
heldItems = heldItems.filter(m => m in Object.values(berryTypeToHeldItem));
|
||||
}
|
||||
|
||||
if (!heldItems.length) {
|
||||
@ -2724,29 +2728,26 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
|
||||
// Decrease item amount and update icon
|
||||
target.loseHeldItem(removedItem);
|
||||
globalScene.updateModifiers(target.isPlayer());
|
||||
globalScene.updateItems(target.isPlayer());
|
||||
|
||||
if (this.berriesOnly) {
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:incineratedItem",
|
||||
{ pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: allHeldItems[removedItem].name }));
|
||||
} else {
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:knockedOffItem",
|
||||
{ pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: allHeldItems[removedItem].name }));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||
return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
const heldItems = target.getHeldItems();
|
||||
return heldItems.length ? 5 : 0;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
const heldItems = target.getHeldItems();
|
||||
return heldItems.length ? -5 : 0;
|
||||
}
|
||||
}
|
||||
@ -2755,7 +2756,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
* Attribute that causes targets of the move to eat a berry. Used for Teatime, Stuff Cheeks
|
||||
*/
|
||||
export class EatBerryAttr extends MoveEffectAttr {
|
||||
protected chosenBerry: BerryModifier;
|
||||
protected chosenBerry: HeldItemId;
|
||||
constructor(selfTarget: boolean) {
|
||||
super(selfTarget);
|
||||
}
|
||||
@ -2784,9 +2785,9 @@ export class EatBerryAttr extends MoveEffectAttr {
|
||||
this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)];
|
||||
const preserve = new BooleanHolder(false);
|
||||
// check for berry pouch preservation
|
||||
globalScene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.PRESERVE_BERRY, {pokemon: pokemon, doPreserve: preserve});
|
||||
if (!preserve.value) {
|
||||
this.reduceBerryModifier(pokemon);
|
||||
this.reduceBerryItem(pokemon);
|
||||
}
|
||||
|
||||
// Don't update harvest for berries preserved via Berry pouch (no item dupes lol)
|
||||
@ -2795,16 +2796,15 @@ export class EatBerryAttr extends MoveEffectAttr {
|
||||
return true;
|
||||
}
|
||||
|
||||
getTargetHeldBerries(target: Pokemon): BerryModifier[] {
|
||||
return globalScene.findModifiers(m => m instanceof BerryModifier
|
||||
&& (m as BerryModifier).pokemonId === target.id, target.isPlayer()) as BerryModifier[];
|
||||
getTargetHeldBerries(target: Pokemon): HeldItemId[] {
|
||||
return target.getHeldItems().filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY));
|
||||
}
|
||||
|
||||
reduceBerryModifier(target: Pokemon) {
|
||||
reduceBerryItem(target: Pokemon) {
|
||||
if (this.chosenBerry) {
|
||||
target.loseHeldItem(this.chosenBerry);
|
||||
}
|
||||
globalScene.updateModifiers(target.isPlayer());
|
||||
globalScene.updateItems(target.isPlayer());
|
||||
}
|
||||
|
||||
|
||||
@ -2818,10 +2818,10 @@ export class EatBerryAttr extends MoveEffectAttr {
|
||||
*/
|
||||
protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) {
|
||||
// consumer eats berry, owner triggers unburden and similar effects
|
||||
getBerryEffectFunc(this.chosenBerry.berryType)(consumer);
|
||||
getBerryEffectFunc((allHeldItems[this.chosenBerry] as BerryHeldItem).berryType)(consumer);
|
||||
applyAbAttrs("PostItemLostAbAttr", {pokemon: berryOwner});
|
||||
applyAbAttrs("HealFromBerryUseAbAttr", {pokemon: consumer});
|
||||
consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest);
|
||||
consumer.recordEatenBerry((allHeldItems[this.chosenBerry] as BerryHeldItem).berryType, updateHarvest);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2860,9 +2860,9 @@ export class StealEatBerryAttr extends EatBerryAttr {
|
||||
// pick a random berry and eat it
|
||||
this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)];
|
||||
applyAbAttrs("PostItemLostAbAttr", {pokemon: target});
|
||||
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name });
|
||||
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: allHeldItems[this.chosenBerry].name });
|
||||
globalScene.phaseManager.queueMessage(message);
|
||||
this.reduceBerryModifier(target);
|
||||
this.reduceBerryItem(target);
|
||||
this.eatBerry(user, target);
|
||||
|
||||
return true;
|
||||
@ -6479,9 +6479,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
}
|
||||
}
|
||||
|
||||
// clear out enemy held item modifiers of the switch out target
|
||||
globalScene.clearEnemyHeldItemModifiers(switchOutTarget);
|
||||
|
||||
if (!allyPokemon?.isActive(true) && switchOutTarget.hp) {
|
||||
globalScene.phaseManager.pushNew("BattleEndPhase", false);
|
||||
|
||||
@ -8076,14 +8073,14 @@ const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Po
|
||||
|
||||
const failIfGhostTypeCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => !target.isOfType(PokemonType.GHOST);
|
||||
|
||||
const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.getHeldItems().filter(i => i.isTransferable)?.length > 0;
|
||||
const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.heldItemManager.getTransferableHeldItems().length > 0;
|
||||
|
||||
const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => {
|
||||
const heldItems = target.getHeldItems().filter(i => i.isTransferable);
|
||||
const heldItems = target.heldItemManager.getTransferableHeldItems();
|
||||
if (heldItems.length === 0) {
|
||||
return "";
|
||||
}
|
||||
const itemName = heldItems[0]?.type?.name ?? "item";
|
||||
const itemName = allHeldItems[heldItems[0]].name ?? "item";
|
||||
const message: string = i18next.t("moveTriggers:attackedByItem", { pokemonName: getPokemonNameWithAffix(target), itemName: itemName });
|
||||
return message;
|
||||
};
|
||||
@ -9380,7 +9377,7 @@ export function initMoves() {
|
||||
.condition((user, target, move) => !target.status && !target.isSafeguarded(user))
|
||||
.reflectable(),
|
||||
new AttackMove(MoveId.KNOCK_OFF, PokemonType.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferable).length > 0 ? 1.5 : 1)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.heldItemManager.getTransferableHeldItems().length > 0 ? 1.5 : 1)
|
||||
.attr(RemoveHeldItemAttr, false)
|
||||
.edgeCase(),
|
||||
// Should not be able to remove held item if user faints due to Rough Skin, Iron Barbs, etc.
|
||||
@ -10106,7 +10103,7 @@ export function initMoves() {
|
||||
.condition((user, target, move) => !target.turnData.acted)
|
||||
.attr(ForceLastAttr),
|
||||
new AttackMove(MoveId.ACROBATICS, PokemonType.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferable).reduce((v, m) => v + m.stackCount, 0))),
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.heldItemManager.getTransferableHeldItems().reduce((v, m) => v + user.heldItemManager.getStack(m), 0))),
|
||||
new StatusMove(MoveId.REFLECT_TYPE, PokemonType.NORMAL, -1, 15, -1, 0, 5)
|
||||
.ignoresSubstitute()
|
||||
.attr(CopyTypeAttr),
|
||||
@ -10855,7 +10852,7 @@ export function initMoves() {
|
||||
.attr(EatBerryAttr, true)
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true)
|
||||
.condition((user) => {
|
||||
const userBerries = globalScene.findModifiers(m => m instanceof BerryModifier, user.isPlayer());
|
||||
const userBerries = user.getHeldItems().filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY));
|
||||
return userBerries.length > 0;
|
||||
})
|
||||
.edgeCase(), // Stuff Cheeks should not be selectable when the user does not have a berry, see wiki
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import type { IEggOptions } from "#data/egg";
|
||||
import { EggSourceType } from "#enums/egg-source-types";
|
||||
import { EggTier } from "#enums/egg-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
@ -150,7 +150,7 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Battle the stat trainer for an Egg and great rewards
|
||||
// Battle the stat trainer for an Egg and great allRewards
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
@ -164,8 +164,8 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SACRED_ASH],
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA],
|
||||
guaranteedRewardSpecs: [RewardId.SACRED_ASH],
|
||||
guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ULTRA],
|
||||
fillRemaining: true,
|
||||
},
|
||||
[eggOptions],
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allHeldItems } from "#data/data-lists";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
@ -12,37 +12,44 @@ import { PokeballType } from "#enums/pokeball";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
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 { BerryModifier, PokemonInstantReviveModifier } from "#modifiers/modifier";
|
||||
import type { BerryModifierType, PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
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";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
catchPokemon,
|
||||
getHighestLevelPlayerPokemon,
|
||||
} from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import { catchPokemon, getHighestLevelPlayerPokemon } 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 { PersistentModifierRequirement } from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import type { HeldModifierConfig } from "#types/held-modifier-config";
|
||||
import { randInt } from "#utils/common";
|
||||
import { HeldItemRequirement } from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import { pickWeightedIndex, randInt } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import i18next from "i18next";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounters/absoluteAvarice";
|
||||
|
||||
function berrySprite(spriteKey: string, x: number, y: number): MysteryEncounterSpriteConfig {
|
||||
return {
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: x,
|
||||
y: y,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute Avarice encounter.
|
||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3805 | GitHub Issue #3805}
|
||||
@ -53,7 +60,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(20, 180)
|
||||
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 6)) // Must have at least 6 berries to spawn
|
||||
.withSceneRequirement(new HeldItemRequirement(HeldItemCategoryId.BERRY, 6)) // Must have at least 6 berries to spawn
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
@ -74,105 +81,17 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
repeat: true,
|
||||
x: -5,
|
||||
},
|
||||
{
|
||||
spriteKey: "lum_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 7,
|
||||
y: -14,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "salac_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 2,
|
||||
y: 4,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "lansat_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 32,
|
||||
y: 5,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "liechi_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 6,
|
||||
y: -5,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "sitrus_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 7,
|
||||
y: 8,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "enigma_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 26,
|
||||
y: -4,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "leppa_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 16,
|
||||
y: -27,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "petaya_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 30,
|
||||
y: -17,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "ganlon_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 16,
|
||||
y: -11,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "apicot_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 14,
|
||||
y: -2,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "starf_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 18,
|
||||
y: 9,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
berrySprite("lum_berry", 7, -14),
|
||||
berrySprite("salac_berry", 2, 4),
|
||||
berrySprite("lansat_berry", 32, 5),
|
||||
berrySprite("liechi_berry", 6, -5),
|
||||
berrySprite("sitrus_berry", 7, 8),
|
||||
berrySprite("enigma_berry", 26, -4),
|
||||
berrySprite("leppa_berry", 16, -27),
|
||||
berrySprite("petaya_berry", 30, -17),
|
||||
berrySprite("ganlon_berry", 16, -11),
|
||||
berrySprite("apicot_berry", 14, -2),
|
||||
berrySprite("starf_berry", 18, 9),
|
||||
])
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
@ -191,35 +110,17 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
globalScene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
|
||||
globalScene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");
|
||||
|
||||
// Get all player berry items, remove from party, and store reference
|
||||
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||
// Get all berries in party, with references to the pokemon
|
||||
const berryItems = getPartyBerries();
|
||||
|
||||
// Sort berries by party member ID to more easily re-add later if necessary
|
||||
const berryItemsMap = new Map<number, BerryModifier[]>();
|
||||
globalScene.getPlayerParty().forEach(pokemon => {
|
||||
const pokemonBerries = berryItems.filter(b => b.pokemonId === pokemon.id);
|
||||
if (pokemonBerries?.length > 0) {
|
||||
berryItemsMap.set(pokemon.id, pokemonBerries);
|
||||
}
|
||||
encounter.misc = { berryItemsMap: berryItems };
|
||||
|
||||
// Adds stolen berries to the Greedent item configuration
|
||||
const bossHeldItemConfig: HeldItemConfiguration = [];
|
||||
berryItems.forEach(map => {
|
||||
bossHeldItemConfig.push({ entry: map.item, count: 1 });
|
||||
});
|
||||
|
||||
encounter.misc = { berryItemsMap };
|
||||
|
||||
// Generates copies of the stolen berries to put on the Greedent
|
||||
const bossModifierConfigs: HeldModifierConfig[] = [];
|
||||
berryItems.forEach(berryMod => {
|
||||
// Can't define stack count on a ModifierType, have to just create separate instances for each stack
|
||||
// Overflow berries will be "lost" on the boss, but it's un-catchable anyway
|
||||
for (let i = 0; i < berryMod.stackCount; i++) {
|
||||
const modifierType = generateModifierType(modifierTypes.BERRY, [
|
||||
berryMod.berryType,
|
||||
]) as PokemonHeldItemModifierType;
|
||||
bossModifierConfigs.push({ modifier: modifierType });
|
||||
}
|
||||
});
|
||||
|
||||
// Do NOT remove the real berries yet or else it will be persisted in the session data
|
||||
|
||||
// +1 SpDef below wave 50, SpDef and Speed otherwise
|
||||
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
|
||||
globalScene.currentBattle.waveIndex < 50 ? [Stat.SPDEF] : [Stat.SPDEF, Stat.SPD];
|
||||
@ -234,7 +135,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
bossSegments: 3,
|
||||
shiny: false, // Shiny lock because of consistency issues between the different options
|
||||
moveSet: [MoveId.THRASH, MoveId.CRUNCH, MoveId.BODY_PRESS, MoveId.SLACK_OFF],
|
||||
modifierConfigs: bossModifierConfigs,
|
||||
heldItemConfig: bossHeldItemConfig,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
|
||||
@ -261,12 +162,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
|
||||
// Remove the berries from the party
|
||||
// Session has been safely saved at this point, so data won't be lost
|
||||
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||
berryItems.forEach(berryMod => {
|
||||
globalScene.removeModifier(berryMod);
|
||||
const berryItems = getPartyBerries();
|
||||
berryItems.forEach(map => {
|
||||
globalScene.getPokemonById(map.pokemonId)?.heldItemManager.remove(map.item.id as HeldItemId);
|
||||
});
|
||||
|
||||
globalScene.updateModifiers(true);
|
||||
globalScene.updateItems(true);
|
||||
|
||||
return true;
|
||||
})
|
||||
@ -286,19 +187,14 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Provides 1x Reviver Seed to each party member at end of battle
|
||||
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED);
|
||||
encounter.setDialogueToken(
|
||||
"foodReward",
|
||||
revSeed?.name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"),
|
||||
allHeldItems[HeldItemId.REVIVER_SEED].name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"),
|
||||
);
|
||||
const givePartyPokemonReviverSeeds = () => {
|
||||
const party = globalScene.getPlayerParty();
|
||||
party.forEach(p => {
|
||||
const heldItems = p.getHeldItems();
|
||||
if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) {
|
||||
const seedModifier = revSeed.newModifier(p);
|
||||
globalScene.addModifier(seedModifier, false, false, false, true);
|
||||
}
|
||||
p.heldItemManager.add(HeldItemId.REVIVER_SEED);
|
||||
});
|
||||
queueEncounterMessage(`${namespace}:option.1.food_stash`);
|
||||
};
|
||||
@ -329,28 +225,27 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const berryMap = encounter.misc.berryItemsMap;
|
||||
const berryMap = encounter.misc.berryItemsMap as PokemonItemMap[];
|
||||
|
||||
// Returns 2/5 of the berries stolen to each Pokemon
|
||||
const party = globalScene.getPlayerParty();
|
||||
party.forEach(pokemon => {
|
||||
const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
|
||||
const berryTypesAsArray: BerryType[] = [];
|
||||
stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.berryType)));
|
||||
const returnedBerryCount = Math.floor(((berryTypesAsArray.length ?? 0) * 2) / 5);
|
||||
const stolenBerries = berryMap.filter(map => map.pokemonId === pokemon.id);
|
||||
const stolenBerryCount = stolenBerries.reduce((a, b) => a + (b.item as HeldItemSpecs).stack, 0);
|
||||
const returnedBerryCount = Math.floor(((stolenBerryCount ?? 0) * 2) / 5);
|
||||
|
||||
if (returnedBerryCount > 0) {
|
||||
for (let i = 0; i < returnedBerryCount; i++) {
|
||||
// Shuffle remaining berry types and pop
|
||||
Phaser.Math.RND.shuffle(berryTypesAsArray);
|
||||
const randBerryType = berryTypesAsArray.pop();
|
||||
|
||||
const berryModType = generateModifierType(modifierTypes.BERRY, [randBerryType]) as BerryModifierType;
|
||||
applyModifierTypeToPlayerPokemon(pokemon, berryModType);
|
||||
const berryWeights = stolenBerries.map(b => (b.item as HeldItemSpecs).stack);
|
||||
const which = pickWeightedIndex(berryWeights) ?? 0;
|
||||
const randBerry = stolenBerries[which];
|
||||
pokemon.heldItemManager.add(randBerry.item.id as HeldItemId);
|
||||
(randBerry.item as HeldItemSpecs).stack -= 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
await globalScene.updateModifiers(true);
|
||||
await globalScene.updateItems(true);
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { speciesStarterCosts } from "#balance/starters";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allRewards, allTrainerItems } from "#data/data-lists";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import {
|
||||
generateModifierType,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterExp,
|
||||
updatePlayerMoney,
|
||||
@ -109,8 +109,8 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB
|
||||
}
|
||||
}
|
||||
|
||||
const shinyCharm = generateModifierType(modifierTypes.SHINY_CHARM);
|
||||
encounter.setDialogueToken("itemName", shinyCharm?.name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name"));
|
||||
const name = allTrainerItems[TrainerItemId.SHINY_CHARM].name;
|
||||
encounter.setDialogueToken("itemName", name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name"));
|
||||
encounter.setDialogueToken("liepardName", getPokemonSpecies(SpeciesId.LIEPARD).getName());
|
||||
|
||||
return true;
|
||||
@ -136,7 +136,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Give the player a Shiny Charm
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.SHINY_CHARM);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.SHINY_CHARM);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build(),
|
||||
@ -184,7 +184,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
// Leave encounter with no allRewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
|
@ -1,23 +1,22 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RewardPoolType } from "#enums/reward-pool-type";
|
||||
import { PERMANENT_STATS, Stat } from "#enums/stat";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { BerryModifier } from "#modifiers/modifier";
|
||||
import type { BerryModifierType, ModifierTypeOption } from "#modifiers/modifier-type";
|
||||
import { regenerateModifierPoolThresholds } from "#modifiers/modifier-type";
|
||||
import { berryTypeToHeldItem } from "#items/berry";
|
||||
import type { RewardOption } from "#items/reward";
|
||||
import { generateRewardPoolWeights, getRewardPoolForType } from "#items/reward-pool-utils";
|
||||
import { generateRewardOptionFromId } from "#items/reward-utils";
|
||||
import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
generateModifierTypeOption,
|
||||
getRandomEncounterSpecies,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
@ -25,7 +24,6 @@ import {
|
||||
setEncounterRewards,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
getEncounterPokemonLevelForWave,
|
||||
getHighestStatPlayerPokemon,
|
||||
getSpriteKeysFromPokemon,
|
||||
@ -90,7 +88,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
: globalScene.currentBattle.waveIndex > 40
|
||||
? 4
|
||||
: 2;
|
||||
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
|
||||
generateRewardPoolWeights(getRewardPoolForType(RewardPoolType.PLAYER), globalScene.getPlayerParty(), 0);
|
||||
encounter.misc = { numBerries };
|
||||
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
|
||||
@ -161,20 +159,16 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
}
|
||||
};
|
||||
|
||||
const shopOptions: ModifierTypeOption[] = [];
|
||||
const shopOptions: RewardOption[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Generate shop berries
|
||||
const mod = generateModifierTypeOption(modifierTypes.BERRY);
|
||||
const mod = generateRewardOptionFromId(RewardId.BERRY);
|
||||
if (mod) {
|
||||
shopOptions.push(mod);
|
||||
}
|
||||
}
|
||||
|
||||
setEncounterRewards(
|
||||
{ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false },
|
||||
undefined,
|
||||
doBerryRewards,
|
||||
);
|
||||
setEncounterRewards({ guaranteedRewardOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
|
||||
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||
},
|
||||
)
|
||||
@ -192,10 +186,10 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
const speedDiff = fastestPokemon.getStat(Stat.SPD) / (enemySpeed * 1.1);
|
||||
const numBerries: number = encounter.misc.numBerries;
|
||||
|
||||
const shopOptions: ModifierTypeOption[] = [];
|
||||
const shopOptions: RewardOption[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Generate shop berries
|
||||
const mod = generateModifierTypeOption(modifierTypes.BERRY);
|
||||
const mod = generateRewardOptionFromId(RewardId.BERRY);
|
||||
if (mod) {
|
||||
shopOptions.push(mod);
|
||||
}
|
||||
@ -248,7 +242,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
};
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeOptions: shopOptions,
|
||||
guaranteedRewardOptions: shopOptions,
|
||||
fillRemaining: false,
|
||||
},
|
||||
undefined,
|
||||
@ -281,7 +275,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
setEncounterExp(fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeOptions: shopOptions,
|
||||
guaranteedRewardOptions: shopOptions,
|
||||
fillRemaining: false,
|
||||
},
|
||||
undefined,
|
||||
@ -303,7 +297,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
// Leave encounter with no allRewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
@ -312,35 +306,17 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
|
||||
function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) {
|
||||
const berryType = randSeedItem(getEnumValues(BerryType));
|
||||
const berry = generateModifierType(modifierTypes.BERRY, [berryType]) as BerryModifierType;
|
||||
const berry = berryTypeToHeldItem[berryType];
|
||||
|
||||
const party = globalScene.getPlayerParty();
|
||||
|
||||
// Will try to apply to prioritized pokemon first, then do normal application method if it fails
|
||||
if (prioritizedPokemon) {
|
||||
const heldBerriesOfType = globalScene.findModifier(
|
||||
m =>
|
||||
m instanceof BerryModifier &&
|
||||
m.pokemonId === prioritizedPokemon.id &&
|
||||
(m as BerryModifier).berryType === berryType,
|
||||
true,
|
||||
) as BerryModifier;
|
||||
|
||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
|
||||
applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry);
|
||||
return;
|
||||
}
|
||||
// Will give the berry to a Pokemon, starting from the prioritized one
|
||||
if (prioritizedPokemon?.heldItemManager.add(berry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate over the party until berry was successfully given
|
||||
for (const pokemon of party) {
|
||||
const heldBerriesOfType = globalScene.findModifier(
|
||||
m => m instanceof BerryModifier && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType,
|
||||
true,
|
||||
) as BerryModifier;
|
||||
|
||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
|
||||
applyModifierTypeToPlayerPokemon(pokemon, berry);
|
||||
if (pokemon.heldItemManager.add(berry)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,26 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { allMoves, modifierTypes } from "#data/data-lists";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { allHeldItems, allMoves } from "#data/data-lists";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import {
|
||||
AttackTypeBoosterModifier,
|
||||
BypassSpeedChanceModifier,
|
||||
ContactHeldItemTransferChanceModifier,
|
||||
GigantamaxAccessModifier,
|
||||
MegaEvolutionAccessModifier,
|
||||
} from "#modifiers/modifier";
|
||||
import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#modifiers/modifier-type";
|
||||
import type { RewardOption } from "#items/reward";
|
||||
import { generateRewardOptionFromId } from "#items/reward-utils";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { getEncounterText, showEncounterDialogue } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
generateModifierTypeOption,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectOptionThenPokemon,
|
||||
@ -39,9 +33,8 @@ import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
|
||||
import {
|
||||
AttackTypeBoosterHeldItemTypeRequirement,
|
||||
CombinationPokemonRequirement,
|
||||
HeldItemRequirement,
|
||||
HoldingItemRequirement,
|
||||
TypeRequirement,
|
||||
} from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import { getRandomPartyMemberFunc, trainerConfigs } from "#trainers/trainer-config";
|
||||
@ -140,6 +133,8 @@ const POOL_3_POKEMON: { species: SpeciesId; formIndex?: number }[] = [
|
||||
|
||||
const POOL_4_POKEMON = [SpeciesId.GENESECT, SpeciesId.SLITHER_WING, SpeciesId.BUZZWOLE, SpeciesId.PHEROMOSA];
|
||||
|
||||
const REQUIRED_ITEMS = [HeldItemId.QUICK_CLAW, HeldItemId.GRIP_CLAW, HeldItemId.SILVER_POWDER];
|
||||
|
||||
const PHYSICAL_TUTOR_MOVES = [
|
||||
MoveId.MEGAHORN,
|
||||
MoveId.ATTACK_ORDER,
|
||||
@ -183,8 +178,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
// Must have at least 1 Bug type on team, OR have a bug item somewhere on the team
|
||||
new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1),
|
||||
new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1),
|
||||
new HoldingItemRequirement(REQUIRED_ITEMS, 1),
|
||||
new TypeRequirement(PokemonType.BUG, false, 1),
|
||||
),
|
||||
)
|
||||
@ -256,13 +250,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
},
|
||||
];
|
||||
|
||||
const requiredItems = [
|
||||
generateModifierType(modifierTypes.QUICK_CLAW),
|
||||
generateModifierType(modifierTypes.GRIP_CLAW),
|
||||
generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.BUG]),
|
||||
];
|
||||
|
||||
const requiredItemString = requiredItems.map(m => m?.name ?? "unknown").join("/");
|
||||
const requiredItemString = REQUIRED_ITEMS.map(m => allHeldItems[m].name ?? "unknown").join("/");
|
||||
encounter.setDialogueToken("requiredBugItems", requiredItemString);
|
||||
|
||||
return true;
|
||||
@ -298,7 +286,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
moveTutorOptions,
|
||||
};
|
||||
|
||||
// Assigns callback that teaches move before continuing to rewards
|
||||
// Assigns callback that teaches move before continuing to RewardId
|
||||
encounter.onRewards = doBugTypeMoveTutor;
|
||||
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
@ -318,7 +306,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
// Player shows off their bug types
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Player gets different rewards depending on the number of bug types they have
|
||||
// Player gets different RewardId depending on the number of bug types they have
|
||||
const numBugTypes = globalScene.getPlayerParty().filter(p => p.isOfType(PokemonType.BUG, true)).length;
|
||||
const numBugTypesText = i18next.t(`${namespace}:numBugTypes`, {
|
||||
count: numBugTypes,
|
||||
@ -327,7 +315,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
|
||||
if (numBugTypes < 2) {
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL],
|
||||
guaranteedRewardSpecs: [RewardId.SUPER_LURE, RewardId.GREAT_BALL],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
@ -338,7 +326,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
];
|
||||
} else if (numBugTypes < 4) {
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL],
|
||||
guaranteedRewardSpecs: [HeldItemId.QUICK_CLAW, RewardId.MAX_LURE, RewardId.ULTRA_BALL],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
@ -349,7 +337,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
];
|
||||
} else if (numBugTypes < 6) {
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL],
|
||||
guaranteedRewardSpecs: [HeldItemId.GRIP_CLAW, RewardId.MAX_LURE, RewardId.ROGUE_BALL],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
@ -361,38 +349,38 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
} else {
|
||||
// If the player has any evolution/form change items that are valid for their party,
|
||||
// spawn one of those items in addition to Dynamax Band, Mega Band, and Master Ball
|
||||
const modifierOptions: ModifierTypeOption[] = [generateModifierTypeOption(modifierTypes.MASTER_BALL)!];
|
||||
const specialOptions: ModifierTypeOption[] = [];
|
||||
const rewardOptions: RewardOption[] = [generateRewardOptionFromId(RewardId.MASTER_BALL)!];
|
||||
const specialOptions: RewardOption[] = [];
|
||||
|
||||
if (!globalScene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) {
|
||||
modifierOptions.push(generateModifierTypeOption(modifierTypes.MEGA_BRACELET)!);
|
||||
if (!globalScene.trainerItems.hasItem(TrainerItemId.MEGA_BRACELET)) {
|
||||
rewardOptions.push(generateRewardOptionFromId(TrainerItemId.MEGA_BRACELET)!);
|
||||
}
|
||||
if (!globalScene.findModifier(m => m instanceof GigantamaxAccessModifier)) {
|
||||
modifierOptions.push(generateModifierTypeOption(modifierTypes.DYNAMAX_BAND)!);
|
||||
if (!globalScene.trainerItems.hasItem(TrainerItemId.DYNAMAX_BAND)) {
|
||||
rewardOptions.push(generateRewardOptionFromId(TrainerItemId.DYNAMAX_BAND)!);
|
||||
}
|
||||
const nonRareEvolutionModifier = generateModifierTypeOption(modifierTypes.EVOLUTION_ITEM);
|
||||
if (nonRareEvolutionModifier) {
|
||||
specialOptions.push(nonRareEvolutionModifier);
|
||||
const nonRareEvolutionReward = generateRewardOptionFromId(RewardId.EVOLUTION_ITEM);
|
||||
if (nonRareEvolutionReward) {
|
||||
specialOptions.push(nonRareEvolutionReward);
|
||||
}
|
||||
const rareEvolutionModifier = generateModifierTypeOption(modifierTypes.RARE_EVOLUTION_ITEM);
|
||||
if (rareEvolutionModifier) {
|
||||
specialOptions.push(rareEvolutionModifier);
|
||||
const rareEvolutionReward = generateRewardOptionFromId(RewardId.RARE_EVOLUTION_ITEM);
|
||||
if (rareEvolutionReward) {
|
||||
specialOptions.push(rareEvolutionReward);
|
||||
}
|
||||
const formChangeModifier = generateModifierTypeOption(modifierTypes.FORM_CHANGE_ITEM);
|
||||
if (formChangeModifier) {
|
||||
specialOptions.push(formChangeModifier);
|
||||
const formChangeReward = generateRewardOptionFromId(RewardId.FORM_CHANGE_ITEM);
|
||||
if (formChangeReward) {
|
||||
specialOptions.push(formChangeReward);
|
||||
}
|
||||
const rareFormChangeModifier = generateModifierTypeOption(modifierTypes.RARE_FORM_CHANGE_ITEM);
|
||||
if (rareFormChangeModifier) {
|
||||
specialOptions.push(rareFormChangeModifier);
|
||||
const rareFormChangeReward = generateRewardOptionFromId(RewardId.RARE_FORM_CHANGE_ITEM);
|
||||
if (rareFormChangeReward) {
|
||||
specialOptions.push(rareFormChangeReward);
|
||||
}
|
||||
if (specialOptions.length > 0) {
|
||||
// TODO: should this use `randSeedItem`?
|
||||
modifierOptions.push(specialOptions[randSeedInt(specialOptions.length)]);
|
||||
rewardOptions.push(specialOptions[randSeedInt(specialOptions.length)]);
|
||||
}
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: modifierOptions,
|
||||
guaranteedRewardOptions: rewardOptions,
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
@ -414,8 +402,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
// Meets one or both of the below reqs
|
||||
new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1),
|
||||
new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1),
|
||||
new HoldingItemRequirement(REQUIRED_ITEMS, 1),
|
||||
),
|
||||
)
|
||||
.withDialogue({
|
||||
@ -438,25 +425,19 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(item => {
|
||||
return (
|
||||
(item instanceof BypassSpeedChanceModifier ||
|
||||
item instanceof ContactHeldItemTransferChanceModifier ||
|
||||
(item instanceof AttackTypeBoosterModifier &&
|
||||
(item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)) &&
|
||||
item.isTransferable
|
||||
);
|
||||
});
|
||||
const validItems = pokemon.heldItemManager
|
||||
.getTransferableHeldItems()
|
||||
.filter(item => REQUIRED_ITEMS.some(i => i === item));
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
return validItems.map((item: HeldItemId) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
label: allHeldItems[item].name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("selectedItem", modifier.type.name);
|
||||
encounter.setDialogueToken("selectedItem", allHeldItems[item].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
chosenItem: item,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
@ -467,14 +448,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon has valid item, it can be selected
|
||||
const hasValidItem = pokemon.getHeldItems().some(item => {
|
||||
return (
|
||||
item instanceof BypassSpeedChanceModifier ||
|
||||
item instanceof ContactHeldItemTransferChanceModifier ||
|
||||
(item instanceof AttackTypeBoosterModifier &&
|
||||
(item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)
|
||||
);
|
||||
});
|
||||
const hasValidItem = pokemon.getHeldItems().some(item => REQUIRED_ITEMS.some(i => i === item));
|
||||
if (!hasValidItem) {
|
||||
return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null;
|
||||
}
|
||||
@ -486,18 +460,18 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const modifier = encounter.misc.chosenModifier;
|
||||
const lostItem = encounter.misc.chosenItem;
|
||||
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
chosenPokemon.loseHeldItem(modifier, false);
|
||||
globalScene.updateModifiers(true, true);
|
||||
chosenPokemon.loseHeldItem(lostItem, false);
|
||||
globalScene.updateItems(true);
|
||||
|
||||
const bugNet = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!;
|
||||
bugNet.type.tier = ModifierTier.ROGUE;
|
||||
const bugNet = generateRewardOptionFromId(TrainerItemId.GOLDEN_BUG_NET)!;
|
||||
bugNet.type.tier = RarityTier.ROGUE;
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [bugNet],
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.REVIVER_SEED],
|
||||
guaranteedRewardOptions: [bugNet],
|
||||
guaranteedRewardSpecs: [HeldItemId.REVIVER_SEED],
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle(true);
|
||||
@ -771,7 +745,7 @@ function doBugTypeMoveTutor(): Promise<void> {
|
||||
);
|
||||
}
|
||||
|
||||
// Complete battle and go to rewards
|
||||
// Complete battle and go to RewardId
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { EncounterBattleAnim } from "#data/battle-anims";
|
||||
import { allAbilities, modifierTypes } from "#data/data-lists";
|
||||
import { allAbilities } from "#data/data-lists";
|
||||
import { CustomPokemonData } from "#data/pokemon-data";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
@ -18,17 +16,17 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
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 { BerryModifier } from "#modifiers/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
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";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
@ -36,10 +34,7 @@ import {
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
applyAbilityOverrideToPokemon,
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
} from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import { applyAbilityOverrideToPokemon } 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";
|
||||
@ -283,16 +278,16 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
|
||||
const party = globalScene.getPlayerParty();
|
||||
let mostHeldItemsPokemon = party[0];
|
||||
let count = mostHeldItemsPokemon
|
||||
.getHeldItems()
|
||||
.filter(m => m.isTransferable && !(m instanceof BerryModifier))
|
||||
.reduce((v, m) => v + m.stackCount, 0);
|
||||
let count = mostHeldItemsPokemon.heldItemManager
|
||||
.getTransferableHeldItems()
|
||||
.filter(m => !isItemInCategory(m, HeldItemCategoryId.BERRY))
|
||||
.reduce((v, m) => v + mostHeldItemsPokemon.heldItemManager.getStack(m), 0);
|
||||
|
||||
for (const pokemon of party) {
|
||||
const nextCount = pokemon
|
||||
.getHeldItems()
|
||||
.filter(m => m.isTransferable && !(m instanceof BerryModifier))
|
||||
.reduce((v, m) => v + m.stackCount, 0);
|
||||
const nextCount = pokemon.heldItemManager
|
||||
.getTransferableHeldItems()
|
||||
.filter(m => !isItemInCategory(m, HeldItemCategoryId.BERRY))
|
||||
.reduce((v, m) => v + pokemon.heldItemManager.getStack(m), 0);
|
||||
if (nextCount > count) {
|
||||
mostHeldItemsPokemon = pokemon;
|
||||
count = nextCount;
|
||||
@ -301,16 +296,31 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
|
||||
encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender());
|
||||
|
||||
const items = mostHeldItemsPokemon.getHeldItems();
|
||||
const items = mostHeldItemsPokemon.heldItemManager
|
||||
.getTransferableHeldItems()
|
||||
.filter(m => !isItemInCategory(m, HeldItemCategoryId.BERRY));
|
||||
|
||||
// Shuffles Berries (if they have any)
|
||||
const oldBerries = mostHeldItemsPokemon.heldItemManager
|
||||
.getHeldItems()
|
||||
.filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY));
|
||||
|
||||
let numBerries = 0;
|
||||
for (const m of items.filter(m => m instanceof BerryModifier)) {
|
||||
numBerries += m.stackCount;
|
||||
globalScene.removeModifier(m);
|
||||
for (const berry of oldBerries) {
|
||||
const stack = mostHeldItemsPokemon.heldItemManager.getStack(berry);
|
||||
numBerries += stack;
|
||||
mostHeldItemsPokemon.heldItemManager.remove(berry, stack);
|
||||
}
|
||||
|
||||
generateItemsOfTier(mostHeldItemsPokemon, numBerries, "Berries");
|
||||
assignItemsFromConfiguration(
|
||||
[
|
||||
{
|
||||
entry: HeldItemCategoryId.BERRY,
|
||||
count: numBerries,
|
||||
},
|
||||
],
|
||||
mostHeldItemsPokemon,
|
||||
);
|
||||
|
||||
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
|
||||
// For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier
|
||||
@ -318,20 +328,36 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
let numUltra = 0;
|
||||
let numRogue = 0;
|
||||
|
||||
for (const m of items.filter(m => m.isTransferable && !(m instanceof BerryModifier))) {
|
||||
const type = m.type.withTierFromPool(ModifierPoolType.PLAYER, party);
|
||||
const tier = type.tier ?? ModifierTier.ULTRA;
|
||||
if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
|
||||
numRogue += m.stackCount;
|
||||
globalScene.removeModifier(m);
|
||||
} else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) {
|
||||
numUltra += m.stackCount;
|
||||
globalScene.removeModifier(m);
|
||||
for (const m of items) {
|
||||
const tier = getHeldItemTier(m) ?? RarityTier.ULTRA;
|
||||
const stack = mostHeldItemsPokemon.heldItemManager.getStack(m);
|
||||
if (tier === RarityTier.ROGUE) {
|
||||
numRogue += stack;
|
||||
} else if (tier === RarityTier.ULTRA) {
|
||||
numUltra += stack;
|
||||
}
|
||||
mostHeldItemsPokemon.heldItemManager.remove(m, stack);
|
||||
}
|
||||
|
||||
generateItemsOfTier(mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
|
||||
generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
|
||||
assignItemsFromConfiguration(
|
||||
[
|
||||
{
|
||||
entry: ultraPool,
|
||||
count: numUltra,
|
||||
},
|
||||
],
|
||||
mostHeldItemsPokemon,
|
||||
);
|
||||
|
||||
assignItemsFromConfiguration(
|
||||
[
|
||||
{
|
||||
entry: roguePool,
|
||||
count: numRogue,
|
||||
},
|
||||
],
|
||||
mostHeldItemsPokemon,
|
||||
);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
leaveEncounterWithoutBattle(true);
|
||||
@ -487,68 +513,21 @@ function onYesAbilitySwap(resolve) {
|
||||
selectPokemonForOption(onPokemonSelected, onPokemonNotSelected);
|
||||
}
|
||||
|
||||
function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: ModifierTier | "Berries") {
|
||||
// These pools have to be defined at runtime so that modifierTypes exist
|
||||
// Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon
|
||||
// This is to prevent "over-generating" a random item of a certain type during item swaps
|
||||
const ultraPool = [
|
||||
[modifierTypes.REVIVER_SEED, 1],
|
||||
[modifierTypes.GOLDEN_PUNCH, 5],
|
||||
[modifierTypes.ATTACK_TYPE_BOOSTER, 99],
|
||||
[modifierTypes.QUICK_CLAW, 3],
|
||||
[modifierTypes.WIDE_LENS, 3],
|
||||
];
|
||||
const ultraPool = [
|
||||
{ entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 1 },
|
||||
{ entry: HeldItemId.REVIVER_SEED, weight: 1 },
|
||||
{ entry: HeldItemId.GOLDEN_PUNCH, weight: 1 },
|
||||
{ entry: HeldItemId.QUICK_CLAW, weight: 1 },
|
||||
{ entry: HeldItemId.WIDE_LENS, weight: 1 },
|
||||
];
|
||||
|
||||
const roguePool = [
|
||||
[modifierTypes.LEFTOVERS, 4],
|
||||
[modifierTypes.SHELL_BELL, 4],
|
||||
[modifierTypes.SOUL_DEW, 10],
|
||||
[modifierTypes.SCOPE_LENS, 1],
|
||||
[modifierTypes.BATON, 1],
|
||||
[modifierTypes.FOCUS_BAND, 5],
|
||||
[modifierTypes.KINGS_ROCK, 3],
|
||||
[modifierTypes.GRIP_CLAW, 5],
|
||||
];
|
||||
|
||||
const berryPool = [
|
||||
[BerryType.APICOT, 3],
|
||||
[BerryType.ENIGMA, 2],
|
||||
[BerryType.GANLON, 3],
|
||||
[BerryType.LANSAT, 3],
|
||||
[BerryType.LEPPA, 2],
|
||||
[BerryType.LIECHI, 3],
|
||||
[BerryType.LUM, 2],
|
||||
[BerryType.PETAYA, 3],
|
||||
[BerryType.SALAC, 2],
|
||||
[BerryType.SITRUS, 2],
|
||||
[BerryType.STARF, 3],
|
||||
];
|
||||
|
||||
let pool: any[];
|
||||
if (tier === "Berries") {
|
||||
pool = berryPool;
|
||||
} else {
|
||||
pool = tier === ModifierTier.ULTRA ? ultraPool : roguePool;
|
||||
}
|
||||
|
||||
for (let i = 0; i < numItems; i++) {
|
||||
if (pool.length === 0) {
|
||||
// Stop generating new items if somehow runs out of items to spawn
|
||||
return;
|
||||
}
|
||||
const randIndex = randSeedInt(pool.length);
|
||||
const newItemType = pool[randIndex];
|
||||
let newMod: PokemonHeldItemModifierType;
|
||||
if (tier === "Berries") {
|
||||
newMod = generateModifierType(modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType;
|
||||
} else {
|
||||
newMod = generateModifierType(newItemType[0]) as PokemonHeldItemModifierType;
|
||||
}
|
||||
applyModifierTypeToPlayerPokemon(pokemon, newMod);
|
||||
// Decrement max stacks and remove from pool if at max
|
||||
newItemType[1]--;
|
||||
if (newItemType[1] <= 0) {
|
||||
pool.splice(randIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
const roguePool = [
|
||||
{ entry: HeldItemId.LEFTOVERS, weight: 1 },
|
||||
{ entry: HeldItemId.SHELL_BELL, weight: 1 },
|
||||
{ entry: HeldItemId.SOUL_DEW, weight: 1 },
|
||||
{ entry: HeldItemId.SCOPE_LENS, weight: 1 },
|
||||
{ entry: HeldItemId.BATON, weight: 1 },
|
||||
{ entry: HeldItemId.FOCUS_BAND, weight: 1 },
|
||||
{ entry: HeldItemId.KINGS_ROCK, weight: 1 },
|
||||
{ entry: HeldItemId.GRIP_CLAW, weight: 1 },
|
||||
];
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { EncounterBattleAnim } from "#data/battle-anims";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
@ -153,7 +153,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
}
|
||||
|
||||
const oricorioData = new PokemonData(enemyPokemon);
|
||||
const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData);
|
||||
const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, [], oricorioData);
|
||||
|
||||
// Adds a real Pokemon sprite to the field (required for the animation)
|
||||
for (const enemyPokemon of globalScene.getEnemyParty()) {
|
||||
@ -219,7 +219,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
|
||||
await hideOricorioPokemon();
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.BATON],
|
||||
guaranteedRewardSpecs: [HeldItemId.BATON],
|
||||
fillRemaining: true,
|
||||
});
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allRewards } from "#data/data-lists";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import { PokemonFormChangeItemModifier } from "#modifiers/modifier";
|
||||
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";
|
||||
@ -146,7 +145,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
|
||||
const removedPokemon = getRandomPlayerPokemon(true, false, true);
|
||||
|
||||
// Get all the pokemon's held items
|
||||
const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||
const itemConfig = removedPokemon.heldItemManager.generateHeldItemConfiguration();
|
||||
globalScene.removePokemonFromPlayerParty(removedPokemon);
|
||||
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
@ -155,13 +154,13 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
|
||||
// Store removed pokemon types
|
||||
encounter.misc = {
|
||||
removedTypes: removedPokemon.getTypes(),
|
||||
modifiers,
|
||||
itemConfig: itemConfig,
|
||||
};
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Give the player 5 Rogue Balls
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.ROGUE_BALL);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.ROGUE_BALL);
|
||||
|
||||
// Start encounter with random legendary (7-10 starter strength) that has level additive
|
||||
// If this is a mono-type challenge, always ensure the required type is filtered for
|
||||
@ -173,7 +172,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
|
||||
bossTypes = singleTypeChallenges.map(c => (c.value - 1) as PokemonType);
|
||||
}
|
||||
|
||||
const bossModifiers: PokemonHeldItemModifier[] = encounter.misc.modifiers;
|
||||
const bossItemConfig: HeldItemConfiguration = encounter.misc.itemConfig;
|
||||
// Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+
|
||||
const roll = randSeedInt(100);
|
||||
const starterTier: number | [number, number] = roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [9, 10];
|
||||
@ -181,12 +180,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
modifierConfigs: bossModifiers.map(m => {
|
||||
return {
|
||||
modifier: m,
|
||||
stackCount: m.getStackCount(),
|
||||
};
|
||||
}),
|
||||
heldItemConfig: bossItemConfig,
|
||||
};
|
||||
if (!isNullOrUndefined(bossSpecies.forms) && bossSpecies.forms.length > 0) {
|
||||
pokemonConfig.formIndex = 0;
|
||||
@ -210,7 +204,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
// Leave encounter with no allRewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
|
@ -1,35 +1,26 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { timedEventManager } from "#app/global-event-manager";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allHeldItems, allRewards } from "#data/data-lists";
|
||||
import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#modifiers/modifier";
|
||||
import {
|
||||
BerryModifier,
|
||||
HealingBoosterModifier,
|
||||
LevelIncrementBoosterModifier,
|
||||
MoneyMultiplierModifier,
|
||||
PreserveBerryModifier,
|
||||
} from "#modifiers/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import { getEncounterText, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
updatePlayerMoney,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import { applyModifierTypeToPlayerPokemon } 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 {
|
||||
CombinationPokemonRequirement,
|
||||
HeldItemRequirement,
|
||||
HoldingItemRequirement,
|
||||
MoneyRequirement,
|
||||
} from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import i18next from "#plugins/i18n";
|
||||
@ -41,32 +32,39 @@ import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
const namespace = "mysteryEncounters/delibirdy";
|
||||
|
||||
/** Berries only */
|
||||
const OPTION_2_ALLOWED_MODIFIERS = ["BerryModifier", "PokemonInstantReviveModifier"];
|
||||
const OPTION_2_ALLOWED_HELD_ITEMS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED];
|
||||
|
||||
/** Disallowed items are berries, Reviver Seeds, and Vitamins (form change items and fusion items are not PokemonHeldItemModifiers) */
|
||||
const OPTION_3_DISALLOWED_MODIFIERS = [
|
||||
"BerryModifier",
|
||||
"PokemonInstantReviveModifier",
|
||||
"TerastallizeModifier",
|
||||
"PokemonBaseStatModifier",
|
||||
"PokemonBaseStatTotalModifier",
|
||||
];
|
||||
/** Disallowed items are berries, Reviver Seeds, and Vitamins */
|
||||
const OPTION_3_DISALLOWED_HELD_ITEMS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED];
|
||||
|
||||
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
|
||||
|
||||
async function backupOption() {
|
||||
globalScene.getPlayerPokemon()?.heldItemManager.add(HeldItemId.SHELL_BELL);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", {
|
||||
modifierName: allHeldItems[HeldItemId.SHELL_BELL].name,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
}
|
||||
|
||||
const doEventReward = () => {
|
||||
const event_buff = timedEventManager.getDelibirdyBuff();
|
||||
if (event_buff.length > 0) {
|
||||
const candidates = event_buff.filter(c => {
|
||||
const mtype = generateModifierType(modifierTypes[c]);
|
||||
const existingCharm = globalScene.findModifier(m => m.type.id === mtype?.id);
|
||||
return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount());
|
||||
const fullStack = globalScene.trainerItems.isMaxStack(c);
|
||||
return !fullStack;
|
||||
});
|
||||
if (candidates.length > 0) {
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes[randSeedItem(candidates)]);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", allRewards[randSeedItem(candidates)]);
|
||||
} else {
|
||||
// At max stacks, give a Voucher instead
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.VOUCHER);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.VOUCHER);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -85,8 +83,8 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
// Must also have either option 2 or 3 available to spawn
|
||||
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS),
|
||||
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true),
|
||||
new HoldingItemRequirement(OPTION_2_ALLOWED_HELD_ITEMS),
|
||||
new HoldingItemRequirement(OPTION_3_DISALLOWED_HELD_ITEMS, 1, true),
|
||||
),
|
||||
)
|
||||
.withIntroSpriteConfigs([
|
||||
@ -164,22 +162,13 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withOptionPhase(async () => {
|
||||
// Give the player an Amulet Coin
|
||||
// Check if the player has max stacks of that item already
|
||||
const existing = globalScene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier;
|
||||
const fullStack = globalScene.trainerItems.isMaxStack(TrainerItemId.AMULET_COIN);
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
if (fullStack) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", { modifierName: shellBell.name }),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
backupOption();
|
||||
} else {
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.AMULET_COIN);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.AMULET_COIN);
|
||||
doEventReward();
|
||||
}
|
||||
|
||||
@ -189,7 +178,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS))
|
||||
.withPrimaryPokemonRequirement(new HoldingItemRequirement(OPTION_2_ALLOWED_HELD_ITEMS))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
@ -204,19 +193,17 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(it => {
|
||||
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable;
|
||||
});
|
||||
const validItems = pokemon.heldItemManager.filterRequestedItems(OPTION_2_ALLOWED_HELD_ITEMS, true);
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
return validItems.map((item: HeldItemId) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
label: allHeldItems[item].name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.setDialogueToken("chosenItem", allHeldItems[item].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
chosenItem: item,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
@ -239,59 +226,35 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const modifier: BerryModifier | PokemonInstantReviveModifier = encounter.misc.chosenModifier;
|
||||
const chosenItem: HeldItemId = encounter.misc.chosenItem;
|
||||
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed
|
||||
if (modifier instanceof BerryModifier) {
|
||||
if (isItemInCategory(chosenItem, HeldItemCategoryId.BERRY)) {
|
||||
// Check if the player has max stacks of that Candy Jar already
|
||||
const existing = globalScene.findModifier(
|
||||
m => m instanceof LevelIncrementBoosterModifier,
|
||||
) as LevelIncrementBoosterModifier;
|
||||
const fullStack = globalScene.trainerItems.isMaxStack(TrainerItemId.CANDY_JAR);
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
if (fullStack) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", {
|
||||
modifierName: shellBell.name,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
backupOption();
|
||||
} else {
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.CANDY_JAR);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.CANDY_JAR);
|
||||
doEventReward();
|
||||
}
|
||||
} else {
|
||||
// Check if the player has max stacks of that Berry Pouch already
|
||||
const existing = globalScene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier;
|
||||
const fullStack = globalScene.trainerItems.isMaxStack(TrainerItemId.BERRY_POUCH);
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
if (fullStack) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", {
|
||||
modifierName: shellBell.name,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
backupOption();
|
||||
} else {
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.BERRY_POUCH);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.BERRY_POUCH);
|
||||
doEventReward();
|
||||
}
|
||||
}
|
||||
|
||||
chosenPokemon.loseHeldItem(modifier, false);
|
||||
chosenPokemon.loseHeldItem(chosenItem, false);
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
@ -299,7 +262,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true))
|
||||
.withPrimaryPokemonRequirement(new HoldingItemRequirement(OPTION_3_DISALLOWED_HELD_ITEMS, 1, true))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
@ -314,21 +277,17 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(it => {
|
||||
return (
|
||||
!OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable
|
||||
);
|
||||
});
|
||||
const validItems = pokemon.heldItemManager.filterRequestedItems(OPTION_3_DISALLOWED_HELD_ITEMS, true, true);
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
return validItems.map((item: HeldItemId) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
label: allHeldItems[item].name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.setDialogueToken("chosenItem", allHeldItems[item].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
chosenItem: item,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
@ -351,30 +310,22 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const modifier = encounter.misc.chosenModifier;
|
||||
const chosenItem = encounter.misc.chosenItem;
|
||||
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Check if the player has max stacks of Healing Charm already
|
||||
const existing = globalScene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier;
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
const fullStack = globalScene.trainerItems.isMaxStack(TrainerItemId.HEALING_CHARM);
|
||||
|
||||
if (fullStack) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerParty()[0], shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", { modifierName: shellBell.name }),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
backupOption();
|
||||
} else {
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.HEALING_CHARM);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.HEALING_CHARM);
|
||||
doEventReward();
|
||||
}
|
||||
|
||||
chosenPokemon.loseHeldItem(modifier, false);
|
||||
chosenPokemon.loseHeldItem(chosenItem, false);
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { leaveEncounterWithoutBattle, setEncounterRewards } from "#mystery-encounters/encounter-phase-utils";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import type { ModifierTypeFunc } from "#types/modifier-types";
|
||||
import type { RewardSpecs } from "#types/rewards";
|
||||
import { randSeedInt } from "#utils/common";
|
||||
|
||||
/** i18n namespace for encounter */
|
||||
@ -59,23 +59,23 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu
|
||||
},
|
||||
async () => {
|
||||
// Choose TMs
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
const rewards: RewardSpecs[] = [];
|
||||
let i = 0;
|
||||
while (i < 5) {
|
||||
// 2/2/1 weight on TM rarity
|
||||
const roll = randSeedInt(5);
|
||||
if (roll < 2) {
|
||||
modifiers.push(modifierTypes.TM_COMMON);
|
||||
rewards.push(RewardId.TM_COMMON);
|
||||
} else if (roll < 4) {
|
||||
modifiers.push(modifierTypes.TM_GREAT);
|
||||
rewards.push(RewardId.TM_GREAT);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.TM_ULTRA);
|
||||
rewards.push(RewardId.TM_ULTRA);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: modifiers,
|
||||
guaranteedRewardSpecs: rewards,
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
@ -88,21 +88,21 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu
|
||||
},
|
||||
async () => {
|
||||
// Choose Vitamins
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
const rewards: RewardSpecs[] = [];
|
||||
let i = 0;
|
||||
while (i < 3) {
|
||||
// 2/1 weight on base stat booster vs PP Up
|
||||
const roll = randSeedInt(3);
|
||||
if (roll === 0) {
|
||||
modifiers.push(modifierTypes.PP_UP);
|
||||
rewards.push(RewardId.PP_UP);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.BASE_STAT_BOOSTER);
|
||||
rewards.push(RewardId.BASE_STAT_BOOSTER);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: modifiers,
|
||||
guaranteedRewardSpecs: rewards,
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
@ -115,21 +115,21 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu
|
||||
},
|
||||
async () => {
|
||||
// Choose X Items
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
const rewards: RewardSpecs[] = [];
|
||||
let i = 0;
|
||||
while (i < 5) {
|
||||
// 4/1 weight on base stat booster vs Dire Hit
|
||||
const roll = randSeedInt(5);
|
||||
if (roll === 0) {
|
||||
modifiers.push(modifierTypes.DIRE_HIT);
|
||||
rewards.push(RewardId.DIRE_HIT);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.TEMP_STAT_STAGE_BOOSTER);
|
||||
rewards.push(RewardId.TEMP_STAT_STAGE_BOOSTER);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: modifiers,
|
||||
guaranteedRewardSpecs: rewards,
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
@ -142,25 +142,25 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu
|
||||
},
|
||||
async () => {
|
||||
// Choose Pokeballs
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
const rewards: RewardSpecs[] = [];
|
||||
let i = 0;
|
||||
while (i < 4) {
|
||||
// 10/30/20/5 weight on pokeballs
|
||||
const roll = randSeedInt(65);
|
||||
if (roll < 10) {
|
||||
modifiers.push(modifierTypes.POKEBALL);
|
||||
rewards.push(RewardId.POKEBALL);
|
||||
} else if (roll < 40) {
|
||||
modifiers.push(modifierTypes.GREAT_BALL);
|
||||
rewards.push(RewardId.GREAT_BALL);
|
||||
} else if (roll < 60) {
|
||||
modifiers.push(modifierTypes.ULTRA_BALL);
|
||||
rewards.push(RewardId.ULTRA_BALL);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.ROGUE_BALL);
|
||||
rewards.push(RewardId.ROGUE_BALL);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: modifiers,
|
||||
guaranteedRewardSpecs: rewards,
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
@ -9,7 +8,6 @@ import { Stat } from "#enums/stat";
|
||||
import type { PlayerPokemon } from "#field/pokemon";
|
||||
import type { PokemonMove } from "#moves/pokemon-move";
|
||||
import {
|
||||
generateModifierTypeOption,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
setEncounterExp,
|
||||
@ -96,15 +94,15 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.ATK])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.DEF])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
|
||||
generateModifierTypeOption(modifierTypes.DIRE_HIT)!,
|
||||
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
|
||||
generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.ATK])!,
|
||||
generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.DEF])!,
|
||||
generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
|
||||
generateRewardOptionFromId(RewardId.DIRE_HIT)!,
|
||||
generateRewardOptionFromId(RewardId.RARER_CANDY)!,
|
||||
];
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: modifiers,
|
||||
guaranteedRewardOptions: modifiers,
|
||||
fillRemaining: false,
|
||||
});
|
||||
}
|
||||
@ -144,15 +142,15 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPATK])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPDEF])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
|
||||
generateModifierTypeOption(modifierTypes.DIRE_HIT)!,
|
||||
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
|
||||
generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.SPATK])!,
|
||||
generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.SPDEF])!,
|
||||
generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
|
||||
generateRewardOptionFromId(RewardId.DIRE_HIT)!,
|
||||
generateRewardOptionFromId(RewardId.RARER_CANDY)!,
|
||||
];
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: modifiers,
|
||||
guaranteedRewardOptions: modifiers,
|
||||
fillRemaining: false,
|
||||
});
|
||||
}
|
||||
@ -192,15 +190,15 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.ACC])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
|
||||
generateModifierTypeOption(modifierTypes.GREAT_BALL)!,
|
||||
generateModifierTypeOption(modifierTypes.IV_SCANNER)!,
|
||||
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
|
||||
generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.ACC])!,
|
||||
generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
|
||||
generateRewardOptionFromId(RewardId.GREAT_BALL)!,
|
||||
generateRewardOptionFromId(RewardId.IV_SCANNER)!,
|
||||
generateRewardOptionFromId(RewardId.RARER_CANDY)!,
|
||||
];
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: modifiers,
|
||||
guaranteedRewardOptions: modifiers,
|
||||
fillRemaining: false,
|
||||
});
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { EncounterBattleAnim } from "#data/battle-anims";
|
||||
import { allAbilities, modifierTypes } from "#data/data-lists";
|
||||
import { allAbilities, allHeldItems } from "#data/data-lists";
|
||||
import { Gender } from "#data/gender";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
@ -18,12 +19,11 @@ import { Stat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { AttackTypeBoosterModifierType } from "#modifiers/modifier-type";
|
||||
import { getNewHeldItemFromCategory } from "#items/held-item-pool";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
@ -31,11 +31,7 @@ import {
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
applyAbilityOverrideToPokemon,
|
||||
applyDamageToPokemon,
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
} from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import { applyAbilityOverrideToPokemon, applyDamageToPokemon } 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";
|
||||
@ -254,7 +250,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
|
||||
}
|
||||
}
|
||||
|
||||
// No rewards
|
||||
// No allRewards
|
||||
leaveEncounterWithoutBattle(true);
|
||||
},
|
||||
)
|
||||
@ -299,19 +295,17 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
|
||||
|
||||
function giveLeadPokemonAttackTypeBoostItem() {
|
||||
// Give first party pokemon attack type boost item for free at end of battle
|
||||
const leadPokemon = globalScene.getPlayerParty()?.[0];
|
||||
const leadPokemon = globalScene.getPlayerParty()[0];
|
||||
if (leadPokemon) {
|
||||
// Generate type booster held item, default to Charcoal if item fails to generate
|
||||
let boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType;
|
||||
if (!boosterModifierType) {
|
||||
boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
|
||||
PokemonType.FIRE,
|
||||
]) as AttackTypeBoosterModifierType;
|
||||
let item = getNewHeldItemFromCategory(HeldItemCategoryId.TYPE_ATTACK_BOOSTER, leadPokemon);
|
||||
if (!item) {
|
||||
item = HeldItemId.CHARCOAL;
|
||||
}
|
||||
applyModifierTypeToPlayerPokemon(leadPokemon, boosterModifierType);
|
||||
leadPokemon.heldItemManager.add(item);
|
||||
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
encounter.setDialogueToken("itemName", boosterModifierType.name);
|
||||
encounter.setDialogueToken("itemName", allHeldItems[item].name);
|
||||
encounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
|
||||
queueEncounterMessage(`${namespace}:found_item`);
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
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 { ModifierTypeOption } from "#modifiers/modifier-type";
|
||||
import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#modifiers/modifier-type";
|
||||
import type { RewardOption, TrainerItemReward } from "#items/reward";
|
||||
import { generatePlayerRewardOptions, generateRewardPoolWeights, getRewardPoolForType } from "#items/reward-pool-utils";
|
||||
import { isTmReward } from "#items/reward-utils";
|
||||
import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
@ -89,18 +91,18 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
// Waves 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER
|
||||
const tier =
|
||||
globalScene.currentBattle.waveIndex > 160
|
||||
? ModifierTier.MASTER
|
||||
? RarityTier.MASTER
|
||||
: globalScene.currentBattle.waveIndex > 120
|
||||
? ModifierTier.ROGUE
|
||||
? RarityTier.ROGUE
|
||||
: globalScene.currentBattle.waveIndex > 40
|
||||
? ModifierTier.ULTRA
|
||||
: ModifierTier.GREAT;
|
||||
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
|
||||
let item: ModifierTypeOption | null = null;
|
||||
// TMs and Candy Jar excluded from possible rewards as they're too swingy in value for a singular item reward
|
||||
while (!item || item.type.id.includes("TM_") || item.type.id === "CANDY_JAR") {
|
||||
item = getPlayerModifierTypeOptions(1, globalScene.getPlayerParty(), [], {
|
||||
guaranteedModifierTiers: [tier],
|
||||
? RarityTier.ULTRA
|
||||
: RarityTier.GREAT;
|
||||
generateRewardPoolWeights(getRewardPoolForType(RewardPoolType.PLAYER), globalScene.getPlayerParty(), 0);
|
||||
let item: RewardOption | null = null;
|
||||
// TMs and Candy Jar excluded from possible allRewards as they're too swingy in value for a singular item reward
|
||||
while (!item || isTmReward(item.type) || (item.type as TrainerItemReward).itemId === TrainerItemId.CANDY_JAR) {
|
||||
item = generatePlayerRewardOptions(1, globalScene.getPlayerParty(), [], {
|
||||
guaranteedRarityTiers: [tier],
|
||||
allowLuckUpgrades: false,
|
||||
})[0];
|
||||
}
|
||||
@ -151,9 +153,9 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
async () => {
|
||||
// Pick battle
|
||||
// Pokemon will randomly boost 1 stat by 2 stages
|
||||
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
|
||||
const item = globalScene.currentBattle.mysteryEncounter!.misc as RewardOption;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [item],
|
||||
guaranteedRewardOptions: [item],
|
||||
fillRemaining: false,
|
||||
});
|
||||
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||
@ -175,9 +177,9 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
.withOptionPhase(async () => {
|
||||
// Pick steal
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
|
||||
const item = globalScene.currentBattle.mysteryEncounter!.misc as RewardOption;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [item],
|
||||
guaranteedRewardOptions: [item],
|
||||
fillRemaining: false,
|
||||
});
|
||||
|
||||
@ -199,7 +201,7 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
// Leave encounter with no allRewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { SpeciesFormChangeActiveTrigger } from "#data/form-change-triggers";
|
||||
import { getPokeballAtlasKey, getPokeballTintColor } from "#data/pokeball";
|
||||
import { FieldPosition } from "#enums/field-position";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
@ -160,7 +160,7 @@ export const FunAndGamesEncounter: MysteryEncounter = MysteryEncounterBuilder.wi
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
// Leave encounter with no RewardId or exp
|
||||
await transitionMysteryEncounterIntroVisuals(true, true);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
@ -281,21 +281,21 @@ function handleNextTurn() {
|
||||
if (healthRatio < 0.03) {
|
||||
// Grand prize
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.MULTI_LENS],
|
||||
guaranteedRewardSpecs: [HeldItemId.MULTI_LENS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
resultMessageKey = `${namespace}:best_result`;
|
||||
} else if (healthRatio < 0.15) {
|
||||
// 2nd prize
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SCOPE_LENS],
|
||||
guaranteedRewardSpecs: [HeldItemId.SCOPE_LENS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
resultMessageKey = `${namespace}:great_result`;
|
||||
} else if (healthRatio < 0.33) {
|
||||
// 3rd prize
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.WIDE_LENS],
|
||||
guaranteedRewardSpecs: [HeldItemId.WIDE_LENS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
resultMessageKey = `${namespace}:good_result`;
|
||||
@ -387,7 +387,7 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise<void> {
|
||||
globalScene.add.existing(pokemon);
|
||||
globalScene.field.add(pokemon);
|
||||
addPokeballOpenParticles(pokemon.x, pokemon.y - 16, pokemon.pokeball);
|
||||
globalScene.updateModifiers(true);
|
||||
globalScene.updateItems(true);
|
||||
globalScene.updateFieldScale();
|
||||
pokemon.showInfo();
|
||||
pokemon.playAnim();
|
||||
|
@ -1,33 +1,30 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { timedEventManager } from "#app/global-event-manager";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { allSpecies } from "#data/data-lists";
|
||||
import { allHeldItems, allSpecies } from "#data/data-lists";
|
||||
import { Gender, getGenderSymbol } from "#data/gender";
|
||||
import { getNatureName } from "#data/nature";
|
||||
import { getPokeballAtlasKey, getPokeballTintColor } from "#data/pokeball";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { getTypeRgb } from "#data/type";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { HeldItemCategoryId, type HeldItemId, isItemInCategory } from "#enums/held-item-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import type { PokeballType } from "#enums/pokeball";
|
||||
import { RewardPoolType } from "#enums/reward-pool-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { doShinySparkleAnim } from "#field/anims";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { EnemyPokemon } from "#field/pokemon";
|
||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import {
|
||||
HiddenAbilityRateBoosterModifier,
|
||||
PokemonFormChangeItemModifier,
|
||||
ShinyRateBoosterModifier,
|
||||
SpeciesStatBoosterModifier,
|
||||
} from "#modifiers/modifier";
|
||||
import type { ModifierTypeOption } from "#modifiers/modifier-type";
|
||||
import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#modifiers/modifier-type";
|
||||
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";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { getEncounterText, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import {
|
||||
@ -209,9 +206,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon;
|
||||
const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon;
|
||||
const modifiers = tradedPokemon
|
||||
.getHeldItems()
|
||||
.filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier));
|
||||
const heldItemConfig = tradedPokemon.heldItemManager
|
||||
.generateHeldItemConfiguration()
|
||||
.filter(ic => !isItemInCategory(ic.entry as HeldItemId, HeldItemCategoryId.SPECIES_STAT_BOOSTER));
|
||||
|
||||
// Generate a trainer name
|
||||
const traderName = generateRandomTraderName();
|
||||
@ -235,16 +232,12 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
dataSource.variant,
|
||||
dataSource.ivs,
|
||||
dataSource.nature,
|
||||
heldItemConfig,
|
||||
dataSource,
|
||||
);
|
||||
globalScene.getPlayerParty().push(newPlayerPokemon);
|
||||
await newPlayerPokemon.loadAssets();
|
||||
|
||||
for (const mod of modifiers) {
|
||||
mod.pokemonId = newPlayerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
|
||||
// Show the trade animation
|
||||
await showTradeBackground();
|
||||
await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon);
|
||||
@ -278,7 +271,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
if (timedEventManager.isEventActive()) {
|
||||
shinyThreshold.value *= timedEventManager.getShinyMultiplier();
|
||||
}
|
||||
globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold });
|
||||
|
||||
// Base shiny chance of 512/65536 -> 1/128, affected by events and Shiny Charms
|
||||
// Maximum shiny chance of 4096/65536 -> 1/16, cannot improve further after that
|
||||
@ -292,7 +285,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
if (tradePokemon.species.abilityHidden) {
|
||||
if (tradePokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new NumberHolder(64);
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, {
|
||||
numberHolder: hiddenAbilityChance,
|
||||
});
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
|
||||
@ -331,9 +326,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon;
|
||||
const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon;
|
||||
const modifiers = tradedPokemon
|
||||
.getHeldItems()
|
||||
.filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier));
|
||||
const heldItemConfig = tradedPokemon.heldItemManager
|
||||
.generateHeldItemConfiguration()
|
||||
.filter(ic => !isItemInCategory(ic.entry as HeldItemId, HeldItemCategoryId.SPECIES_STAT_BOOSTER));
|
||||
|
||||
// Generate a trainer name
|
||||
const traderName = generateRandomTraderName();
|
||||
@ -356,16 +351,12 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
dataSource.variant,
|
||||
dataSource.ivs,
|
||||
dataSource.nature,
|
||||
heldItemConfig,
|
||||
dataSource,
|
||||
);
|
||||
globalScene.getPlayerParty().push(newPlayerPokemon);
|
||||
await newPlayerPokemon.loadAssets();
|
||||
|
||||
for (const mod of modifiers) {
|
||||
mod.pokemonId = newPlayerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
|
||||
// Show the trade animation
|
||||
await showTradeBackground();
|
||||
await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon);
|
||||
@ -390,17 +381,15 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(it => {
|
||||
return it.isTransferable;
|
||||
});
|
||||
const validItems = pokemon.heldItemManager.getTransferableHeldItems();
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
return validItems.map((id: HeldItemId) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
label: allHeldItems[id].name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.misc.chosenModifier = modifier;
|
||||
encounter.setDialogueToken("chosenItem", allHeldItems[id].name);
|
||||
encounter.misc.chosenHeldItem = id;
|
||||
encounter.misc.chosenPokemon = pokemon;
|
||||
return true;
|
||||
},
|
||||
@ -411,10 +400,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon has items to trade
|
||||
const meetsReqs =
|
||||
pokemon.getHeldItems().filter(it => {
|
||||
return it.isTransferable;
|
||||
}).length > 0;
|
||||
const meetsReqs = pokemon.heldItemManager.getTransferableHeldItems().length > 0;
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null;
|
||||
}
|
||||
@ -426,44 +412,36 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const modifier = encounter.misc.chosenModifier as PokemonHeldItemModifier;
|
||||
const heldItemId = encounter.misc.chosenHeldItem as HeldItemId;
|
||||
const party = globalScene.getPlayerParty();
|
||||
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Check tier of the traded item, the received item will be one tier up
|
||||
const type = modifier.type.withTierFromPool(ModifierPoolType.PLAYER, party);
|
||||
let tier = type.tier ?? ModifierTier.GREAT;
|
||||
// Eggs and White Herb are not in the pool
|
||||
if (type.id === "WHITE_HERB") {
|
||||
tier = ModifierTier.GREAT;
|
||||
} else if (type.id === "LUCKY_EGG") {
|
||||
tier = ModifierTier.ULTRA;
|
||||
} else if (type.id === "GOLDEN_EGG") {
|
||||
tier = ModifierTier.ROGUE;
|
||||
}
|
||||
let tier = getHeldItemTier(heldItemId) ?? RarityTier.GREAT;
|
||||
|
||||
// Increment tier by 1
|
||||
if (tier < ModifierTier.MASTER) {
|
||||
if (tier < RarityTier.MASTER) {
|
||||
tier++;
|
||||
}
|
||||
|
||||
regenerateModifierPoolThresholds(party, ModifierPoolType.PLAYER, 0);
|
||||
let item: ModifierTypeOption | null = null;
|
||||
// TMs excluded from possible rewards
|
||||
while (!item || item.type.id.includes("TM_")) {
|
||||
item = getPlayerModifierTypeOptions(1, party, [], {
|
||||
guaranteedModifierTiers: [tier],
|
||||
generateRewardPoolWeights(getRewardPoolForType(RewardPoolType.PLAYER), party, 0);
|
||||
let item: RewardOption | null = null;
|
||||
// TMs excluded from possible allRewards
|
||||
while (!item || isTmReward(item.type)) {
|
||||
item = generatePlayerRewardOptions(1, party, [], {
|
||||
guaranteedRarityTiers: [tier],
|
||||
allowLuckUpgrades: false,
|
||||
})[0];
|
||||
}
|
||||
|
||||
encounter.setDialogueToken("itemName", item.type.name);
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [item],
|
||||
guaranteedRewardOptions: [item],
|
||||
fillRemaining: false,
|
||||
});
|
||||
|
||||
chosenPokemon.loseHeldItem(modifier, false);
|
||||
await globalScene.updateModifiers(true, true);
|
||||
chosenPokemon.heldItemManager.remove(heldItemId);
|
||||
await globalScene.updateItems(true);
|
||||
|
||||
// Generate a trainer name
|
||||
const traderName = generateRandomTraderName();
|
||||
@ -484,7 +462,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
// Leave encounter with no allRewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import { initBattleWithEnemyConfig, setEncounterRewards } from "#mystery-encounters/encounter-phase-utils";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
@ -147,7 +147,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM],
|
||||
guaranteedRewardSpecs: [RewardId.TM_COMMON, RewardId.TM_GREAT, RewardId.MEMORY_MUSHROOM],
|
||||
fillRemaining: true,
|
||||
});
|
||||
|
||||
@ -175,7 +175,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
|
||||
guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.GREAT, RarityTier.GREAT],
|
||||
fillRemaining: true,
|
||||
});
|
||||
|
||||
@ -206,7 +206,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter
|
||||
encounter.expMultiplier = 0.9;
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT],
|
||||
guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ULTRA, RarityTier.GREAT],
|
||||
fillRemaining: true,
|
||||
});
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
@ -141,25 +141,25 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) {
|
||||
// Choose between 2 COMMON / 2 GREAT tier items (20%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.GREAT, ModifierTier.GREAT],
|
||||
guaranteedRarityTiers: [RarityTier.COMMON, RarityTier.COMMON, RarityTier.GREAT, RarityTier.GREAT],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
// Display result message then proceed to allRewards
|
||||
queueEncounterMessage(`${namespace}:option.1.normal`);
|
||||
leaveEncounterWithoutBattle();
|
||||
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) {
|
||||
// Choose between 3 ULTRA tier items (30%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA],
|
||||
guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.ULTRA],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
// Display result message then proceed to allRewards
|
||||
queueEncounterMessage(`${namespace}:option.1.good`);
|
||||
leaveEncounterWithoutBattle();
|
||||
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) {
|
||||
// Choose between 2 ROGUE tier items (10%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
|
||||
guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
// Display result message then proceed to allRewards
|
||||
queueEncounterMessage(`${namespace}:option.1.great`);
|
||||
leaveEncounterWithoutBattle();
|
||||
} else if (
|
||||
@ -168,9 +168,9 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
) {
|
||||
// Choose 1 MASTER tier item (5%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.MASTER],
|
||||
guaranteedRarityTiers: [RarityTier.MASTER],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
// Display result message then proceed to allRewards
|
||||
queueEncounterMessage(`${namespace}:option.1.amazing`);
|
||||
leaveEncounterWithoutBattle();
|
||||
} else {
|
||||
@ -208,7 +208,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
// Leave encounter with no allRewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
|
@ -8,9 +8,10 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import type { EnemyPokemon } from "#field/pokemon";
|
||||
import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#modifiers/modifier";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { getEncounterText, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import {
|
||||
initSubsequentOptionSelect,
|
||||
@ -124,7 +125,7 @@ export const SafariZoneEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
// Leave encounter with no allRewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
@ -297,7 +298,9 @@ async function summonSafariPokemon() {
|
||||
const hiddenIndex = pokemon.species.ability2 ? 2 : 1;
|
||||
if (pokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new NumberHolder(256);
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, {
|
||||
numberHolder: hiddenAbilityChance,
|
||||
});
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
|
||||
@ -332,8 +335,7 @@ async function summonSafariPokemon() {
|
||||
// shows up and the IV scanner breaks. For now, we place the IV scanner code
|
||||
// separately so that at least the IV scanner works.
|
||||
|
||||
const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier);
|
||||
if (ivScannerModifier) {
|
||||
if (globalScene.trainerItems.hasItem(TrainerItemId.IV_SCANNER)) {
|
||||
globalScene.phaseManager.pushNew("ScanIvsPhase", pokemon.getBattlerIndex());
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allHeldItems } from "#data/data-lists";
|
||||
import { getNatureName } from "#data/nature";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
@ -8,9 +8,9 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import type { Nature } from "#enums/nature";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { getNewVitaminHeldItem } from "#items/held-item-pool";
|
||||
import { getEncounterText, queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
setEncounterExp,
|
||||
@ -18,7 +18,6 @@ import {
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
applyDamageToPokemon,
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
isPokemonValidForEncounterOptionSelection,
|
||||
} from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
@ -96,15 +95,12 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
// Update money
|
||||
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Calculate modifiers and dialogue tokens
|
||||
const modifiers = [
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
];
|
||||
encounter.setDialogueToken("boost1", modifiers[0].name);
|
||||
encounter.setDialogueToken("boost2", modifiers[1].name);
|
||||
const items = [getNewVitaminHeldItem(), getNewVitaminHeldItem()];
|
||||
encounter.setDialogueToken("boost1", allHeldItems[items[0]].name);
|
||||
encounter.setDialogueToken("boost2", allHeldItems[items[1]].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
modifiers: modifiers,
|
||||
items: items,
|
||||
};
|
||||
};
|
||||
|
||||
@ -131,10 +127,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
// Choose Cheap Option
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
const modifiers = encounter.misc.modifiers;
|
||||
const items = encounter.misc.items;
|
||||
|
||||
for (const modType of modifiers) {
|
||||
await applyModifierTypeToPlayerPokemon(chosenPokemon, modType);
|
||||
for (const item of items) {
|
||||
chosenPokemon.heldItemManager.add(item);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
@ -179,15 +175,12 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
// Update money
|
||||
updatePlayerMoney(-(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Calculate modifiers and dialogue tokens
|
||||
const modifiers = [
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
];
|
||||
encounter.setDialogueToken("boost1", modifiers[0].name);
|
||||
encounter.setDialogueToken("boost2", modifiers[1].name);
|
||||
const items = [getNewVitaminHeldItem(), getNewVitaminHeldItem()];
|
||||
encounter.setDialogueToken("boost1", allHeldItems[items[0]].name);
|
||||
encounter.setDialogueToken("boost2", allHeldItems[items[1]].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
modifiers: modifiers,
|
||||
items: items,
|
||||
};
|
||||
};
|
||||
|
||||
@ -202,10 +195,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
// Choose Expensive Option
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
const modifiers = encounter.misc.modifiers;
|
||||
const items = encounter.misc.items;
|
||||
|
||||
for (const modType of modifiers) {
|
||||
await applyModifierTypeToPlayerPokemon(chosenPokemon, modType);
|
||||
for (const item of items) {
|
||||
chosenPokemon.heldItemManager.add(item);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
@ -234,7 +227,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
// Leave encounter with no allRewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { CustomPokemonData } from "#data/pokemon-data";
|
||||
import { AiType } from "#enums/ai-type";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
@ -11,14 +10,11 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig, EnemyPokemonConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
@ -78,24 +74,12 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
status: [StatusEffect.SLEEP, 6], // Extra turns on timer for Snorlax's start of fight moves
|
||||
nature: Nature.DOCILE,
|
||||
moveSet: [MoveId.BODY_SLAM, MoveId.CRUNCH, MoveId.SLEEP_TALK, MoveId.REST],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType,
|
||||
stackCount: randSeedInt(2, 0),
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType,
|
||||
stackCount: randSeedInt(2, 0),
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.SITRUS_BERRY, count: 1 },
|
||||
{ entry: HeldItemId.ENIGMA_BERRY, count: 1 },
|
||||
{ entry: HeldItemId.HP_UP, count: 1 },
|
||||
{ entry: HeldItemId.SOOTHE_BELL, count: randSeedInt(2, 0) },
|
||||
{ entry: HeldItemId.LUCKY_EGG, count: randSeedInt(2, 0) },
|
||||
],
|
||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||
aiType: AiType.SMART, // Required to ensure Snorlax uses Sleep Talk while it is asleep
|
||||
@ -131,7 +115,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS],
|
||||
guaranteedRewardSpecs: [HeldItemId.LEFTOVERS],
|
||||
fillRemaining: true,
|
||||
});
|
||||
encounter.startOfBattleEffects.push({
|
||||
@ -178,7 +162,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
// Steal the Snorlax's Leftovers
|
||||
const instance = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS],
|
||||
guaranteedRewardSpecs: [HeldItemId.LEFTOVERS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
// Snorlax exp to Pokemon that did the stealing
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
@ -13,11 +13,9 @@ import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import { getBiomeKey } from "#field/arena";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { EnemyPokemon } from "#field/pokemon";
|
||||
import { getPartyLuckValue } from "#modifiers/modifier-type";
|
||||
import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierTypeOption,
|
||||
initBattleWithEnemyConfig,
|
||||
setEncounterExp,
|
||||
setEncounterRewards,
|
||||
@ -34,6 +32,7 @@ import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encou
|
||||
import { MoneyRequirement, WaveModulusRequirement } from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import { PokemonData } from "#system/pokemon-data";
|
||||
import { randSeedInt } from "#utils/common";
|
||||
import { getPartyLuckValue } from "#utils/party";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounters/teleportingHijinks";
|
||||
@ -173,10 +172,8 @@ export const TeleportingHijinksEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
],
|
||||
};
|
||||
|
||||
const magnet = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.STEEL])!;
|
||||
const metalCoat = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.ELECTRIC])!;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [magnet, metalCoat],
|
||||
guaranteedRewardSpecs: [HeldItemId.MAGNET, HeldItemId.METAL_COAT],
|
||||
fillRemaining: true,
|
||||
});
|
||||
await transitionMysteryEncounterIntroVisuals(true, true);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { speciesStarterCosts } from "#balance/starters";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import type { IEggOptions } from "#data/egg";
|
||||
import { getPokeballTintColor } from "#data/pokeball";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { EggSourceType } from "#enums/egg-source-types";
|
||||
import { EggTier } from "#enums/egg-type";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
@ -294,7 +294,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs);
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
|
||||
guaranteedRewardSpecs: [HeldItemId.SOOTHE_BELL],
|
||||
fillRemaining: true,
|
||||
},
|
||||
eggOptions,
|
||||
@ -304,7 +304,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
// Remove all Pokemon from the party except the chosen Pokemon
|
||||
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon1);
|
||||
|
||||
// Configure outro dialogue for egg rewards
|
||||
// Configure outro dialogue for egg allRewards
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
@ -353,7 +353,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs);
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
|
||||
guaranteedRewardSpecs: [HeldItemId.SOOTHE_BELL],
|
||||
fillRemaining: true,
|
||||
},
|
||||
eggOptions,
|
||||
@ -363,7 +363,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
// Remove all Pokemon from the party except the chosen Pokemon
|
||||
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon2);
|
||||
|
||||
// Configure outro dialogue for egg rewards
|
||||
// Configure outro dialogue for egg allRewards
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
@ -412,7 +412,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs);
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
|
||||
guaranteedRewardSpecs: [HeldItemId.SOOTHE_BELL],
|
||||
fillRemaining: true,
|
||||
},
|
||||
eggOptions,
|
||||
@ -422,7 +422,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
// Remove all Pokemon from the party except the chosen Pokemon
|
||||
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon3);
|
||||
|
||||
// Configure outro dialogue for egg rewards
|
||||
// Configure outro dialogue for egg allRewards
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
@ -614,7 +614,6 @@ function removePokemonFromPartyAndStoreHeldItems(encounter: MysteryEncounter, ch
|
||||
party[chosenIndex] = party[0];
|
||||
party[0] = chosenPokemon;
|
||||
encounter.misc.originalParty = globalScene.getPlayerParty().slice(1);
|
||||
encounter.misc.originalPartyHeldItems = encounter.misc.originalParty.map(p => p.getHeldItems());
|
||||
globalScene["party"] = [chosenPokemon];
|
||||
}
|
||||
|
||||
@ -623,14 +622,7 @@ function restorePartyAndHeldItems() {
|
||||
// Restore original party
|
||||
globalScene.getPlayerParty().push(...encounter.misc.originalParty);
|
||||
|
||||
// Restore held items
|
||||
const originalHeldItems = encounter.misc.originalPartyHeldItems;
|
||||
for (const pokemonHeldItemsList of originalHeldItems) {
|
||||
for (const heldItem of pokemonHeldItemsList) {
|
||||
globalScene.addModifier(heldItem, true, false, false, true);
|
||||
}
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
globalScene.updateItems(true);
|
||||
}
|
||||
|
||||
function onGameOver() {
|
||||
@ -648,7 +640,7 @@ function onGameOver() {
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
chosenPokemon.friendship = 0;
|
||||
|
||||
// Clear all rewards that would have been earned
|
||||
// Clear all allRewards that would have been earned
|
||||
encounter.doEncounterRewards = undefined;
|
||||
|
||||
// Set flag that encounter was failed
|
||||
|
@ -238,7 +238,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
// Leave encounter with no allRewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { CustomPokemonData } from "#data/pokemon-data";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
@ -13,19 +12,16 @@ import { Nature } from "#enums/nature";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import { modifyPlayerPokemonBST } from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
@ -95,23 +91,12 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||
nature: Nature.HARDY,
|
||||
moveSet: [MoveId.INFESTATION, MoveId.SALT_CURE, MoveId.GASTRO_ACID, MoveId.HEAL_ORDER],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.SITRUS_BERRY, count: 1 },
|
||||
{ entry: HeldItemId.ENIGMA_BERRY, count: 1 },
|
||||
{ entry: HeldItemId.APICOT_BERRY, count: 1 },
|
||||
{ entry: HeldItemId.GANLON_BERRY, count: 1 },
|
||||
{ entry: HeldItemId.LUM_BERRY, count: 2 },
|
||||
],
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
@ -171,11 +156,11 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
sortedParty.forEach((pokemon, index) => {
|
||||
if (index < 2) {
|
||||
// -15 to the two highest BST mons
|
||||
modifyPlayerPokemonBST(pokemon, false);
|
||||
pokemon.heldItemManager.add(HeldItemId.SHUCKLE_JUICE_BAD);
|
||||
encounter.setDialogueToken("highBstPokemon" + (index + 1), pokemon.getNameToRender());
|
||||
} else {
|
||||
// +10 for the rest
|
||||
modifyPlayerPokemonBST(pokemon, true);
|
||||
pokemon.heldItemManager.add(HeldItemId.SHUCKLE_JUICE_GOOD);
|
||||
}
|
||||
});
|
||||
|
||||
@ -207,7 +192,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SOUL_DEW],
|
||||
guaranteedRewardSpecs: [HeldItemId.SOUL_DEW],
|
||||
fillRemaining: true,
|
||||
});
|
||||
encounter.startOfBattleEffects.push(
|
||||
|
@ -1,27 +1,25 @@
|
||||
import { applyAbAttrs } from "#abilities/apply-ab-attrs";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allRewards } from "#data/data-lists";
|
||||
import { SpeciesFormChangeAbilityTrigger } from "#data/form-change-triggers";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import type { Reward } from "#items/reward";
|
||||
import { generateRewardOptionFromId } from "#items/reward-utils";
|
||||
import { showEncounterDialogue, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
generateModifierTypeOption,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
@ -32,6 +30,8 @@ import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import i18next from "i18next";
|
||||
|
||||
// TODO: make all items unstealable
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounters/theWinstrateChallenge";
|
||||
|
||||
@ -119,7 +119,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounter
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Spawn 5 trainer battles back to back with Macho Brace in rewards
|
||||
// Spawn 5 trainer battles back to back with Macho Brace in allRewards
|
||||
globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = async () => {
|
||||
await endTrainerBattleAndShowDialogue();
|
||||
};
|
||||
@ -142,7 +142,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounter
|
||||
// Refuse the challenge, they full heal the party and give the player a Rarer Candy
|
||||
globalScene.phaseManager.unshiftNew("PartyHealPhase", true);
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY],
|
||||
guaranteedRewardSpecs: [RewardId.RARER_CANDY],
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
@ -158,17 +158,17 @@ async function spawnNextTrainerOrEndEncounter() {
|
||||
await showEncounterDialogue(`${namespace}:victory`, `${namespace}:speaker`);
|
||||
|
||||
// Give 10x Voucher
|
||||
const newModifier = modifierTypes.VOUCHER_PREMIUM().newModifier();
|
||||
globalScene.addModifier(newModifier);
|
||||
const reward = allRewards[RewardId.VOUCHER_PREMIUM]();
|
||||
globalScene.applyReward(reward as Reward, {});
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }));
|
||||
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: (reward as Reward).name }));
|
||||
|
||||
await showEncounterDialogue(`${namespace}:victory_2`, `${namespace}:speaker`);
|
||||
globalScene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in
|
||||
const machoBrace = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!;
|
||||
machoBrace.type.tier = ModifierTier.MASTER;
|
||||
globalScene.ui.clearText(); // Clears "Winstrate" title from screen as allRewards get animated in
|
||||
const machoBrace = generateRewardOptionFromId(HeldItemId.MACHO_BRACE)!;
|
||||
machoBrace.type.tier = RarityTier.MASTER;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [machoBrace],
|
||||
guaranteedRewardOptions: [machoBrace],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.doContinueEncounter = undefined;
|
||||
@ -258,16 +258,9 @@ function getVictorTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 0, // Guts
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [MoveId.FACADE, MoveId.BRAVE_BIRD, MoveId.PROTECT, MoveId.QUICK_ATTACK],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.FLAME_ORB, count: 1 },
|
||||
{ entry: HeldItemId.FOCUS_BAND, count: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -276,16 +269,9 @@ function getVictorTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 1, // Guts
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [MoveId.FACADE, MoveId.OBSTRUCT, MoveId.NIGHT_SLASH, MoveId.FIRE_PUNCH],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.FLAME_ORB, count: 1 },
|
||||
{ entry: HeldItemId.LEFTOVERS, count: 2 },
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -302,16 +288,9 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 0, // Natural Cure
|
||||
nature: Nature.CALM,
|
||||
moveSet: [MoveId.SYNTHESIS, MoveId.SLUDGE_BOMB, MoveId.GIGA_DRAIN, MoveId.SLEEP_POWDER],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType,
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.SOUL_DEW, count: 1 },
|
||||
{ entry: HeldItemId.QUICK_CLAW, count: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -320,21 +299,9 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig {
|
||||
formIndex: 1,
|
||||
nature: Nature.TIMID,
|
||||
moveSet: [MoveId.PSYSHOCK, MoveId.MOONBLAST, MoveId.SHADOW_BALL, MoveId.WILL_O_WISP],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
|
||||
PokemonType.PSYCHIC,
|
||||
]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
|
||||
PokemonType.FAIRY,
|
||||
]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.TWISTED_SPOON, count: 1 },
|
||||
{ entry: HeldItemId.FAIRY_FEATHER, count: 1 },
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -351,17 +318,9 @@ function getViviTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 3, // Lightning Rod
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [MoveId.WATERFALL, MoveId.MEGAHORN, MoveId.KNOCK_OFF, MoveId.REST],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
|
||||
stackCount: 4,
|
||||
isTransferable: false,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.LUM_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.HP_UP, count: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -370,16 +329,9 @@ function getViviTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 1, // Poison Heal
|
||||
nature: Nature.JOLLY,
|
||||
moveSet: [MoveId.SPORE, MoveId.SWORDS_DANCE, MoveId.SEED_BOMB, MoveId.DRAIN_PUNCH],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
|
||||
stackCount: 4,
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
|
||||
isTransferable: false,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.HP_UP, count: 4 },
|
||||
{ entry: HeldItemId.TOXIC_ORB, count: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -388,13 +340,7 @@ function getViviTrainerConfig(): EnemyPartyConfig {
|
||||
formIndex: 1,
|
||||
nature: Nature.CALM,
|
||||
moveSet: [MoveId.EARTH_POWER, MoveId.FIRE_BLAST, MoveId.YAWN, MoveId.PROTECT],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
|
||||
stackCount: 3,
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
heldItemConfig: [{ entry: HeldItemId.QUICK_CLAW, count: 3 }],
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -410,12 +356,7 @@ function getVickyTrainerConfig(): EnemyPartyConfig {
|
||||
formIndex: 1,
|
||||
nature: Nature.IMPISH,
|
||||
moveSet: [MoveId.AXE_KICK, MoveId.ICE_PUNCH, MoveId.ZEN_HEADBUTT, MoveId.BULLET_PUNCH],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType,
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
heldItemConfig: [{ entry: HeldItemId.SHELL_BELL, count: 1 }],
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -431,13 +372,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 0, // Soundproof
|
||||
nature: Nature.MODEST,
|
||||
moveSet: [MoveId.THUNDERBOLT, MoveId.GIGA_DRAIN, MoveId.FOUL_PLAY, MoveId.THUNDER_WAVE],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
heldItemConfig: [{ entry: HeldItemId.ZINC, count: 2 }],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(SpeciesId.SWALOT),
|
||||
@ -445,51 +380,18 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 2, // Gluttony
|
||||
nature: Nature.QUIET,
|
||||
moveSet: [MoveId.SLUDGE_BOMB, MoveId.GIGA_DRAIN, MoveId.ICE_BEAM, MoveId.EARTHQUAKE],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.STARF]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SALAC]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LANSAT]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LIECHI]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.PETAYA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LEPPA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.SITRUS_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.APICOT_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.GANLON_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.STARF_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.SALAC_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.LUM_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.LANSAT_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.LIECHI_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.PETAYA_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.ENIGMA_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.LEPPA_BERRY, count: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -498,13 +400,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 2, // Tangled Feet
|
||||
nature: Nature.JOLLY,
|
||||
moveSet: [MoveId.DRILL_PECK, MoveId.QUICK_ATTACK, MoveId.THRASH, MoveId.KNOCK_OFF],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
heldItemConfig: [{ entry: HeldItemId.KINGS_ROCK, count: 2 }],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(SpeciesId.ALAKAZAM),
|
||||
@ -512,13 +408,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
|
||||
formIndex: 1,
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [MoveId.PSYCHIC, MoveId.SHADOW_BALL, MoveId.FOCUS_BLAST, MoveId.THUNDERBOLT],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
heldItemConfig: [{ entry: HeldItemId.WIDE_LENS, count: 2 }],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(SpeciesId.DARMANITAN),
|
||||
@ -526,13 +416,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 0, // Sheer Force
|
||||
nature: Nature.IMPISH,
|
||||
moveSet: [MoveId.EARTHQUAKE, MoveId.U_TURN, MoveId.FLARE_BLITZ, MoveId.ROCK_SLIDE],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
heldItemConfig: [{ entry: HeldItemId.QUICK_CLAW, count: 2 }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -12,7 +12,6 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { getStatKey } from "#enums/stat";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
@ -26,7 +25,6 @@ import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
|
||||
import { PokemonData } from "#system/pokemon-data";
|
||||
import type { HeldModifierConfig } from "#types/held-modifier-config";
|
||||
import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler";
|
||||
import { isNullOrUndefined, randSeedShuffle } from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
@ -102,8 +100,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
// Spawn light training session with chosen pokemon
|
||||
// Every 50 waves, add +1 boss segment, capping at 5
|
||||
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 50), 5);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(playerPokemon, segments, modifiers);
|
||||
const config = getEnemyConfig(playerPokemon, segments);
|
||||
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
@ -152,13 +149,8 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
globalScene.gameData.setPokemonCaught(playerPokemon, false);
|
||||
}
|
||||
|
||||
// Add pokemon and mods back
|
||||
globalScene.getPlayerParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
mod.pokemonId = playerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
// Make held items show up again
|
||||
globalScene.updateItems(true);
|
||||
queueEncounterMessage(`${namespace}:option.1.finished`);
|
||||
};
|
||||
|
||||
@ -217,8 +209,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
// Spawn medium training session with chosen pokemon
|
||||
// Every 40 waves, add +1 boss segment, capping at 6
|
||||
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 40), 6);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(playerPokemon, segments, modifiers);
|
||||
const config = getEnemyConfig(playerPokemon, segments);
|
||||
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
@ -227,13 +218,8 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
playerPokemon.setCustomNature(encounter.misc.chosenNature);
|
||||
globalScene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature);
|
||||
|
||||
// Add pokemon and modifiers back
|
||||
globalScene.getPlayerParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
mod.pokemonId = playerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
// Make held items show up again
|
||||
globalScene.updateItems(true);
|
||||
};
|
||||
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
@ -308,8 +294,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
// Every 30 waves, add +1 boss segment, capping at 6
|
||||
// Also starts with +1 to all stats
|
||||
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 30), 6);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(playerPokemon, segments, modifiers);
|
||||
const config = getEnemyConfig(playerPokemon, segments);
|
||||
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
||||
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
@ -340,13 +325,8 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
playerPokemon.calculateStats();
|
||||
globalScene.gameData.setPokemonCaught(playerPokemon, false);
|
||||
|
||||
// Add pokemon and mods back
|
||||
globalScene.getPlayerParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
mod.pokemonId = playerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
// Make held items show up again
|
||||
globalScene.updateItems(true);
|
||||
};
|
||||
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
@ -366,25 +346,19 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
// Leave encounter with no allRewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
||||
function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifiers: ModifiersHolder): EnemyPartyConfig {
|
||||
function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number): EnemyPartyConfig {
|
||||
playerPokemon.resetSummonData();
|
||||
|
||||
// Passes modifiers by reference
|
||||
modifiers.value = playerPokemon.getHeldItems();
|
||||
const modifierConfigs = modifiers.value.map(mod => {
|
||||
return {
|
||||
modifier: mod.clone(),
|
||||
isTransferable: false,
|
||||
stackCount: mod.stackCount,
|
||||
};
|
||||
}) as HeldModifierConfig[];
|
||||
// TODO: fix various things, like make enemy items untransferable, make sure form change items can come back
|
||||
const config = playerPokemon.heldItemManager.generateHeldItemConfiguration();
|
||||
|
||||
const data = new PokemonData(playerPokemon);
|
||||
return {
|
||||
@ -396,12 +370,8 @@ function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifier
|
||||
formIndex: playerPokemon.formIndex,
|
||||
level: playerPokemon.level,
|
||||
dataSource: data,
|
||||
modifierConfigs: modifierConfigs,
|
||||
heldItemConfig: config,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
class ModifiersHolder {
|
||||
public value: PokemonHeldItemModifier[] = [];
|
||||
}
|
||||
|
@ -1,28 +1,27 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allHeldItems, allTrainerItems } from "#data/data-lists";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#modifiers/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import { assignItemToFirstFreePokemon } from "#items/item-utility";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig, EnemyPokemonConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import { applyModifierTypeToPlayerPokemon } 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";
|
||||
@ -83,41 +82,13 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
formIndex: 1, // Gmax
|
||||
bossSegmentModifier: 1, // +1 Segment from normal
|
||||
moveSet: [MoveId.GUNK_SHOT, MoveId.STOMPING_TANTRUM, MoveId.HAMMER_ARM, MoveId.PAYBACK],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
|
||||
stackCount: randSeedInt(2, 0),
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType,
|
||||
stackCount: randSeedInt(2, 1),
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType,
|
||||
stackCount: randSeedInt(3, 1),
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.GOLDEN_EGG) as PokemonHeldItemModifierType,
|
||||
stackCount: randSeedInt(2, 0),
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemCategoryId.BERRY, count: 4 },
|
||||
{ entry: HeldItemCategoryId.BASE_STAT_BOOST, count: 2 },
|
||||
{ entry: HeldItemId.TOXIC_ORB, count: randSeedInt(2, 0) },
|
||||
{ entry: HeldItemId.SOOTHE_BELL, count: randSeedInt(2, 1) },
|
||||
{ entry: HeldItemId.LUCKY_EGG, count: randSeedInt(3, 1) },
|
||||
{ entry: HeldItemId.GOLDEN_EGG, count: randSeedInt(2, 0) },
|
||||
],
|
||||
};
|
||||
const config: EnemyPartyConfig = {
|
||||
@ -157,18 +128,14 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
await tryApplyDigRewardItems();
|
||||
|
||||
const blackSludge = generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [
|
||||
SHOP_ITEM_COST_MULTIPLIER,
|
||||
]);
|
||||
const modifier = blackSludge?.newModifier();
|
||||
if (modifier) {
|
||||
await globalScene.addModifier(modifier, false, false, false, true);
|
||||
const blackSludge = globalScene.trainerItems.add(TrainerItemId.BLACK_SLUDGE);
|
||||
if (blackSludge) {
|
||||
globalScene.playSound("battle_anims/PRSFX- Venom Drench", {
|
||||
volume: 2,
|
||||
});
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", {
|
||||
modifierName: modifier.type.name,
|
||||
modifierName: allTrainerItems[TrainerItemId.BLACK_SLUDGE].name,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
@ -200,7 +167,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT],
|
||||
guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ULTRA, RarityTier.GREAT],
|
||||
fillRemaining: true,
|
||||
});
|
||||
encounter.startOfBattleEffects.push(
|
||||
@ -224,44 +191,18 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
.build();
|
||||
|
||||
async function tryApplyDigRewardItems() {
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
const leftovers = generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType;
|
||||
|
||||
const party = globalScene.getPlayerParty();
|
||||
|
||||
// Iterate over the party until an item was successfully given
|
||||
// First leftovers
|
||||
for (const pokemon of party) {
|
||||
const heldItems = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
|
||||
|
||||
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) {
|
||||
await applyModifierTypeToPlayerPokemon(pokemon, leftovers);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assignItemToFirstFreePokemon(HeldItemId.LEFTOVERS, party);
|
||||
|
||||
// Second leftovers
|
||||
for (const pokemon of party) {
|
||||
const heldItems = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
|
||||
|
||||
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) {
|
||||
await applyModifierTypeToPlayerPokemon(pokemon, leftovers);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assignItemToFirstFreePokemon(HeldItemId.LEFTOVERS, party);
|
||||
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGainCount", {
|
||||
modifierName: leftovers.name,
|
||||
modifierName: allHeldItems[HeldItemId.LEFTOVERS].name,
|
||||
count: 2,
|
||||
}),
|
||||
null,
|
||||
@ -270,23 +211,12 @@ async function tryApplyDigRewardItems() {
|
||||
);
|
||||
|
||||
// Only Shell bell
|
||||
for (const pokemon of party) {
|
||||
const heldItems = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
|
||||
|
||||
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) {
|
||||
await applyModifierTypeToPlayerPokemon(pokemon, shellBell);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assignItemToFirstFreePokemon(HeldItemId.SHELL_BELL, party);
|
||||
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGainCount", {
|
||||
modifierName: shellBell.name,
|
||||
modifierName: allHeldItems[HeldItemId.SHELL_BELL].name,
|
||||
count: 1,
|
||||
}),
|
||||
null,
|
||||
|
@ -2,6 +2,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { HeldItemCategoryId, type HeldItemId } from "#enums/held-item-id";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
@ -10,7 +11,8 @@ 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 { BerryModifier } from "#modifiers/modifier";
|
||||
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";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
@ -29,10 +31,10 @@ import {
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
|
||||
import { MoveRequirement, PersistentModifierRequirement } from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import { HeldItemRequirement, MoveRequirement } from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import { CHARMING_MOVES } from "#mystery-encounters/requirement-groups";
|
||||
import { PokemonData } from "#system/pokemon-data";
|
||||
import { isNullOrUndefined, randSeedInt } from "#utils/common";
|
||||
import { isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounters/uncommonBreed";
|
||||
@ -187,7 +189,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withSceneRequirement(new HeldItemRequirement(HeldItemCategoryId.BERRY, 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
@ -202,20 +204,16 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
// Give it some food
|
||||
|
||||
// Remove 4 random berries from player's party
|
||||
// Get all player berry items, remove from party, and store reference
|
||||
const berryItems: BerryModifier[] = globalScene.findModifiers(
|
||||
m => m instanceof BerryModifier,
|
||||
) as BerryModifier[];
|
||||
const berryMap = getPartyBerries();
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const index = randSeedInt(berryItems.length);
|
||||
const randBerry = berryItems[index];
|
||||
randBerry.stackCount--;
|
||||
if (randBerry.stackCount === 0) {
|
||||
globalScene.removeModifier(randBerry);
|
||||
berryItems.splice(index, 1);
|
||||
}
|
||||
const berryWeights = berryMap.map(b => (b.item as HeldItemSpecs).stack);
|
||||
const index = pickWeightedIndex(berryWeights) ?? 0;
|
||||
const randBerry = berryMap[index];
|
||||
globalScene.getPokemonById(randBerry.pokemonId)?.heldItemManager.remove(randBerry.item.id as HeldItemId);
|
||||
(randBerry.item as HeldItemSpecs).stack -= 1;
|
||||
}
|
||||
await globalScene.updateModifiers(true, true);
|
||||
await globalScene.updateItems(true);
|
||||
|
||||
// Pokemon joins the team, with 2 egg moves
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { allSpecies, modifierTypes } from "#data/data-lists";
|
||||
import { allSpecies } from "#data/data-lists";
|
||||
import { getLevelTotalExp } from "#data/exp";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
@ -11,17 +11,17 @@ import { Nature } from "#enums/nature";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#modifiers/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import type { HeldItemConfiguration } from "#items/held-item-data-types";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig, EnemyPokemonConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
@ -38,7 +38,6 @@ 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 { HeldModifierConfig } from "#types/held-modifier-config";
|
||||
import { isNullOrUndefined, NumberHolder, randSeedInt, randSeedShuffle } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
|
||||
@ -220,12 +219,12 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
|
||||
await doNewTeamPostProcess(transformations);
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [
|
||||
modifierTypes.MEMORY_MUSHROOM,
|
||||
modifierTypes.ROGUE_BALL,
|
||||
modifierTypes.MINT,
|
||||
modifierTypes.MINT,
|
||||
modifierTypes.MINT,
|
||||
guaranteedRewardSpecs: [
|
||||
RewardId.MEMORY_MUSHROOM,
|
||||
RewardId.ROGUE_BALL,
|
||||
RewardId.MINT,
|
||||
RewardId.MINT,
|
||||
RewardId.MINT,
|
||||
],
|
||||
fillRemaining: false,
|
||||
});
|
||||
@ -244,7 +243,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Battle your "future" team for some item rewards
|
||||
// Battle your "future" team for some item RewardId
|
||||
const transformations: PokemonTransformation[] =
|
||||
globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations;
|
||||
|
||||
@ -260,20 +259,14 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
dataSource.player = false;
|
||||
|
||||
// Copy held items to new pokemon
|
||||
const newPokemonHeldItemConfigs: HeldModifierConfig[] = [];
|
||||
for (const item of transformation.heldItems) {
|
||||
newPokemonHeldItemConfigs.push({
|
||||
modifier: item.clone() as PokemonHeldItemModifier,
|
||||
stackCount: item.getStackCount(),
|
||||
isTransferable: false,
|
||||
});
|
||||
}
|
||||
// TODO: Make items untransferable
|
||||
const newPokemonHeldItemConfig = transformation.heldItems;
|
||||
|
||||
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
||||
if (shouldGetOldGateau(newPokemon)) {
|
||||
newPokemonHeldItemConfigs.push({
|
||||
modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false,
|
||||
newPokemonHeldItemConfig.push({
|
||||
entry: HeldItemId.OLD_GATEAU,
|
||||
count: 1,
|
||||
});
|
||||
}
|
||||
|
||||
@ -282,7 +275,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD,
|
||||
level: previousPokemon.level,
|
||||
dataSource: dataSource,
|
||||
modifierConfigs: newPokemonHeldItemConfigs,
|
||||
heldItemConfig: newPokemonHeldItemConfig,
|
||||
};
|
||||
|
||||
enemyPokemonConfigs.push(enemyConfig);
|
||||
@ -301,7 +294,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
};
|
||||
|
||||
const onBeforeRewards = () => {
|
||||
// Before battle rewards, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently)
|
||||
// Before battle RewardId, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently)
|
||||
// One random pokemon will get its passive unlocked
|
||||
const passiveDisabledPokemon = globalScene.getPlayerParty().filter(p => !p.passive);
|
||||
if (passiveDisabledPokemon?.length > 0) {
|
||||
@ -314,13 +307,13 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.GREAT,
|
||||
ModifierTier.GREAT,
|
||||
guaranteedRarityTiers: [
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.GREAT,
|
||||
RarityTier.GREAT,
|
||||
],
|
||||
fillRemaining: false,
|
||||
},
|
||||
@ -364,7 +357,7 @@ interface PokemonTransformation {
|
||||
previousPokemon: PlayerPokemon;
|
||||
newSpecies: PokemonSpecies;
|
||||
newPokemon: PlayerPokemon;
|
||||
heldItems: PokemonHeldItemModifier[];
|
||||
heldItems: HeldItemConfiguration;
|
||||
}
|
||||
|
||||
function getTeamTransformations(): PokemonTransformation[] {
|
||||
@ -389,9 +382,7 @@ function getTeamTransformations(): PokemonTransformation[] {
|
||||
for (let i = 0; i < numPokemon; i++) {
|
||||
const removed = removedPokemon[i];
|
||||
const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id);
|
||||
pokemonTransformations[index].heldItems = removed
|
||||
.getHeldItems()
|
||||
.filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||
pokemonTransformations[index].heldItems = removed.heldItemManager.generateHeldItemConfiguration();
|
||||
|
||||
const bst = removed.getSpeciesForm().getBaseStatTotal();
|
||||
let newBstRange: [number, number];
|
||||
@ -447,17 +438,14 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
|
||||
}
|
||||
|
||||
// Copy old items to new pokemon
|
||||
for (const item of transformation.heldItems) {
|
||||
item.pokemonId = newPokemon.id;
|
||||
globalScene.addModifier(item, false, false, false, true);
|
||||
}
|
||||
const heldItemConfiguration = transformation.heldItems;
|
||||
|
||||
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
||||
if (shouldGetOldGateau(newPokemon)) {
|
||||
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU();
|
||||
const modifier = modType?.newModifier(newPokemon);
|
||||
if (modifier) {
|
||||
globalScene.addModifier(modifier, false, false, false, true);
|
||||
}
|
||||
heldItemConfiguration.push({
|
||||
entry: HeldItemId.OLD_GATEAU,
|
||||
count: 1,
|
||||
});
|
||||
}
|
||||
|
||||
newPokemon.calculateStats();
|
||||
@ -499,7 +487,9 @@ async function postProcessTransformedPokemon(
|
||||
const hiddenIndex = newPokemon.species.ability2 ? 2 : 1;
|
||||
if (newPokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new NumberHolder(256);
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, {
|
||||
numberHolder: hiddenAbilityChance,
|
||||
});
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { allAbilities } from "#data/data-lists";
|
||||
import { allAbilities, allHeldItems } from "#data/data-lists";
|
||||
import { SpeciesFormChangeItemTrigger } from "#data/form-change-triggers";
|
||||
import { pokemonFormChanges } from "#data/pokemon-forms";
|
||||
import type { AbilityId } from "#enums/ability-id";
|
||||
import { FormChangeItem } from "#enums/form-change-item";
|
||||
import { getHeldItemCategory, type HeldItemCategoryId, type HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
@ -13,8 +14,6 @@ import { StatusEffect } from "#enums/status-effect";
|
||||
import { TimeOfDay } from "#enums/time-of-day";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import type { PlayerPokemon } from "#field/pokemon";
|
||||
import { AttackTypeBoosterModifier } from "#modifiers/modifier";
|
||||
import type { AttackTypeBoosterModifierType } from "#modifiers/modifier-type";
|
||||
import { coerceArray, isNullOrUndefined } from "#utils/common";
|
||||
|
||||
export interface EncounterRequirement {
|
||||
@ -351,39 +350,6 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
|
||||
}
|
||||
}
|
||||
|
||||
export class PersistentModifierRequirement extends EncounterSceneRequirement {
|
||||
requiredHeldItemModifiers: string[];
|
||||
minNumberOfItems: number;
|
||||
|
||||
constructor(heldItem: string | string[], minNumberOfItems = 1) {
|
||||
super();
|
||||
this.minNumberOfItems = minNumberOfItems;
|
||||
this.requiredHeldItemModifiers = coerceArray(heldItem);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
const partyPokemon = globalScene.getPlayerParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this.requiredHeldItemModifiers?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
let modifierCount = 0;
|
||||
for (const modifier of this.requiredHeldItemModifiers) {
|
||||
const matchingMods = globalScene.findModifiers(m => m.constructor.name === modifier);
|
||||
if (matchingMods?.length > 0) {
|
||||
for (const matchingMod of matchingMods) {
|
||||
modifierCount += matchingMod.stackCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modifierCount >= this.minNumberOfItems;
|
||||
}
|
||||
|
||||
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
|
||||
return ["requiredItem", this.requiredHeldItemModifiers[0]];
|
||||
}
|
||||
}
|
||||
|
||||
export class MoneyRequirement extends EncounterSceneRequirement {
|
||||
requiredMoney: number; // Static value
|
||||
scalingMultiplier: number; // Calculates required money based off wave index
|
||||
@ -832,73 +798,14 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
|
||||
}
|
||||
}
|
||||
|
||||
export class HeldItemRequirement extends EncounterPokemonRequirement {
|
||||
requiredHeldItemModifiers: string[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
requireTransferable: boolean;
|
||||
|
||||
constructor(heldItem: string | string[], minNumberOfPokemon = 1, invertQuery = false, requireTransferable = true) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItemModifiers = coerceArray(heldItem);
|
||||
this.requireTransferable = requireTransferable;
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
const partyPokemon = globalScene.getPlayerParty();
|
||||
if (isNullOrUndefined(partyPokemon)) {
|
||||
return false;
|
||||
}
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter(pokemon =>
|
||||
this.requiredHeldItemModifiers.some(heldItem => {
|
||||
return pokemon.getHeldItems().some(it => {
|
||||
return it.constructor.name === heldItem && (!this.requireTransferable || it.isTransferable);
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
||||
// E.g. functions as a blacklist
|
||||
return partyPokemon.filter(
|
||||
pokemon =>
|
||||
pokemon.getHeldItems().filter(it => {
|
||||
return (
|
||||
!this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) &&
|
||||
(!this.requireTransferable || it.isTransferable)
|
||||
);
|
||||
}).length > 0,
|
||||
);
|
||||
}
|
||||
|
||||
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = pokemon?.getHeldItems().filter(it => {
|
||||
return (
|
||||
this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) &&
|
||||
(!this.requireTransferable || it.isTransferable)
|
||||
);
|
||||
});
|
||||
if (requiredItems && requiredItems.length > 0) {
|
||||
return ["heldItem", requiredItems[0].type.name];
|
||||
}
|
||||
return ["heldItem", ""];
|
||||
}
|
||||
}
|
||||
|
||||
export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRequirement {
|
||||
requiredHeldItemTypes: PokemonType[];
|
||||
export class HoldingItemRequirement extends EncounterPokemonRequirement {
|
||||
requiredHeldItems: (HeldItemId | HeldItemCategoryId)[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
requireTransferable: boolean;
|
||||
|
||||
constructor(
|
||||
heldItemTypes: PokemonType | PokemonType[],
|
||||
heldItem: HeldItemId | HeldItemCategoryId | (HeldItemId | HeldItemCategoryId)[],
|
||||
minNumberOfPokemon = 1,
|
||||
invertQuery = false,
|
||||
requireTransferable = true,
|
||||
@ -906,7 +813,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItemTypes = coerceArray(heldItemTypes);
|
||||
this.requiredHeldItems = coerceArray(heldItem);
|
||||
this.requireTransferable = requireTransferable;
|
||||
}
|
||||
|
||||
@ -921,45 +828,92 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
|
||||
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter(pokemon =>
|
||||
this.requiredHeldItemTypes.some(heldItemType => {
|
||||
return pokemon.getHeldItems().some(it => {
|
||||
return (
|
||||
it instanceof AttackTypeBoosterModifier &&
|
||||
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType &&
|
||||
(!this.requireTransferable || it.isTransferable)
|
||||
);
|
||||
});
|
||||
this.requiredHeldItems.some(heldItem => {
|
||||
return this.requireTransferable
|
||||
? pokemon.heldItemManager.hasTransferableItem(heldItem)
|
||||
: pokemon.heldItemManager.hasItem(heldItem);
|
||||
}),
|
||||
);
|
||||
}
|
||||
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
||||
// E.g. functions as a blacklist
|
||||
return partyPokemon.filter(
|
||||
pokemon =>
|
||||
pokemon.getHeldItems().filter(it => {
|
||||
return !this.requiredHeldItemTypes.some(
|
||||
heldItemType =>
|
||||
it instanceof AttackTypeBoosterModifier &&
|
||||
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType &&
|
||||
(!this.requireTransferable || it.isTransferable),
|
||||
);
|
||||
}).length > 0,
|
||||
return partyPokemon.filter(pokemon =>
|
||||
pokemon.getHeldItems().some(item => {
|
||||
return (
|
||||
!this.requiredHeldItems.some(heldItem => item === heldItem || getHeldItemCategory(item) === heldItem) &&
|
||||
(!this.requireTransferable || allHeldItems[item].isTransferable)
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = pokemon?.getHeldItems().filter(it => {
|
||||
const requiredItems = pokemon?.getHeldItems().filter(item => {
|
||||
return (
|
||||
this.requiredHeldItemTypes.some(
|
||||
heldItemType =>
|
||||
it instanceof AttackTypeBoosterModifier &&
|
||||
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType,
|
||||
) &&
|
||||
(!this.requireTransferable || it.isTransferable)
|
||||
this.requiredHeldItems.some(heldItem => item === heldItem) &&
|
||||
(!this.requireTransferable || allHeldItems[item].isTransferable)
|
||||
);
|
||||
});
|
||||
if (requiredItems && requiredItems.length > 0) {
|
||||
return ["heldItem", requiredItems[0].type.name];
|
||||
return ["heldItem", allHeldItems[requiredItems[0]].name];
|
||||
}
|
||||
return ["heldItem", ""];
|
||||
}
|
||||
}
|
||||
|
||||
export class HeldItemRequirement extends EncounterSceneRequirement {
|
||||
requiredHeldItems: (HeldItemId | HeldItemCategoryId)[];
|
||||
minNumberOfItems: number;
|
||||
invertQuery: boolean;
|
||||
requireTransferable: boolean;
|
||||
|
||||
constructor(
|
||||
heldItem: HeldItemId | HeldItemCategoryId | (HeldItemId | HeldItemCategoryId)[],
|
||||
minNumberOfItems = 1,
|
||||
invertQuery = false,
|
||||
requireTransferable = true,
|
||||
) {
|
||||
super();
|
||||
this.minNumberOfItems = minNumberOfItems;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItems = coerceArray(heldItem);
|
||||
this.requireTransferable = requireTransferable;
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
const partyPokemon = globalScene.getPlayerParty();
|
||||
if (isNullOrUndefined(partyPokemon)) {
|
||||
return false;
|
||||
}
|
||||
console.log("COUNTED:", this.queryPartyForItems(partyPokemon), this.minNumberOfItems);
|
||||
return this.queryPartyForItems(partyPokemon) >= this.minNumberOfItems;
|
||||
}
|
||||
|
||||
queryPartyForItems(partyPokemon: PlayerPokemon[]): number {
|
||||
let count = 0;
|
||||
for (const pokemon of partyPokemon) {
|
||||
for (const item of pokemon.getHeldItems()) {
|
||||
const itemInList = this.requiredHeldItems.some(
|
||||
heldItem => item === heldItem || getHeldItemCategory(item) === heldItem,
|
||||
);
|
||||
const requiredItem = this.invertQuery ? !itemInList : itemInList;
|
||||
if (requiredItem && (!this.requireTransferable || allHeldItems[item].isTransferable)) {
|
||||
count += pokemon.heldItemManager.getStack(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = pokemon?.getHeldItems().filter(item => {
|
||||
return (
|
||||
this.requiredHeldItems.some(heldItem => item === heldItem) &&
|
||||
(!this.requireTransferable || allHeldItems[item].isTransferable)
|
||||
);
|
||||
});
|
||||
if (requiredItems && requiredItems.length > 0) {
|
||||
return ["heldItem", allHeldItems[requiredItems[0]].name];
|
||||
}
|
||||
return ["heldItem", ""];
|
||||
}
|
||||
|
@ -174,11 +174,11 @@ export class MysteryEncounter implements IMysteryEncounter {
|
||||
onVisualsStart?: () => boolean;
|
||||
/** Event triggered prior to {@linkcode CommandPhase}, during {@linkcode TurnInitPhase} */
|
||||
onTurnStart?: () => boolean;
|
||||
/** Event prior to any rewards logic in {@linkcode MysteryEncounterRewardsPhase} */
|
||||
/** Event prior to any allRewards logic in {@linkcode MysteryEncounterRewardsPhase} */
|
||||
onRewards?: () => Promise<void>;
|
||||
/** Will provide the player party EXP before rewards are displayed for that wave */
|
||||
/** Will provide the player party EXP before allRewards are displayed for that wave */
|
||||
doEncounterExp?: () => boolean;
|
||||
/** Will provide the player a rewards shop for that wave */
|
||||
/** Will provide the player a allRewards shop for that wave */
|
||||
doEncounterRewards?: () => boolean;
|
||||
/** Will execute callback during VictoryPhase of a continuousEncounter */
|
||||
doContinueEncounter?: () => Promise<void>;
|
||||
@ -238,10 +238,10 @@ export class MysteryEncounter implements IMysteryEncounter {
|
||||
encounterMode: MysteryEncounterMode;
|
||||
/**
|
||||
* Flag for checking if it's the first time a shop is being shown for an encounter.
|
||||
* Defaults to true so that the first shop does not override the specified rewards.
|
||||
* Defaults to true so that the first shop does not override the specified allRewards.
|
||||
* Will be set to false after a shop is shown (so can't reroll same rarity items for free)
|
||||
*/
|
||||
lockEncounterRewardTiers: boolean;
|
||||
lockEncounterRarityTiers: boolean;
|
||||
/**
|
||||
* Will be set automatically, indicates special moves in startOfBattleEffects are complete (so will not repeat)
|
||||
*/
|
||||
@ -296,7 +296,7 @@ export class MysteryEncounter implements IMysteryEncounter {
|
||||
|
||||
// Reset any dirty flags or encounter data
|
||||
this.startOfBattleEffectsComplete = false;
|
||||
this.lockEncounterRewardTiers = true;
|
||||
this.lockEncounterRarityTiers = true;
|
||||
this.dialogueTokens = {};
|
||||
this.enemyPartyConfigs = [];
|
||||
this.startOfBattleEffects = [];
|
||||
@ -562,7 +562,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
continuousEncounter = false;
|
||||
catchAllowed = false;
|
||||
fleeAllowed = true;
|
||||
lockEncounterRewardTiers = false;
|
||||
lockEncounterRarityTiers = false;
|
||||
startOfBattleEffectsComplete = false;
|
||||
hasBattleAnimationsWithoutTargets = false;
|
||||
skipEnemyBattleTurns = false;
|
||||
@ -929,13 +929,13 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Can set custom encounter rewards via this callback function
|
||||
* If rewards are always deterministic for an encounter, this is a good way to set them
|
||||
* Can set custom encounter allRewards via this callback function
|
||||
* If allRewards are always deterministic for an encounter, this is a good way to set them
|
||||
*
|
||||
* NOTE: If rewards are dependent on options selected, runtime data, etc.,
|
||||
* NOTE: If allRewards are dependent on options selected, runtime data, etc.,
|
||||
* It may be better to programmatically set doEncounterRewards elsewhere.
|
||||
* There is a helper function in mystery-encounter utils, setEncounterRewards(), which can be called programmatically to set rewards
|
||||
* @param doEncounterRewards Synchronous callback function to perform during rewards phase of the encounter
|
||||
* There is a helper function in mystery-encounter utils, setEncounterRewards(), which can be called programmatically to set allRewards
|
||||
* @param doEncounterRewards Synchronous callback function to perform during allRewards phase of the encounter
|
||||
* @returns
|
||||
*/
|
||||
withRewards(doEncounterRewards: () => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterRewards">> {
|
||||
@ -946,10 +946,10 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
* Can set custom encounter exp via this callback function
|
||||
* If exp always deterministic for an encounter, this is a good way to set them
|
||||
*
|
||||
* NOTE: If rewards are dependent on options selected, runtime data, etc.,
|
||||
* NOTE: If allRewards are dependent on options selected, runtime data, etc.,
|
||||
* It may be better to programmatically set doEncounterExp elsewhere.
|
||||
* There is a helper function in mystery-encounter utils, setEncounterExp(), which can be called programmatically to set rewards
|
||||
* @param doEncounterExp Synchronous callback function to perform during rewards phase of the encounter
|
||||
* There is a helper function in mystery-encounter utils, setEncounterExp(), which can be called programmatically to set allRewards
|
||||
* @param doEncounterExp Synchronous callback function to perform during allRewards phase of the encounter
|
||||
* @returns
|
||||
*/
|
||||
withExp(doEncounterExp: () => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterExp">> {
|
||||
|
@ -5,7 +5,6 @@ import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { BiomePoolTier, biomeLinks } from "#balance/biomes";
|
||||
import { initMoveAnim, loadMoveAnimAssets } from "#data/battle-anims";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import type { IEggOptions } from "#data/egg";
|
||||
import { Egg } from "#data/egg";
|
||||
import type { Gender } from "#data/gender";
|
||||
@ -14,11 +13,9 @@ import type { CustomPokemonData } from "#data/pokemon-data";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { Status } from "#data/status-effect";
|
||||
import type { AiType } from "#enums/ai-type";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import type { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { FieldPosition } from "#enums/field-position";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import type { Nature } from "#enums/nature";
|
||||
@ -31,13 +28,8 @@ import { UiMode } from "#enums/ui-mode";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { EnemyPokemon } from "#field/pokemon";
|
||||
import { Trainer } from "#field/trainer";
|
||||
import type { CustomModifierSettings, ModifierType } from "#modifiers/modifier-type";
|
||||
import {
|
||||
getPartyLuckValue,
|
||||
ModifierTypeGenerator,
|
||||
ModifierTypeOption,
|
||||
regenerateModifierPoolThresholds,
|
||||
} from "#modifiers/modifier-type";
|
||||
import type { HeldItemConfiguration } from "#items/held-item-data-types";
|
||||
import type { CustomRewardSettings } from "#items/reward-pool-utils";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { MysteryEncounterOption } from "#mystery-encounters/mystery-encounter-option";
|
||||
@ -45,11 +37,11 @@ import type { Variant } from "#sprites/variant";
|
||||
import type { PokemonData } from "#system/pokemon-data";
|
||||
import type { TrainerConfig } from "#trainers/trainer-config";
|
||||
import { trainerConfigs } from "#trainers/trainer-config";
|
||||
import type { HeldModifierConfig } from "#types/held-modifier-config";
|
||||
import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstact-option-select-ui-handler";
|
||||
import type { PartyOption, PokemonSelectFilter } from "#ui/party-ui-handler";
|
||||
import { PartyUiMode } from "#ui/party-ui-handler";
|
||||
import { coerceArray, isNullOrUndefined, randomString, randSeedInt, randSeedItem } from "#utils/common";
|
||||
import { getPartyLuckValue } from "#utils/party";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import i18next from "i18next";
|
||||
|
||||
@ -101,7 +93,7 @@ export interface EnemyPokemonConfig {
|
||||
/** Can set just the status, or pass a timer on the status turns */
|
||||
status?: StatusEffect | [StatusEffect, number];
|
||||
mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
|
||||
modifierConfigs?: HeldModifierConfig[];
|
||||
heldItemConfig?: HeldItemConfiguration;
|
||||
tags?: BattlerTagType[];
|
||||
dataSource?: PokemonData;
|
||||
tera?: PokemonType;
|
||||
@ -199,6 +191,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
|
||||
battle.enemyLevels.forEach((level, e) => {
|
||||
let enemySpecies: PokemonSpecies | undefined;
|
||||
let heldItemConfig: HeldItemConfiguration = [];
|
||||
let dataSource: PokemonData | undefined;
|
||||
let isBoss = false;
|
||||
if (!loaded) {
|
||||
@ -210,12 +203,14 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
dataSource = config.dataSource;
|
||||
enemySpecies = config.species;
|
||||
isBoss = config.isBoss;
|
||||
heldItemConfig = config.heldItemConfig ?? [];
|
||||
battle.enemyParty[e] = globalScene.addEnemyPokemon(
|
||||
enemySpecies,
|
||||
level,
|
||||
TrainerSlot.TRAINER,
|
||||
isBoss,
|
||||
false,
|
||||
heldItemConfig,
|
||||
dataSource,
|
||||
);
|
||||
} else {
|
||||
@ -225,6 +220,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
if (partyConfig?.pokemonConfigs && e < partyConfig.pokemonConfigs.length) {
|
||||
const config = partyConfig.pokemonConfigs[e];
|
||||
level = config.level ? config.level : level;
|
||||
heldItemConfig = config.heldItemConfig ?? [];
|
||||
dataSource = config.dataSource;
|
||||
enemySpecies = config.species;
|
||||
isBoss = config.isBoss;
|
||||
@ -241,6 +237,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
TrainerSlot.NONE,
|
||||
isBoss,
|
||||
false,
|
||||
heldItemConfig,
|
||||
dataSource,
|
||||
);
|
||||
}
|
||||
@ -427,16 +424,6 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
enemyPokemon_2.x += 300;
|
||||
}
|
||||
});
|
||||
if (!loaded) {
|
||||
regenerateModifierPoolThresholds(
|
||||
globalScene.getEnemyField(),
|
||||
battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD,
|
||||
);
|
||||
const customModifierTypes = partyConfig?.pokemonConfigs
|
||||
?.filter(config => config?.modifierConfigs)
|
||||
.map(config => config.modifierConfigs!);
|
||||
globalScene.generateEnemyModifiers(customModifierTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -485,45 +472,6 @@ export function updatePlayerMoney(changeValue: number, playSound = true, showMes
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts modifier bullshit to an actual item
|
||||
* @param modifier
|
||||
* @param pregenArgs Can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc.
|
||||
*/
|
||||
export function generateModifierType(modifier: () => ModifierType, pregenArgs?: any[]): ModifierType | null {
|
||||
const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier);
|
||||
if (!modifierId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let result: ModifierType = modifierTypes[modifierId]();
|
||||
|
||||
// Populates item id and tier (order matters)
|
||||
result = result
|
||||
.withIdFromFunc(modifierTypes[modifierId])
|
||||
.withTierFromPool(ModifierPoolType.PLAYER, globalScene.getPlayerParty());
|
||||
|
||||
return result instanceof ModifierTypeGenerator
|
||||
? result.generateType(globalScene.getPlayerParty(), pregenArgs)
|
||||
: result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts modifier bullshit to an actual item
|
||||
* @param modifier
|
||||
* @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc.
|
||||
*/
|
||||
export function generateModifierTypeOption(
|
||||
modifier: () => ModifierType,
|
||||
pregenArgs?: any[],
|
||||
): ModifierTypeOption | null {
|
||||
const result = generateModifierType(modifier, pregenArgs);
|
||||
if (result) {
|
||||
return new ModifierTypeOption(result, 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is intended for use inside onPreOptionPhase() of an encounter option
|
||||
* @param onPokemonSelected - Any logic that needs to be performed when Pokemon is chosen
|
||||
@ -738,12 +686,12 @@ export function selectOptionThenPokemon(
|
||||
/**
|
||||
* Will initialize reward phases to follow the mystery encounter
|
||||
* Can have shop displayed or skipped
|
||||
* @param customShopRewards - adds a shop phase with the specified rewards / reward tiers
|
||||
* @param customShopRewards - adds a shop phase with the specified allRewards / reward tiers
|
||||
* @param eggRewards
|
||||
* @param preRewardsCallback - can execute an arbitrary callback before the new phases if necessary (useful for updating items/party/injecting new phases before {@linkcode MysteryEncounterRewardsPhase})
|
||||
*/
|
||||
export function setEncounterRewards(
|
||||
customShopRewards?: CustomModifierSettings,
|
||||
customShopRewards?: CustomRewardSettings,
|
||||
eggRewards?: IEggOptions[],
|
||||
preRewardsCallback?: Function,
|
||||
) {
|
||||
@ -753,7 +701,7 @@ export function setEncounterRewards(
|
||||
}
|
||||
|
||||
if (customShopRewards) {
|
||||
globalScene.phaseManager.unshiftNew("SelectModifierPhase", 0, undefined, customShopRewards);
|
||||
globalScene.phaseManager.unshiftNew("SelectRewardPhase", 0, undefined, customShopRewards);
|
||||
} else {
|
||||
globalScene.phaseManager.tryRemovePhase(p => p.is("MysteryEncounterRewardsPhase"));
|
||||
}
|
||||
@ -820,8 +768,8 @@ export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSet
|
||||
|
||||
/**
|
||||
* Can be used to exit an encounter without any battles or followup
|
||||
* Will skip any shops and rewards, and queue the next encounter phase as normal
|
||||
* @param addHealPhase - when true, will add a shop phase to end of encounter with 0 rewards but healing items are available
|
||||
* Will skip any shops and allRewards, and queue the next encounter phase as normal
|
||||
* @param addHealPhase - when true, will add a shop phase to end of encounter with 0 allRewards but healing items are available
|
||||
* @param encounterMode - Can set custom encounter mode if necessary (may be required for forcing Pokemon to return before next phase)
|
||||
*/
|
||||
export function leaveEncounterWithoutBattle(
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { speciesStarterCosts } from "#balance/starters";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { Gender } from "#data/gender";
|
||||
import {
|
||||
doPokeballBounceAnim,
|
||||
@ -13,6 +12,7 @@ import { CustomPokemonData } from "#data/pokemon-data";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { getStatusEffectCatchRateMultiplier } from "#data/status-effect";
|
||||
import type { AbilityId } from "#enums/ability-id";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import type { PokeballType } from "#enums/pokeball";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
@ -22,8 +22,6 @@ import { StatusEffect } from "#enums/status-effect";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { addPokeballCaptureStars, addPokeballOpenParticles } from "#field/anims";
|
||||
import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import {
|
||||
getEncounterText,
|
||||
queueEncounterMessage,
|
||||
@ -372,60 +370,13 @@ export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) {
|
||||
applyHpChangeToPokemon(pokemon, heal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will modify all of a Pokemon's base stats by a flat value
|
||||
* Base stats can never go below 1
|
||||
* @param pokemon
|
||||
* @param value
|
||||
*/
|
||||
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, good: boolean) {
|
||||
const modType = modifierTypes
|
||||
.MYSTERY_ENCOUNTER_SHUCKLE_JUICE()
|
||||
.generateType(globalScene.getPlayerParty(), [good ? 10 : -15])
|
||||
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE);
|
||||
const modifier = modType?.newModifier(pokemon);
|
||||
if (modifier) {
|
||||
globalScene.addModifier(modifier, false, false, false, true);
|
||||
pokemon.calculateStats();
|
||||
export function applyHeldItemWithFallback(pokemon: Pokemon, item: HeldItemId, fallbackItem?: HeldItemId) {
|
||||
const added = pokemon.heldItemManager.add(item);
|
||||
if (!added && fallbackItem) {
|
||||
pokemon.heldItemManager.add(fallbackItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will attempt to add a new modifier to a Pokemon.
|
||||
* If the Pokemon already has max stacks of that item, it will instead apply 'fallbackModifierType', if specified.
|
||||
* @param scene
|
||||
* @param pokemon
|
||||
* @param modType
|
||||
* @param fallbackModifierType
|
||||
*/
|
||||
export async function applyModifierTypeToPlayerPokemon(
|
||||
pokemon: PlayerPokemon,
|
||||
modType: PokemonHeldItemModifierType,
|
||||
fallbackModifierType?: PokemonHeldItemModifierType,
|
||||
) {
|
||||
// Check if the Pokemon has max stacks of that item already
|
||||
const modifier = modType.newModifier(pokemon);
|
||||
const existing = globalScene.findModifier(
|
||||
(m): m is PokemonHeldItemModifier =>
|
||||
m instanceof PokemonHeldItemModifier &&
|
||||
m.type.id === modType.id &&
|
||||
m.pokemonId === pokemon.id &&
|
||||
m.matchType(modifier),
|
||||
) as PokemonHeldItemModifier | undefined;
|
||||
|
||||
// At max stacks
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
if (!fallbackModifierType) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply fallback
|
||||
return applyModifierTypeToPlayerPokemon(pokemon, fallbackModifierType);
|
||||
}
|
||||
|
||||
globalScene.addModifier(modifier, false, false, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative to using AttemptCapturePhase
|
||||
* Assumes player sprite is visible on the screen (this is intended for non-combat uses)
|
||||
@ -690,20 +641,10 @@ export async function catchPokemon(
|
||||
}
|
||||
};
|
||||
const addToParty = (slotIndex?: number) => {
|
||||
const newPokemon = pokemon.addToParty(pokeballType, slotIndex);
|
||||
const modifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier, false);
|
||||
pokemon.addToParty(pokeballType, slotIndex);
|
||||
if (globalScene.getPlayerParty().filter(p => p.isShiny()).length === 6) {
|
||||
globalScene.validateAchv(achvs.SHINY_PARTY);
|
||||
}
|
||||
Promise.all(modifiers.map(m => globalScene.addModifier(m, true))).then(() => {
|
||||
globalScene.updateModifiers(true);
|
||||
removePokemon();
|
||||
if (newPokemon) {
|
||||
newPokemon.loadAssets().then(end);
|
||||
} else {
|
||||
end();
|
||||
}
|
||||
});
|
||||
};
|
||||
Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => {
|
||||
if (globalScene.getPlayerParty().length === 6) {
|
||||
@ -728,6 +669,7 @@ export async function catchPokemon(
|
||||
pokemon.variant,
|
||||
pokemon.ivs,
|
||||
pokemon.nature,
|
||||
pokemon.heldItemManager.generateHeldItemConfiguration(),
|
||||
pokemon,
|
||||
);
|
||||
globalScene.ui.setMode(
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { NumberHolder } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
@ -93,7 +94,9 @@ export function getCriticalCaptureChance(modifiedCatchRate: number): number {
|
||||
}
|
||||
const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr);
|
||||
const catchingCharmMultiplier = new NumberHolder(1);
|
||||
globalScene.findModifier(m => m.is("CriticalCatchChanceBoosterModifier"))?.apply(catchingCharmMultiplier);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.CRITICAL_CATCH_CHANCE_BOOSTER, {
|
||||
numberHolder: catchingCharmMultiplier,
|
||||
});
|
||||
const dexMultiplier =
|
||||
globalScene.gameMode.isDaily || dexCount > 800
|
||||
? 2.5
|
||||
|
@ -10,7 +10,6 @@ import { StatusEffect } from "#enums/status-effect";
|
||||
import type { TimeOfDay } from "#enums/time-of-day";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { PokemonFormChangeItemModifier } from "#modifiers/modifier";
|
||||
import { type Constructor, coerceArray } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
@ -77,16 +76,12 @@ export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger {
|
||||
}
|
||||
|
||||
canChange(pokemon: Pokemon): boolean {
|
||||
return !!globalScene.findModifier(r => {
|
||||
// Assume that if m has the `formChangeItem` property, then it is a PokemonFormChangeItemModifier
|
||||
const m = r as PokemonFormChangeItemModifier;
|
||||
return (
|
||||
"formChangeItem" in m &&
|
||||
m.pokemonId === pokemon.id &&
|
||||
m.formChangeItem === this.item &&
|
||||
m.active === this.active
|
||||
);
|
||||
});
|
||||
const matchItem = pokemon.heldItemManager.formChangeItems[this.item];
|
||||
if (!matchItem) {
|
||||
return false;
|
||||
}
|
||||
console.log("CAN CHANGE FORMS:", matchItem.active === this.active);
|
||||
return matchItem.active === this.active;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,8 @@ import { globalScene } from "#app/global-scene";
|
||||
import { randSeedInt } from "#app/utils/common";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { TrainerVariant } from "#enums/trainer-variant";
|
||||
|
||||
@ -45,8 +45,8 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.GREAT, RarityTier.GREAT],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig()
|
||||
@ -77,8 +77,8 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.GREAT, RarityTier.GREAT],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_2]: new FixedBattleConfig()
|
||||
@ -150,8 +150,8 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA],
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.ULTRA],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_4]: new FixedBattleConfig()
|
||||
@ -212,14 +212,8 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
TrainerType.PENNY,
|
||||
]),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
],
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.ULTRA],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.RIVAL_5]: new FixedBattleConfig()
|
||||
@ -231,14 +225,8 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
],
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ULTRA, RarityTier.ULTRA],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig()
|
||||
@ -258,14 +246,14 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
TrainerType.PENNY_2,
|
||||
]),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
@ -362,14 +350,14 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.GREAT,
|
||||
ModifierTier.GREAT,
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.GREAT,
|
||||
RarityTier.GREAT,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
|
@ -3,7 +3,7 @@ import { globalScene } from "#app/global-scene";
|
||||
import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions";
|
||||
import { signatureSpecies } from "#balance/signature-species";
|
||||
import { tmSpecies } from "#balance/tms";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allRewards } from "#data/data-lists";
|
||||
import { doubleBattleDialogue } from "#data/double-battle-dialogue";
|
||||
import { Gender } from "#data/gender";
|
||||
import type { PokemonSpecies, PokemonSpeciesFilter } from "#data/pokemon-species";
|
||||
@ -31,10 +31,10 @@ import {
|
||||
TrainerPartyTemplate,
|
||||
trainerPartyTemplates,
|
||||
} from "#trainers/trainer-party-template";
|
||||
import type { ModifierTypeFunc } from "#types/modifier-types";
|
||||
import type { RewardFunc } from "#types/rewards";
|
||||
import type {
|
||||
GenAIFunc,
|
||||
GenModifiersFunc,
|
||||
GenTrainerItemsFunc,
|
||||
PartyMemberFunc,
|
||||
PartyMemberFuncs,
|
||||
PartyTemplateFunc,
|
||||
@ -107,9 +107,9 @@ export class TrainerConfig {
|
||||
public femaleEncounterBgm: string;
|
||||
public doubleEncounterBgm: string;
|
||||
public victoryBgm: string;
|
||||
public genModifiersFunc: GenModifiersFunc;
|
||||
public genTrainerItemsFunc: GenTrainerItemsFunc;
|
||||
public genAIFuncs: GenAIFunc[] = [];
|
||||
public modifierRewardFuncs: ModifierTypeFunc[] = [];
|
||||
public rewardFuncs: RewardFunc[] = [];
|
||||
public partyTemplates: TrainerPartyTemplate[];
|
||||
public partyTemplateFunc: PartyTemplateFunc;
|
||||
public partyMemberFuncs: PartyMemberFuncs = {};
|
||||
@ -459,8 +459,8 @@ export class TrainerConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
setGenModifiersFunc(genModifiersFunc: GenModifiersFunc): TrainerConfig {
|
||||
this.genModifiersFunc = genModifiersFunc;
|
||||
setGenTrainerItemsFunc(genTrainerItemsFunc: GenTrainerItemsFunc): TrainerConfig {
|
||||
this.genTrainerItemsFunc = genTrainerItemsFunc;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -470,7 +470,7 @@ export class TrainerConfig {
|
||||
* @param slot Optional, a specified slot that should be terastallized. Wraps to match party size (-1 will get the last slot and so on).
|
||||
* @returns this
|
||||
*/
|
||||
setRandomTeraModifiers(count: () => number, slot?: number): TrainerConfig {
|
||||
setRandomTeraType(count: () => number, slot?: number): TrainerConfig {
|
||||
this.genAIFuncs.push((party: EnemyPokemon[]) => {
|
||||
const shedinjaCanTera = !this.hasSpecialtyType() || this.specialtyType === PokemonType.BUG; // Better to check one time than 6
|
||||
const partyMemberIndexes = new Array(party.length)
|
||||
@ -501,23 +501,11 @@ export class TrainerConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
// function getRandomTeraModifiers(party: EnemyPokemon[], count: integer, types?: Type[]): PersistentModifier[] {
|
||||
// const ret: PersistentModifier[] = [];
|
||||
// const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i);
|
||||
// for (let t = 0; t < Math.min(count, party.length); t++) {
|
||||
// const randomIndex = Utils.randSeedItem(partyMemberIndexes);
|
||||
// partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1);
|
||||
// ret.push(modifierTypes.TERA_SHARD().generateType([], [ Utils.randSeedItem(types ? types : party[randomIndex].getTypes()) ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier); // TODO: is the bang correct?
|
||||
// }
|
||||
// return ret;
|
||||
// }
|
||||
|
||||
setModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig {
|
||||
this.modifierRewardFuncs = modifierTypeFuncs.map(func => () => {
|
||||
const modifierTypeFunc = func();
|
||||
const modifierType = modifierTypeFunc();
|
||||
modifierType.withIdFromFunc(modifierTypeFunc);
|
||||
return modifierType;
|
||||
setRewardFuncs(...rewardFuncs: (() => RewardFunc)[]): TrainerConfig {
|
||||
this.rewardFuncs = rewardFuncs.map(func => () => {
|
||||
const rewardFunc = func();
|
||||
const reward = rewardFunc();
|
||||
return reward;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
@ -683,7 +671,7 @@ export class TrainerConfig {
|
||||
this.setHasVoucher(true);
|
||||
this.setBattleBgm("battle_unova_gym");
|
||||
this.setVictoryBgm("victory_gym");
|
||||
this.setRandomTeraModifiers(
|
||||
this.setRandomTeraType(
|
||||
() => (ignoreMinTeraWave || globalScene.currentBattle.waveIndex >= GYM_LEADER_TERA_WAVE ? 1 : 0),
|
||||
teraSlot,
|
||||
);
|
||||
@ -744,7 +732,7 @@ export class TrainerConfig {
|
||||
this.setHasVoucher(true);
|
||||
this.setBattleBgm("battle_unova_elite");
|
||||
this.setVictoryBgm("victory_gym");
|
||||
this.setRandomTeraModifiers(() => 1, teraSlot);
|
||||
this.setRandomTeraType(() => 1, teraSlot);
|
||||
|
||||
return this;
|
||||
}
|
||||
@ -921,11 +909,11 @@ export class TrainerConfig {
|
||||
clone = this.battleBgm ? clone.setBattleBgm(this.battleBgm) : clone;
|
||||
clone = this.encounterBgm ? clone.setEncounterBgm(this.encounterBgm) : clone;
|
||||
clone = this.victoryBgm ? clone.setVictoryBgm(this.victoryBgm) : clone;
|
||||
clone = this.genModifiersFunc ? clone.setGenModifiersFunc(this.genModifiersFunc) : clone;
|
||||
clone = this.genTrainerItemsFunc ? clone.setGenTrainerItemsFunc(this.genTrainerItemsFunc) : clone;
|
||||
|
||||
if (this.modifierRewardFuncs) {
|
||||
if (this.rewardFuncs) {
|
||||
// Clones array instead of passing ref
|
||||
clone.modifierRewardFuncs = this.modifierRewardFuncs.slice(0);
|
||||
clone.rewardFuncs = this.rewardFuncs.slice(0);
|
||||
}
|
||||
|
||||
if (this.partyTemplates) {
|
||||
@ -993,6 +981,7 @@ export function getRandomPartyMemberFunc(
|
||||
undefined,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
postProcess,
|
||||
);
|
||||
};
|
||||
@ -1017,7 +1006,16 @@ function getSpeciesFilterRandomPartyMemberFunc(
|
||||
.getTrainerSpeciesForLevel(level, true, strength, waveIndex),
|
||||
);
|
||||
|
||||
return globalScene.addEnemyPokemon(species, level, trainerSlot, undefined, false, undefined, postProcess);
|
||||
return globalScene.addEnemyPokemon(
|
||||
species,
|
||||
level,
|
||||
trainerSlot,
|
||||
undefined,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
postProcess,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@ -4473,9 +4471,9 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
.setBattleBgm("battle_rival")
|
||||
.setMixedBattleBgm("battle_rival")
|
||||
.setPartyTemplates(trainerPartyTemplates.RIVAL)
|
||||
.setModifierRewardFuncs(
|
||||
() => modifierTypes.SUPER_EXP_CHARM,
|
||||
() => modifierTypes.EXP_SHARE,
|
||||
.setRewardFuncs(
|
||||
() => allRewards.SUPER_EXP_CHARM,
|
||||
() => allRewards.EXP_SHARE,
|
||||
)
|
||||
.setPartyMemberFunc(
|
||||
0,
|
||||
@ -4543,7 +4541,7 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
.setBattleBgm("battle_rival")
|
||||
.setMixedBattleBgm("battle_rival")
|
||||
.setPartyTemplates(trainerPartyTemplates.RIVAL_2)
|
||||
.setModifierRewardFuncs(() => modifierTypes.EXP_SHARE)
|
||||
.setRewardFuncs(() => allRewards.EXP_SHARE)
|
||||
.setPartyMemberFunc(
|
||||
0,
|
||||
getRandomPartyMemberFunc(
|
||||
@ -4696,7 +4694,7 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
.setBattleBgm("battle_rival_2")
|
||||
.setMixedBattleBgm("battle_rival_2")
|
||||
.setPartyTemplates(trainerPartyTemplates.RIVAL_4)
|
||||
.setModifierRewardFuncs(() => modifierTypes.TERA_ORB)
|
||||
.setRewardFuncs(() => allRewards.TERA_ORB)
|
||||
.setPartyMemberFunc(
|
||||
0,
|
||||
getRandomPartyMemberFunc(
|
||||
|
37
src/enums/held-item-effect.ts
Normal file
37
src/enums/held-item-effect.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import type { ObjectValues } from "#types/type-helpers";
|
||||
|
||||
/**
|
||||
* Enum representing the various "classes" of item effects that can be applied.
|
||||
*/
|
||||
export const HeldItemEffect = {
|
||||
ATTACK_TYPE_BOOST: 1,
|
||||
TURN_END_HEAL: 2,
|
||||
HIT_HEAL: 3,
|
||||
RESET_NEGATIVE_STAT_STAGE: 4,
|
||||
EXP_BOOSTER: 5,
|
||||
// Should we actually distinguish different berry effects?
|
||||
BERRY: 6,
|
||||
BASE_STAT_BOOSTER: 7,
|
||||
INSTANT_REVIVE: 8,
|
||||
STAT_BOOST: 9,
|
||||
CRIT_BOOST: 10,
|
||||
TURN_END_STATUS: 11,
|
||||
SURVIVE_CHANCE: 12,
|
||||
BYPASS_SPEED_CHANCE: 13,
|
||||
FLINCH_CHANCE: 14,
|
||||
FIELD_EFFECT: 15,
|
||||
FRIENDSHIP_BOOSTER: 16,
|
||||
NATURE_WEIGHT_BOOSTER: 17,
|
||||
ACCURACY_BOOSTER: 18,
|
||||
MULTI_HIT: 19,
|
||||
DAMAGE_MONEY_REWARD: 20,
|
||||
BATON: 21,
|
||||
TURN_END_ITEM_STEAL: 22,
|
||||
CONTACT_ITEM_STEAL_CHANCE: 23,
|
||||
EVO_TRACKER: 40,
|
||||
BASE_STAT_TOTAL: 50,
|
||||
BASE_STAT_FLAT: 51,
|
||||
INCREMENTING_STAT: 52,
|
||||
} as const;
|
||||
|
||||
export type HeldItemEffect = ObjectValues<typeof HeldItemEffect>;
|
151
src/enums/held-item-id.ts
Normal file
151
src/enums/held-item-id.ts
Normal file
@ -0,0 +1,151 @@
|
||||
import type { ObjectValues } from "#types/type-helpers";
|
||||
|
||||
// TODO: make category the lower 2 bytes
|
||||
export const HeldItemId = {
|
||||
NONE: 0x0000,
|
||||
|
||||
// Berries
|
||||
SITRUS_BERRY: 0x0101,
|
||||
LUM_BERRY: 0x0102,
|
||||
ENIGMA_BERRY: 0x0103,
|
||||
LIECHI_BERRY: 0x0104,
|
||||
GANLON_BERRY: 0x0105,
|
||||
PETAYA_BERRY: 0x0106,
|
||||
APICOT_BERRY: 0x0107,
|
||||
SALAC_BERRY: 0x0108,
|
||||
LANSAT_BERRY: 0x0109,
|
||||
STARF_BERRY: 0x010A,
|
||||
LEPPA_BERRY: 0x010B,
|
||||
|
||||
// Other items that are consumed
|
||||
REVIVER_SEED: 0x0201,
|
||||
WHITE_HERB: 0x0202,
|
||||
|
||||
// Type Boosters
|
||||
SILK_SCARF: 0x0301,
|
||||
BLACK_BELT: 0x0302,
|
||||
SHARP_BEAK: 0x0303,
|
||||
POISON_BARB: 0x0304,
|
||||
SOFT_SAND: 0x0305,
|
||||
HARD_STONE: 0x0306,
|
||||
SILVER_POWDER: 0x0307,
|
||||
SPELL_TAG: 0x0308,
|
||||
METAL_COAT: 0x0309,
|
||||
CHARCOAL: 0x030A,
|
||||
MYSTIC_WATER: 0x030B,
|
||||
MIRACLE_SEED: 0x030C,
|
||||
MAGNET: 0x030D,
|
||||
TWISTED_SPOON: 0x030E,
|
||||
NEVER_MELT_ICE: 0x030F,
|
||||
DRAGON_FANG: 0x0310,
|
||||
BLACK_GLASSES: 0x0311,
|
||||
FAIRY_FEATHER: 0x0312,
|
||||
|
||||
// Species Stat Boosters
|
||||
LIGHT_BALL: 0x0401,
|
||||
THICK_CLUB: 0x0402,
|
||||
METAL_POWDER: 0x0403,
|
||||
QUICK_POWDER: 0x0404,
|
||||
DEEP_SEA_SCALE: 0x0405,
|
||||
DEEP_SEA_TOOTH: 0x0406,
|
||||
|
||||
// Crit Boosters
|
||||
SCOPE_LENS: 0x0501,
|
||||
LEEK: 0x0502,
|
||||
|
||||
// Items increasing gains
|
||||
LUCKY_EGG: 0x0601,
|
||||
GOLDEN_EGG: 0x0602,
|
||||
SOOTHE_BELL: 0x0603,
|
||||
|
||||
// Unique items
|
||||
FOCUS_BAND: 0x0701,
|
||||
QUICK_CLAW: 0x0702,
|
||||
KINGS_ROCK: 0x0703,
|
||||
LEFTOVERS: 0x0704,
|
||||
SHELL_BELL: 0x0705,
|
||||
MYSTICAL_ROCK: 0x0706,
|
||||
WIDE_LENS: 0x0707,
|
||||
MULTI_LENS: 0x0708,
|
||||
GOLDEN_PUNCH: 0x0709,
|
||||
GRIP_CLAW: 0x070A,
|
||||
TOXIC_ORB: 0x070B,
|
||||
FLAME_ORB: 0x070C,
|
||||
SOUL_DEW: 0x070D,
|
||||
BATON: 0x070E,
|
||||
MINI_BLACK_HOLE: 0x070F,
|
||||
EVIOLITE: 0x0710,
|
||||
|
||||
// Vitamins
|
||||
HP_UP: 0x0801,
|
||||
PROTEIN: 0x0802,
|
||||
IRON: 0x0803,
|
||||
CALCIUM: 0x0804,
|
||||
ZINC: 0x0805,
|
||||
CARBOS: 0x0806,
|
||||
|
||||
// Other stat boosting items
|
||||
SHUCKLE_JUICE_GOOD: 0x0901,
|
||||
SHUCKLE_JUICE_BAD: 0x0902,
|
||||
OLD_GATEAU: 0x0903,
|
||||
MACHO_BRACE: 0x0904,
|
||||
|
||||
// Evo trackers
|
||||
GIMMIGHOUL_EVO_TRACKER: 0x0A01,
|
||||
} as const;
|
||||
|
||||
export type HeldItemId = ObjectValues<typeof HeldItemId>;
|
||||
|
||||
type HeldItemNameMap = {
|
||||
[k in HeldItemName as (typeof HeldItemId)[k]]: k
|
||||
}
|
||||
|
||||
type HeldItemName = keyof typeof HeldItemId;
|
||||
|
||||
/** `const object` mapping all held item IDs to their respective names. */
|
||||
// TODO: This stores names as UPPER_SNAKE_CASE, but the locales are in PascalCase...
|
||||
export const HeldItemNames = Object.freeze(Object.entries(HeldItemId).reduce(
|
||||
// Use a type-safe reducer to force number keys and values
|
||||
(acc, [key, value]) => {
|
||||
acc[value] = key;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
)) as HeldItemNameMap;
|
||||
|
||||
export const HeldItemCategoryId = {
|
||||
NONE: 0x0000,
|
||||
BERRY: 0x0100,
|
||||
CONSUMABLE: 0x0200,
|
||||
TYPE_ATTACK_BOOSTER: 0x0300,
|
||||
SPECIES_STAT_BOOSTER: 0x0400,
|
||||
CRIT_BOOSTER: 0x0500,
|
||||
GAIN_INCREASE: 0x0600,
|
||||
UNIQUE: 0x0700,
|
||||
VITAMIN: 0x0800,
|
||||
BASE_STAT_BOOST: 0x0900,
|
||||
EVO_TRACKER: 0x0A00,
|
||||
} as const;
|
||||
|
||||
export type HeldItemCategoryId = ObjectValues<typeof HeldItemCategoryId>;
|
||||
|
||||
const ITEM_CATEGORY_MASK = 0xFF00
|
||||
|
||||
export function getHeldItemCategory(itemId: HeldItemId): HeldItemCategoryId {
|
||||
return (itemId & ITEM_CATEGORY_MASK) as HeldItemCategoryId;
|
||||
}
|
||||
|
||||
export function isCategoryId(id: number): id is HeldItemCategoryId {
|
||||
return (Object.values(HeldItemCategoryId) as number[]).includes(id);
|
||||
}
|
||||
|
||||
export function isItemInCategory(itemId: HeldItemId, category: HeldItemCategoryId): boolean {
|
||||
return getHeldItemCategory(itemId) === category;
|
||||
}
|
||||
|
||||
export function isItemInRequested(
|
||||
itemId: HeldItemId,
|
||||
requestedItems: (HeldItemCategoryId | HeldItemId)[]
|
||||
): boolean {
|
||||
return requestedItems.some(entry => itemId === entry || (itemId & ITEM_CATEGORY_MASK) === entry);
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
export enum ModifierPoolType {
|
||||
PLAYER,
|
||||
WILD,
|
||||
TRAINER,
|
||||
ENEMY_BUFF,
|
||||
DAILY_STARTER
|
||||
}
|
100
src/enums/reward-id.ts
Normal file
100
src/enums/reward-id.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import type { ObjectValues } from "#types/type-helpers";
|
||||
|
||||
export const RewardId = {
|
||||
NONE: 0x0000,
|
||||
|
||||
POKEBALL: 0x2001,
|
||||
GREAT_BALL: 0x2002,
|
||||
ULTRA_BALL: 0x2003,
|
||||
ROGUE_BALL: 0x2004,
|
||||
MASTER_BALL: 0x2005,
|
||||
|
||||
VOUCHER: 0x2101,
|
||||
VOUCHER_PLUS: 0x2102,
|
||||
VOUCHER_PREMIUM: 0x2103,
|
||||
|
||||
NUGGET: 0x2201,
|
||||
BIG_NUGGET: 0x2202,
|
||||
RELIC_GOLD: 0x2203,
|
||||
|
||||
RARE_CANDY: 0x2301,
|
||||
RARER_CANDY: 0x2302,
|
||||
|
||||
EVOLUTION_ITEM: 0x2401,
|
||||
RARE_EVOLUTION_ITEM: 0x2402,
|
||||
|
||||
POTION: 0x2501,
|
||||
SUPER_POTION: 0x2502,
|
||||
HYPER_POTION: 0x2503,
|
||||
MAX_POTION: 0x2504,
|
||||
FULL_HEAL: 0x2505,
|
||||
FULL_RESTORE: 0x2506,
|
||||
|
||||
REVIVE: 0x2601,
|
||||
MAX_REVIVE: 0x2602,
|
||||
SACRED_ASH: 0x2603,
|
||||
|
||||
ETHER: 0x2701,
|
||||
MAX_ETHER: 0x2702,
|
||||
|
||||
ELIXIR: 0x2801,
|
||||
MAX_ELIXIR: 0x2802,
|
||||
|
||||
PP_UP: 0x2901,
|
||||
PP_MAX: 0x2902,
|
||||
|
||||
TM_COMMON: 0x2A01,
|
||||
TM_GREAT: 0x2A02,
|
||||
TM_ULTRA: 0x2A03,
|
||||
|
||||
MINT: 0x2B01,
|
||||
TERA_SHARD: 0x2B02,
|
||||
MEMORY_MUSHROOM: 0x2B03,
|
||||
DNA_SPLICERS: 0x2B04,
|
||||
|
||||
HELD_ITEM: 0x2C01,
|
||||
SPECIES_STAT_BOOSTER: 0x2C02,
|
||||
RARE_SPECIES_STAT_BOOSTER: 0x2C03,
|
||||
BASE_STAT_BOOSTER: 0x2C04,
|
||||
ATTACK_TYPE_BOOSTER: 0x2C05,
|
||||
BERRY: 0x2C06,
|
||||
|
||||
TRAINER_ITEM: 0x2D01,
|
||||
TEMP_STAT_STAGE_BOOSTER: 0x2D02,
|
||||
DIRE_HIT: 0x2D03,
|
||||
LURE: 0x2D04,
|
||||
SUPER_LURE: 0x2D05,
|
||||
MAX_LURE: 0x2D06,
|
||||
|
||||
FORM_CHANGE_ITEM: 0x2E01,
|
||||
RARE_FORM_CHANGE_ITEM: 0x2E02,
|
||||
} as const;
|
||||
|
||||
export type RewardId = ObjectValues<typeof RewardId>;
|
||||
|
||||
export const RewardCategoryId = {
|
||||
NONE: 0x0000,
|
||||
POKEBALL: 0x0100,
|
||||
VOUCHER: 0x0200,
|
||||
MONEY: 0x0300,
|
||||
CANDY: 0x0400,
|
||||
EVOLUTION_ITEM: 0x0500,
|
||||
HEALING: 0x0600,
|
||||
REVIVE: 0x0700,
|
||||
ETHER: 0x0800,
|
||||
ELIXIR: 0x0900,
|
||||
PP_UP: 0x0A00,
|
||||
TM: 0x0B00,
|
||||
OTHER: 0x0C00,
|
||||
HELD_ITEM: 0x0D00,
|
||||
TRAINER_ITEM: 0x0E00,
|
||||
FORM_CHANGE_ITEM: 0x0F00,
|
||||
} as const;
|
||||
|
||||
export type RewardCategoryId = ObjectValues<typeof RewardCategoryId>;
|
||||
|
||||
const ITEM_CATEGORY_MASK = 0xFF00
|
||||
|
||||
export function getRewardCategory(itemId: RewardId): RewardCategoryId {
|
||||
return (itemId & ITEM_CATEGORY_MASK) as RewardCategoryId;
|
||||
}
|
13
src/enums/reward-pool-type.ts
Normal file
13
src/enums/reward-pool-type.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export enum RewardPoolType {
|
||||
PLAYER,
|
||||
}
|
||||
|
||||
export enum HeldItemPoolType {
|
||||
WILD,
|
||||
TRAINER,
|
||||
DAILY_STARTER,
|
||||
}
|
||||
|
||||
export enum TrainerItemPoolType {
|
||||
ENEMY_BUFF,
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export enum ModifierTier {
|
||||
export enum RarityTier {
|
||||
COMMON,
|
||||
GREAT,
|
||||
ULTRA,
|
68
src/enums/trainer-item-id.ts
Normal file
68
src/enums/trainer-item-id.ts
Normal file
@ -0,0 +1,68 @@
|
||||
export const TrainerItemId = {
|
||||
NONE: 0x0000,
|
||||
|
||||
MAP: 0x1001,
|
||||
IV_SCANNER: 0x1002,
|
||||
LOCK_CAPSULE: 0x1003,
|
||||
MEGA_BRACELET: 0x1004,
|
||||
DYNAMAX_BAND: 0x1005,
|
||||
TERA_ORB: 0x1006,
|
||||
|
||||
GOLDEN_POKEBALL: 0x1007,
|
||||
|
||||
OVAL_CHARM: 0x1008,
|
||||
EXP_SHARE: 0x1009,
|
||||
EXP_BALANCE: 0x100A,
|
||||
|
||||
CANDY_JAR: 0x100B,
|
||||
BERRY_POUCH: 0x100C,
|
||||
|
||||
HEALING_CHARM: 0x100D,
|
||||
EXP_CHARM: 0x100E,
|
||||
SUPER_EXP_CHARM: 0x100F,
|
||||
GOLDEN_EXP_CHARM: 0x1010,
|
||||
AMULET_COIN: 0x1011,
|
||||
|
||||
ABILITY_CHARM: 0x1012,
|
||||
SHINY_CHARM: 0x1013,
|
||||
CATCHING_CHARM: 0x1014,
|
||||
|
||||
BLACK_SLUDGE: 0x1015,
|
||||
GOLDEN_BUG_NET: 0x1016,
|
||||
|
||||
LURE: 0x1101,
|
||||
SUPER_LURE: 0x1102,
|
||||
MAX_LURE: 0x1103,
|
||||
|
||||
X_ATTACK: 0x1201,
|
||||
X_DEFENSE: 0x1202,
|
||||
X_SP_ATK: 0x1203,
|
||||
X_SP_DEF: 0x1204,
|
||||
X_SPEED: 0x1205,
|
||||
X_ACCURACY: 0x1206,
|
||||
DIRE_HIT: 0x1207,
|
||||
|
||||
ENEMY_DAMAGE_BOOSTER: 0x1301,
|
||||
ENEMY_DAMAGE_REDUCTION: 0x1302,
|
||||
ENEMY_HEAL: 0x1303,
|
||||
ENEMY_ATTACK_POISON_CHANCE: 0x1304,
|
||||
ENEMY_ATTACK_PARALYZE_CHANCE: 0x1305,
|
||||
ENEMY_ATTACK_BURN_CHANCE: 0x1306,
|
||||
ENEMY_STATUS_EFFECT_HEAL_CHANCE: 0x1307,
|
||||
ENEMY_ENDURE_CHANCE: 0x1308,
|
||||
ENEMY_FUSED_CHANCE: 0x1309,
|
||||
} as const;
|
||||
|
||||
export type TrainerItemId = (typeof TrainerItemId)[keyof typeof TrainerItemId];
|
||||
|
||||
type TrainerItemName = keyof typeof TrainerItemId;
|
||||
type TrainerItemValue = typeof TrainerItemId[TrainerItemName];
|
||||
|
||||
// Use a type-safe reducer to force number keys and values
|
||||
export const TrainerItemNames: Record<TrainerItemValue, TrainerItemName> = Object.entries(TrainerItemId).reduce(
|
||||
(acc, [key, value]) => {
|
||||
acc[value as TrainerItemValue] = key as TrainerItemName;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<TrainerItemValue, TrainerItemName>
|
||||
);
|
@ -5,7 +5,7 @@ export enum UiMode {
|
||||
FIGHT,
|
||||
BALL,
|
||||
TARGET_SELECT,
|
||||
MODIFIER_SELECT,
|
||||
REWARD_SELECT,
|
||||
SAVE_SLOT,
|
||||
PARTY,
|
||||
SUMMARY,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { BerryModifier } from "#modifiers/modifier";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { Move } from "#moves/move";
|
||||
|
||||
/** Alias for all {@linkcode BattleScene} events */
|
||||
@ -81,12 +82,13 @@ export class MoveUsedEvent extends Event {
|
||||
* @extends Event
|
||||
*/
|
||||
export class BerryUsedEvent extends Event {
|
||||
/** The {@linkcode BerryModifier} being used */
|
||||
public berryModifier: BerryModifier;
|
||||
constructor(berry: BerryModifier) {
|
||||
/** The {@linkcode BerryType} being used */
|
||||
public pokemon: Pokemon;
|
||||
public berryType: BerryType;
|
||||
constructor(pokemon: Pokemon, berryType: BerryType) {
|
||||
super(BattleSceneEventType.BERRY_USED);
|
||||
|
||||
this.berryModifier = berry;
|
||||
this.pokemon = pokemon;
|
||||
this.berryType = berryType;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||
import type { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { CommonAnim } from "#enums/move-anims-common";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
@ -33,7 +34,7 @@ import { TrainerType } from "#enums/trainer-type";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#events/arena";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { FieldEffectModifier } from "#modifiers/modifier";
|
||||
import { applyHeldItems } from "#items/all-held-items";
|
||||
import type { Move } from "#moves/move";
|
||||
import type { AbstractConstructor } from "#types/type-helpers";
|
||||
import { type Constructor, isNullOrUndefined, NumberHolder, randSeedInt } from "#utils/common";
|
||||
@ -341,7 +342,7 @@ export class Arena {
|
||||
|
||||
if (!isNullOrUndefined(user)) {
|
||||
weatherDuration.value = 5;
|
||||
globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, weatherDuration);
|
||||
applyHeldItems(HeldItemEffect.FIELD_EFFECT, { pokemon: user, fieldDuration: weatherDuration });
|
||||
}
|
||||
|
||||
this.weather = weather ? new Weather(weather, weatherDuration.value) : null;
|
||||
@ -428,7 +429,7 @@ export class Arena {
|
||||
|
||||
if (!isNullOrUndefined(user)) {
|
||||
terrainDuration.value = 5;
|
||||
globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, terrainDuration);
|
||||
applyHeldItems(HeldItemEffect.FIELD_EFFECT, { pokemon: user, fieldDuration: terrainDuration });
|
||||
}
|
||||
|
||||
this.terrain = terrain ? new Terrain(terrain, terrainDuration.value) : null;
|
||||
|
@ -336,7 +336,7 @@ export class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Container {
|
||||
tryPlaySprite(
|
||||
sprite: Phaser.GameObjects.Sprite,
|
||||
tintSprite: Phaser.GameObjects.Sprite,
|
||||
animConfig: Phaser.Types.Animations.PlayAnimationConfig,
|
||||
animConfig: PlayAnimationConfig,
|
||||
): boolean {
|
||||
// Show an error in the console if there isn't a texture loaded
|
||||
if (sprite.texture.key === "__MISSING") {
|
||||
|
@ -79,9 +79,10 @@ import { ChallengeType } from "#enums/challenge-type";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { DexAttr } from "#enums/dex-attr";
|
||||
import { FieldPosition } from "#enums/field-position";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { LearnMoveSituation } from "#enums/learn-move-situation";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveFlags } from "#enums/move-flags";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
@ -91,6 +92,7 @@ import { Nature } from "#enums/nature";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { PokemonAnimType } from "#enums/pokemon-anim-type";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import {
|
||||
@ -108,27 +110,11 @@ import type { TrainerSlot } from "#enums/trainer-slot";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { doShinySparkleAnim } from "#field/anims";
|
||||
import {
|
||||
BaseStatModifier,
|
||||
CritBoosterModifier,
|
||||
EnemyDamageBoosterModifier,
|
||||
EnemyDamageReducerModifier,
|
||||
EnemyFusionChanceModifier,
|
||||
EvoTrackerModifier,
|
||||
HiddenAbilityRateBoosterModifier,
|
||||
PokemonBaseStatFlatModifier,
|
||||
PokemonBaseStatTotalModifier,
|
||||
PokemonFriendshipBoosterModifier,
|
||||
PokemonHeldItemModifier,
|
||||
PokemonIncrementingStatModifier,
|
||||
PokemonMultiHitModifier,
|
||||
PokemonNatureWeightModifier,
|
||||
ShinyRateBoosterModifier,
|
||||
StatBoosterModifier,
|
||||
SurviveDamageModifier,
|
||||
TempCritBoosterModifier,
|
||||
TempStatStageBoosterModifier,
|
||||
} from "#modifiers/modifier";
|
||||
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 { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { applyMoveAttrs } from "#moves/apply-attrs";
|
||||
import type { Move } from "#moves/move";
|
||||
import { getMoveTargets } from "#moves/move-utils";
|
||||
@ -289,6 +275,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
private shinySparkle: Phaser.GameObjects.Sprite;
|
||||
|
||||
public heldItemManager: HeldItemManager;
|
||||
|
||||
// TODO: Rework this eventually
|
||||
constructor(
|
||||
x: number,
|
||||
@ -302,6 +290,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
variant?: Variant,
|
||||
ivs?: number[],
|
||||
nature?: Nature,
|
||||
heldItemConfig?: HeldItemConfiguration,
|
||||
dataSource?: Pokemon | PokemonData,
|
||||
) {
|
||||
super(globalScene, x, y);
|
||||
@ -331,6 +320,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.exp = dataSource?.exp || getLevelTotalExp(this.level, species.growthRate);
|
||||
this.levelExp = dataSource?.levelExp || 0;
|
||||
|
||||
this.heldItemManager = new HeldItemManager();
|
||||
if (heldItemConfig) {
|
||||
assignItemsFromConfiguration(heldItemConfig, this);
|
||||
}
|
||||
|
||||
if (dataSource) {
|
||||
this.id = dataSource.id;
|
||||
this.hp = dataSource.hp;
|
||||
@ -408,7 +402,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (level > 1) {
|
||||
const fused = new BooleanHolder(globalScene.gameMode.isSplicedOnly);
|
||||
if (!fused.value && this.isEnemy() && !this.hasTrainer()) {
|
||||
globalScene.applyModifier(EnemyFusionChanceModifier, false, fused);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.ENEMY_FUSED_CHANCE, { booleanHolder: fused });
|
||||
}
|
||||
|
||||
if (fused.value) {
|
||||
@ -596,7 +590,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
// Roll for hidden ability chance, applying any ability charms for enemy mons
|
||||
const hiddenAbilityChance = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE);
|
||||
if (!this.hasTrainer()) {
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, {
|
||||
numberHolder: hiddenAbilityChance,
|
||||
});
|
||||
}
|
||||
|
||||
// If the roll succeeded and we have one, use HA; otherwise pick a random ability
|
||||
@ -1152,14 +1148,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.setScale(this.getSpriteScale());
|
||||
}
|
||||
|
||||
getHeldItems(): PokemonHeldItemModifier[] {
|
||||
if (!globalScene) {
|
||||
return [];
|
||||
}
|
||||
return globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id,
|
||||
this.isPlayer(),
|
||||
) as PokemonHeldItemModifier[];
|
||||
getHeldItems(): HeldItemId[] {
|
||||
return this.heldItemManager.getHeldItems();
|
||||
}
|
||||
|
||||
updateScale(): void {
|
||||
@ -1383,8 +1373,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
getCritStage(source: Pokemon, move: Move): number {
|
||||
const critStage = new NumberHolder(0);
|
||||
applyMoveAttrs("HighCritAttr", source, this, move, critStage);
|
||||
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
|
||||
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
|
||||
applyHeldItems(HeldItemEffect.CRIT_BOOST, { pokemon: source, critStage: critStage });
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.TEMP_CRIT_BOOSTER, { numberHolder: critStage });
|
||||
applyAbAttrs("BonusCritAbAttr", { pokemon: source, critStage });
|
||||
const critBoostTag = source.getTag(CritBoostTag);
|
||||
if (critBoostTag) {
|
||||
@ -1439,7 +1429,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
): number {
|
||||
const statVal = new NumberHolder(this.getStat(stat, false));
|
||||
if (!ignoreHeldItems) {
|
||||
globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statVal);
|
||||
applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: this, stat: stat, statValue: statVal });
|
||||
}
|
||||
|
||||
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
|
||||
@ -1549,7 +1539,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
const statHolder = new NumberHolder(Math.floor((2 * baseStats[s] + this.ivs[s]) * this.level * 0.01));
|
||||
if (s === Stat.HP) {
|
||||
statHolder.value = statHolder.value + this.level + 10;
|
||||
globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder);
|
||||
applyHeldItems(HeldItemEffect.INCREMENTING_STAT, { pokemon: this, stat: s, statHolder: statHolder });
|
||||
if (this.hasAbility(AbilityId.WONDER_GUARD, false, true)) {
|
||||
statHolder.value = 1;
|
||||
}
|
||||
@ -1564,14 +1554,14 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
} else {
|
||||
statHolder.value += 5;
|
||||
const natureStatMultiplier = new NumberHolder(getNatureStatMultiplier(this.getNature(), s));
|
||||
globalScene.applyModifier(PokemonNatureWeightModifier, this.isPlayer(), this, natureStatMultiplier);
|
||||
applyHeldItems(HeldItemEffect.NATURE_WEIGHT_BOOSTER, { pokemon: this, multiplier: natureStatMultiplier });
|
||||
if (natureStatMultiplier.value !== 1) {
|
||||
statHolder.value = Math.max(
|
||||
Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"](statHolder.value * natureStatMultiplier.value),
|
||||
1,
|
||||
);
|
||||
}
|
||||
globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder);
|
||||
applyHeldItems(HeldItemEffect.INCREMENTING_STAT, { pokemon: this, stat: s, statHolder: statHolder });
|
||||
}
|
||||
|
||||
statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER);
|
||||
@ -1584,9 +1574,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
const baseStats = this.getSpeciesForm(true).baseStats.slice(0);
|
||||
applyChallenges(ChallengeType.FLIP_STAT, this, baseStats);
|
||||
// Shuckle Juice
|
||||
globalScene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats);
|
||||
applyHeldItems(HeldItemEffect.BASE_STAT_TOTAL, { pokemon: this, baseStats: baseStats });
|
||||
// Old Gateau
|
||||
globalScene.applyModifiers(PokemonBaseStatFlatModifier, this.isPlayer(), this, baseStats);
|
||||
applyHeldItems(HeldItemEffect.BASE_STAT_FLAT, { pokemon: this, baseStats: baseStats });
|
||||
if (this.isFusion()) {
|
||||
const fusionBaseStats = this.getFusionSpeciesForm(true).baseStats;
|
||||
applyChallenges(ChallengeType.FLIP_STAT, this, fusionBaseStats);
|
||||
@ -1600,7 +1590,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
}
|
||||
// Vitamins
|
||||
globalScene.applyModifiers(BaseStatModifier, this.isPlayer(), this, baseStats);
|
||||
applyHeldItems(HeldItemEffect.BASE_STAT_BOOSTER, { pokemon: this, baseStats: baseStats });
|
||||
|
||||
return baseStats;
|
||||
}
|
||||
@ -2231,8 +2221,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the weight of the Pokemon with subtractive modifiers (Autotomize) happening first
|
||||
* and then multiplicative modifiers happening after (Heavy Metal and Light Metal)
|
||||
* Gets the weight of the Pokemon with subtractive abilities (Autotomize) happening first
|
||||
* and then multiplicative abilities happening after (Heavy Metal and Light Metal)
|
||||
* @returns the kg of the Pokemon (minimum of 0.1)
|
||||
*/
|
||||
public getWeight(): number {
|
||||
@ -2857,7 +2847,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
}
|
||||
if (!this.hasTrainer()) {
|
||||
globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold });
|
||||
}
|
||||
} else {
|
||||
shinyThreshold.value = thresholdOverride;
|
||||
@ -2879,17 +2869,17 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
*
|
||||
* The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536`
|
||||
* @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
|
||||
* @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride}
|
||||
* @param applyItemsToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride}
|
||||
* @returns `true` if the Pokemon has been set as a shiny, `false` otherwise
|
||||
*/
|
||||
public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean {
|
||||
public trySetShinySeed(thresholdOverride?: number, applyItemsToOverride?: boolean): boolean {
|
||||
if (!this.shiny) {
|
||||
const shinyThreshold = new NumberHolder(thresholdOverride ?? BASE_SHINY_CHANCE);
|
||||
if (applyModifiersToOverride) {
|
||||
if (applyItemsToOverride) {
|
||||
if (timedEventManager.isEventActive()) {
|
||||
shinyThreshold.value *= timedEventManager.getShinyMultiplier();
|
||||
}
|
||||
globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold });
|
||||
}
|
||||
|
||||
this.shiny = randSeedInt(65536) < shinyThreshold.value;
|
||||
@ -2951,17 +2941,17 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
*
|
||||
* The base hidden ability odds are {@linkcode BASE_HIDDEN_ABILITY_CHANCE} / `65536`
|
||||
* @param thresholdOverride number that is divided by `2^16` (`65536`) to get the HA chance, overrides {@linkcode haThreshold} if set (bypassing HA rate modifiers such as Ability Charm)
|
||||
* @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Ability Charm to {@linkcode thresholdOverride}
|
||||
* @param applyItemsToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Ability Charm to {@linkcode thresholdOverride}
|
||||
* @returns `true` if the Pokemon has been set to have its hidden ability, `false` otherwise
|
||||
*/
|
||||
public tryRerollHiddenAbilitySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean {
|
||||
public tryRerollHiddenAbilitySeed(thresholdOverride?: number, applyItemsToOverride?: boolean): boolean {
|
||||
if (!this.species.abilityHidden) {
|
||||
return false;
|
||||
}
|
||||
const haThreshold = new NumberHolder(thresholdOverride ?? BASE_HIDDEN_ABILITY_CHANCE);
|
||||
if (applyModifiersToOverride) {
|
||||
if (applyItemsToOverride) {
|
||||
if (!this.hasTrainer()) {
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, { numberHolder: haThreshold });
|
||||
}
|
||||
}
|
||||
|
||||
@ -2975,7 +2965,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
public generateFusionSpecies(forStarter?: boolean): void {
|
||||
const hiddenAbilityChance = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE);
|
||||
if (!this.hasTrainer()) {
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, {
|
||||
numberHolder: hiddenAbilityChance,
|
||||
});
|
||||
}
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
@ -3103,11 +3095,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
}
|
||||
if (compatible && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) {
|
||||
if (tmPoolTiers[moveId] === ModifierTier.COMMON && this.level >= 15) {
|
||||
if (tmPoolTiers[moveId] === RarityTier.COMMON && this.level >= 15) {
|
||||
movePool.push([moveId, 4]);
|
||||
} else if (tmPoolTiers[moveId] === ModifierTier.GREAT && this.level >= 30) {
|
||||
} else if (tmPoolTiers[moveId] === RarityTier.GREAT && this.level >= 30) {
|
||||
movePool.push([moveId, 8]);
|
||||
} else if (tmPoolTiers[moveId] === ModifierTier.ULTRA && this.level >= 50) {
|
||||
} else if (tmPoolTiers[moveId] === RarityTier.ULTRA && this.level >= 50) {
|
||||
movePool.push([moveId, 14]);
|
||||
}
|
||||
}
|
||||
@ -3485,7 +3477,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (!ignoreStatStage.value) {
|
||||
const statStageMultiplier = new NumberHolder(Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value));
|
||||
if (!ignoreHeldItems) {
|
||||
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), stat, statStageMultiplier);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.TEMP_STAT_STAGE_BOOSTER, {
|
||||
numberHolder: statStageMultiplier,
|
||||
});
|
||||
}
|
||||
return Math.min(statStageMultiplier.value, 4);
|
||||
}
|
||||
@ -3519,7 +3513,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", { pokemon: this, stat: Stat.EVA, ignored: ignoreEvaStatStage });
|
||||
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
|
||||
|
||||
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.TEMP_ACCURACY_BOOSTER, { numberHolder: userAccStage });
|
||||
|
||||
userAccStage.value = ignoreAccStatStage.value ? 0 : Math.min(userAccStage.value, 6);
|
||||
targetEvaStage.value = ignoreEvaStatStage.value ? 0 : targetEvaStage.value;
|
||||
@ -3772,14 +3766,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
applyMoveAttrs("FixedDamageAttr", source, this, move, fixedDamage);
|
||||
if (fixedDamage.value) {
|
||||
const multiLensMultiplier = new NumberHolder(1);
|
||||
globalScene.applyModifiers(
|
||||
PokemonMultiHitModifier,
|
||||
source.isPlayer(),
|
||||
source,
|
||||
move.id,
|
||||
null,
|
||||
multiLensMultiplier,
|
||||
);
|
||||
applyHeldItems(HeldItemEffect.MULTI_HIT, {
|
||||
pokemon: source,
|
||||
moveId: move.id,
|
||||
damageMultiplier: multiLensMultiplier,
|
||||
});
|
||||
fixedDamage.value = toDmgValue(fixedDamage.value * multiLensMultiplier.value);
|
||||
|
||||
return {
|
||||
@ -3823,14 +3814,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
/** Multiplier for moves enhanced by Multi-Lens and/or Parental Bond */
|
||||
const multiStrikeEnhancementMultiplier = new NumberHolder(1);
|
||||
globalScene.applyModifiers(
|
||||
PokemonMultiHitModifier,
|
||||
source.isPlayer(),
|
||||
source,
|
||||
move.id,
|
||||
null,
|
||||
multiStrikeEnhancementMultiplier,
|
||||
);
|
||||
applyHeldItems(HeldItemEffect.MULTI_HIT, {
|
||||
pokemon: source,
|
||||
moveId: move.id,
|
||||
damageMultiplier: multiStrikeEnhancementMultiplier,
|
||||
});
|
||||
|
||||
if (!ignoreSourceAbility) {
|
||||
applyAbAttrs("AddSecondStrikeAbAttr", {
|
||||
@ -3949,10 +3937,10 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
/** Apply the enemy's Damage and Resistance tokens */
|
||||
if (!source.isPlayer()) {
|
||||
globalScene.applyModifiers(EnemyDamageBoosterModifier, false, damage);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.ENEMY_DAMAGE_BOOSTER, { numberHolder: damage });
|
||||
}
|
||||
if (!this.isPlayer()) {
|
||||
globalScene.applyModifiers(EnemyDamageReducerModifier, false, damage);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.ENEMY_DAMAGE_REDUCER, { numberHolder: damage });
|
||||
}
|
||||
|
||||
const abAttrParams: PreAttackModifyDamageAbAttrParams = {
|
||||
@ -3962,7 +3950,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
simulated,
|
||||
damage,
|
||||
};
|
||||
/** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */
|
||||
/** Apply this Pokemon's post-calc defensive attributes (e.g. Fur Coat) */
|
||||
if (!ignoreAbility) {
|
||||
applyAbAttrs("ReceivedMoveDamageMultiplierAbAttr", abAttrParams);
|
||||
|
||||
@ -4064,7 +4052,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
surviveDamage.value = this.lapseTag(BattlerTagType.ENDURE_TOKEN);
|
||||
}
|
||||
if (!surviveDamage.value) {
|
||||
globalScene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage);
|
||||
applyHeldItems(HeldItemEffect.SURVIVE_CHANCE, { pokemon: this, surviveDamage: surviveDamage });
|
||||
}
|
||||
if (surviveDamage.value) {
|
||||
damage = this.hp - 1;
|
||||
@ -4513,7 +4501,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.setScale(this.getSpriteScale());
|
||||
this.loadAssets().then(() => {
|
||||
this.calculateStats();
|
||||
globalScene.updateModifiers(this.isPlayer(), true);
|
||||
globalScene.updateItems(this.isPlayer());
|
||||
Promise.all([this.updateInfo(), globalScene.updateFieldScale()]).then(() => resolve());
|
||||
});
|
||||
});
|
||||
@ -5622,16 +5610,14 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* Should be `false` for all item loss occurring outside of battle (MEs, etc.).
|
||||
* @returns Whether the item was removed successfully.
|
||||
*/
|
||||
public loseHeldItem(heldItem: PokemonHeldItemModifier, forBattle = true): boolean {
|
||||
public loseHeldItem(heldItemId: HeldItemId, forBattle = true): boolean {
|
||||
// TODO: What does a -1 pokemon id mean?
|
||||
if (heldItem.pokemonId !== -1 && heldItem.pokemonId !== this.id) {
|
||||
if (!this.heldItemManager.hasItem(heldItemId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
heldItem.stackCount--;
|
||||
if (heldItem.stackCount <= 0) {
|
||||
globalScene.removeModifier(heldItem, this.isEnemy());
|
||||
}
|
||||
this.heldItemManager.remove(heldItemId);
|
||||
|
||||
if (forBattle) {
|
||||
applyAbAttrs("PostItemLostAbAttr", { pokemon: this });
|
||||
}
|
||||
@ -5653,13 +5639,6 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
this.turnData.berriesEaten.push(berryType);
|
||||
}
|
||||
|
||||
getPersistentTreasureCount(): number {
|
||||
return (
|
||||
this.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length +
|
||||
globalScene.findModifiers(m => m.is("MoneyMultiplierModifier") || m.is("ExtraModifierModifier")).length
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class PlayerPokemon extends Pokemon {
|
||||
@ -5676,9 +5655,24 @@ export class PlayerPokemon extends Pokemon {
|
||||
variant?: Variant,
|
||||
ivs?: number[],
|
||||
nature?: Nature,
|
||||
heldItemConfig?: HeldItemConfiguration,
|
||||
dataSource?: Pokemon | PokemonData,
|
||||
) {
|
||||
super(106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
|
||||
super(
|
||||
106,
|
||||
148,
|
||||
species,
|
||||
level,
|
||||
abilityIndex,
|
||||
formIndex,
|
||||
gender,
|
||||
shiny,
|
||||
variant,
|
||||
ivs,
|
||||
nature,
|
||||
heldItemConfig,
|
||||
dataSource,
|
||||
);
|
||||
|
||||
if (Overrides.STATUS_OVERRIDE) {
|
||||
this.status = new Status(Overrides.STATUS_OVERRIDE, 0, 4);
|
||||
@ -5822,7 +5816,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
fusionStarterSpeciesId ? globalScene.gameData.starterData[fusionStarterSpeciesId] : null,
|
||||
].filter(d => !!d);
|
||||
const amount = new NumberHolder(friendship);
|
||||
globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
|
||||
applyHeldItems(HeldItemEffect.FRIENDSHIP_BOOSTER, { pokemon: this, friendship: amount });
|
||||
const candyFriendshipMultiplier = globalScene.gameMode.isClassic
|
||||
? timedEventManager.getClassicFriendshipMultiplier()
|
||||
: 1;
|
||||
@ -5882,6 +5876,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
this.variant,
|
||||
this.ivs,
|
||||
this.nature,
|
||||
this.heldItemManager.generateHeldItemConfiguration(),
|
||||
this,
|
||||
);
|
||||
this.fusionSpecies = originalFusionSpecies;
|
||||
@ -5904,6 +5899,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
this.variant,
|
||||
this.ivs,
|
||||
this.nature,
|
||||
this.heldItemManager.generateHeldItemConfiguration(),
|
||||
this,
|
||||
);
|
||||
}
|
||||
@ -5976,9 +5972,9 @@ export class PlayerPokemon extends Pokemon {
|
||||
});
|
||||
};
|
||||
if (preEvolution.speciesId === SpeciesId.GIMMIGHOUL) {
|
||||
const evotracker = this.getHeldItems().filter(m => m instanceof EvoTrackerModifier)[0] ?? null;
|
||||
const evotracker = this.heldItemManager.hasItem(HeldItemId.GIMMIGHOUL_EVO_TRACKER);
|
||||
if (evotracker) {
|
||||
globalScene.removeModifier(evotracker);
|
||||
this.heldItemManager.remove(HeldItemId.GIMMIGHOUL_EVO_TRACKER, 0, true);
|
||||
}
|
||||
}
|
||||
if (!globalScene.gameMode.isDaily || this.metBiome > -1) {
|
||||
@ -6031,16 +6027,12 @@ export class PlayerPokemon extends Pokemon {
|
||||
|
||||
globalScene.getPlayerParty().push(newPokemon);
|
||||
newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies);
|
||||
const modifiers = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
modifiers.forEach(m => {
|
||||
const clonedModifier = m.clone() as PokemonHeldItemModifier;
|
||||
clonedModifier.pokemonId = newPokemon.id;
|
||||
globalScene.addModifier(clonedModifier, true);
|
||||
//TODO: This currently does not consider any values associated with the items e.g. disabled
|
||||
const heldItems = this.getHeldItems();
|
||||
heldItems.forEach(item => {
|
||||
newPokemon.heldItemManager.add(item, this.heldItemManager.getStack(item));
|
||||
});
|
||||
globalScene.updateModifiers(true);
|
||||
globalScene.updateItems(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6061,6 +6053,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
this.variant,
|
||||
this.ivs,
|
||||
this.nature,
|
||||
this.heldItemManager.generateHeldItemConfiguration(),
|
||||
this,
|
||||
);
|
||||
ret.loadAssets().then(() => resolve(ret));
|
||||
@ -6085,7 +6078,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
const updateAndResolve = () => {
|
||||
this.loadAssets().then(() => {
|
||||
this.calculateStats();
|
||||
globalScene.updateModifiers(true, true);
|
||||
globalScene.updateItems(true);
|
||||
this.updateInfo(true).then(() => resolve());
|
||||
});
|
||||
};
|
||||
@ -6151,15 +6144,11 @@ export class PlayerPokemon extends Pokemon {
|
||||
}
|
||||
|
||||
// combine the two mons' held items
|
||||
const fusedPartyMemberHeldModifiers = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
for (const modifier of fusedPartyMemberHeldModifiers) {
|
||||
globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false);
|
||||
const fusedPartyMemberHeldItems = pokemon.getHeldItems();
|
||||
for (const item of fusedPartyMemberHeldItems) {
|
||||
globalScene.tryTransferHeldItem(item, pokemon, this, false, pokemon.heldItemManager.getStack(item), true, false);
|
||||
}
|
||||
globalScene.updateModifiers(true, true);
|
||||
globalScene.removePartyMemberModifiers(fusedPartyMemberIndex);
|
||||
globalScene.updateItems(true);
|
||||
globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0];
|
||||
const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this);
|
||||
pokemon
|
||||
@ -6207,6 +6196,7 @@ export class EnemyPokemon extends Pokemon {
|
||||
trainerSlot: TrainerSlot,
|
||||
boss: boolean,
|
||||
shinyLock = false,
|
||||
heldItemConfig?: HeldItemConfiguration,
|
||||
dataSource?: PokemonData,
|
||||
) {
|
||||
super(
|
||||
@ -6221,6 +6211,7 @@ export class EnemyPokemon extends Pokemon {
|
||||
!shinyLock && dataSource ? dataSource.variant : undefined,
|
||||
undefined,
|
||||
dataSource ? dataSource.nature : undefined,
|
||||
heldItemConfig,
|
||||
dataSource,
|
||||
);
|
||||
|
||||
@ -6858,6 +6849,7 @@ export class EnemyPokemon extends Pokemon {
|
||||
this.variant,
|
||||
this.ivs,
|
||||
this.nature,
|
||||
this.heldItemManager.generateHeldItemConfiguration(),
|
||||
this,
|
||||
);
|
||||
|
||||
|
@ -12,7 +12,7 @@ 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 { PersistentModifier } from "#modifiers/modifier";
|
||||
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";
|
||||
@ -634,7 +634,7 @@ export class Trainer extends Phaser.GameObjects.Container {
|
||||
return maxScorePartyMemberIndexes[0];
|
||||
}
|
||||
|
||||
getPartyMemberModifierChanceMultiplier(index: number): number {
|
||||
getPartyMemberItemChanceMultiplier(index: number): number {
|
||||
switch (this.getPartyTemplate().getStrength(index)) {
|
||||
case PartyMemberStrength.WEAKER:
|
||||
return 0.75;
|
||||
@ -647,14 +647,14 @@ export class Trainer extends Phaser.GameObjects.Container {
|
||||
case PartyMemberStrength.STRONGER:
|
||||
return 0.375;
|
||||
default:
|
||||
console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0");
|
||||
console.warn("getPartyMemberItemChanceMultiplier not defined. Using default 0");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
genModifiers(party: EnemyPokemon[]): PersistentModifier[] {
|
||||
if (this.config.genModifiersFunc) {
|
||||
return this.config.genModifiersFunc(party);
|
||||
genTrainerItems(party: EnemyPokemon[]): TrainerItemConfiguration {
|
||||
if (this.config.genTrainerItemsFunc) {
|
||||
return this.config.genTrainerItemsFunc(party);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
@ -323,7 +323,7 @@ export class GameMode implements GameModeConfig {
|
||||
}
|
||||
}
|
||||
|
||||
getEnemyModifierChance(isBoss: boolean): number {
|
||||
getEnemyItemChance(isBoss: boolean): number {
|
||||
switch (this.modeId) {
|
||||
case GameModes.CLASSIC:
|
||||
case GameModes.CHALLENGE:
|
||||
|
@ -6,8 +6,12 @@ import { initSpecies } from "#balance/pokemon-species";
|
||||
import { initChallenges } from "#data/challenge";
|
||||
import { initTrainerTypeDialogue } from "#data/dialogue";
|
||||
import { initPokemonForms } from "#data/pokemon-forms";
|
||||
import { initModifierPools } from "#modifiers/init-modifier-pools";
|
||||
import { initModifierTypes } from "#modifiers/modifier-type";
|
||||
import { initHeldItems } from "#items/all-held-items";
|
||||
import { initRewards } from "#items/all-rewards";
|
||||
import { initTrainerItems } from "#items/all-trainer-items";
|
||||
import { initHeldItemPools } from "#items/init-held-item-pools";
|
||||
import { initRewardPools } from "#items/init-reward-pools";
|
||||
import { initTrainerItemPools } from "#items/init-trainer-item-pools";
|
||||
import { initMoves } from "#moves/move";
|
||||
import { initMysteryEncounters } from "#mystery-encounters/mystery-encounters";
|
||||
import { initAchievements } from "#system/achv";
|
||||
@ -16,10 +20,9 @@ import { initStatsKeys } from "#ui/game-stats-ui-handler";
|
||||
|
||||
/** Initialize the game. */
|
||||
export function initializeGame() {
|
||||
initModifierTypes();
|
||||
initModifierPools();
|
||||
initAchievements();
|
||||
initItems();
|
||||
initVouchers();
|
||||
initAchievements();
|
||||
initStatsKeys();
|
||||
initPokemonPrevolutions();
|
||||
initPokemonStarters();
|
||||
@ -33,3 +36,15 @@ export function initializeGame() {
|
||||
initChallenges();
|
||||
initMysteryEncounters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-method to initialize all the item-related code.
|
||||
*/
|
||||
function initItems() {
|
||||
initHeldItems();
|
||||
initHeldItemPools();
|
||||
initTrainerItems();
|
||||
initTrainerItemPools();
|
||||
initRewards();
|
||||
initRewardPools();
|
||||
}
|
||||
|
194
src/items/all-held-items.ts
Normal file
194
src/items/all-held-items.ts
Normal file
@ -0,0 +1,194 @@
|
||||
import { allHeldItems } from "#data/data-lists";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { type PermanentStat, Stat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { AccuracyBoosterHeldItem, type AccuracyBoostParams } from "#items/accuracy-booster";
|
||||
import {
|
||||
AttackTypeBoosterHeldItem,
|
||||
type AttackTypeBoostParams,
|
||||
attackTypeToHeldItem,
|
||||
} from "#items/attack-type-booster";
|
||||
import { BaseStatBoosterHeldItem, type BaseStatBoosterParams, permanentStatToHeldItem } from "#items/base-stat-booster";
|
||||
import { BaseStatFlatHeldItem, type BaseStatFlatParams } from "#items/base-stat-flat";
|
||||
import { BaseStatTotalHeldItem, type BaseStatTotalParams } from "#items/base-stat-total";
|
||||
import { BatonHeldItem, type BatonParams } from "#items/baton";
|
||||
import { BerryHeldItem, type BerryParams, berryTypeToHeldItem } from "#items/berry";
|
||||
import { BypassSpeedChanceHeldItem, type BypassSpeedChanceParams } from "#items/bypass-speed-chance";
|
||||
import { CritBoostHeldItem, type CritBoostParams, SpeciesCritBoostHeldItem } from "#items/crit-booster";
|
||||
import { DamageMoneyRewardHeldItem, type DamageMoneyRewardParams } from "#items/damage-money-reward";
|
||||
import { type EvoTrackerParams, GimmighoulEvoTrackerHeldItem } from "#items/evo-tracker";
|
||||
import { ExpBoosterHeldItem, type ExpBoostParams } from "#items/exp-booster";
|
||||
import { FieldEffectHeldItem, type FieldEffectParams } from "#items/field-effect";
|
||||
import { FlinchChanceHeldItem, type FlinchChanceParams } from "#items/flinch-chance";
|
||||
import { FriendshipBoosterHeldItem, type FriendshipBoostParams } from "#items/friendship-booster";
|
||||
import { HitHealHeldItem, type HitHealParams } from "#items/hit-heal";
|
||||
import { IncrementingStatHeldItem, type IncrementingStatParams } from "#items/incrementing-stat";
|
||||
import { InstantReviveHeldItem, type InstantReviveParams } from "#items/instant-revive";
|
||||
import { ContactItemStealChanceHeldItem, type ItemStealParams, TurnEndItemStealHeldItem } from "#items/item-steal";
|
||||
import { MultiHitHeldItem, type MultiHitParams } from "#items/multi-hit";
|
||||
import { NatureWeightBoosterHeldItem, type NatureWeightBoostParams } from "#items/nature-weight-booster";
|
||||
import { ResetNegativeStatStageHeldItem, type ResetNegativeStatStageParams } from "#items/reset-negative-stat-stage";
|
||||
import { EvolutionStatBoostHeldItem, SpeciesStatBoostHeldItem, type StatBoostParams } from "#items/stat-booster";
|
||||
import { SurviveChanceHeldItem, type SurviveChanceParams } from "#items/survive-chance";
|
||||
import { TurnEndHealHeldItem, type TurnEndHealParams } from "#items/turn-end-heal";
|
||||
import { TurnEndStatusHeldItem, type TurnEndStatusParams } from "#items/turn-end-status";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
|
||||
export function initHeldItems() {
|
||||
for (const berry of getEnumValues(BerryType)) {
|
||||
const maxStackCount = [BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(berry) ? 2 : 3;
|
||||
const berryId = berryTypeToHeldItem[berry];
|
||||
allHeldItems[berryId] = new BerryHeldItem(berry, maxStackCount);
|
||||
}
|
||||
|
||||
allHeldItems[HeldItemId.REVIVER_SEED] = new InstantReviveHeldItem(HeldItemId.REVIVER_SEED, 1);
|
||||
allHeldItems[HeldItemId.WHITE_HERB] = new ResetNegativeStatStageHeldItem(HeldItemId.WHITE_HERB, 2);
|
||||
|
||||
// SILK_SCARF, BLACK_BELT, etc...
|
||||
for (const [typeKey, heldItemType] of Object.entries(attackTypeToHeldItem)) {
|
||||
// TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2114957526
|
||||
const pokemonType = Number(typeKey) as PokemonType;
|
||||
allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2);
|
||||
}
|
||||
|
||||
// Items that boost specific stats
|
||||
allHeldItems[HeldItemId.EVIOLITE] = new EvolutionStatBoostHeldItem(
|
||||
HeldItemId.EVIOLITE,
|
||||
1,
|
||||
[Stat.DEF, Stat.SPDEF],
|
||||
1.5,
|
||||
);
|
||||
allHeldItems[HeldItemId.LIGHT_BALL] = new SpeciesStatBoostHeldItem(
|
||||
HeldItemId.LIGHT_BALL,
|
||||
1,
|
||||
[Stat.ATK, Stat.SPATK],
|
||||
2,
|
||||
[SpeciesId.PIKACHU],
|
||||
);
|
||||
allHeldItems[HeldItemId.THICK_CLUB] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.ATK], 2, [
|
||||
SpeciesId.CUBONE,
|
||||
SpeciesId.MAROWAK,
|
||||
SpeciesId.ALOLA_MAROWAK,
|
||||
]);
|
||||
allHeldItems[HeldItemId.METAL_POWDER] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.DEF], 2, [
|
||||
SpeciesId.DITTO,
|
||||
]);
|
||||
allHeldItems[HeldItemId.QUICK_POWDER] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.SPD], 2, [
|
||||
SpeciesId.DITTO,
|
||||
]);
|
||||
allHeldItems[HeldItemId.DEEP_SEA_SCALE] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.SPDEF], 2, [
|
||||
SpeciesId.CLAMPERL,
|
||||
]);
|
||||
allHeldItems[HeldItemId.DEEP_SEA_TOOTH] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.SPATK], 2, [
|
||||
SpeciesId.CLAMPERL,
|
||||
]);
|
||||
|
||||
// Items that boost the crit rate
|
||||
allHeldItems[HeldItemId.SCOPE_LENS] = new CritBoostHeldItem(HeldItemId.SCOPE_LENS, 1, 1);
|
||||
allHeldItems[HeldItemId.LEEK] = new SpeciesCritBoostHeldItem(HeldItemId.LEEK, 1, 2, [
|
||||
SpeciesId.FARFETCHD,
|
||||
SpeciesId.GALAR_FARFETCHD,
|
||||
SpeciesId.SIRFETCHD,
|
||||
]);
|
||||
|
||||
allHeldItems[HeldItemId.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItemId.LUCKY_EGG, 99, 40);
|
||||
allHeldItems[HeldItemId.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItemId.GOLDEN_EGG, 99, 100);
|
||||
allHeldItems[HeldItemId.SOOTHE_BELL] = new FriendshipBoosterHeldItem(HeldItemId.SOOTHE_BELL, 3);
|
||||
|
||||
allHeldItems[HeldItemId.LEFTOVERS] = new TurnEndHealHeldItem(HeldItemId.LEFTOVERS, 4);
|
||||
allHeldItems[HeldItemId.SHELL_BELL] = new HitHealHeldItem(HeldItemId.SHELL_BELL, 4);
|
||||
|
||||
allHeldItems[HeldItemId.FOCUS_BAND] = new SurviveChanceHeldItem(HeldItemId.FOCUS_BAND, 5);
|
||||
allHeldItems[HeldItemId.QUICK_CLAW] = new BypassSpeedChanceHeldItem(HeldItemId.QUICK_CLAW, 3);
|
||||
allHeldItems[HeldItemId.KINGS_ROCK] = new FlinchChanceHeldItem(HeldItemId.KINGS_ROCK, 3, 10);
|
||||
allHeldItems[HeldItemId.MYSTICAL_ROCK] = new FieldEffectHeldItem(HeldItemId.MYSTICAL_ROCK, 2);
|
||||
allHeldItems[HeldItemId.SOUL_DEW] = new NatureWeightBoosterHeldItem(HeldItemId.SOUL_DEW, 10);
|
||||
allHeldItems[HeldItemId.WIDE_LENS] = new AccuracyBoosterHeldItem(HeldItemId.WIDE_LENS, 3, 5);
|
||||
allHeldItems[HeldItemId.MULTI_LENS] = new MultiHitHeldItem(HeldItemId.MULTI_LENS, 2);
|
||||
allHeldItems[HeldItemId.GOLDEN_PUNCH] = new DamageMoneyRewardHeldItem(HeldItemId.GOLDEN_PUNCH, 5);
|
||||
allHeldItems[HeldItemId.BATON] = new BatonHeldItem(HeldItemId.BATON, 1);
|
||||
allHeldItems[HeldItemId.GRIP_CLAW] = new ContactItemStealChanceHeldItem(HeldItemId.GRIP_CLAW, 5, 10);
|
||||
allHeldItems[HeldItemId.MINI_BLACK_HOLE] = new TurnEndItemStealHeldItem(HeldItemId.MINI_BLACK_HOLE, 1)
|
||||
.unstealable()
|
||||
.untransferable();
|
||||
|
||||
allHeldItems[HeldItemId.FLAME_ORB] = new TurnEndStatusHeldItem(HeldItemId.FLAME_ORB, 1, StatusEffect.BURN);
|
||||
allHeldItems[HeldItemId.TOXIC_ORB] = new TurnEndStatusHeldItem(HeldItemId.TOXIC_ORB, 1, StatusEffect.TOXIC);
|
||||
|
||||
// vitamins
|
||||
for (const [statKey, heldItemType] of Object.entries(permanentStatToHeldItem)) {
|
||||
const stat = Number(statKey) as PermanentStat;
|
||||
allHeldItems[heldItemType] = new BaseStatBoosterHeldItem(heldItemType, 30, stat)
|
||||
.unstealable()
|
||||
.untransferable()
|
||||
.unsuppressable();
|
||||
}
|
||||
|
||||
allHeldItems[HeldItemId.SHUCKLE_JUICE_GOOD] = new BaseStatTotalHeldItem(HeldItemId.SHUCKLE_JUICE_GOOD, 1, 10)
|
||||
.unstealable()
|
||||
.untransferable()
|
||||
.unsuppressable();
|
||||
allHeldItems[HeldItemId.SHUCKLE_JUICE_BAD] = new BaseStatTotalHeldItem(HeldItemId.SHUCKLE_JUICE_BAD, 1, -15)
|
||||
.unstealable()
|
||||
.untransferable()
|
||||
.unsuppressable();
|
||||
allHeldItems[HeldItemId.OLD_GATEAU] = new BaseStatFlatHeldItem(HeldItemId.OLD_GATEAU, 1)
|
||||
.unstealable()
|
||||
.untransferable()
|
||||
.unsuppressable();
|
||||
allHeldItems[HeldItemId.MACHO_BRACE] = new IncrementingStatHeldItem(HeldItemId.MACHO_BRACE, 50)
|
||||
.unstealable()
|
||||
.untransferable()
|
||||
.unsuppressable();
|
||||
allHeldItems[HeldItemId.GIMMIGHOUL_EVO_TRACKER] = new GimmighoulEvoTrackerHeldItem(
|
||||
HeldItemId.GIMMIGHOUL_EVO_TRACKER,
|
||||
999,
|
||||
SpeciesId.GIMMIGHOUL,
|
||||
10,
|
||||
);
|
||||
}
|
||||
|
||||
type ApplyHeldItemsParams = {
|
||||
[HeldItemEffect.ATTACK_TYPE_BOOST]: AttackTypeBoostParams;
|
||||
[HeldItemEffect.TURN_END_HEAL]: TurnEndHealParams;
|
||||
[HeldItemEffect.HIT_HEAL]: HitHealParams;
|
||||
[HeldItemEffect.RESET_NEGATIVE_STAT_STAGE]: ResetNegativeStatStageParams;
|
||||
[HeldItemEffect.EXP_BOOSTER]: ExpBoostParams;
|
||||
[HeldItemEffect.BERRY]: BerryParams;
|
||||
[HeldItemEffect.BASE_STAT_BOOSTER]: BaseStatBoosterParams;
|
||||
[HeldItemEffect.INSTANT_REVIVE]: InstantReviveParams;
|
||||
[HeldItemEffect.STAT_BOOST]: StatBoostParams;
|
||||
[HeldItemEffect.CRIT_BOOST]: CritBoostParams;
|
||||
[HeldItemEffect.TURN_END_STATUS]: TurnEndStatusParams;
|
||||
[HeldItemEffect.SURVIVE_CHANCE]: SurviveChanceParams;
|
||||
[HeldItemEffect.BYPASS_SPEED_CHANCE]: BypassSpeedChanceParams;
|
||||
[HeldItemEffect.FLINCH_CHANCE]: FlinchChanceParams;
|
||||
[HeldItemEffect.FIELD_EFFECT]: FieldEffectParams;
|
||||
[HeldItemEffect.FRIENDSHIP_BOOSTER]: FriendshipBoostParams;
|
||||
[HeldItemEffect.NATURE_WEIGHT_BOOSTER]: NatureWeightBoostParams;
|
||||
[HeldItemEffect.ACCURACY_BOOSTER]: AccuracyBoostParams;
|
||||
[HeldItemEffect.MULTI_HIT]: MultiHitParams;
|
||||
[HeldItemEffect.DAMAGE_MONEY_REWARD]: DamageMoneyRewardParams;
|
||||
[HeldItemEffect.BATON]: BatonParams;
|
||||
[HeldItemEffect.CONTACT_ITEM_STEAL_CHANCE]: ItemStealParams;
|
||||
[HeldItemEffect.TURN_END_ITEM_STEAL]: ItemStealParams;
|
||||
[HeldItemEffect.BASE_STAT_TOTAL]: BaseStatTotalParams;
|
||||
[HeldItemEffect.BASE_STAT_FLAT]: BaseStatFlatParams;
|
||||
[HeldItemEffect.INCREMENTING_STAT]: IncrementingStatParams;
|
||||
[HeldItemEffect.EVO_TRACKER]: EvoTrackerParams;
|
||||
};
|
||||
|
||||
export function applyHeldItems<T extends HeldItemEffect>(effect: T, params: ApplyHeldItemsParams[T]) {
|
||||
const pokemon = params.pokemon;
|
||||
if (pokemon) {
|
||||
for (const item of Object.keys(pokemon.heldItemManager.heldItems)) {
|
||||
if (allHeldItems[item].effects.includes(effect)) {
|
||||
allHeldItems[item].apply(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
182
src/items/all-rewards.ts
Normal file
182
src/items/all-rewards.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import { allRewards } from "#data/data-lists";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
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 {
|
||||
AddMoneyReward,
|
||||
AddPokeballReward,
|
||||
AddVoucherReward,
|
||||
AllPokemonFullReviveReward,
|
||||
AllPokemonLevelIncrementReward,
|
||||
AttackTypeBoosterRewardGenerator,
|
||||
BaseStatBoosterRewardGenerator,
|
||||
BerryRewardGenerator,
|
||||
EvolutionItemRewardGenerator,
|
||||
FormChangeItemRewardGenerator,
|
||||
FusePokemonReward,
|
||||
LapsingTrainerItemReward,
|
||||
MintRewardGenerator,
|
||||
PokemonAllMovePpRestoreReward,
|
||||
PokemonHpRestoreReward,
|
||||
PokemonLevelIncrementReward,
|
||||
PokemonPpRestoreReward,
|
||||
PokemonPpUpReward,
|
||||
PokemonReviveReward,
|
||||
PokemonStatusHealReward,
|
||||
RememberMoveReward,
|
||||
SpeciesStatBoosterRewardGenerator,
|
||||
TempStatStageBoosterRewardGenerator,
|
||||
TeraTypeRewardGenerator,
|
||||
TmRewardGenerator,
|
||||
} from "./reward";
|
||||
|
||||
export function initRewards() {
|
||||
// Pokeball rewards
|
||||
allRewards[RewardId.POKEBALL] = () => new AddPokeballReward("pb", PokeballType.POKEBALL, 5, RewardId.POKEBALL);
|
||||
allRewards[RewardId.GREAT_BALL] = () => new AddPokeballReward("gb", PokeballType.GREAT_BALL, 5, RewardId.GREAT_BALL);
|
||||
allRewards[RewardId.ULTRA_BALL] = () => new AddPokeballReward("ub", PokeballType.ULTRA_BALL, 5, RewardId.ULTRA_BALL);
|
||||
allRewards[RewardId.ROGUE_BALL] = () => new AddPokeballReward("rb", PokeballType.ROGUE_BALL, 5, RewardId.ROGUE_BALL);
|
||||
allRewards[RewardId.MASTER_BALL] = () =>
|
||||
new AddPokeballReward("mb", PokeballType.MASTER_BALL, 1, RewardId.MASTER_BALL);
|
||||
|
||||
// Voucher rewards
|
||||
allRewards[RewardId.VOUCHER] = () => new AddVoucherReward(VoucherType.REGULAR, 1, RewardId.VOUCHER);
|
||||
allRewards[RewardId.VOUCHER_PLUS] = () => new AddVoucherReward(VoucherType.PLUS, 1, RewardId.VOUCHER_PLUS);
|
||||
allRewards[RewardId.VOUCHER_PREMIUM] = () => new AddVoucherReward(VoucherType.PREMIUM, 1, RewardId.VOUCHER_PREMIUM);
|
||||
|
||||
// Money rewards
|
||||
allRewards[RewardId.NUGGET] = () =>
|
||||
new AddMoneyReward(
|
||||
"modifierType:ModifierType.NUGGET",
|
||||
"nugget",
|
||||
1,
|
||||
"modifierType:ModifierType.MoneyRewardModifierType.extra.small",
|
||||
RewardId.NUGGET,
|
||||
);
|
||||
allRewards[RewardId.BIG_NUGGET] = () =>
|
||||
new AddMoneyReward(
|
||||
"modifierType:ModifierType.BIG_NUGGET",
|
||||
"big_nugget",
|
||||
2.5,
|
||||
"modifierType:ModifierType.MoneyRewardModifierType.extra.moderate",
|
||||
RewardId.BIG_NUGGET,
|
||||
);
|
||||
allRewards[RewardId.RELIC_GOLD] = () =>
|
||||
new AddMoneyReward(
|
||||
"modifierType:ModifierType.RELIC_GOLD",
|
||||
"relic_gold",
|
||||
10,
|
||||
"modifierType:ModifierType.MoneyRewardModifierType.extra.large",
|
||||
RewardId.RELIC_GOLD,
|
||||
);
|
||||
|
||||
// Party-wide consumables
|
||||
allRewards[RewardId.RARER_CANDY] = () =>
|
||||
new AllPokemonLevelIncrementReward("modifierType:ModifierType.RARER_CANDY", "rarer_candy");
|
||||
allRewards[RewardId.SACRED_ASH] = () =>
|
||||
new AllPokemonFullReviveReward("modifierType:ModifierType.SACRED_ASH", "sacred_ash");
|
||||
|
||||
// Pokemon consumables
|
||||
allRewards[RewardId.RARE_CANDY] = () =>
|
||||
new PokemonLevelIncrementReward("modifierType:ModifierType.RARE_CANDY", "rare_candy");
|
||||
|
||||
allRewards[RewardId.EVOLUTION_ITEM] = () => new EvolutionItemRewardGenerator(false, RewardId.EVOLUTION_ITEM);
|
||||
allRewards[RewardId.RARE_EVOLUTION_ITEM] = () => new EvolutionItemRewardGenerator(true, RewardId.RARE_EVOLUTION_ITEM);
|
||||
|
||||
allRewards[RewardId.POTION] = () =>
|
||||
new PokemonHpRestoreReward("modifierType:ModifierType.POTION", "potion", RewardId.POTION, 20, 10);
|
||||
allRewards[RewardId.SUPER_POTION] = () =>
|
||||
new PokemonHpRestoreReward("modifierType:ModifierType.SUPER_POTION", "super_potion", RewardId.SUPER_POTION, 50, 25);
|
||||
allRewards[RewardId.HYPER_POTION] = () =>
|
||||
new PokemonHpRestoreReward(
|
||||
"modifierType:ModifierType.HYPER_POTION",
|
||||
"hyper_potion",
|
||||
RewardId.HYPER_POTION,
|
||||
200,
|
||||
50,
|
||||
);
|
||||
allRewards[RewardId.MAX_POTION] = () =>
|
||||
new PokemonHpRestoreReward("modifierType:ModifierType.MAX_POTION", "max_potion", RewardId.MAX_POTION, 0, 100);
|
||||
allRewards[RewardId.FULL_RESTORE] = () =>
|
||||
new PokemonHpRestoreReward(
|
||||
"modifierType:ModifierType.FULL_RESTORE",
|
||||
"full_restore",
|
||||
RewardId.FULL_RESTORE,
|
||||
0,
|
||||
100,
|
||||
true,
|
||||
);
|
||||
|
||||
allRewards[RewardId.REVIVE] = () =>
|
||||
new PokemonReviveReward("modifierType:ModifierType.REVIVE", "revive", RewardId.REVIVE, 50);
|
||||
allRewards[RewardId.MAX_REVIVE] = () =>
|
||||
new PokemonReviveReward("modifierType:ModifierType.MAX_REVIVE", "max_revive", RewardId.MAX_REVIVE, 100);
|
||||
|
||||
allRewards[RewardId.FULL_HEAL] = () =>
|
||||
new PokemonStatusHealReward("modifierType:ModifierType.FULL_HEAL", "full_heal");
|
||||
|
||||
allRewards[RewardId.ETHER] = () =>
|
||||
new PokemonPpRestoreReward("modifierType:ModifierType.ETHER", "ether", RewardId.ETHER, 10);
|
||||
allRewards[RewardId.MAX_ETHER] = () =>
|
||||
new PokemonPpRestoreReward("modifierType:ModifierType.MAX_ETHER", "max_ether", RewardId.MAX_ETHER, -1);
|
||||
|
||||
allRewards[RewardId.ELIXIR] = () =>
|
||||
new PokemonAllMovePpRestoreReward("modifierType:ModifierType.ELIXIR", "elixir", RewardId.ELIXIR, 10);
|
||||
allRewards[RewardId.MAX_ELIXIR] = () =>
|
||||
new PokemonAllMovePpRestoreReward("modifierType:ModifierType.MAX_ELIXIR", "max_elixir", RewardId.MAX_ELIXIR, -1);
|
||||
|
||||
allRewards[RewardId.PP_UP] = () =>
|
||||
new PokemonPpUpReward("modifierType:ModifierType.PP_UP", "pp_up", RewardId.PP_UP, 1);
|
||||
allRewards[RewardId.PP_MAX] = () =>
|
||||
new PokemonPpUpReward("modifierType:ModifierType.PP_MAX", "pp_max", RewardId.PP_MAX, 3);
|
||||
|
||||
/*REPEL] = () => new DoubleBattleChanceBoosterReward('Repel', 5),
|
||||
SUPER_REPEL] = () => new DoubleBattleChanceBoosterReward('Super Repel', 10),
|
||||
MAX_REPEL] = () => new DoubleBattleChanceBoosterReward('Max Repel', 25),*/
|
||||
|
||||
allRewards[RewardId.MINT] = () => new MintRewardGenerator();
|
||||
|
||||
allRewards[RewardId.TERA_SHARD] = () => new TeraTypeRewardGenerator();
|
||||
|
||||
allRewards[RewardId.TM_COMMON] = () => new TmRewardGenerator(RarityTier.COMMON);
|
||||
allRewards[RewardId.TM_GREAT] = () => new TmRewardGenerator(RarityTier.GREAT);
|
||||
allRewards[RewardId.TM_ULTRA] = () => new TmRewardGenerator(RarityTier.ULTRA);
|
||||
|
||||
allRewards[RewardId.MEMORY_MUSHROOM] = () =>
|
||||
new RememberMoveReward("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom");
|
||||
|
||||
allRewards[RewardId.DNA_SPLICERS] = () =>
|
||||
new FusePokemonReward("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers");
|
||||
|
||||
// Form change items
|
||||
allRewards[RewardId.FORM_CHANGE_ITEM] = () => new FormChangeItemRewardGenerator(false, RewardId.FORM_CHANGE_ITEM);
|
||||
allRewards[RewardId.RARE_FORM_CHANGE_ITEM] = () =>
|
||||
new FormChangeItemRewardGenerator(true, RewardId.RARE_FORM_CHANGE_ITEM);
|
||||
|
||||
// Held items
|
||||
|
||||
allRewards[RewardId.SPECIES_STAT_BOOSTER] = () => new SpeciesStatBoosterRewardGenerator(false);
|
||||
allRewards[RewardId.RARE_SPECIES_STAT_BOOSTER] = () => new SpeciesStatBoosterRewardGenerator(true);
|
||||
|
||||
allRewards[RewardId.BASE_STAT_BOOSTER] = () => new BaseStatBoosterRewardGenerator();
|
||||
|
||||
allRewards[RewardId.ATTACK_TYPE_BOOSTER] = () => new AttackTypeBoosterRewardGenerator();
|
||||
|
||||
allRewards[RewardId.BERRY] = () => new BerryRewardGenerator();
|
||||
|
||||
// MINI_BLACK_HOLE] = () => new HeldItemReward(HeldItemId.MINI_BLACK_HOLE),
|
||||
|
||||
// Trainer items
|
||||
|
||||
allRewards[RewardId.LURE] = () => new LapsingTrainerItemReward(TrainerItemId.LURE, RewardId.LURE);
|
||||
allRewards[RewardId.SUPER_LURE] = () => new LapsingTrainerItemReward(TrainerItemId.SUPER_LURE, RewardId.SUPER_LURE);
|
||||
allRewards[RewardId.MAX_LURE] = () => new LapsingTrainerItemReward(TrainerItemId.MAX_LURE, RewardId.MAX_LURE);
|
||||
|
||||
allRewards[RewardId.TEMP_STAT_STAGE_BOOSTER] = () => new TempStatStageBoosterRewardGenerator();
|
||||
|
||||
allRewards[RewardId.DIRE_HIT] = () =>
|
||||
new LapsingTrainerItemReward(TrainerItemId.DIRE_HIT, RewardId.TEMP_STAT_STAGE_BOOSTER);
|
||||
// GOLDEN_POKEBALL] = () => new TrainerItemReward(TrainerItemId.GOLDEN_POKEBALL),
|
||||
}
|
114
src/items/all-trainer-items.ts
Normal file
114
src/items/all-trainer-items.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { allTrainerItems } from "#data/data-lists";
|
||||
import { Stat, type TempBattleStat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import {
|
||||
CriticalCatchChanceBoosterTrainerItem,
|
||||
DoubleBattleChanceBoosterTrainerItem,
|
||||
EnemyAttackStatusEffectChanceTrainerItem,
|
||||
EnemyDamageBoosterTrainerItem,
|
||||
EnemyDamageReducerTrainerItem,
|
||||
EnemyEndureChanceTrainerItem,
|
||||
EnemyFusionChanceTrainerItem,
|
||||
EnemyStatusEffectHealChanceTrainerItem,
|
||||
EnemyTurnHealTrainerItem,
|
||||
ExpBoosterTrainerItem,
|
||||
ExtraRewardTrainerItem,
|
||||
HealingBoosterTrainerItem,
|
||||
HealShopCostTrainerItem,
|
||||
HiddenAbilityChanceBoosterTrainerItem,
|
||||
LevelIncrementBoosterTrainerItem,
|
||||
MoneyMultiplierTrainerItem,
|
||||
PreserveBerryTrainerItem,
|
||||
ShinyRateBoosterTrainerItem,
|
||||
TempAccuracyBoosterTrainerItem,
|
||||
TempCritBoosterTrainerItem,
|
||||
TempStatStageBoosterTrainerItem,
|
||||
TrainerItem,
|
||||
tempStatToTrainerItem,
|
||||
} from "#items/trainer-item";
|
||||
|
||||
export function initTrainerItems() {
|
||||
allTrainerItems[TrainerItemId.MAP] = new TrainerItem(TrainerItemId.MAP, 1);
|
||||
allTrainerItems[TrainerItemId.IV_SCANNER] = new TrainerItem(TrainerItemId.IV_SCANNER, 1);
|
||||
allTrainerItems[TrainerItemId.LOCK_CAPSULE] = new TrainerItem(TrainerItemId.LOCK_CAPSULE, 1);
|
||||
allTrainerItems[TrainerItemId.MEGA_BRACELET] = new TrainerItem(TrainerItemId.MEGA_BRACELET, 1);
|
||||
allTrainerItems[TrainerItemId.DYNAMAX_BAND] = new TrainerItem(TrainerItemId.DYNAMAX_BAND, 1);
|
||||
allTrainerItems[TrainerItemId.TERA_ORB] = new TrainerItem(TrainerItemId.TERA_ORB, 1);
|
||||
|
||||
allTrainerItems[TrainerItemId.OVAL_CHARM] = new TrainerItem(TrainerItemId.OVAL_CHARM, 5);
|
||||
allTrainerItems[TrainerItemId.EXP_SHARE] = new TrainerItem(TrainerItemId.EXP_SHARE, 5);
|
||||
allTrainerItems[TrainerItemId.EXP_BALANCE] = new TrainerItem(TrainerItemId.EXP_BALANCE, 4);
|
||||
|
||||
allTrainerItems[TrainerItemId.CANDY_JAR] = new LevelIncrementBoosterTrainerItem(TrainerItemId.CANDY_JAR, 99);
|
||||
allTrainerItems[TrainerItemId.BERRY_POUCH] = new PreserveBerryTrainerItem(TrainerItemId.BERRY_POUCH, 3);
|
||||
|
||||
allTrainerItems[TrainerItemId.HEALING_CHARM] = new HealingBoosterTrainerItem(TrainerItemId.HEALING_CHARM, 0.1, 5);
|
||||
allTrainerItems[TrainerItemId.EXP_CHARM] = new ExpBoosterTrainerItem(TrainerItemId.EXP_CHARM, 25, 99);
|
||||
allTrainerItems[TrainerItemId.SUPER_EXP_CHARM] = new ExpBoosterTrainerItem(TrainerItemId.SUPER_EXP_CHARM, 60, 30);
|
||||
allTrainerItems[TrainerItemId.GOLDEN_EXP_CHARM] = new ExpBoosterTrainerItem(TrainerItemId.GOLDEN_EXP_CHARM, 100, 10);
|
||||
allTrainerItems[TrainerItemId.AMULET_COIN] = new MoneyMultiplierTrainerItem(TrainerItemId.AMULET_COIN, 5);
|
||||
|
||||
allTrainerItems[TrainerItemId.ABILITY_CHARM] = new HiddenAbilityChanceBoosterTrainerItem(
|
||||
TrainerItemId.ABILITY_CHARM,
|
||||
4,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.GOLDEN_POKEBALL] = new ExtraRewardTrainerItem(TrainerItemId.GOLDEN_POKEBALL, 3);
|
||||
allTrainerItems[TrainerItemId.SHINY_CHARM] = new ShinyRateBoosterTrainerItem(TrainerItemId.SHINY_CHARM, 4);
|
||||
allTrainerItems[TrainerItemId.CATCHING_CHARM] = new CriticalCatchChanceBoosterTrainerItem(
|
||||
TrainerItemId.CATCHING_CHARM,
|
||||
3,
|
||||
);
|
||||
|
||||
allTrainerItems[TrainerItemId.BLACK_SLUDGE] = new HealShopCostTrainerItem(TrainerItemId.BLACK_SLUDGE, 2.5, 1);
|
||||
allTrainerItems[TrainerItemId.GOLDEN_BUG_NET] = new TrainerItem(TrainerItemId.GOLDEN_BUG_NET, 1);
|
||||
|
||||
allTrainerItems[TrainerItemId.LURE] = new DoubleBattleChanceBoosterTrainerItem(TrainerItemId.LURE, 10);
|
||||
allTrainerItems[TrainerItemId.SUPER_LURE] = new DoubleBattleChanceBoosterTrainerItem(TrainerItemId.SUPER_LURE, 15);
|
||||
allTrainerItems[TrainerItemId.MAX_LURE] = new DoubleBattleChanceBoosterTrainerItem(TrainerItemId.MAX_LURE, 30);
|
||||
|
||||
for (const [statKey, trainerItemType] of Object.entries(tempStatToTrainerItem)) {
|
||||
const stat = Number(statKey) as TempBattleStat;
|
||||
if (stat === Stat.ACC) {
|
||||
allTrainerItems[trainerItemType] = new TempAccuracyBoosterTrainerItem(trainerItemType, 5);
|
||||
} else {
|
||||
allTrainerItems[trainerItemType] = new TempStatStageBoosterTrainerItem(trainerItemType, stat, 5);
|
||||
}
|
||||
}
|
||||
allTrainerItems[TrainerItemId.DIRE_HIT] = new TempCritBoosterTrainerItem(TrainerItemId.DIRE_HIT, 5);
|
||||
|
||||
allTrainerItems[TrainerItemId.ENEMY_DAMAGE_BOOSTER] = new EnemyDamageBoosterTrainerItem(
|
||||
TrainerItemId.ENEMY_DAMAGE_BOOSTER,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_DAMAGE_REDUCTION] = new EnemyDamageReducerTrainerItem(
|
||||
TrainerItemId.ENEMY_DAMAGE_REDUCTION,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_HEAL] = new EnemyTurnHealTrainerItem(TrainerItemId.ENEMY_HEAL, 10);
|
||||
allTrainerItems[TrainerItemId.ENEMY_ATTACK_POISON_CHANCE] = new EnemyAttackStatusEffectChanceTrainerItem(
|
||||
TrainerItemId.ENEMY_ATTACK_POISON_CHANCE,
|
||||
StatusEffect.POISON,
|
||||
10,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_ATTACK_PARALYZE_CHANCE] = new EnemyAttackStatusEffectChanceTrainerItem(
|
||||
TrainerItemId.ENEMY_ATTACK_PARALYZE_CHANCE,
|
||||
StatusEffect.PARALYSIS,
|
||||
10,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_ATTACK_BURN_CHANCE] = new EnemyAttackStatusEffectChanceTrainerItem(
|
||||
TrainerItemId.ENEMY_ATTACK_BURN_CHANCE,
|
||||
StatusEffect.BURN,
|
||||
10,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE] = new EnemyStatusEffectHealChanceTrainerItem(
|
||||
TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE,
|
||||
10,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_ENDURE_CHANCE] = new EnemyEndureChanceTrainerItem(
|
||||
TrainerItemId.ENEMY_ENDURE_CHANCE,
|
||||
10,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_FUSED_CHANCE] = new EnemyFusionChanceTrainerItem(
|
||||
TrainerItemId.ENEMY_FUSED_CHANCE,
|
||||
10,
|
||||
);
|
||||
}
|
47
src/items/apply-trainer-items.ts
Normal file
47
src/items/apply-trainer-items.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { allTrainerItems } from "#data/data-lists";
|
||||
import {
|
||||
type BooleanHolderParams,
|
||||
type NumberHolderParams,
|
||||
type PokemonParams,
|
||||
type PreserveBerryParams,
|
||||
TrainerItemEffect,
|
||||
} from "#items/trainer-item";
|
||||
import type { TrainerItemManager } from "#items/trainer-item-manager";
|
||||
|
||||
export type ApplyTrainerItemsParams = {
|
||||
[TrainerItemEffect.LEVEL_INCREMENT_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.PRESERVE_BERRY]: PreserveBerryParams;
|
||||
[TrainerItemEffect.HEALING_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.EXP_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.MONEY_MULTIPLIER]: NumberHolderParams;
|
||||
[TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.SHINY_RATE_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.CRITICAL_CATCH_CHANCE_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.EXTRA_REWARD]: NumberHolderParams;
|
||||
[TrainerItemEffect.HEAL_SHOP_COST]: NumberHolderParams;
|
||||
[TrainerItemEffect.DOUBLE_BATTLE_CHANCE_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.TEMP_STAT_STAGE_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.TEMP_ACCURACY_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.TEMP_CRIT_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.ENEMY_DAMAGE_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.ENEMY_DAMAGE_REDUCER]: NumberHolderParams;
|
||||
[TrainerItemEffect.ENEMY_HEAL]: PokemonParams;
|
||||
[TrainerItemEffect.ENEMY_ATTACK_STATUS_CHANCE]: PokemonParams;
|
||||
[TrainerItemEffect.ENEMY_STATUS_HEAL_CHANCE]: PokemonParams;
|
||||
[TrainerItemEffect.ENEMY_ENDURE_CHANCE]: PokemonParams;
|
||||
[TrainerItemEffect.ENEMY_FUSED_CHANCE]: BooleanHolderParams;
|
||||
};
|
||||
|
||||
export function applyTrainerItems<T extends TrainerItemEffect>(
|
||||
effect: T,
|
||||
manager: TrainerItemManager,
|
||||
params: ApplyTrainerItemsParams[T],
|
||||
) {
|
||||
if (manager) {
|
||||
for (const item of Object.keys(manager.trainerItems)) {
|
||||
if (allTrainerItems[item].effects.includes(effect)) {
|
||||
allTrainerItems[item].apply(manager, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
93
src/items/held-item-data-types.ts
Normal file
93
src/items/held-item-data-types.ts
Normal file
@ -0,0 +1,93 @@
|
||||
// TODO: move all types to `src/@types/` and all functions to a utility place
|
||||
|
||||
import type { FormChangeItem } from "#enums/form-change-item";
|
||||
import type { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
|
||||
import type { RarityTier } from "#enums/reward-tier";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
|
||||
export type HeldItemData = {
|
||||
stack: number;
|
||||
/**
|
||||
* Whether this item is currently disabled.
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* The item's current cooldown.
|
||||
* @defaultValue `0`
|
||||
*/
|
||||
cooldown?: number;
|
||||
};
|
||||
|
||||
export type HeldItemDataMap = {
|
||||
[key in HeldItemId]?: HeldItemData;
|
||||
};
|
||||
|
||||
export type HeldItemSpecs = HeldItemData & {
|
||||
id: HeldItemId;
|
||||
};
|
||||
|
||||
export function isHeldItemSpecs(entry: any): entry is HeldItemSpecs {
|
||||
return typeof entry.id === "number" && "stack" in entry;
|
||||
}
|
||||
|
||||
// Types used for form change items
|
||||
interface FormChangeItemData {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export type FormChangeItemPropertyMap = {
|
||||
[key in FormChangeItem]?: FormChangeItemData;
|
||||
};
|
||||
|
||||
export type FormChangeItemSpecs = FormChangeItemData & {
|
||||
id: FormChangeItem;
|
||||
};
|
||||
|
||||
export function isFormChangeItemSpecs(entry: any): entry is FormChangeItemSpecs {
|
||||
return typeof entry.id === "number" && "active" in entry;
|
||||
}
|
||||
|
||||
export type HeldItemWeights = {
|
||||
[key in HeldItemId]?: number;
|
||||
};
|
||||
|
||||
export type HeldItemWeightFunc = (party: Pokemon[]) => number;
|
||||
|
||||
export type HeldItemCategoryEntry = HeldItemData & {
|
||||
id: HeldItemCategoryId;
|
||||
customWeights?: HeldItemWeights;
|
||||
};
|
||||
|
||||
export function isHeldItemCategoryEntry(entry: any): entry is HeldItemCategoryEntry {
|
||||
return entry?.id && isHeldItemCategoryEntry(entry.id) && "customWeights" in entry;
|
||||
}
|
||||
|
||||
type HeldItemPoolEntry = {
|
||||
entry: HeldItemId | HeldItemCategoryId | HeldItemCategoryEntry | HeldItemSpecs;
|
||||
weight: number | HeldItemWeightFunc;
|
||||
};
|
||||
|
||||
export type HeldItemPool = HeldItemPoolEntry[];
|
||||
|
||||
export function isHeldItemPool(value: any): value is HeldItemPool {
|
||||
return Array.isArray(value) && value.every(entry => "entry" in entry && "weight" in entry);
|
||||
}
|
||||
|
||||
export type HeldItemTieredPool = {
|
||||
[key in RarityTier]?: HeldItemPool;
|
||||
};
|
||||
|
||||
type HeldItemConfigurationEntry = {
|
||||
entry: HeldItemId | HeldItemCategoryId | HeldItemCategoryEntry | HeldItemSpecs | HeldItemPool | FormChangeItemSpecs;
|
||||
count?: number | (() => number);
|
||||
};
|
||||
|
||||
export type HeldItemConfiguration = HeldItemConfigurationEntry[];
|
||||
|
||||
export type PokemonItemMap = {
|
||||
item: HeldItemSpecs | FormChangeItemSpecs;
|
||||
pokemonId: number;
|
||||
};
|
||||
|
||||
export type HeldItemSaveData = (HeldItemSpecs | FormChangeItemSpecs)[];
|
47
src/items/held-item-default-tiers.ts
Normal file
47
src/items/held-item-default-tiers.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { getHeldItemCategory, HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
|
||||
export const heldItemRarities = {
|
||||
[HeldItemCategoryId.BERRY]: RarityTier.COMMON,
|
||||
|
||||
[HeldItemCategoryId.BASE_STAT_BOOST]: RarityTier.GREAT,
|
||||
[HeldItemId.WHITE_HERB]: RarityTier.GREAT,
|
||||
[HeldItemId.METAL_POWDER]: RarityTier.GREAT,
|
||||
[HeldItemId.QUICK_POWDER]: RarityTier.GREAT,
|
||||
[HeldItemId.DEEP_SEA_SCALE]: RarityTier.GREAT,
|
||||
[HeldItemId.DEEP_SEA_TOOTH]: RarityTier.GREAT,
|
||||
[HeldItemId.SOOTHE_BELL]: RarityTier.GREAT,
|
||||
|
||||
[HeldItemCategoryId.TYPE_ATTACK_BOOSTER]: RarityTier.ULTRA,
|
||||
[HeldItemId.REVIVER_SEED]: RarityTier.ULTRA,
|
||||
[HeldItemId.LIGHT_BALL]: RarityTier.ULTRA,
|
||||
[HeldItemId.EVIOLITE]: RarityTier.ULTRA,
|
||||
[HeldItemId.QUICK_CLAW]: RarityTier.ULTRA,
|
||||
[HeldItemId.MYSTICAL_ROCK]: RarityTier.ULTRA,
|
||||
[HeldItemId.WIDE_LENS]: RarityTier.ULTRA,
|
||||
[HeldItemId.GOLDEN_PUNCH]: RarityTier.ULTRA,
|
||||
[HeldItemId.TOXIC_ORB]: RarityTier.ULTRA,
|
||||
[HeldItemId.FLAME_ORB]: RarityTier.ULTRA,
|
||||
[HeldItemId.LUCKY_EGG]: RarityTier.ULTRA,
|
||||
|
||||
[HeldItemId.FOCUS_BAND]: RarityTier.ROGUE,
|
||||
[HeldItemId.KINGS_ROCK]: RarityTier.ROGUE,
|
||||
[HeldItemId.LEFTOVERS]: RarityTier.ROGUE,
|
||||
[HeldItemId.SHELL_BELL]: RarityTier.ROGUE,
|
||||
[HeldItemId.GRIP_CLAW]: RarityTier.ROGUE,
|
||||
[HeldItemId.SOUL_DEW]: RarityTier.ROGUE,
|
||||
[HeldItemId.BATON]: RarityTier.ROGUE,
|
||||
[HeldItemId.GOLDEN_EGG]: RarityTier.ULTRA,
|
||||
|
||||
[HeldItemId.MINI_BLACK_HOLE]: RarityTier.MASTER,
|
||||
[HeldItemId.MULTI_LENS]: RarityTier.MASTER,
|
||||
};
|
||||
|
||||
export function getHeldItemTier(item: HeldItemId): RarityTier {
|
||||
let tier = heldItemRarities[item];
|
||||
if (!tier) {
|
||||
const category = getHeldItemCategory(item);
|
||||
tier = heldItemRarities[category];
|
||||
}
|
||||
return tier ?? RarityTier.LUXURY;
|
||||
}
|
238
src/items/held-item-manager.ts
Normal file
238
src/items/held-item-manager.ts
Normal file
@ -0,0 +1,238 @@
|
||||
import { allHeldItems } from "#data/data-lists";
|
||||
import type { FormChangeItem } from "#enums/form-change-item";
|
||||
import {
|
||||
type HeldItemCategoryId,
|
||||
type HeldItemId,
|
||||
isCategoryId,
|
||||
isItemInCategory,
|
||||
isItemInRequested,
|
||||
} from "#enums/held-item-id";
|
||||
import {
|
||||
type FormChangeItemPropertyMap,
|
||||
type FormChangeItemSpecs,
|
||||
type HeldItemConfiguration,
|
||||
type HeldItemDataMap,
|
||||
type HeldItemSaveData,
|
||||
type HeldItemSpecs,
|
||||
isHeldItemSpecs,
|
||||
} from "#items/held-item-data-types";
|
||||
import { getTypedEntries, getTypedKeys } from "#utils/common";
|
||||
|
||||
export class HeldItemManager {
|
||||
public heldItems: HeldItemDataMap;
|
||||
public formChangeItems: FormChangeItemPropertyMap;
|
||||
|
||||
constructor() {
|
||||
this.heldItems = {};
|
||||
this.formChangeItems = {};
|
||||
}
|
||||
|
||||
getItemSpecs(id: HeldItemId): HeldItemSpecs | undefined {
|
||||
const item = this.heldItems[id];
|
||||
if (item) {
|
||||
const itemSpecs: HeldItemSpecs = {
|
||||
...item,
|
||||
id,
|
||||
};
|
||||
return itemSpecs;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
generateHeldItemConfiguration(restrictedIds?: HeldItemId[]): HeldItemConfiguration {
|
||||
const config: HeldItemConfiguration = [];
|
||||
for (const [id, item] of getTypedEntries(this.heldItems)) {
|
||||
if (item && (!restrictedIds || id in restrictedIds)) {
|
||||
const specs: HeldItemSpecs = { ...item, id };
|
||||
config.push({ entry: specs, count: 1 });
|
||||
}
|
||||
}
|
||||
for (const [id, item] of getTypedEntries(this.formChangeItems)) {
|
||||
if (item) {
|
||||
const specs: FormChangeItemSpecs = { ...item, id };
|
||||
config.push({ entry: specs, count: 1 });
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
generateSaveData(): HeldItemSaveData {
|
||||
const saveData: HeldItemSaveData = [];
|
||||
for (const [id, item] of getTypedEntries(this.heldItems)) {
|
||||
if (item) {
|
||||
const specs: HeldItemSpecs = { ...item, id };
|
||||
saveData.push(specs);
|
||||
}
|
||||
}
|
||||
for (const [id, item] of getTypedEntries(this.formChangeItems)) {
|
||||
if (item) {
|
||||
const specs: FormChangeItemSpecs = { ...item, id };
|
||||
saveData.push(specs);
|
||||
}
|
||||
}
|
||||
return saveData;
|
||||
}
|
||||
|
||||
getHeldItems(): HeldItemId[] {
|
||||
return getTypedKeys(this.heldItems);
|
||||
}
|
||||
|
||||
getTransferableHeldItems(): HeldItemId[] {
|
||||
return getTypedKeys(this.heldItems).filter(k => allHeldItems[k].isTransferable);
|
||||
}
|
||||
|
||||
getStealableHeldItems(): HeldItemId[] {
|
||||
return getTypedKeys(this.heldItems).filter(k => allHeldItems[k].isStealable);
|
||||
}
|
||||
|
||||
getSuppressableHeldItems(): HeldItemId[] {
|
||||
return getTypedKeys(this.heldItems).filter(k => allHeldItems[k].isSuppressable);
|
||||
}
|
||||
|
||||
hasItem(itemType: HeldItemId | HeldItemCategoryId): boolean {
|
||||
if (isCategoryId(itemType)) {
|
||||
return getTypedKeys(this.heldItems).some(id => isItemInCategory(id, itemType as HeldItemCategoryId));
|
||||
}
|
||||
return itemType in this.heldItems;
|
||||
}
|
||||
|
||||
hasTransferableItem(itemType: HeldItemId | HeldItemCategoryId): boolean {
|
||||
if (isCategoryId(itemType)) {
|
||||
return getTypedKeys(this.heldItems).some(
|
||||
id => isItemInCategory(id, itemType as HeldItemCategoryId) && allHeldItems[id].isTransferable,
|
||||
);
|
||||
}
|
||||
return itemType in this.heldItems && allHeldItems[itemType].isTransferable;
|
||||
}
|
||||
|
||||
getStack(itemType: HeldItemId): number {
|
||||
const item = this.heldItems[itemType];
|
||||
return item ? item.stack : 0;
|
||||
}
|
||||
|
||||
// Use for tests if necessary to go over stack limit
|
||||
setStack(itemType: HeldItemId, stack: number): void {
|
||||
const item = this.heldItems[itemType];
|
||||
if (item) {
|
||||
item.stack = stack;
|
||||
}
|
||||
}
|
||||
|
||||
isMaxStack(itemType: HeldItemId): boolean {
|
||||
const item = this.heldItems[itemType];
|
||||
return item ? item.stack >= allHeldItems[itemType].getMaxStackCount() : false;
|
||||
}
|
||||
|
||||
overrideItems(newItems: HeldItemDataMap) {
|
||||
this.heldItems = newItems;
|
||||
// The following is to allow randomly generated item configs to have stack 0
|
||||
for (const [item, properties] of getTypedEntries(this.heldItems)) {
|
||||
if (!properties || properties.stack <= 0) {
|
||||
delete this.heldItems[item];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(itemType: HeldItemId | HeldItemSpecs, addStack = 1): boolean {
|
||||
if (isHeldItemSpecs(itemType)) {
|
||||
return this.addItemWithSpecs(itemType);
|
||||
}
|
||||
|
||||
const maxStack = allHeldItems[itemType].getMaxStackCount();
|
||||
const item = this.heldItems[itemType];
|
||||
|
||||
if (item) {
|
||||
// TODO: We may want an error message of some kind instead
|
||||
if (item.stack < maxStack) {
|
||||
item.stack = Math.min(item.stack + addStack, maxStack);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
this.heldItems[itemType] = { stack: Math.min(addStack, maxStack) };
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
addItemWithSpecs(itemSpecs: HeldItemSpecs): boolean {
|
||||
const id = itemSpecs.id;
|
||||
const maxStack = allHeldItems[id].getMaxStackCount();
|
||||
const item = this.heldItems[id];
|
||||
|
||||
const tempStack = item?.stack ?? 0;
|
||||
|
||||
this.heldItems[id] = itemSpecs;
|
||||
this.heldItems[id].stack = Math.min(itemSpecs.stack + tempStack, maxStack);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
remove(itemType: HeldItemId, removeStack = 1, all = false) {
|
||||
const item = this.heldItems[itemType];
|
||||
|
||||
if (item) {
|
||||
item.stack -= removeStack;
|
||||
|
||||
if (all || item.stack <= 0) {
|
||||
delete this.heldItems[itemType];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterRequestedItems(requestedItems: (HeldItemCategoryId | HeldItemId)[], transferableOnly = true, exclude = false) {
|
||||
const currentItems = transferableOnly ? this.getTransferableHeldItems() : this.getHeldItems();
|
||||
return currentItems.filter(it => !exclude && isItemInRequested(it, requestedItems));
|
||||
}
|
||||
|
||||
getHeldItemCount(): number {
|
||||
let total = 0;
|
||||
for (const properties of Object.values(this.heldItems)) {
|
||||
total += properties?.stack ?? 0;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
addFormChangeItem(id: FormChangeItem) {
|
||||
if (!(id in this.formChangeItems)) {
|
||||
this.formChangeItems[id] = { active: false };
|
||||
}
|
||||
}
|
||||
|
||||
addFormChangeItemWithSpecs(item: FormChangeItemSpecs) {
|
||||
if (!(item.id in this.formChangeItems)) {
|
||||
this.formChangeItems[item.id] = { active: item.active };
|
||||
}
|
||||
}
|
||||
|
||||
hasFormChangeItem(id: FormChangeItem): boolean {
|
||||
return id in this.formChangeItems;
|
||||
}
|
||||
|
||||
hasActiveFormChangeItem(id: FormChangeItem): boolean {
|
||||
const item = this.formChangeItems[id];
|
||||
if (item) {
|
||||
return item.active;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getFormChangeItems(): FormChangeItem[] {
|
||||
return getTypedKeys(this.formChangeItems).map(k => k);
|
||||
}
|
||||
|
||||
getActiveFormChangeItems(): FormChangeItem[] {
|
||||
return this.getFormChangeItems().filter(m => this.formChangeItems[m]?.active);
|
||||
}
|
||||
|
||||
toggleActive(id: FormChangeItem) {
|
||||
const item = this.formChangeItems[id];
|
||||
if (item) {
|
||||
item.active = !item.active;
|
||||
}
|
||||
}
|
||||
|
||||
clearItems() {
|
||||
this.heldItems = {};
|
||||
this.formChangeItems = {};
|
||||
}
|
||||
}
|
324
src/items/held-item-pool.ts
Normal file
324
src/items/held-item-pool.ts
Normal file
@ -0,0 +1,324 @@
|
||||
import { allHeldItems } from "#data/data-lists";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { HeldItemCategoryId, HeldItemId, HeldItemNames, isCategoryId } from "#enums/held-item-id";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import { HeldItemPoolType } from "#enums/reward-pool-type";
|
||||
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 { berryTypeToHeldItem } from "#items/berry";
|
||||
import {
|
||||
type HeldItemConfiguration,
|
||||
type HeldItemPool,
|
||||
type HeldItemSaveData,
|
||||
type HeldItemSpecs,
|
||||
type HeldItemTieredPool,
|
||||
type HeldItemWeights,
|
||||
isFormChangeItemSpecs,
|
||||
isHeldItemCategoryEntry,
|
||||
isHeldItemPool,
|
||||
isHeldItemSpecs,
|
||||
} from "#items/held-item-data-types";
|
||||
import { coerceArray, isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
|
||||
export const wildHeldItemPool: HeldItemTieredPool = {};
|
||||
|
||||
export const trainerHeldItemPool: HeldItemTieredPool = {};
|
||||
|
||||
export const dailyStarterHeldItemPool: HeldItemTieredPool = {};
|
||||
|
||||
export function assignDailyRunStarterHeldItems(party: PlayerPokemon[]) {
|
||||
for (const p of party) {
|
||||
for (let m = 0; m < 3; m++) {
|
||||
const tierValue = randSeedInt(64);
|
||||
|
||||
const tier = getDailyRarityTier(tierValue);
|
||||
|
||||
const item = getNewHeldItemFromPool(
|
||||
getHeldItemPool(HeldItemPoolType.DAILY_STARTER)[tier] as HeldItemPool,
|
||||
p,
|
||||
party,
|
||||
);
|
||||
p.heldItemManager.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDailyRarityTier(tierValue: number): RarityTier {
|
||||
if (tierValue > 25) {
|
||||
return RarityTier.COMMON;
|
||||
}
|
||||
if (tierValue > 12) {
|
||||
return RarityTier.GREAT;
|
||||
}
|
||||
if (tierValue > 4) {
|
||||
return RarityTier.ULTRA;
|
||||
}
|
||||
if (tierValue > 0) {
|
||||
return RarityTier.ROGUE;
|
||||
}
|
||||
return RarityTier.MASTER;
|
||||
}
|
||||
|
||||
function getHeldItemPool(poolType: HeldItemPoolType): HeldItemTieredPool {
|
||||
let pool: HeldItemTieredPool;
|
||||
switch (poolType) {
|
||||
case HeldItemPoolType.WILD:
|
||||
pool = wildHeldItemPool;
|
||||
break;
|
||||
case HeldItemPoolType.TRAINER:
|
||||
pool = trainerHeldItemPool;
|
||||
break;
|
||||
case HeldItemPoolType.DAILY_STARTER:
|
||||
pool = dailyStarterHeldItemPool;
|
||||
break;
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
// TODO: Add proper documentation to this function (once it fully works...)
|
||||
export function assignEnemyHeldItemsForWave(
|
||||
waveIndex: number,
|
||||
count: number,
|
||||
enemy: EnemyPokemon,
|
||||
poolType: HeldItemPoolType.WILD | HeldItemPoolType.TRAINER,
|
||||
upgradeChance = 0,
|
||||
): void {
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const item = getNewHeldItemFromTieredPool(
|
||||
getHeldItemPool(poolType),
|
||||
enemy,
|
||||
upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0,
|
||||
);
|
||||
if (item) {
|
||||
enemy.heldItemManager.add(item);
|
||||
}
|
||||
}
|
||||
if (!(waveIndex % 1000)) {
|
||||
enemy.heldItemManager.add(HeldItemId.MINI_BLACK_HOLE);
|
||||
}
|
||||
}
|
||||
|
||||
function getRandomTier(): RarityTier {
|
||||
const tierValue = randSeedInt(1024);
|
||||
|
||||
if (tierValue > 255) {
|
||||
return RarityTier.COMMON;
|
||||
}
|
||||
if (tierValue > 60) {
|
||||
return RarityTier.GREAT;
|
||||
}
|
||||
if (tierValue > 12) {
|
||||
return RarityTier.ULTRA;
|
||||
}
|
||||
if (tierValue) {
|
||||
return RarityTier.ROGUE;
|
||||
}
|
||||
return RarityTier.MASTER;
|
||||
}
|
||||
|
||||
function determineItemPoolTier(pool: HeldItemTieredPool, upgradeCount?: number): RarityTier {
|
||||
let tier = getRandomTier();
|
||||
|
||||
if (!upgradeCount) {
|
||||
upgradeCount = 0;
|
||||
}
|
||||
|
||||
tier += upgradeCount;
|
||||
while (tier && !pool[tier]?.length) {
|
||||
tier--;
|
||||
if (upgradeCount) {
|
||||
upgradeCount--;
|
||||
}
|
||||
}
|
||||
|
||||
return tier;
|
||||
}
|
||||
|
||||
function getNewHeldItemFromTieredPool(
|
||||
pool: HeldItemTieredPool,
|
||||
pokemon: Pokemon,
|
||||
upgradeCount: number,
|
||||
): HeldItemId | HeldItemSpecs {
|
||||
const tier = determineItemPoolTier(pool, upgradeCount);
|
||||
const tierPool = pool[tier];
|
||||
|
||||
return getNewHeldItemFromPool(tierPool!, pokemon);
|
||||
}
|
||||
|
||||
export function getNewVitaminHeldItem(customWeights: HeldItemWeights = {}, target?: Pokemon): HeldItemId {
|
||||
const items = PERMANENT_STATS.map(s => permanentStatToHeldItem[s]);
|
||||
const weights = items.map(t => (target?.heldItemManager.isMaxStack(t) ? 0 : (customWeights[t] ?? 1)));
|
||||
const pickedIndex = pickWeightedIndex(weights);
|
||||
return !isNullOrUndefined(pickedIndex) ? items[pickedIndex] : 0;
|
||||
}
|
||||
|
||||
export function getNewBerryHeldItem(customWeights: HeldItemWeights = {}, target?: Pokemon): HeldItemId {
|
||||
const berryTypes = getEnumValues(BerryType);
|
||||
const items = berryTypes.map(b => berryTypeToHeldItem[b]);
|
||||
|
||||
const weights = items.map(t =>
|
||||
target?.heldItemManager.isMaxStack(t)
|
||||
? 0
|
||||
: (customWeights[t] ??
|
||||
(t === HeldItemId.SITRUS_BERRY || t === HeldItemId.LUM_BERRY || t === HeldItemId.LEPPA_BERRY))
|
||||
? 2
|
||||
: 1,
|
||||
);
|
||||
|
||||
const pickedIndex = pickWeightedIndex(weights);
|
||||
return !isNullOrUndefined(pickedIndex) ? items[pickedIndex] : 0;
|
||||
}
|
||||
|
||||
export function getNewAttackTypeBoosterHeldItem(
|
||||
pokemon: Pokemon | Pokemon[],
|
||||
customWeights: HeldItemWeights = {},
|
||||
target?: Pokemon,
|
||||
): HeldItemId | null {
|
||||
const party = coerceArray(pokemon);
|
||||
|
||||
// TODO: make this consider moves or abilities that change types
|
||||
const attackMoveTypes = party.flatMap(p =>
|
||||
p
|
||||
.getMoveset()
|
||||
.filter(m => m.getMove().is("AttackMove"))
|
||||
.map(m => p.getMoveType(m.getMove(), true)),
|
||||
);
|
||||
if (!attackMoveTypes.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const attackMoveTypeWeights = attackMoveTypes.reduce((map, type) => {
|
||||
const current = map.get(type) ?? 0;
|
||||
if (current < 3) {
|
||||
map.set(type, current + 1);
|
||||
}
|
||||
return map;
|
||||
}, new Map<PokemonType, number>());
|
||||
|
||||
const types = Array.from(attackMoveTypeWeights.keys());
|
||||
|
||||
const weights = types.map(type =>
|
||||
target?.heldItemManager.isMaxStack(attackTypeToHeldItem[type])
|
||||
? 0
|
||||
: (customWeights[attackTypeToHeldItem[type]] ?? attackMoveTypeWeights.get(type)!),
|
||||
);
|
||||
|
||||
const pickedIndex = pickWeightedIndex(weights);
|
||||
return !isNullOrUndefined(pickedIndex) ? attackTypeToHeldItem[types[pickedIndex]] : 0;
|
||||
}
|
||||
|
||||
export function getNewHeldItemFromCategory(
|
||||
id: HeldItemCategoryId,
|
||||
pokemon: Pokemon | Pokemon[],
|
||||
customWeights: HeldItemWeights = {},
|
||||
target?: Pokemon,
|
||||
): HeldItemId | null {
|
||||
if (id === HeldItemCategoryId.BERRY) {
|
||||
return getNewBerryHeldItem(customWeights, target);
|
||||
}
|
||||
if (id === HeldItemCategoryId.VITAMIN) {
|
||||
return getNewVitaminHeldItem(customWeights, target);
|
||||
}
|
||||
if (id === HeldItemCategoryId.TYPE_ATTACK_BOOSTER) {
|
||||
return getNewAttackTypeBoosterHeldItem(pokemon, customWeights, target);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getPoolWeights(pool: HeldItemPool, pokemon: Pokemon): number[] {
|
||||
return pool.map(p => {
|
||||
let weight = typeof p.weight === "function" ? p.weight(coerceArray(pokemon)) : p.weight;
|
||||
|
||||
if (typeof p.entry === "number" && !isCategoryId(p.entry)) {
|
||||
const itemId = p.entry as HeldItemId;
|
||||
console.log("ITEM ID: ", itemId, HeldItemNames[itemId]);
|
||||
console.log(allHeldItems[itemId]);
|
||||
|
||||
if (pokemon.heldItemManager.getStack(itemId) >= allHeldItems[itemId].getMaxStackCount()) {
|
||||
weight = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return weight;
|
||||
});
|
||||
}
|
||||
|
||||
function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon, party?: Pokemon[]): HeldItemId | HeldItemSpecs {
|
||||
const weights = getPoolWeights(pool, pokemon);
|
||||
|
||||
const pickedIndex = pickWeightedIndex(weights);
|
||||
if (isNullOrUndefined(pickedIndex)) {
|
||||
return 0;
|
||||
}
|
||||
const entry = pool[pickedIndex].entry;
|
||||
|
||||
if (typeof entry === "number") {
|
||||
if (isCategoryId(entry)) {
|
||||
return getNewHeldItemFromCategory(entry, party ?? pokemon, {}, pokemon) as HeldItemId;
|
||||
}
|
||||
return entry as HeldItemId;
|
||||
}
|
||||
|
||||
if (isHeldItemCategoryEntry(entry)) {
|
||||
return getNewHeldItemFromCategory(entry.id, party ?? pokemon, entry?.customWeights, pokemon) as HeldItemId;
|
||||
}
|
||||
|
||||
return entry as HeldItemSpecs;
|
||||
}
|
||||
|
||||
function assignItemsFromCategory(id: HeldItemCategoryId, pokemon: Pokemon, count: number) {
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const newItem = getNewHeldItemFromCategory(id, pokemon, {}, pokemon);
|
||||
if (newItem) {
|
||||
pokemon.heldItemManager.add(newItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function assignItemsFromConfiguration(config: HeldItemConfiguration, pokemon: Pokemon) {
|
||||
config.forEach(item => {
|
||||
const { entry, count } = item;
|
||||
const actualCount = typeof count === "function" ? count() : (count ?? 1);
|
||||
|
||||
if (typeof entry === "number") {
|
||||
if (isCategoryId(entry)) {
|
||||
assignItemsFromCategory(entry, pokemon, actualCount);
|
||||
}
|
||||
pokemon.heldItemManager.add(entry as HeldItemId, actualCount);
|
||||
}
|
||||
|
||||
if (isHeldItemSpecs(entry)) {
|
||||
pokemon.heldItemManager.add(entry);
|
||||
}
|
||||
|
||||
if (isFormChangeItemSpecs(entry)) {
|
||||
pokemon.heldItemManager.addFormChangeItemWithSpecs(entry);
|
||||
}
|
||||
|
||||
if (isHeldItemCategoryEntry(entry)) {
|
||||
assignItemsFromCategory(entry.id, pokemon, actualCount);
|
||||
}
|
||||
|
||||
if (isHeldItemPool(entry)) {
|
||||
for (let i = 1; i <= actualCount; i++) {
|
||||
const newItem = getNewHeldItemFromPool(entry, pokemon);
|
||||
if (newItem) {
|
||||
pokemon.heldItemManager.add(newItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Handle form change items
|
||||
export function saveDataToConfig(saveData: HeldItemSaveData): HeldItemConfiguration {
|
||||
const config: HeldItemConfiguration = [];
|
||||
for (const specs of saveData) {
|
||||
config.push({ entry: specs, count: 1 });
|
||||
}
|
||||
return config;
|
||||
}
|
139
src/items/held-item.ts
Normal file
139
src/items/held-item.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { applyAbAttrs } from "#abilities/apply-ab-attrs";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { type HeldItemId, HeldItemNames } from "#enums/held-item-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import i18next from "i18next";
|
||||
|
||||
export class HeldItem {
|
||||
// public pokemonId: number;
|
||||
// TODO: Should this be readonly?
|
||||
public type: HeldItemId;
|
||||
public readonly maxStackCount: number;
|
||||
public isTransferable = true;
|
||||
public isStealable = true;
|
||||
public isSuppressable = true;
|
||||
|
||||
//TODO: If this is actually never changed by any subclass, perhaps it should not be here
|
||||
public soundName = "se/restore";
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount = 1) {
|
||||
this.type = type;
|
||||
this.maxStackCount = maxStackCount;
|
||||
|
||||
this.isTransferable = true;
|
||||
this.isStealable = true;
|
||||
this.isSuppressable = true;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return i18next.t(`modifierType:ModifierType.${HeldItemNames[this.type]}.name`);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t(`modifierType:ModifierType.${HeldItemNames[this.type]}.description`);
|
||||
}
|
||||
|
||||
get iconName(): string {
|
||||
return `${HeldItemNames[this.type]?.toLowerCase()}`;
|
||||
}
|
||||
|
||||
// TODO: Aren't these fine as just properties to set in the subclass definition?
|
||||
untransferable(): HeldItem {
|
||||
this.isTransferable = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
unstealable(): HeldItem {
|
||||
this.isStealable = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
unsuppressable(): HeldItem {
|
||||
this.isSuppressable = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2114950716
|
||||
getMaxStackCount(): number {
|
||||
return this.maxStackCount;
|
||||
}
|
||||
|
||||
createSummaryIcon(pokemon?: Pokemon, overrideStackCount?: number): Phaser.GameObjects.Container {
|
||||
const stackCount = overrideStackCount ?? (pokemon ? this.getStackCount(pokemon) : 0);
|
||||
|
||||
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);
|
||||
|
||||
const stackText = this.getIconStackText(stackCount);
|
||||
if (stackText) {
|
||||
container.add(stackText);
|
||||
}
|
||||
|
||||
container.setScale(0.5);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
createPokemonIcon(pokemon: Pokemon): Phaser.GameObjects.Container {
|
||||
const container = globalScene.add.container(0, 0);
|
||||
|
||||
const pokemonIcon = globalScene.addPokemonIcon(pokemon, -2, 10, 0, 0.5, undefined, true);
|
||||
container.add(pokemonIcon);
|
||||
container.setName(pokemon.id.toString());
|
||||
|
||||
const item = globalScene.add
|
||||
.sprite(16, 16, "items")
|
||||
.setScale(0.5)
|
||||
.setOrigin(0, 0.5)
|
||||
.setTexture("items", this.iconName);
|
||||
container.add(item);
|
||||
|
||||
const stackText = this.getIconStackText(this.getStackCount(pokemon));
|
||||
if (stackText) {
|
||||
container.add(stackText);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
getIconStackText(stackCount: number): Phaser.GameObjects.BitmapText | null {
|
||||
if (this.getMaxStackCount() === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const text = globalScene.add.bitmapText(10, 15, "item-count", stackCount.toString(), 11);
|
||||
text.letterSpacing = -0.5;
|
||||
if (stackCount >= this.getMaxStackCount()) {
|
||||
// TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2114955458
|
||||
text.setTint(0xf89890);
|
||||
}
|
||||
text.setOrigin(0);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
getStackCount(pokemon: Pokemon): number {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
return stackCount;
|
||||
}
|
||||
|
||||
getScoreMultiplier(): number {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConsumableHeldItem extends HeldItem {
|
||||
// Sometimes berries are not eaten, some stuff may not proc unburden...
|
||||
consume(pokemon: Pokemon, isPlayer: boolean, remove = true, unburden = true): void {
|
||||
if (remove) {
|
||||
pokemon.heldItemManager.remove(this.type, 1);
|
||||
// TODO: Turn this into updateItemBar or something
|
||||
globalScene.updateItems(isPlayer);
|
||||
}
|
||||
if (unburden) {
|
||||
applyAbAttrs("PostItemLostAbAttr", { pokemon: pokemon });
|
||||
}
|
||||
}
|
||||
}
|
46
src/items/held-items/accuracy-booster.ts
Normal file
46
src/items/held-items/accuracy-booster.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { NumberHolder } from "#utils/common";
|
||||
|
||||
export interface AccuracyBoostParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** Holds the move's accuracy, which may be modified after item application */
|
||||
moveAccuracy: NumberHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @sealed
|
||||
*/
|
||||
export class AccuracyBoosterHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.ACCURACY_BOOSTER];
|
||||
|
||||
private accuracyAmount: number;
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount: number, accuracy: number) {
|
||||
super(type, maxStackCount);
|
||||
this.accuracyAmount = accuracy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if {@linkcode PokemonMoveAccuracyBoosterHeldItem} should be applied
|
||||
* @param pokemon - The {@linkcode Pokemon} to apply the move accuracy boost to
|
||||
* @param moveAccuracy - {@linkcode NumberHolder} holding the move accuracy boost
|
||||
* @returns `true` if {@linkcode PokemonMoveAccuracyBoosterHeldItem} should be applied
|
||||
*/
|
||||
// override shouldApply(pokemon?: Pokemon, moveAccuracy?: NumberHolder): boolean {
|
||||
// return super.shouldApply(pokemon, moveAccuracy) && !!moveAccuracy;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Applies {@linkcode PokemonMoveAccuracyBoosterHeldItem}
|
||||
*/
|
||||
apply({ pokemon, moveAccuracy }: AccuracyBoostParams): true {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
moveAccuracy.value += this.accuracyAmount * stackCount;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
76
src/items/held-items/attack-type-booster.ts
Normal file
76
src/items/held-items/attack-type-booster.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { HeldItemId, HeldItemNames } from "#enums/held-item-id";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { NumberHolder } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface AttackTypeBoostParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** The resolved type of the move */
|
||||
moveType: PokemonType;
|
||||
/** Holder for the damage value */
|
||||
// TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2119660807
|
||||
movePower: NumberHolder;
|
||||
}
|
||||
|
||||
interface AttackTypeToHeldItemMap {
|
||||
[key: number]: HeldItemId;
|
||||
}
|
||||
|
||||
export const attackTypeToHeldItem: AttackTypeToHeldItemMap = {
|
||||
[PokemonType.NORMAL]: HeldItemId.SILK_SCARF,
|
||||
[PokemonType.FIGHTING]: HeldItemId.BLACK_BELT,
|
||||
[PokemonType.FLYING]: HeldItemId.SHARP_BEAK,
|
||||
[PokemonType.POISON]: HeldItemId.POISON_BARB,
|
||||
[PokemonType.GROUND]: HeldItemId.SOFT_SAND,
|
||||
[PokemonType.ROCK]: HeldItemId.HARD_STONE,
|
||||
[PokemonType.BUG]: HeldItemId.SILVER_POWDER,
|
||||
[PokemonType.GHOST]: HeldItemId.SPELL_TAG,
|
||||
[PokemonType.STEEL]: HeldItemId.METAL_COAT,
|
||||
[PokemonType.FIRE]: HeldItemId.CHARCOAL,
|
||||
[PokemonType.WATER]: HeldItemId.MYSTIC_WATER,
|
||||
[PokemonType.GRASS]: HeldItemId.MIRACLE_SEED,
|
||||
[PokemonType.ELECTRIC]: HeldItemId.MAGNET,
|
||||
[PokemonType.PSYCHIC]: HeldItemId.TWISTED_SPOON,
|
||||
[PokemonType.ICE]: HeldItemId.NEVER_MELT_ICE,
|
||||
[PokemonType.DRAGON]: HeldItemId.DRAGON_FANG,
|
||||
[PokemonType.DARK]: HeldItemId.BLACK_GLASSES,
|
||||
[PokemonType.FAIRY]: HeldItemId.FAIRY_FEATHER,
|
||||
};
|
||||
|
||||
export class AttackTypeBoosterHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_HEAL];
|
||||
public moveType: PokemonType;
|
||||
public powerBoost: number;
|
||||
|
||||
// This constructor may need a revision
|
||||
constructor(type: HeldItemId, maxStackCount: number, moveType: PokemonType, powerBoost: number) {
|
||||
super(type, maxStackCount);
|
||||
this.moveType = moveType;
|
||||
this.powerBoost = powerBoost;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return i18next.t(`modifierType:AttackTypeBoosterItem.${HeldItemNames[this.type]?.toLowerCase()}`);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", {
|
||||
moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`),
|
||||
});
|
||||
}
|
||||
|
||||
get iconName(): string {
|
||||
return `${HeldItemNames[this.type]?.toLowerCase()}`;
|
||||
}
|
||||
|
||||
apply({ pokemon, moveType, movePower }: AttackTypeBoostParams): void {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
if (moveType === this.moveType && movePower.value >= 1) {
|
||||
movePower.value = Math.floor(movePower.value * (1 + stackCount * this.powerBoost));
|
||||
}
|
||||
}
|
||||
}
|
78
src/items/held-items/base-stat-booster.ts
Normal file
78
src/items/held-items/base-stat-booster.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { getStatKey, type PermanentStat, Stat } from "#enums/stat";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface BaseStatBoosterParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** The base stats of the {@linkcode pokemon} */
|
||||
baseStats: number[];
|
||||
}
|
||||
|
||||
type PermanentStatToHeldItemMap = {
|
||||
[key in PermanentStat]: HeldItemId;
|
||||
};
|
||||
|
||||
export const permanentStatToHeldItem: PermanentStatToHeldItemMap = {
|
||||
[Stat.HP]: HeldItemId.HP_UP,
|
||||
[Stat.ATK]: HeldItemId.PROTEIN,
|
||||
[Stat.DEF]: HeldItemId.IRON,
|
||||
[Stat.SPATK]: HeldItemId.CALCIUM,
|
||||
[Stat.SPDEF]: HeldItemId.ZINC,
|
||||
[Stat.SPD]: HeldItemId.CARBOS,
|
||||
};
|
||||
|
||||
export const statBoostItems: Record<PermanentStat, string> = {
|
||||
[Stat.HP]: "hp_up",
|
||||
[Stat.ATK]: "protein",
|
||||
[Stat.DEF]: "iron",
|
||||
[Stat.SPATK]: "calcium",
|
||||
[Stat.SPDEF]: "zinc",
|
||||
[Stat.SPD]: "carbos",
|
||||
};
|
||||
|
||||
export class BaseStatBoosterHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.BASE_STAT_BOOSTER];
|
||||
public stat: PermanentStat;
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount: number, stat: PermanentStat) {
|
||||
super(type, maxStackCount);
|
||||
this.stat = stat;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return i18next.t(`modifierType:BaseStatBoosterItem.${statBoostItems[this.stat]}`);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.BaseStatBoosterModifierType.description", {
|
||||
stat: i18next.t(getStatKey(this.stat)),
|
||||
});
|
||||
}
|
||||
|
||||
get iconName(): string {
|
||||
return statBoostItems[this.stat];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if {@linkcode BaseStatModifier} should be applied to the specified {@linkcode Pokemon}.
|
||||
* @param _pokemon - The {@linkcode Pokemon} to be modified
|
||||
* @param baseStats - The base stats of the {@linkcode Pokemon}
|
||||
* @returns `true` if the {@linkcode Pokemon} should be modified
|
||||
*/
|
||||
// override shouldApply(_pokemon?: Pokemon, baseStats?: number[]): boolean {
|
||||
// return super.shouldApply(_pokemon, baseStats) && Array.isArray(baseStats);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Applies the {@linkcode BaseStatModifier} to the specified {@linkcode Pokemon}.
|
||||
*/
|
||||
apply({ pokemon, baseStats }: BaseStatBoosterParams): boolean {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
baseStats[this.stat] = Math.floor(baseStats[this.stat] * (1 + stackCount * 0.1));
|
||||
return true;
|
||||
}
|
||||
}
|
64
src/items/held-items/base-stat-flat.ts
Normal file
64
src/items/held-items/base-stat-flat.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { Stat } from "#enums/stat";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface BaseStatFlatParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** The amount of exp to gain */
|
||||
baseStats: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently used by Old Gateau item
|
||||
*/
|
||||
export class BaseStatFlatHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.BASE_STAT_FLAT];
|
||||
public isTransferable = false;
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.PokemonBaseStatFlatModifierType.description");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@linkcode PokemonBaseStatFlatModifier} should be applied to the {@linkcode Pokemon}.
|
||||
* @param pokemon The {@linkcode Pokemon} that holds the item
|
||||
* @param baseStats The base stats of the {@linkcode Pokemon}
|
||||
* @returns `true` if the {@linkcode PokemonBaseStatFlatModifier} should be applied
|
||||
*/
|
||||
// override shouldApply(pokemon?: Pokemon, baseStats?: number[]): boolean {
|
||||
// return super.shouldApply(pokemon, baseStats) && Array.isArray(baseStats);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Applies the {@linkcode PokemonBaseStatFlatModifier}
|
||||
*/
|
||||
apply({ pokemon, baseStats }: BaseStatFlatParams): true {
|
||||
const stats = this.getStats(pokemon);
|
||||
const statModifier = 20;
|
||||
// Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats
|
||||
baseStats.forEach((v, i) => {
|
||||
if (stats.includes(i)) {
|
||||
const newVal = Math.floor(v + statModifier);
|
||||
baseStats[i] = Math.min(Math.max(newVal, 1), 999999);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lowest of HP/Spd, lowest of Atk/SpAtk, and lowest of Def/SpDef
|
||||
* @returns Array of 3 {@linkcode Stat}s to boost
|
||||
*/
|
||||
getStats(pokemon: Pokemon): [HpOrSpeed: Stat, AtkOrSpAtk: Stat, DefOrSpDef: Stat] {
|
||||
const baseStats = pokemon.getSpeciesForm().baseStats.slice(0);
|
||||
return [
|
||||
baseStats[Stat.HP] < baseStats[Stat.SPD] ? Stat.HP : Stat.SPD,
|
||||
baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK,
|
||||
baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF,
|
||||
];
|
||||
}
|
||||
}
|
70
src/items/held-items/base-stat-total.ts
Normal file
70
src/items/held-items/base-stat-total.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface BaseStatTotalParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** Array of the pokemon's base stat; modified in place after item application */
|
||||
baseStats: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently used by Shuckle Juice item
|
||||
*/
|
||||
export class BaseStatTotalHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.BASE_STAT_TOTAL];
|
||||
public isTransferable = false;
|
||||
public statModifier: number;
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount: number, statModifier: number) {
|
||||
super(type, maxStackCount);
|
||||
this.statModifier = statModifier;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.statModifier > 0
|
||||
? i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD.name")
|
||||
: i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD.name");
|
||||
}
|
||||
|
||||
// TODO: where is this description shown?
|
||||
get description(): string {
|
||||
return this.statModifier > 0
|
||||
? i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD.description")
|
||||
: i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD.description");
|
||||
}
|
||||
|
||||
get iconName(): string {
|
||||
return this.statModifier > 0 ? "berry_juice_good" : "berry_juice_bad";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if {@linkcode PokemonBaseStatTotalModifier} should be applied to the specified {@linkcode Pokemon}.
|
||||
* @param pokemon the {@linkcode Pokemon} to be modified
|
||||
* @param baseStats the base stats of the {@linkcode Pokemon}
|
||||
* @returns `true` if the {@linkcode Pokemon} should be modified
|
||||
*/
|
||||
// override shouldApply(pokemon?: Pokemon, baseStats?: number[]): boolean {
|
||||
// return super.shouldApply(pokemon, baseStats) && Array.isArray(baseStats);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Applies the {@linkcode PokemonBaseStatTotalModifier}
|
||||
* @param _pokemon the {@linkcode Pokemon} to be modified
|
||||
* @param baseStats the base stats of the {@linkcode Pokemon}
|
||||
* @returns always `true`
|
||||
*/
|
||||
apply({ baseStats }: BaseStatTotalParams): true {
|
||||
// Modifies the passed in baseStats[] array
|
||||
baseStats.forEach((v, i) => {
|
||||
// HP is affected by half as much as other stats
|
||||
const newVal = i === 0 ? Math.floor(v + this.statModifier / 2) : Math.floor(v + this.statModifier);
|
||||
baseStats[i] = Math.min(Math.max(newVal, 1), 999999);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
23
src/items/held-items/baton.ts
Normal file
23
src/items/held-items/baton.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { NumberHolder } from "#utils/common";
|
||||
|
||||
export interface BatonParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** The amount of exp to gain */
|
||||
expAmount: NumberHolder;
|
||||
}
|
||||
|
||||
export class BatonHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.BATON];
|
||||
|
||||
/**
|
||||
* Applies {@linkcode SwitchEffectTransferModifier}
|
||||
* @returns always `true`
|
||||
*/
|
||||
apply(): true {
|
||||
return true;
|
||||
}
|
||||
}
|
96
src/items/held-items/berry.ts
Normal file
96
src/items/held-items/berry.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getBerryEffectDescription, getBerryEffectFunc, getBerryName, getBerryPredicate } from "#data/berry";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { BerryUsedEvent } from "#events/battle-scene";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { ConsumableHeldItem } from "#items/held-item";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import type { ObjectValues } from "#types/type-helpers";
|
||||
import { BooleanHolder } from "#utils/common";
|
||||
|
||||
type BerryTypeToHeldItemMap = {
|
||||
[key in ObjectValues<typeof BerryType>]: HeldItemId;
|
||||
};
|
||||
|
||||
// TODO: Rework this to use a bitwise XOR
|
||||
export const berryTypeToHeldItem = {
|
||||
[BerryType.SITRUS]: HeldItemId.SITRUS_BERRY,
|
||||
[BerryType.LUM]: HeldItemId.LUM_BERRY,
|
||||
[BerryType.ENIGMA]: HeldItemId.ENIGMA_BERRY,
|
||||
[BerryType.LIECHI]: HeldItemId.LIECHI_BERRY,
|
||||
[BerryType.GANLON]: HeldItemId.GANLON_BERRY,
|
||||
[BerryType.PETAYA]: HeldItemId.PETAYA_BERRY,
|
||||
[BerryType.APICOT]: HeldItemId.APICOT_BERRY,
|
||||
[BerryType.SALAC]: HeldItemId.SALAC_BERRY,
|
||||
[BerryType.LANSAT]: HeldItemId.LANSAT_BERRY,
|
||||
[BerryType.STARF]: HeldItemId.STARF_BERRY,
|
||||
[BerryType.LEPPA]: HeldItemId.LEPPA_BERRY,
|
||||
} satisfies BerryTypeToHeldItemMap;
|
||||
|
||||
export interface BerryParams {
|
||||
/** The pokemon with the berry */
|
||||
pokemon: Pokemon;
|
||||
}
|
||||
|
||||
// TODO: Maybe split up into subclasses?
|
||||
export class BerryHeldItem extends ConsumableHeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.BERRY];
|
||||
public berryType: BerryType;
|
||||
|
||||
constructor(berryType: BerryType, maxStackCount = 1) {
|
||||
const type = berryTypeToHeldItem[berryType];
|
||||
super(type, maxStackCount);
|
||||
|
||||
this.berryType = berryType;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return getBerryName(this.berryType);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return getBerryEffectDescription(this.berryType);
|
||||
}
|
||||
|
||||
get iconName(): string {
|
||||
return `${BerryType[this.berryType].toLowerCase()}_berry`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if {@linkcode BerryModifier} should be applied
|
||||
* @param pokemon The {@linkcode Pokemon} that holds the berry
|
||||
* @returns `true` if {@linkcode BerryModifier} should be applied
|
||||
*/
|
||||
shouldApply(pokemon: Pokemon): boolean {
|
||||
return getBerryPredicate(this.berryType)(pokemon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies {@linkcode BerryHeldItem}
|
||||
* @returns always `true`
|
||||
*/
|
||||
apply({ pokemon }: BerryParams): boolean {
|
||||
if (!this.shouldApply(pokemon)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const preserve = new BooleanHolder(false);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.PRESERVE_BERRY, { pokemon: pokemon, doPreserve: preserve });
|
||||
const consumed = !preserve.value;
|
||||
|
||||
// munch the berry and trigger unburden-like effects
|
||||
getBerryEffectFunc(this.berryType)(pokemon);
|
||||
this.consume(pokemon, pokemon.isPlayer(), consumed);
|
||||
|
||||
// TODO: Update this method to work with held items
|
||||
// Update berry eaten trackers for Belch, Harvest, Cud Chew, etc.
|
||||
// Don't recover it if we proc berry pouch (no item duplication)
|
||||
pokemon.recordEatenBerry(this.berryType, consumed);
|
||||
|
||||
globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(pokemon, this.berryType));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
57
src/items/held-items/bypass-speed-chance.ts
Normal file
57
src/items/held-items/bypass-speed-chance.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { Command } from "#enums/command";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { BooleanHolder } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface BypassSpeedChanceParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** Holder for whether the speed should be bypassed */
|
||||
doBypassSpeed: BooleanHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for items that allow a Pokémon to bypass the speed chance (Quick Claw).
|
||||
*/
|
||||
export class BypassSpeedChanceHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.BYPASS_SPEED_CHANCE];
|
||||
|
||||
/**
|
||||
* Checks if {@linkcode BypassSpeedChanceModifier} should be applied
|
||||
* @param pokemon the {@linkcode Pokemon} that holds the item
|
||||
* @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed
|
||||
* @returns `true` if {@linkcode BypassSpeedChanceModifier} should be applied
|
||||
*/
|
||||
// override shouldApply(pokemon?: Pokemon, doBypassSpeed?: BooleanHolder): boolean {
|
||||
// return super.shouldApply(pokemon, doBypassSpeed) && !!doBypassSpeed;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Applies {@linkcode BypassSpeedChanceModifier}
|
||||
* @returns `true` if {@linkcode BypassSpeedChanceModifier} has been applied
|
||||
*/
|
||||
apply({ pokemon, doBypassSpeed }: BypassSpeedChanceParams): boolean {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
if (!doBypassSpeed.value && pokemon.randBattleSeedInt(10) < stackCount) {
|
||||
doBypassSpeed.value = true;
|
||||
const isCommandFight =
|
||||
globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT;
|
||||
|
||||
if (isCommandFight) {
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("modifier:bypassSpeedChanceApply", {
|
||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||
itemName: this.name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
88
src/items/held-items/crit-booster.ts
Normal file
88
src/items/held-items/crit-booster.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { NumberHolder } from "#utils/common";
|
||||
|
||||
export interface CritBoostParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** The critical hit stage */
|
||||
critStage: NumberHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for held items that apply critical-hit stage boost(s).
|
||||
* using a multiplier.
|
||||
* @extends PokemonHeldItemModifier
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class CritBoostHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.CRIT_BOOST];
|
||||
|
||||
/** The amount of stages by which the held item increases the current critical-hit stage value */
|
||||
protected stageIncrement: number;
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount: number, stageIncrement: number) {
|
||||
super(type, maxStackCount);
|
||||
|
||||
this.stageIncrement = stageIncrement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the current critical-hit stage value by {@linkcode stageIncrement}.
|
||||
* @param _pokemon {@linkcode Pokemon} N/A
|
||||
* @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level
|
||||
* @returns always `true`
|
||||
*/
|
||||
apply({ critStage }: CritBoostParams): boolean {
|
||||
critStage.value += this.stageIncrement;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for held items that apply critical-hit stage boost(s)
|
||||
* if the holder is of a specific {@linkcode SpeciesId}.
|
||||
* @extends CritBoosterModifier
|
||||
* @see {@linkcode shouldApply}
|
||||
*/
|
||||
export class SpeciesCritBoostHeldItem extends CritBoostHeldItem {
|
||||
/** The species that the held item's critical-hit stage boost applies to */
|
||||
private species: SpeciesId[];
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount: number, stageIncrement: number, species: SpeciesId[]) {
|
||||
super(type, maxStackCount, stageIncrement);
|
||||
|
||||
this.species = species;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the holder's {@linkcode SpeciesId} (or its fused species) is listed
|
||||
* in {@linkcode species}.
|
||||
* @param pokemon {@linkcode Pokemon} that holds the held item
|
||||
* @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level
|
||||
* @returns `true` if the critical-hit level can be incremented, false otherwise
|
||||
*/
|
||||
// override shouldApply(pokemon: Pokemon, critStage: NumberHolder): boolean {
|
||||
// return (
|
||||
// super.shouldApply(pokemon, critStage) &&
|
||||
// (this.species.includes(pokemon.getSpeciesForm(true).speciesId) ||
|
||||
// (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId)))
|
||||
// );
|
||||
// }
|
||||
|
||||
apply(params: CritBoostParams): boolean {
|
||||
const pokemon = params.pokemon;
|
||||
const fitsSpecies =
|
||||
this.species.includes(pokemon.getSpeciesForm(true).speciesId) ||
|
||||
(pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId));
|
||||
|
||||
if (fitsSpecies) {
|
||||
return super.apply(params);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
30
src/items/held-items/damage-money-reward.ts
Normal file
30
src/items/held-items/damage-money-reward.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { NumberHolder } from "#utils/common";
|
||||
|
||||
export interface DamageMoneyRewardParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** The damage, used to calculate the money reward */
|
||||
damage: number;
|
||||
}
|
||||
|
||||
export class DamageMoneyRewardHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.DAMAGE_MONEY_REWARD];
|
||||
|
||||
/**
|
||||
* Applies {@linkcode DamageMoneyRewardModifier}
|
||||
* @returns always `true`
|
||||
*/
|
||||
apply({ pokemon, damage }: DamageMoneyRewardParams): boolean {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
const moneyAmount = new NumberHolder(Math.floor(damage * (0.5 * stackCount)));
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount });
|
||||
globalScene.addMoney(moneyAmount.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
58
src/items/held-items/evo-tracker.ts
Normal file
58
src/items/held-items/evo-tracker.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface EvoTrackerParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
}
|
||||
|
||||
export class EvoTrackerHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.EVO_TRACKER];
|
||||
|
||||
protected species: SpeciesId;
|
||||
protected required: number;
|
||||
public isTransferable = false;
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount: number, species: SpeciesId, required: number) {
|
||||
super(type, maxStackCount);
|
||||
this.species = species;
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the {@linkcode EvoTrackerModifier}
|
||||
* @returns always `true`
|
||||
*/
|
||||
apply(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class GimmighoulEvoTrackerHeldItem extends EvoTrackerHeldItem {
|
||||
get name(): string {
|
||||
return i18next.t("modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL.name");
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL.description");
|
||||
}
|
||||
|
||||
get iconName(): string {
|
||||
return "relic_gold";
|
||||
}
|
||||
|
||||
getStackCount(pokemon: Pokemon): number {
|
||||
const stackCount =
|
||||
pokemon.heldItemManager.getStack(this.type) +
|
||||
pokemon.heldItemManager.getStack(HeldItemId.GOLDEN_PUNCH) +
|
||||
globalScene.trainerItems.getStack(TrainerItemId.AMULET_COIN) +
|
||||
globalScene.trainerItems.getStack(TrainerItemId.GOLDEN_POKEBALL);
|
||||
return stackCount;
|
||||
}
|
||||
}
|
53
src/items/held-items/exp-booster.ts
Normal file
53
src/items/held-items/exp-booster.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { NumberHolder } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface ExpBoostParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** Holds the amount of experience gained, which may be modified after item application */
|
||||
expAmount: NumberHolder;
|
||||
}
|
||||
|
||||
export class ExpBoosterHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.EXP_BOOSTER];
|
||||
private boostPercent: number;
|
||||
private boostMultiplier: number;
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount: number, boostPercent: number) {
|
||||
super(type, maxStackCount);
|
||||
this.boostPercent = boostPercent;
|
||||
this.boostMultiplier = boostPercent * 0.01;
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.PokemonExpBoosterModifierType.description", {
|
||||
boostPercent: this.boostPercent,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: What do we do with this? Need to look up all the shouldApply
|
||||
/**
|
||||
* Checks if {@linkcode PokemonExpBoosterModifier} should be applied
|
||||
* @param pokemon The {@linkcode Pokemon} to apply the exp boost to
|
||||
* @param boost {@linkcode NumberHolder} holding the exp boost value
|
||||
* @returns `true` if {@linkcode PokemonExpBoosterModifier} should be applied
|
||||
*/
|
||||
// override shouldApply(pokemon: Pokemon, boost: NumberHolder): boolean {
|
||||
// return super.shouldApply(pokemon, boost) && !!boost;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Applies {@linkcode PokemonExpBoosterModifier}
|
||||
* @returns always `true`
|
||||
*/
|
||||
apply({ pokemon, expAmount }: ExpBoostParams): true {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
expAmount.value = Math.floor(expAmount.value * (1 + stackCount * this.boostMultiplier));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
32
src/items/held-items/field-effect.ts
Normal file
32
src/items/held-items/field-effect.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { NumberHolder } from "#utils/common";
|
||||
|
||||
export interface FieldEffectParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** Holder for the field effect duration*/
|
||||
fieldDuration: NumberHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for held items, namely Mystical Rock, that extend the
|
||||
* duration of weather and terrain effects.
|
||||
* @extends PokemonHeldItemModifier
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class FieldEffectHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.FIELD_EFFECT];
|
||||
|
||||
/**
|
||||
* Provides two more turns per stack to any weather or terrain effect caused
|
||||
* by the holder.
|
||||
* @returns always `true`
|
||||
*/
|
||||
apply({ pokemon, fieldDuration }: FieldEffectParams): true {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
fieldDuration.value += 2 * stackCount;
|
||||
return true;
|
||||
}
|
||||
}
|
55
src/items/held-items/flinch-chance.ts
Normal file
55
src/items/held-items/flinch-chance.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { BooleanHolder } from "#utils/common";
|
||||
|
||||
export interface FlinchChanceParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** Holds whether the attack will cause a flinch */
|
||||
flinched: BooleanHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a
|
||||
* set {@linkcode StatusEffect} at the end of a turn.
|
||||
* @extends PokemonHeldItemModifier
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class FlinchChanceHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.FLINCH_CHANCE];
|
||||
private chance: number;
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount: number, chance: number) {
|
||||
super(type, maxStackCount);
|
||||
|
||||
this.chance = chance; // 10
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if {@linkcode FlinchChanceModifier} should be applied
|
||||
* @param pokemon the {@linkcode Pokemon} that holds the item
|
||||
* @param flinched {@linkcode BooleanHolder} that is `true` if the pokemon flinched
|
||||
* @returns `true` if {@linkcode FlinchChanceModifier} should be applied
|
||||
*/
|
||||
// override shouldApply(pokemon?: Pokemon, flinched?: BooleanHolder): boolean {
|
||||
// return super.shouldApply(pokemon, flinched) && !!flinched;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Applies {@linkcode FlinchChanceModifier} to randomly flinch targets hit.
|
||||
* @returns `true` if {@linkcode FlinchChanceModifier} was applied successfully
|
||||
*/
|
||||
apply({ pokemon, flinched }: FlinchChanceParams): boolean {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
// The check for pokemon.summonData is to ensure that a crash doesn't occur when a Pokemon with King's Rock procs a flinch
|
||||
// TODO: Since summonData is always defined now, we can probably remove this
|
||||
if (pokemon.summonData && !flinched.value && pokemon.randBattleSeedInt(100) < stackCount * this.chance) {
|
||||
flinched.value = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
31
src/items/held-items/friendship-booster.ts
Normal file
31
src/items/held-items/friendship-booster.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { NumberHolder } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface FriendshipBoostParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** Holder for the friendship amount to be changed by the item */
|
||||
friendship: NumberHolder;
|
||||
}
|
||||
|
||||
export class FriendshipBoosterHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.FRIENDSHIP_BOOSTER];
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.PokemonFriendshipBoosterModifierType.description");
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies {@linkcode PokemonFriendshipBoosterModifier}
|
||||
* @returns always `true`
|
||||
*/
|
||||
apply({ pokemon, friendship }: FriendshipBoostParams): true {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
friendship.value = Math.floor(friendship.value * (1 + 0.5 * stackCount));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
52
src/items/held-items/hit-heal.ts
Normal file
52
src/items/held-items/hit-heal.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import { PokemonHealPhase } from "#phases/pokemon-heal-phase";
|
||||
import { toDmgValue } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface HitHealParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
}
|
||||
|
||||
export class HitHealHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_HEAL];
|
||||
|
||||
get name(): string {
|
||||
return i18next.t("modifierType:ModifierType.SHELL_BELL.name");
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.SHELL_BELL.description");
|
||||
}
|
||||
|
||||
get iconName(): string {
|
||||
return "shell_bell";
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies {@linkcode HitHealModifier}
|
||||
* @returns `true` if the {@linkcode Pokemon} was healed
|
||||
*/
|
||||
apply({ pokemon }: HitHealParams): boolean {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
if (pokemon.turnData.totalDamageDealt > 0 && !pokemon.isFullHp()) {
|
||||
// TODO: this shouldn't be undefined AFAIK
|
||||
globalScene.phaseManager.unshiftPhase(
|
||||
new PokemonHealPhase(
|
||||
pokemon.getBattlerIndex(),
|
||||
toDmgValue(pokemon.turnData.totalDamageDealt / 8) * stackCount,
|
||||
i18next.t("modifier:hitHealApply", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
typeName: this.name,
|
||||
}),
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
69
src/items/held-items/incrementing-stat.ts
Normal file
69
src/items/held-items/incrementing-stat.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { Stat } from "#enums/stat";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { NumberHolder } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface IncrementingStatParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** The stat whose value is being impacted */
|
||||
stat: Stat;
|
||||
/** Holds the stat's value, which may be modified after item application */
|
||||
// TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2135612276
|
||||
statHolder: NumberHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently used by Macho Brace item
|
||||
*/
|
||||
export class IncrementingStatHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.INCREMENTING_STAT];
|
||||
public isTransferable = false;
|
||||
|
||||
/**
|
||||
* Checks if the {@linkcode PokemonIncrementingStatModifier} should be applied to the {@linkcode Pokemon}.
|
||||
* @param pokemon The {@linkcode Pokemon} that holds the item
|
||||
* @param stat The affected {@linkcode Stat}
|
||||
* @param statHolder The {@linkcode NumberHolder} that holds the stat
|
||||
* @returns `true` if the {@linkcode PokemonBaseStatFlatModifier} should be applied
|
||||
*/
|
||||
// override shouldApply(pokemon?: Pokemon, stat?: Stat, statHolder?: NumberHolder): boolean {
|
||||
// return super.shouldApply(pokemon, stat, statHolder) && !!statHolder;
|
||||
// }
|
||||
|
||||
get name(): string {
|
||||
return i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE.name") + " (new)";
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE.description");
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the {@linkcode PokemonIncrementingStatModifier}
|
||||
* @returns always `true`
|
||||
*/
|
||||
apply({ pokemon, statHolder, stat }: IncrementingStatParams): boolean {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
|
||||
// Modifies the passed in stat number holder by +2 per stack for HP, +1 per stack for other stats
|
||||
// If the Macho Brace is at max stacks (50), adds additional 10% to total HP and 5% to other stats
|
||||
const isHp = stat === Stat.HP;
|
||||
|
||||
if (isHp) {
|
||||
statHolder.value += 2 * stackCount;
|
||||
if (stackCount === this.maxStackCount) {
|
||||
statHolder.value = Math.floor(statHolder.value * 1.1);
|
||||
}
|
||||
} else {
|
||||
statHolder.value += stackCount;
|
||||
if (stackCount === this.maxStackCount) {
|
||||
statHolder.value = Math.floor(statHolder.value * 1.05);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
67
src/items/held-items/instant-revive.ts
Normal file
67
src/items/held-items/instant-revive.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { applyAbAttrs } from "#abilities/apply-ab-attrs";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { ConsumableHeldItem } from "#items/held-item";
|
||||
import { PokemonHealPhase } from "#phases/pokemon-heal-phase";
|
||||
import { toDmgValue } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface InstantReviveParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for held items, namely White Herb, that restore adverse stat
|
||||
* stages in battle.
|
||||
* @extends PokemonHeldItemModifier
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class InstantReviveHeldItem extends ConsumableHeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.INSTANT_REVIVE];
|
||||
|
||||
get name(): string {
|
||||
return i18next.t("modifierType:ModifierType.REVIVER_SEED.name");
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.REVIVER_SEED.description");
|
||||
}
|
||||
|
||||
get iconName(): string {
|
||||
return "reviver_seed";
|
||||
}
|
||||
/**
|
||||
* Goes through the holder's stat stages and, if any are negative, resets that
|
||||
* stat stage back to 0.
|
||||
* @returns `true` if any stat stages were reset, false otherwise
|
||||
*/
|
||||
apply({ pokemon }: InstantReviveParams): boolean {
|
||||
// Restore the Pokemon to half HP
|
||||
globalScene.phaseManager.unshiftPhase(
|
||||
new PokemonHealPhase(
|
||||
pokemon.getBattlerIndex(),
|
||||
toDmgValue(pokemon.getMaxHp() / 2),
|
||||
i18next.t("modifier:pokemonInstantReviveApply", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
typeName: this.name,
|
||||
}),
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
),
|
||||
);
|
||||
|
||||
// Remove the Pokemon's FAINT status
|
||||
pokemon.resetStatus(true, false, true, false);
|
||||
|
||||
// Reapply Commander on the Pokemon's side of the field, if applicable
|
||||
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||
for (const p of field) {
|
||||
applyAbAttrs("CommanderAbAttr", { pokemon: p });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
169
src/items/held-items/item-steal.ts
Normal file
169
src/items/held-items/item-steal.ts
Normal file
@ -0,0 +1,169 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { allHeldItems } from "#data/data-lists";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import { coerceArray, randSeedFloat } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface ItemStealParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** The pokemon to steal from (optional) */
|
||||
// TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2135607083
|
||||
target?: Pokemon;
|
||||
}
|
||||
|
||||
// constructor(type: HeldItemId, maxStackCount: number, boostPercent: number) {
|
||||
|
||||
/**
|
||||
* Abstract class for held items that steal other Pokemon's items.
|
||||
* @see {@linkcode TurnEndItemStealHeldItem}
|
||||
* @see {@linkcode ContactItemStealChanceHeldItem}
|
||||
*/
|
||||
export abstract class ItemTransferHeldItem extends HeldItem {
|
||||
/**
|
||||
* Steals an item, chosen randomly, from a set of target Pokemon.
|
||||
* @param pokemon The {@linkcode Pokemon} holding this item
|
||||
* @param target The {@linkcode Pokemon} to steal from (optional)
|
||||
* @param _args N/A
|
||||
* @returns `true` if an item was stolen; false otherwise.
|
||||
*/
|
||||
apply(params: ItemStealParams): boolean {
|
||||
const opponents = this.getTargets(params);
|
||||
|
||||
if (!opponents.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pokemon = params.pokemon;
|
||||
//TODO: Simplify this logic here
|
||||
const targetPokemon = opponents[pokemon.randBattleSeedInt(opponents.length)];
|
||||
|
||||
const transferredItemCount = this.getTransferredItemCount(params);
|
||||
if (!transferredItemCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Change this logic to use held items
|
||||
const transferredRewards: HeldItemId[] = [];
|
||||
const heldItems = targetPokemon.heldItemManager.getTransferableHeldItems();
|
||||
|
||||
for (let i = 0; i < transferredItemCount; i++) {
|
||||
if (!heldItems.length) {
|
||||
break;
|
||||
}
|
||||
const randItemIndex = pokemon.randBattleSeedInt(heldItems.length);
|
||||
const randItem = heldItems[randItemIndex];
|
||||
// TODO: Fix this after updating the various methods in battle-scene.ts
|
||||
if (globalScene.tryTransferHeldItem(randItem, targetPokemon, pokemon, false)) {
|
||||
transferredRewards.push(randItem);
|
||||
heldItems.splice(randItemIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (const mt of transferredRewards) {
|
||||
globalScene.phaseManager.queueMessage(this.getTransferMessage(params, mt));
|
||||
}
|
||||
|
||||
return !!transferredRewards.length;
|
||||
}
|
||||
|
||||
abstract getTargets(params: ItemStealParams): Pokemon[];
|
||||
|
||||
abstract getTransferredItemCount(params: ItemStealParams): number;
|
||||
|
||||
abstract getTransferMessage(params: ItemStealParams, itemId: HeldItemId): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier for held items that steal items from the enemy at the end of
|
||||
* each turn.
|
||||
* @see {@linkcode allRewards[MINI_BLACK_HOLE]}
|
||||
*/
|
||||
export class TurnEndItemStealHeldItem extends ItemTransferHeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_ITEM_STEAL];
|
||||
isTransferable = true;
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.TurnHeldItemTransferModifierType.description");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the targets to transfer items from when this applies.
|
||||
* @param pokemon the {@linkcode Pokemon} holding this item
|
||||
* @param _args N/A
|
||||
* @returns the opponents of the source {@linkcode Pokemon}
|
||||
*/
|
||||
getTargets(params: ItemStealParams): Pokemon[] {
|
||||
return params.pokemon instanceof Pokemon ? params.pokemon.getOpponents() : [];
|
||||
}
|
||||
|
||||
getTransferredItemCount(_params: ItemStealParams): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getTransferMessage(params: ItemStealParams, itemId: HeldItemId): string {
|
||||
return i18next.t("modifier:turnHeldItemTransferApply", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(params.target),
|
||||
itemName: allHeldItems[itemId].name,
|
||||
pokemonName: params.pokemon.getNameToRender(),
|
||||
typeName: this.name,
|
||||
});
|
||||
}
|
||||
|
||||
setTransferrableFalse(): void {
|
||||
this.isTransferable = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier for held items that add a chance to steal items from the target of a
|
||||
* successful attack.
|
||||
* @see {@linkcode allRewards[GRIP_CLAW]}
|
||||
* @see {@linkcode HeldItemTransferModifier}
|
||||
*/
|
||||
export class ContactItemStealChanceHeldItem extends ItemTransferHeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.CONTACT_ITEM_STEAL_CHANCE];
|
||||
public readonly chancePercent: number;
|
||||
public readonly chance: number;
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount: number, chancePercent: number) {
|
||||
super(type, maxStackCount);
|
||||
|
||||
this.chancePercent = chancePercent;
|
||||
this.chance = chancePercent / 100;
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.ContactHeldItemTransferChanceModifierType.description", {
|
||||
chancePercent: this.chancePercent,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the target to steal items from when this applies.
|
||||
* @param _holderPokemon The {@linkcode Pokemon} holding this item
|
||||
* @param targetPokemon The {@linkcode Pokemon} the holder is targeting with an attack
|
||||
* @returns The target {@linkcode Pokemon} as array for further use in `apply` implementations
|
||||
*/
|
||||
getTargets({ target }: ItemStealParams): Pokemon[] {
|
||||
return target ? coerceArray(target) : [];
|
||||
}
|
||||
|
||||
getTransferredItemCount({ pokemon }: ItemStealParams): number {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
return randSeedFloat() <= this.chance * stackCount ? 1 : 0;
|
||||
}
|
||||
|
||||
getTransferMessage({ pokemon, target }: ItemStealParams, itemId: HeldItemId): string {
|
||||
return i18next.t("modifier:contactHeldItemTransferApply", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(target),
|
||||
itemName: allHeldItems[itemId].name,
|
||||
pokemonName: pokemon.getNameToRender(),
|
||||
typeName: this.name,
|
||||
});
|
||||
}
|
||||
}
|
89
src/items/held-items/multi-hit.ts
Normal file
89
src/items/held-items/multi-hit.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { allMoves } from "#data/data-lists";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import { isNullOrUndefined, type NumberHolder } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface MultiHitParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** The move being used */
|
||||
moveId: MoveId;
|
||||
/** Holder for the move's hit count for the turn */
|
||||
count?: NumberHolder;
|
||||
/** Holder for the damage multiplier applied to a strike of the move */
|
||||
damageMultiplier?: NumberHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a
|
||||
* set {@linkcode StatusEffect} at the end of a turn.
|
||||
* @extends PokemonHeldItemModifier
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class MultiHitHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.MULTI_HIT];
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.PokemonMultiHitModifierType.description");
|
||||
}
|
||||
|
||||
/**
|
||||
* For each stack, converts 25 percent of attack damage into an additional strike.
|
||||
* @returns Whether the item applies its effects to move
|
||||
*/
|
||||
apply({ pokemon, count, moveId, damageMultiplier }: MultiHitParams): boolean {
|
||||
const move = allMoves[moveId];
|
||||
/*
|
||||
* The move must meet Parental Bond's restrictions for this item
|
||||
* to apply. This means
|
||||
* - Only attacks are boosted
|
||||
* - Multi-strike moves, charge moves, and self-sacrificial moves are not boosted
|
||||
* (though Multi-Lens can still affect moves boosted by Parental Bond)
|
||||
* - Multi-target moves are not boosted *unless* they can only hit a single Pokemon
|
||||
* - Fling, Uproar, Rollout, Ice Ball, and Endeavor are not boosted
|
||||
*/
|
||||
if (!move.canBeMultiStrikeEnhanced(pokemon)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isNullOrUndefined(count)) {
|
||||
return this.applyHitCountBoost(pokemon, count);
|
||||
}
|
||||
if (!isNullOrUndefined(damageMultiplier)) {
|
||||
return this.applyDamageModifier(pokemon, damageMultiplier);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Adds strikes to a move equal to the number of stacked Multi-Lenses */
|
||||
private applyHitCountBoost(pokemon: Pokemon, count: NumberHolder): boolean {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
count.value += stackCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If applied to the first hit of a move, sets the damage multiplier
|
||||
* equal to (1 - the number of stacked Multi-Lenses).
|
||||
* Additional strikes beyond that are given a 0.25x damage multiplier
|
||||
*/
|
||||
private applyDamageModifier(pokemon: Pokemon, damageMultiplier: NumberHolder): boolean {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
if (pokemon.turnData.hitsLeft === pokemon.turnData.hitCount) {
|
||||
// Reduce first hit by 25% for each stack count
|
||||
damageMultiplier.value *= 1 - 0.25 * stackCount;
|
||||
return true;
|
||||
}
|
||||
if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== stackCount + 1) {
|
||||
// Deal 25% damage for each remaining Multi Lens hit
|
||||
damageMultiplier.value *= 0.25;
|
||||
return true;
|
||||
}
|
||||
// An extra hit not caused by Multi Lens -- assume it is Parental Bond
|
||||
return false;
|
||||
}
|
||||
}
|
29
src/items/held-items/nature-weight-booster.ts
Normal file
29
src/items/held-items/nature-weight-booster.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { NumberHolder } from "#utils/common";
|
||||
|
||||
export interface NatureWeightBoostParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** Holder for the multiplier */
|
||||
multiplier: NumberHolder;
|
||||
}
|
||||
|
||||
export class NatureWeightBoosterHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.NATURE_WEIGHT_BOOSTER];
|
||||
|
||||
/**
|
||||
* Applies {@linkcode PokemonNatureWeightModifier}
|
||||
* @returns `true` if multiplier was applied
|
||||
*/
|
||||
apply({ pokemon, multiplier }: NatureWeightBoostParams): boolean {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
if (multiplier.value !== 1) {
|
||||
multiplier.value += 0.1 * stackCount * (multiplier.value > 1 ? 1 : -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
64
src/items/held-items/reset-negative-stat-stage.ts
Normal file
64
src/items/held-items/reset-negative-stat-stage.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { BATTLE_STATS } from "#enums/stat";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { ConsumableHeldItem } from "#items/held-item";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface ResetNegativeStatStageParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** Whether the move was used by a player pokemon */
|
||||
isPlayer: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for held items, namely White Herb, that restore adverse stat
|
||||
* stages in battle.
|
||||
* @extends PokemonHeldItemModifier
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.RESET_NEGATIVE_STAT_STAGE];
|
||||
|
||||
get name(): string {
|
||||
return i18next.t("modifierType:ModifierType.WHITE_HERB.name");
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.WHITE_HERB.description");
|
||||
}
|
||||
|
||||
get iconName(): string {
|
||||
return "white_herb";
|
||||
}
|
||||
/**
|
||||
* Goes through the holder's stat stages and, if any are negative, resets that
|
||||
* stat stage back to 0.
|
||||
* @returns `true` if any stat stages were reset, false otherwise
|
||||
*/
|
||||
apply({ pokemon, isPlayer }: ResetNegativeStatStageParams): boolean {
|
||||
let statRestored = false;
|
||||
|
||||
for (const s of BATTLE_STATS) {
|
||||
if (pokemon.getStatStage(s) < 0) {
|
||||
pokemon.setStatStage(s, 0);
|
||||
statRestored = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (statRestored) {
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("modifier:resetNegativeStatStageApply", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
typeName: this.name,
|
||||
}),
|
||||
);
|
||||
|
||||
this.consume(pokemon, isPlayer, true, false);
|
||||
}
|
||||
|
||||
return statRestored;
|
||||
}
|
||||
}
|
186
src/items/held-items/stat-booster.ts
Normal file
186
src/items/held-items/stat-booster.ts
Normal file
@ -0,0 +1,186 @@
|
||||
import { pokemonEvolutions } from "#balance/pokemon-evolutions";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
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 { NumberHolder } from "#utils/common";
|
||||
|
||||
export interface StatBoostParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
stat: Stat;
|
||||
statValue: NumberHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for held items that Applies {@linkcode Stat} boost(s)
|
||||
* using a multiplier.
|
||||
* @extends PokemonHeldItemModifier
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class StatBoostHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.STAT_BOOST];
|
||||
/** The stats that the held item boosts */
|
||||
protected stats: Stat[];
|
||||
/** The multiplier used to increase the relevant stat(s) */
|
||||
protected multiplier: number;
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount: number, stats: Stat[], multiplier: number) {
|
||||
super(type, maxStackCount);
|
||||
|
||||
this.stats = stats;
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the incoming stat is listed in {@linkcode stats}
|
||||
* @param _pokemon the {@linkcode Pokemon} that holds the item
|
||||
* @param _stat the {@linkcode Stat} to be boosted
|
||||
* @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat
|
||||
* @returns `true` if the stat could be boosted, false otherwise
|
||||
*/
|
||||
// override shouldApply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean {
|
||||
// return super.shouldApply(pokemon, stat, statValue) && this.stats.includes(stat);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Boosts the incoming stat by a {@linkcode multiplier} if the stat is listed
|
||||
* in {@linkcode stats}.
|
||||
* @returns `true` if the stat boost applies successfully, false otherwise
|
||||
* @see shouldApply
|
||||
*/
|
||||
apply({ statValue }: StatBoostParams): boolean {
|
||||
statValue.value *= this.multiplier;
|
||||
return true;
|
||||
}
|
||||
|
||||
getMaxHeldItemCount(_pokemon: Pokemon): number {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for held items, specifically Eviolite, that apply
|
||||
* {@linkcode Stat} boost(s) using a multiplier if the holder can evolve.
|
||||
* @extends StatBoosterModifier
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class EvolutionStatBoostHeldItem extends StatBoostHeldItem {
|
||||
/**
|
||||
* Checks if the stat boosts can apply and if the holder is not currently
|
||||
* Gigantamax'd.
|
||||
* @param pokemon {@linkcode Pokemon} that holds the held item
|
||||
* @param stat {@linkcode Stat} The {@linkcode Stat} to be boosted
|
||||
* @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat
|
||||
* @returns `true` if the stat boosts can be applied, false otherwise
|
||||
*/
|
||||
// override shouldApply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean {
|
||||
// return super.shouldApply(pokemon, stat, statValue) && !pokemon.isMax();
|
||||
// }
|
||||
|
||||
/**
|
||||
* Boosts the incoming stat value by a {@linkcode EvolutionStatBoosterModifier.multiplier} if the holder
|
||||
* can evolve. Note that, if the holder is a fusion, they will receive
|
||||
* only half of the boost if either of the fused members are fully
|
||||
* evolved. However, if they are both unevolved, the full boost
|
||||
* will apply.
|
||||
* @returns `true` if the stat boost applies successfully, false otherwise
|
||||
* @see shouldApply
|
||||
*/
|
||||
override apply(params: StatBoostParams): boolean {
|
||||
const pokemon = params.pokemon;
|
||||
const isUnevolved = pokemon.getSpeciesForm(true).speciesId in pokemonEvolutions;
|
||||
|
||||
if (pokemon.isFusion() && pokemon.getFusionSpeciesForm(true).speciesId in pokemonEvolutions !== isUnevolved) {
|
||||
// Half boost applied if pokemon is fused and either part of fusion is fully evolved
|
||||
params.statValue.value *= 1 + (this.multiplier - 1) / 2;
|
||||
return true;
|
||||
}
|
||||
if (isUnevolved && !pokemon.isMax()) {
|
||||
// Full boost applied if holder is unfused and unevolved or, if fused, both parts of fusion are unevolved
|
||||
return super.apply(params);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export type SpeciesStatBoosterItemId =
|
||||
| typeof HeldItemId.LIGHT_BALL
|
||||
| typeof HeldItemId.THICK_CLUB
|
||||
| typeof HeldItemId.METAL_POWDER
|
||||
| typeof HeldItemId.QUICK_POWDER
|
||||
| typeof HeldItemId.DEEP_SEA_SCALE
|
||||
| typeof HeldItemId.DEEP_SEA_TOOTH;
|
||||
|
||||
export const SPECIES_STAT_BOOSTER_ITEMS: SpeciesStatBoosterItemId[] = [
|
||||
HeldItemId.LIGHT_BALL,
|
||||
HeldItemId.THICK_CLUB,
|
||||
HeldItemId.METAL_POWDER,
|
||||
HeldItemId.QUICK_POWDER,
|
||||
HeldItemId.DEEP_SEA_SCALE,
|
||||
HeldItemId.DEEP_SEA_TOOTH,
|
||||
];
|
||||
|
||||
/**
|
||||
* Modifier used for held items that Applies {@linkcode Stat} boost(s) using a
|
||||
* multiplier if the holder is of a specific {@linkcode SpeciesId}.
|
||||
* @extends StatBoostHeldItem
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class SpeciesStatBoostHeldItem extends StatBoostHeldItem {
|
||||
/** The species that the held item's stat boost(s) apply to */
|
||||
public species: SpeciesId[];
|
||||
|
||||
constructor(
|
||||
type: SpeciesStatBoosterItemId,
|
||||
maxStackCount: number,
|
||||
stats: Stat[],
|
||||
multiplier: number,
|
||||
species: SpeciesId[],
|
||||
) {
|
||||
super(type, maxStackCount, stats, multiplier);
|
||||
this.species = species;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the incoming stat is listed in {@linkcode stats} and if the holder's {@linkcode SpeciesId}
|
||||
* (or its fused species) is listed in {@linkcode species}.
|
||||
* @param pokemon {@linkcode Pokemon} that holds the item
|
||||
* @param stat {@linkcode Stat} being checked at the time
|
||||
* @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat
|
||||
* @returns `true` if the stat could be boosted, false otherwise
|
||||
*/
|
||||
// override shouldApply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean {
|
||||
// return (
|
||||
// super.shouldApply(pokemon, stat, statValue) &&
|
||||
// (this.species.includes(pokemon.getSpeciesForm(true).speciesId) ||
|
||||
// (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId)))
|
||||
// );
|
||||
// }
|
||||
|
||||
apply(params: StatBoostParams): boolean {
|
||||
const pokemon = params.pokemon;
|
||||
const fitsSpecies =
|
||||
this.species.includes(pokemon.getSpeciesForm(true).speciesId) ||
|
||||
(pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId));
|
||||
|
||||
if (fitsSpecies) {
|
||||
return super.apply(params);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if either parameter is included in the corresponding lists
|
||||
* @param speciesId {@linkcode SpeciesId} being checked
|
||||
* @param stat {@linkcode Stat} being checked
|
||||
* @returns `true` if both parameters are in {@linkcode species} and {@linkcode stats} respectively, false otherwise
|
||||
*/
|
||||
contains(speciesId: SpeciesId, stat: Stat): boolean {
|
||||
return this.species.includes(speciesId) && this.stats.includes(stat);
|
||||
}
|
||||
}
|
54
src/items/held-items/survive-chance.ts
Normal file
54
src/items/held-items/survive-chance.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { BooleanHolder } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface SurviveChanceParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
surviveDamage: BooleanHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a
|
||||
* set {@linkcode StatusEffect} at the end of a turn.
|
||||
* @extends PokemonHeldItemModifier
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class SurviveChanceHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.SURVIVE_CHANCE];
|
||||
|
||||
/**
|
||||
* Checks if the {@linkcode SurviveDamageModifier} should be applied
|
||||
* @param pokemon the {@linkcode Pokemon} that holds the item
|
||||
* @param surviveDamage {@linkcode BooleanHolder} that holds the survive damage
|
||||
* @returns `true` if the {@linkcode SurviveDamageModifier} should be applied
|
||||
*/
|
||||
// override shouldApply(pokemon?: Pokemon, surviveDamage?: BooleanHolder): boolean {
|
||||
// return super.shouldApply(pokemon, surviveDamage) && !!surviveDamage;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Applies {@linkcode SurviveDamageModifier}
|
||||
* @returns `true` if the survive damage has been applied
|
||||
*/
|
||||
apply({ pokemon, surviveDamage }: SurviveChanceParams): boolean {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < stackCount) {
|
||||
surviveDamage.value = true;
|
||||
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("modifier:surviveDamageApply", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
typeName: this.name,
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
36
src/items/held-items/turn-end-heal.ts
Normal file
36
src/items/held-items/turn-end-heal.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import { PokemonHealPhase } from "#phases/pokemon-heal-phase";
|
||||
import { toDmgValue } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface TurnEndHealParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
}
|
||||
|
||||
export class TurnEndHealHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_HEAL];
|
||||
|
||||
apply({ pokemon }: TurnEndHealParams): boolean {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
if (pokemon.isFullHp()) {
|
||||
return false;
|
||||
}
|
||||
globalScene.phaseManager.unshiftPhase(
|
||||
new PokemonHealPhase(
|
||||
pokemon.getBattlerIndex(),
|
||||
toDmgValue(pokemon.getMaxHp() / 16) * stackCount,
|
||||
i18next.t("modifier:turnHealApply", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
typeName: this.name,
|
||||
}),
|
||||
true,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user