diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 20835e745f7..e2c1420565c 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -10,7 +10,7 @@ import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from ".. import * as Utils from "../utils"; import { TempBattleStat, getTempBattleStatBoosterItemName, getTempBattleStatName } from "../data/temp-battle-stat"; import { getBerryEffectDescription, getBerryName } from "../data/berry"; -import { Unlockables } from "../system/unlockables"; +import { isUnlocked, Unlockables } from "../system/unlockables"; import { StatusEffect, getStatusEffectDescriptor } from "../data/status-effect"; import { SpeciesFormKey } from "../data/pokemon-species"; import BattleScene from "../battle-scene"; @@ -1597,7 +1597,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)), new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => { const { gameMode, gameData } = party[0].scene; - if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.unlocks[Unlockables.EVIOLITE])) { + if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && isUnlocked(Unlockables.EVIOLITE, gameData))) { return party.some(p => ((p.getSpeciesForm(true).speciesId in pokemonEvolutions) || (p.isFusion() && (p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions))) && !p.getHeldItems().some(i => i instanceof Modifiers.EvolutionStatBoosterModifier)) ? 10 : 0; } return 0; @@ -1675,7 +1675,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.MULTI_LENS, 18), new WeightedModifierType(modifierTypes.VOUCHER_PREMIUM, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily && !party[0].scene.gameMode.isEndless && !party[0].scene.gameMode.isSplicedOnly ? Math.max(5 - rerollCount * 2, 0) : 0, 5), new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => !party[0].scene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 24 : 0, 24), - new WeightedModifierType(modifierTypes.MINI_BLACK_HOLE, (party: Pokemon[]) => (party[0].scene.gameMode.isDaily || (!party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE])) ? 1 : 0, 1), + new WeightedModifierType(modifierTypes.MINI_BLACK_HOLE, (party: Pokemon[]) => (party[0].scene.gameMode.isDaily || (!party[0].scene.gameMode.isFreshStartChallenge() && isUnlocked(Unlockables.MINI_BLACK_HOLE, party[0].scene.gameData))) ? 1 : 0, 1), ].map(m => { m.setTier(ModifierTier.MASTER); return m; }) @@ -1867,9 +1867,13 @@ export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool } const tierWeights = [ 768 / 1024, 195 / 1024, 48 / 1024, 12 / 1024, 1 / 1024 ]; +export let poolHasEviolite: boolean = false; +export let poolHasBlackHole: boolean = false; export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: ModifierPoolType, rerollCount: integer = 0) { const pool = getModifierPoolForType(poolType); + poolHasEviolite = false; + poolHasBlackHole = false; const ignoredIndexes = {}; const modifierTableData = {}; @@ -1906,6 +1910,12 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod ignoredIndexes[t].push(i++); return total; } + if (modifierType.modifierType.id === "EVIOLITE") { + poolHasEviolite = true; + } + if (modifierType.modifierType.id === "MINI_BLACK_HOLE") { + poolHasBlackHole = true; + } thresholds.set(total, i++); return total; }, 0); diff --git a/src/overrides.ts b/src/overrides.ts index 8b3d628e05e..811461a3c60 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -13,6 +13,7 @@ import { Gender } from "./data/gender"; import { allSpecies } from "./data/pokemon-species"; // eslint-disable-line @typescript-eslint/no-unused-vars import { Variant } from "./data/variant"; import { type ModifierOverride } from "./modifier/modifier-type"; +import { Unlockables } from "./system/unlockables"; /** * Overrides that are using when testing different in game situations @@ -69,6 +70,10 @@ class DefaultOverrides { [PokeballType.MASTER_BALL]: 0, }, }; + /** Forces an item to be UNLOCKED */ + readonly UNLOCK_OVERRIDE: Unlockables[] = []; + /** Forces an item to be NOT UNLOCKED */ + readonly DISABLE_UNLOCK_OVERRIDE: Unlockables[] = []; // ---------------- // PLAYER OVERRIDES diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index c755f0c0feb..023fd382558 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -8,7 +8,7 @@ import { GameModes, GameMode, getGameMode } from "#app/game-mode.js"; import { regenerateModifierPoolThresholds, ModifierPoolType, modifierTypes, getDailyRunStarterModifiers } from "#app/modifier/modifier-type.js"; import { Phase } from "#app/phase.js"; import { SessionSaveData } from "#app/system/game-data.js"; -import { Unlockables } from "#app/system/unlockables.js"; +import { isUnlocked, Unlockables } from "#app/system/unlockables.js"; import { vouchers } from "#app/system/voucher.js"; import { OptionSelectItem, OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler.js"; import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler.js"; @@ -76,7 +76,7 @@ export class TitlePhase extends Phase { this.scene.ui.clearText(); this.end(); }; - if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { + if (isUnlocked(Unlockables.ENDLESS_MODE, this.scene.gameData)) { const options: OptionSelectItem[] = [ { label: GameMode.getModeName(GameModes.CLASSIC), @@ -100,7 +100,7 @@ export class TitlePhase extends Phase { } } ]; - if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { + if (isUnlocked(Unlockables.SPLICED_ENDLESS_MODE, this.scene.gameData)) { options.push({ label: GameMode.getModeName(GameModes.SPLICED_ENDLESS), handler: () => { diff --git a/src/system/unlockables.ts b/src/system/unlockables.ts index 983909373fd..81f390afdb0 100644 --- a/src/system/unlockables.ts +++ b/src/system/unlockables.ts @@ -1,5 +1,7 @@ import i18next from "i18next"; import { GameMode, GameModes } from "../game-mode"; +import Overrides from "#app/overrides"; +import { GameData } from "./game-data"; export enum Unlockables { ENDLESS_MODE, @@ -8,6 +10,16 @@ export enum Unlockables { EVIOLITE } +export function isUnlocked(unlockable: Unlockables, gameData: GameData): boolean { + if (Overrides.UNLOCK_OVERRIDE.includes(unlockable)) { + return true; + } + if (Overrides.DISABLE_UNLOCK_OVERRIDE.includes(unlockable)) { + return false; + } + return !!gameData.unlocks[Unlockables.MINI_BLACK_HOLE]; +} + export function getUnlockableName(unlockable: Unlockables) { switch (unlockable) { case Unlockables.ENDLESS_MODE: diff --git a/src/test/daily_mode.test.ts b/src/test/daily_mode.test.ts index ebc19c0b6de..6ddabae3285 100644 --- a/src/test/daily_mode.test.ts +++ b/src/test/daily_mode.test.ts @@ -1,6 +1,11 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import GameManager from "./utils/gameManager"; import { MapModifier } from "#app/modifier/modifier.js"; +import { SelectModifierPhase } from "../phases/select-modifier-phase"; +import { Moves } from "#app/enums/moves.js"; +import { Abilities } from "#app/enums/abilities.js"; +import { Unlockables } from "#app/system/unlockables.js"; +import { poolHasEviolite, poolHasBlackHole } from "#app/modifier/modifier-type.js"; describe("Daily Mode", () => { let phaserGame: Phaser.Game; @@ -32,13 +37,38 @@ describe("Daily Mode", () => { expect(game.scene.getModifiers(MapModifier).length).toBeGreaterThan(0); }); - /* - Can't figure out how to check the shop's item pool :( describe("Shop modifications", async () => { - const modifierPhase = new SelectModifierPhase(game.scene); - game.scene.unshiftPhase(modifierPhase); - await game.phaseInterceptor.run(SelectModifierPhase); - expect(getModifierThresholdPool(ModifierPoolType.PLAYER)); + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override + .battleType("single") + .startingLevel(200) + .moveset([Moves.SURF]) + .enemyAbility(Abilities.BALL_FETCH) + .startingModifier([{ name: "LOCK_CAPSULE" }]) + .lockUnlockable([Unlockables.MINI_BLACK_HOLE, Unlockables.EVIOLITE]); + }); + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + it("should only allow Mini Black Hole and Eviolite outside of Daily if unlocked", async () => { + await game.classicMode.runToSummon(); + await game.startBattle(); + + game.move.select(Moves.SURF); + await game.phaseInterceptor.to(SelectModifierPhase, false); + expect(poolHasEviolite).toBeFalsy(); + expect(poolHasBlackHole).toBeFalsy(); + }); + it("should allow Eviolite and Mini Black Hole in shop when in Daily Run", async () => { + await game.dailyMode.runToSummon(); + await game.startBattle(); + + game.move.select(Moves.SURF); + await game.phaseInterceptor.to(SelectModifierPhase, false); + expect(poolHasEviolite).toBeTruthy(); + expect(poolHasBlackHole).toBeTruthy(); + }); }); - */ }); diff --git a/src/test/utils/helpers/overridesHelper.ts b/src/test/utils/helpers/overridesHelper.ts index d5eaee003db..505641e7e06 100644 --- a/src/test/utils/helpers/overridesHelper.ts +++ b/src/test/utils/helpers/overridesHelper.ts @@ -10,6 +10,7 @@ import { ModifierOverride } from "#app/modifier/modifier-type.js"; import Overrides from "#app/overrides"; import { vi } from "vitest"; import { GameManagerHelper } from "./gameManagerHelper"; +import { Unlockables } from "#app/system/unlockables.js"; /** * Helper to handle overrides in tests @@ -281,6 +282,18 @@ export class OverridesHelper extends GameManagerHelper { return this; } + unlockUnlockable(unlockable: Unlockables[]) { + vi.spyOn(Overrides, "UNLOCK_OVERRIDE", "get").mockReturnValue(unlockable); + this.log("Temporarily unlocked the following content: ", unlockable); + return this; + } + + lockUnlockable(unlockable: Unlockables[]) { + vi.spyOn(Overrides, "DISABLE_UNLOCK_OVERRIDE", "get").mockReturnValue(unlockable); + this.log("Temporarily re-locked the following content: ", unlockable); + return this; + } + private log(...params: any[]) { console.log("Overrides:", ...params); }