Write shop test and add new overrides

Adds new overrides that allow you to force content to be locked or unlocked
These overrides were also added to the OverridesHelper to make them available to tests

Adds a new check function for content unlocks, which returns `true` if it is overrode to be unlocked, `false` if it is overrode to be locked, and the unlock data mapped to a Boolean otherwise

All existing checks (other than the ones that involve actually unlocking things) for unlockables have been changed to use this

Added a pair of new exporting booleans, specifically for my test, that check if Eviolite or Mini Black Hole are in the loot table

please forgive my jank
This commit is contained in:
RedstonewolfX 2024-08-27 10:56:26 -04:00
parent e9b06bdf1b
commit f5b49d812d
6 changed files with 83 additions and 13 deletions

View File

@ -10,7 +10,7 @@ import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "..
import * as Utils from "../utils"; import * as Utils from "../utils";
import { TempBattleStat, getTempBattleStatBoosterItemName, getTempBattleStatName } from "../data/temp-battle-stat"; import { TempBattleStat, getTempBattleStatBoosterItemName, getTempBattleStatName } from "../data/temp-battle-stat";
import { getBerryEffectDescription, getBerryName } from "../data/berry"; 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 { StatusEffect, getStatusEffectDescriptor } from "../data/status-effect";
import { SpeciesFormKey } from "../data/pokemon-species"; import { SpeciesFormKey } from "../data/pokemon-species";
import BattleScene from "../battle-scene"; import BattleScene from "../battle-scene";
@ -1597,7 +1597,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)), new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)),
new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => {
const { gameMode, gameData } = party[0].scene; 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 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; return 0;
@ -1675,7 +1675,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.MULTI_LENS, 18), 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.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.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 => { ].map(m => {
m.setTier(ModifierTier.MASTER); return 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 ]; 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) { export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: ModifierPoolType, rerollCount: integer = 0) {
const pool = getModifierPoolForType(poolType); const pool = getModifierPoolForType(poolType);
poolHasEviolite = false;
poolHasBlackHole = false;
const ignoredIndexes = {}; const ignoredIndexes = {};
const modifierTableData = {}; const modifierTableData = {};
@ -1906,6 +1910,12 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod
ignoredIndexes[t].push(i++); ignoredIndexes[t].push(i++);
return total; return total;
} }
if (modifierType.modifierType.id === "EVIOLITE") {
poolHasEviolite = true;
}
if (modifierType.modifierType.id === "MINI_BLACK_HOLE") {
poolHasBlackHole = true;
}
thresholds.set(total, i++); thresholds.set(total, i++);
return total; return total;
}, 0); }, 0);

View File

@ -13,6 +13,7 @@ import { Gender } from "./data/gender";
import { allSpecies } from "./data/pokemon-species"; // eslint-disable-line @typescript-eslint/no-unused-vars import { allSpecies } from "./data/pokemon-species"; // eslint-disable-line @typescript-eslint/no-unused-vars
import { Variant } from "./data/variant"; import { Variant } from "./data/variant";
import { type ModifierOverride } from "./modifier/modifier-type"; import { type ModifierOverride } from "./modifier/modifier-type";
import { Unlockables } from "./system/unlockables";
/** /**
* Overrides that are using when testing different in game situations * Overrides that are using when testing different in game situations
@ -69,6 +70,10 @@ class DefaultOverrides {
[PokeballType.MASTER_BALL]: 0, [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 // PLAYER OVERRIDES

View File

@ -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 { regenerateModifierPoolThresholds, ModifierPoolType, modifierTypes, getDailyRunStarterModifiers } from "#app/modifier/modifier-type.js";
import { Phase } from "#app/phase.js"; import { Phase } from "#app/phase.js";
import { SessionSaveData } from "#app/system/game-data.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 { vouchers } from "#app/system/voucher.js";
import { OptionSelectItem, OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler.js"; import { OptionSelectItem, OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler.js";
import { SaveSlotUiMode } from "#app/ui/save-slot-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.scene.ui.clearText();
this.end(); this.end();
}; };
if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { if (isUnlocked(Unlockables.ENDLESS_MODE, this.scene.gameData)) {
const options: OptionSelectItem[] = [ const options: OptionSelectItem[] = [
{ {
label: GameMode.getModeName(GameModes.CLASSIC), 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({ options.push({
label: GameMode.getModeName(GameModes.SPLICED_ENDLESS), label: GameMode.getModeName(GameModes.SPLICED_ENDLESS),
handler: () => { handler: () => {

View File

@ -1,5 +1,7 @@
import i18next from "i18next"; import i18next from "i18next";
import { GameMode, GameModes } from "../game-mode"; import { GameMode, GameModes } from "../game-mode";
import Overrides from "#app/overrides";
import { GameData } from "./game-data";
export enum Unlockables { export enum Unlockables {
ENDLESS_MODE, ENDLESS_MODE,
@ -8,6 +10,16 @@ export enum Unlockables {
EVIOLITE 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) { export function getUnlockableName(unlockable: Unlockables) {
switch (unlockable) { switch (unlockable) {
case Unlockables.ENDLESS_MODE: case Unlockables.ENDLESS_MODE:

View File

@ -1,6 +1,11 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import GameManager from "./utils/gameManager"; import GameManager from "./utils/gameManager";
import { MapModifier } from "#app/modifier/modifier.js"; 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", () => { describe("Daily Mode", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -32,13 +37,38 @@ describe("Daily Mode", () => {
expect(game.scene.getModifiers(MapModifier).length).toBeGreaterThan(0); expect(game.scene.getModifiers(MapModifier).length).toBeGreaterThan(0);
}); });
/*
Can't figure out how to check the shop's item pool :(
describe("Shop modifications", async () => { describe("Shop modifications", async () => {
const modifierPhase = new SelectModifierPhase(game.scene); beforeEach(() => {
game.scene.unshiftPhase(modifierPhase); game = new GameManager(phaserGame);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(getModifierThresholdPool(ModifierPoolType.PLAYER)); 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();
});
}); });
*/
}); });

View File

@ -10,6 +10,7 @@ import { ModifierOverride } from "#app/modifier/modifier-type.js";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { vi } from "vitest"; import { vi } from "vitest";
import { GameManagerHelper } from "./gameManagerHelper"; import { GameManagerHelper } from "./gameManagerHelper";
import { Unlockables } from "#app/system/unlockables.js";
/** /**
* Helper to handle overrides in tests * Helper to handle overrides in tests
@ -281,6 +282,18 @@ export class OverridesHelper extends GameManagerHelper {
return this; 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[]) { private log(...params: any[]) {
console.log("Overrides:", ...params); console.log("Overrides:", ...params);
} }