diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 3cbf4d7b422..3c561206abe 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -323,6 +323,7 @@ export default class BattleScene extends SceneBase { this.conditionalQueue = []; this.phaseQueuePrependSpliceIndex = -1; this.nextCommandPhaseQueue = []; + this.eventManager = new TimedEventManager(); this.updateGameInfo(); } @@ -378,7 +379,6 @@ export default class BattleScene extends SceneBase { this.fieldSpritePipeline = new FieldSpritePipeline(this.game); (this.renderer as Phaser.Renderer.WebGL.WebGLRenderer).pipelines.add("FieldSprite", this.fieldSpritePipeline); - this.eventManager = new TimedEventManager(); this.launchBattle(); } diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index c4b03660bde..0a88c5a699c 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -1,7 +1,7 @@ import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config"; import { ModifierTier } from "#app/modifier/modifier-tier"; -import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { ModifierPoolType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import BattleScene from "#app/battle-scene"; @@ -280,7 +280,7 @@ export const ClowningAroundEncounter: MysteryEncounter = let numRogue = 0; items.filter(m => m.isTransferable && !(m instanceof BerryModifier)) .forEach(m => { - const type = m.type.withTierFromPool(); + 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; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 5cd2fbffd5f..66459c96ede 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -418,7 +418,7 @@ export function generateModifierType(scene: BattleScene, modifier: () => Modifie // Populates item id and tier (order matters) result = result .withIdFromFunc(modifierTypes[modifierId]) - .withTierFromPool(); + .withTierFromPool(ModifierPoolType.PLAYER, scene.getParty()); return result instanceof ModifierTypeGenerator ? result.generateType(scene.getParty(), pregenArgs) : result; } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index e68e9a06fae..dfa46ce3667 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -19,7 +19,7 @@ import { Unlockables } from "#app/system/unlockables"; import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system/voucher"; import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import { getModifierTierTextTint } from "#app/ui/text"; -import { formatMoney, getEnumKeys, getEnumValues, IntegerHolder, NumberHolder, padInt, randSeedInt, randSeedItem } from "#app/utils"; +import { formatMoney, getEnumKeys, getEnumValues, IntegerHolder, isNullOrUndefined, NumberHolder, padInt, randSeedInt, randSeedItem } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; @@ -121,18 +121,41 @@ export class ModifierType { * Populates item tier for ModifierType instance * Tier is a necessary field for items that appear in player shop (determines the Pokeball visual they use) * To find the tier, this function performs a reverse lookup of the item type in modifier pools + * It checks the weight of the item and will use the first tier for which the weight is greater than 0 + * This is to allow items to be in multiple item pools depending on the conditions, for example for events + * If all tiers have a weight of 0 for the item, the first tier where the item was found is used * @param poolType Default 'ModifierPoolType.PLAYER'. Which pool to lookup item tier from + * @param party optional. Needed to check the weight of modifiers with conditional weight (see {@linkcode WeightedModifierTypeWeightFunc}) + * if not provided or empty, the weight check will be ignored + * @param rerollCount Default `0`. Used to check the weight of modifiers with conditional weight (see {@linkcode WeightedModifierTypeWeightFunc}) */ - withTierFromPool(poolType: ModifierPoolType = ModifierPoolType.PLAYER): ModifierType { + withTierFromPool(poolType: ModifierPoolType = ModifierPoolType.PLAYER, party?: PlayerPokemon[], rerollCount: number = 0): ModifierType { + let defaultTier: undefined | ModifierTier; for (const tier of Object.values(getModifierPoolForType(poolType))) { for (const modifier of tier) { if (this.id === modifier.modifierType.id) { - this.tier = modifier.modifierType.tier; - return this; + let weight: number; + if (modifier.weight instanceof Function) { + weight = party ? modifier.weight(party, rerollCount) : 0; + } else { + weight = modifier.weight; + } + if (weight > 0) { + this.tier = modifier.modifierType.tier; + return this; + } else if (isNullOrUndefined(defaultTier)) { + // If weight is 0, keep track of the first tier where the item was found + defaultTier = modifier.modifierType.tier; + } } } } + // Didn't find a pool with weight > 0, fallback to first tier where the item was found, if any + if (defaultTier) { + this.tier = defaultTier; + } + return this; } @@ -2117,7 +2140,7 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo // Populates item id and tier guaranteedMod = guaranteedMod .withIdFromFunc(modifierTypes[modifierId]) - .withTierFromPool(); + .withTierFromPool(ModifierPoolType.PLAYER, party); const modType = guaranteedMod instanceof ModifierTypeGenerator ? guaranteedMod.generateType(party) : guaranteedMod; if (modType) { @@ -2186,7 +2209,7 @@ export function overridePlayerModifierTypeOptions(options: ModifierTypeOption[], } if (modifierType) { - options[i].type = modifierType.withIdFromFunc(modifierFunc).withTierFromPool(); + options[i].type = modifierType.withIdFromFunc(modifierFunc).withTierFromPool(ModifierPoolType.PLAYER, party); } } } diff --git a/src/test/utils/gameWrapper.ts b/src/test/utils/gameWrapper.ts index 48c0007118b..22517502a05 100644 --- a/src/test/utils/gameWrapper.ts +++ b/src/test/utils/gameWrapper.ts @@ -24,6 +24,7 @@ import GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin; import EventEmitter = Phaser.Events.EventEmitter; import UpdateList = Phaser.GameObjects.UpdateList; import { version } from "../../../package.json"; +import { MockTimedEventManager } from "./mocks/mockTimedEventManager"; Object.defineProperty(window, "localStorage", { value: mockLocalStorage(), @@ -232,6 +233,7 @@ export default class GameWrapper { this.scene.make = new MockGameObjectCreator(mockTextureManager); this.scene.time = new MockClock(this.scene); this.scene.remove = vi.fn(); // TODO: this should be stubbed differently + this.scene.eventManager = new MockTimedEventManager(); // Disable Timed Events } } diff --git a/src/test/utils/mocks/mockTimedEventManager.ts b/src/test/utils/mocks/mockTimedEventManager.ts new file mode 100644 index 00000000000..b44729996a7 --- /dev/null +++ b/src/test/utils/mocks/mockTimedEventManager.ts @@ -0,0 +1,17 @@ +import { TimedEventManager } from "#app/timed-event-manager"; + +/** Mock TimedEventManager so that ongoing events don't impact tests */ +export class MockTimedEventManager extends TimedEventManager { + override activeEvent() { + return undefined; + } + override isEventActive(): boolean { + return false; + } + override getFriendshipMultiplier(): number { + return 1; + } + override getShinyMultiplier(): number { + return 1; + } +}