add unit tests and enhancements for item overrides in tests

This commit is contained in:
ImperialSympathizer 2024-07-28 11:56:16 -04:00
parent 7b2f21dc8c
commit 132f5ac343
12 changed files with 634 additions and 91 deletions

View File

@ -1072,12 +1072,14 @@ export default class BattleScene extends SceneBase {
this.field.add(newTrainer);
}
// TODO: remove this once spawn rates are finalized
// TODO: remove these once ME spawn rates are finalized
// let testStartingWeight = 0;
// while (testStartingWeight < 3) {
// calculateMEAggregateStats(this, testStartingWeight);
// testStartingWeight += 2;
// }
// calculateRareSpawnAggregateStats(this, 14);
// Check for mystery encounter
// Can only occur in place of a standard wild battle, waves 10-180
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(newWaveIndex) && newWaveIndex < 180 && newWaveIndex > 10) {

View File

@ -168,11 +168,6 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOutroDialogue([
{
text: `${namespace}:outro`,
}
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
@ -423,8 +418,6 @@ function doGreedentSpriteSteal(scene: BattleScene) {
function doGreedentEatBerries(scene: BattleScene) {
const greedentSprites = scene.currentBattle.mysteryEncounter.introVisuals.getSpriteAtIndex(0);
// scene.playSound("Follow Me");
let index = 1;
scene.tweens.add({
targets: greedentSprites,

View File

@ -1,18 +1,20 @@
import { leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { HeldItemRequirement, MoneyRequirement } from "../mystery-encounter-requirements";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "../mystery-encounter-requirements";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { BerryModifier, PokemonBaseStatModifier, PokemonBaseStatTotalModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, TerastallizeModifier } from "#app/modifier/modifier";
import { BerryModifier, HealingBoosterModifier, HiddenAbilityRateBoosterModifier, LevelIncrementBoosterModifier, PokemonBaseStatModifier, PokemonBaseStatTotalModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, PreserveBerryModifier, TerastallizeModifier } from "#app/modifier/modifier";
import { ModifierRewardPhase } from "#app/phases";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import i18next from "#app/plugins/i18n";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:delibirdy";
@ -39,6 +41,10 @@ export const DelibirdyEncounter: IMysteryEncounter =
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Must have enough money for it to spawn at the very least
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( // 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)
))
.withIntroSpriteConfigs([
{
spriteKey: Species.DELIBIRD.toString(),
@ -82,7 +88,7 @@ export const DelibirdyEncounter: IMysteryEncounter =
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, 2.75)
.withSceneMoneyRequirement(0, 2.75) // Must have money to spawn
.withDialogue({
buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`,
@ -99,7 +105,19 @@ export const DelibirdyEncounter: IMysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Give the player an Ability Charm
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ABILITY_CHARM));
// Check if the player has max stacks of that item already
const existing = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier) as HiddenAbilityRateBoosterModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true);
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ABILITY_CHARM));
}
leaveEncounterWithoutBattle(scene, true);
})
.build()
@ -159,11 +177,34 @@ export const DelibirdyEncounter: IMysteryEncounter =
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const modifier = encounter.misc.chosenModifier;
// Give the player a Candy Jar if they gave a Berry, and a Healing Charm for Reviver Seed
if (modifier.type.name.includes("Berry")) {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.CANDY_JAR));
// Check if the player has max stacks of that Candy Jar already
const existing = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true);
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.CANDY_JAR));
}
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM));
// Check if the player has max stacks of that Healing Charm already
const existing = scene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true);
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM));
}
}
// Remove the modifier if its stacks go to 0
@ -231,8 +272,19 @@ export const DelibirdyEncounter: IMysteryEncounter =
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const modifier = encounter.misc.chosenModifier;
// Give the player a Berry Pouch
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH));
// Check if the player has max stacks of Berry Pouch already
const existing = scene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true);
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH));
}
// Remove the modifier if its stacks go to 0
modifier.stackCount -= 1;

View File

@ -1,11 +1,11 @@
import { BattlerIndex, BattleType } from "#app/battle";
import { biomeLinks } from "#app/data/biomes";
import { biomeLinks, BiomePoolTier } from "#app/data/biomes";
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import Pokemon, { FieldPosition, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier";
import { CustomModifierSettings, getModifierPoolForType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { CustomModifierSettings, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import * as Overrides from "#app/overrides";
import { BattleEndPhase, EggLapsePhase, ExpPhase, GameOverPhase, ModifierRewardPhase, MovePhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases";
import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases";
@ -344,20 +344,10 @@ export function generateModifierTypeOption(scene: BattleScene, modifier: () => M
const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier);
let result: ModifierType = modifierTypes[modifierId]?.();
// Gets tier of item by checking player item pool
const modifierPool = getModifierPoolForType(ModifierPoolType.PLAYER);
Object.keys(modifierPool).every(modifierTier => {
const modType = modifierPool[modifierTier].find(m => {
if (m.modifierType.id === modifierId) {
return m;
}
});
if (modType) {
result = modType.modifierType;
return false;
}
return true;
});
// Populates item id and tier (order matters)
result = result
.withIdFromFunc(modifierTypes[modifierId])
.withTierFromPool();
result = result instanceof ModifierTypeGenerator ? result.generateType(scene.getParty(), pregenArgs) : result;
return new ModifierTypeOption(result, 0);
@ -881,3 +871,72 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
console.log(stats);
}
/**
* TODO: remove once encounter spawn rate is finalized
* Just a helper function to calculate aggregate stats for MEs in a Classic run
* @param scene
* @param luckValue - 0 to 14
*/
export function calculateRareSpawnAggregateStats(scene: BattleScene, luckValue: number) {
const numRuns = 1000;
let run = 0;
const calculateNumRareEncounters = (): any[] => {
const bossEncountersByRarity = [0, 0, 0, 0];
scene.setSeed(Utils.randomString(24));
scene.resetSeed();
// There are 12 wild boss floors
for (let i = 0; i < 12; i++) {
// Roll boss tier
// luck influences encounter rarity
let luckModifier = 0;
if (!isNaN(luckValue)) {
luckModifier = luckValue * 0.5;
}
const tierValue = Utils.randSeedInt(64 - luckModifier);
const tier = tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE;
switch (tier) {
default:
case BiomePoolTier.BOSS:
++bossEncountersByRarity[0];
break;
case BiomePoolTier.BOSS_RARE:
++bossEncountersByRarity[1];
break;
case BiomePoolTier.BOSS_SUPER_RARE:
++bossEncountersByRarity[2];
break;
case BiomePoolTier.BOSS_ULTRA_RARE:
++bossEncountersByRarity[3];
break;
}
}
return bossEncountersByRarity;
};
const encounterRuns: number[][] = [];
while (run < numRuns) {
scene.executeWithSeedOffset(() => {
const bossEncountersByRarity = calculateNumRareEncounters();
encounterRuns.push(bossEncountersByRarity);
}, 1000 * run);
run++;
}
const n = encounterRuns.length;
// const totalEncountersInRun = encounterRuns.map(run => run.reduce((a, b) => a + b));
// const totalMean = totalEncountersInRun.reduce((a, b) => a + b) / n;
// const totalStd = Math.sqrt(totalEncountersInRun.map(x => Math.pow(x - totalMean, 2)).reduce((a, b) => a + b) / n);
const commonMean = encounterRuns.reduce((a, b) => a + b[0], 0) / n;
const rareMean = encounterRuns.reduce((a, b) => a + b[1], 0) / n;
const superRareMean = encounterRuns.reduce((a, b) => a + b[2], 0) / n;
const ultraRareMean = encounterRuns.reduce((a, b) => a + b[3], 0) / n;
const stats = `Avg Commons: ${commonMean}\nAvg Rare: ${rareMean}\nAvg Super Rare: ${superRareMean}\nAvg Ultra Rare: ${ultraRareMean}\n`;
console.log(stats);
}

View File

@ -109,11 +109,35 @@ export class ModifierType {
return null;
}
/**
* Populates item id for ModifierType instance
* @param func
*/
withIdFromFunc(func: ModifierTypeFunc): ModifierType {
this.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === func);
return this;
}
/**
* 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
* @param poolType - Default 'ModifierPoolType.PLAYER'. Which pool to lookup item tier from
*/
withTierFromPool(poolType: ModifierPoolType = ModifierPoolType.PLAYER): ModifierType {
const modifierPool = getModifierPoolForType(poolType);
Object.values(modifierPool).every(weightedModifiers => {
weightedModifiers.every(m => {
if (m.modifierType.id === this.id) {
this.tier = m.modifierType.tier;
return false; // Early lookup return if tier found
}
});
return !this.tier; // Early lookup return if tier found
});
return this;
}
newModifier(...args: any[]): Modifier {
return this.newModifierFunc(this, args);
}
@ -1841,6 +1865,21 @@ export function getModifierTypeFuncById(id: string): ModifierTypeFunc {
return modifierTypes[id];
}
/**
* Generates modifier options for a SelectModifierPhase
* @param count - Determines the number of items to generate
* @param party - Party is required for generating proper modifier pools
* @param modifierTiers - (Optional) If specified, rolls items in the specified tiers. Commonly used for tier-locking with Lock Capsule.
* @param customModifierSettings - (Optional) If specified, can customize the item shop rewards further.
* - `guaranteedModifierTypeOptions?: ModifierTypeOption[]` - If specified, will override the first X items to be specific modifier options (these should be pre-genned).
* - `guaranteedModifierTypeFuncs?: ModifierTypeFunc[]` - If specified, will override the next X items to be auto-generated from specific modifier functions (these don't have to be pre-genned).
* - `guaranteedModifierTiers?: ModifierTier[]` - If specified, will override the next X items to be the specified tier. These can upgrade with luck.
* - `fillRemaining?: boolean` - Default 'false'. If set to true, will fill the remainder of shop items that were not overridden by the 3 options above, up to the 'count' param value.
* - Example: `count = 4`, `customModifierSettings = { guaranteedModifierTiers: [ModifierTier.GREAT], fillRemaining: true }`,
* - The first item in the shop will be `GREAT` tier, and the remaining 3 items will be generated normally.
* - If `fillRemaining = false` in the same scenario, only 1 `GREAT` tier item will appear in the shop (regardless of `count` value).
* - `rerollMultiplier?: number` - If specified, can adjust the amount of money required for a shop reroll. If set to 0, the shop will not allow rerolls at all.
*/
export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemon[], modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings): ModifierTypeOption[] {
const options: ModifierTypeOption[] = [];
const retryCount = Math.min(count * 5, 50);
@ -1849,32 +1888,21 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo
options.push(getModifierTypeOptionWithRetry(options, retryCount, party, modifierTiers?.length > i ? modifierTiers[i] : undefined));
});
} else {
// Guaranteed mods first
if (customModifierSettings?.guaranteedModifierTypeOptions?.length) {
customModifierSettings?.guaranteedModifierTypeOptions.forEach((option) => {
options.push(option);
});
// Guaranteed mod options first
if (customModifierSettings?.guaranteedModifierTypeOptions?.length > 0) {
options.push(...customModifierSettings.guaranteedModifierTypeOptions);
}
// Guaranteed mod funcs second
if (customModifierSettings?.guaranteedModifierTypeFuncs?.length) {
// Guaranteed mod functions second
if (customModifierSettings?.guaranteedModifierTypeFuncs?.length > 0) {
customModifierSettings?.guaranteedModifierTypeFuncs.forEach((mod, i) => {
const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === mod);
let guaranteedMod: ModifierType = modifierTypes[modifierId]?.();
// Gets tier of item by checking player item pool
Object.keys(modifierPool).every(modifierTier => {
const modType = modifierPool[modifierTier].find(m => {
if (m.modifierType.id === modifierId) {
return m;
}
});
if (modType) {
guaranteedMod = modType.modifierType;
return false;
}
return true;
});
// Populates item id and tier
guaranteedMod = guaranteedMod
.withIdFromFunc(modifierTypes[modifierId])
.withTierFromPool();
const modType = guaranteedMod instanceof ModifierTypeGenerator ? guaranteedMod.generateType(party) : guaranteedMod;
const option = new ModifierTypeOption(modType, 0);
@ -1883,7 +1911,7 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo
}
// Guaranteed tiers third
if (customModifierSettings?.guaranteedModifierTiers?.length) {
if (customModifierSettings?.guaranteedModifierTiers?.length > 0) {
customModifierSettings?.guaranteedModifierTiers.forEach((tier) => {
options.push(getModifierTypeOptionWithRetry(options, retryCount, party, tier));
});
@ -1900,8 +1928,12 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo
// OVERRIDE IF NECESSARY
if (Overrides.ITEM_REWARD_OVERRIDE?.length) {
options.forEach((mod, i) => {
// @ts-ignore: keeps throwing don't use string as index error in typedoc run
const override = modifierTypes[Overrides.ITEM_REWARD_OVERRIDE[i]]?.();
let override = modifierTypes[Overrides.ITEM_REWARD_OVERRIDE[i]]?.();
// Populates item id and tier
override = override
.withIdFromFunc(modifierTypes[Overrides.ITEM_REWARD_OVERRIDE[i]])
.withTierFromPool();
mod.type = (override instanceof ModifierTypeGenerator ? override.generateType(party) : override) || mod.type;
});
}

View File

@ -21,7 +21,7 @@ import { VoucherType } from "../system/voucher";
import { FormChangeItem, SpeciesFormChangeItemTrigger } from "../data/pokemon-forms";
import { Nature } from "#app/data/nature";
import * as Overrides from "../overrides";
import { ModifierType, modifierTypes } from "./modifier-type";
import { ModifierType, ModifierTypeGenerator, modifierTypes } from "./modifier-type";
import { Command } from "#app/ui/command-ui-handler.js";
import { Species } from "#enums/species";
import i18next from "i18next";
@ -2683,8 +2683,12 @@ export function overrideModifiers(scene: BattleScene, player: boolean = true): v
if (!modifierTypes.hasOwnProperty(modifierName)) {
return;
} // if the modifier does not exist, we skip it
const modifierType: ModifierType = modifierTypes[modifierName]();
const modifier: PersistentModifier = modifierType.withIdFromFunc(modifierTypes[modifierName]).newModifier() as PersistentModifier;
let modifierType: ModifierType = modifierTypes[modifierName]();
if (modifierType instanceof ModifierTypeGenerator) {
modifierType = (modifierType as ModifierTypeGenerator).generateType(scene.getParty(), [item.type]);
}
modifierType = modifierType.withIdFromFunc(modifierTypes[modifierName]);
const modifier: PersistentModifier = modifierType.newModifier(scene.getParty()[0]) as PersistentModifier;
modifier.stackCount = qty;
if (player) {
scene.addModifier(modifier, true, false, false, true);

View File

@ -117,9 +117,9 @@ export const EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0;
*/
// 1 to 256, set to null to ignore
export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = 256;
export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = null;
export const MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null;
export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.ABSOLUTE_AVARICE;
export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null;
/**
* MODIFIER / ITEM OVERRIDES
@ -137,7 +137,7 @@ export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounter
* - BerryType is for BERRY
* - SpeciesStatBoosterItem is for SPECIES_STAT_BOOSTER
*/
interface ModifierOverride {
export interface ModifierOverride {
name: keyof typeof modifierTypes & string,
count?: integer
type?: TempBattleStat|Stat|Nature|Type|BerryType|SpeciesStatBoosterItem
@ -155,4 +155,4 @@ export const NEVER_CRIT_OVERRIDE: boolean = false;
* If less items are listed than rolled, only some items will be replaced
* If more items are listed than rolled, only the first X items will be shown, where X is the number of items rolled.
*/
export const ITEM_REWARD_OVERRIDE: Array<String> = [];
export const ITEM_REWARD_OVERRIDE: Array<keyof typeof modifierTypes & string> = [];

View File

@ -1,6 +1,6 @@
import { Button } from "#app/enums/buttons";
import { CommandPhase, MessagePhase, VictoryPhase } from "#app/phases";
import { MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases";
import { MysteryEncounterBattlePhase, MysteryEncounterOptionSelectedPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases";
import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler";
import { Mode } from "#app/ui/ui";
import GameManager from "../utils/gameManager";
@ -26,29 +26,39 @@ export async function runMysteryEncounterToEnd(game: GameManager, optionNo: numb
game.onNextPrompt("MysteryEncounterOptionSelectedPhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
uiHandler.processInput(Button.ACTION);
});
// If a battle is started, fast forward to end of the battle
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.clearPhaseQueue();
game.scene.clearPhaseQueueSplice();
game.scene.unshiftPhase(new VictoryPhase(game.scene, 0));
game.endPhase();
});
// Handle end of battle trainer messages
game.onNextPrompt("TrainerVictoryPhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
// Handle egg hatch dialogue
game.onNextPrompt("EggLapsePhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
}, () => game.isCurrentPhase(MysteryEncounterBattlePhase) || game.isCurrentPhase(MysteryEncounterRewardsPhase));
if (isBattle) {
game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
game.setMode(Mode.MESSAGE);
game.endPhase();
}, () => game.isCurrentPhase(CommandPhase));
game.onNextPrompt("CheckSwitchPhase", Mode.MESSAGE, () => {
game.setMode(Mode.MESSAGE);
game.endPhase();
}, () => game.isCurrentPhase(CommandPhase));
// If a battle is started, fast forward to end of the battle
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.clearPhaseQueue();
game.scene.clearPhaseQueueSplice();
game.scene.unshiftPhase(new VictoryPhase(game.scene, 0));
game.endPhase();
});
// Handle end of battle trainer messages
game.onNextPrompt("TrainerVictoryPhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
// Handle egg hatch dialogue
game.onNextPrompt("EggLapsePhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
await game.phaseInterceptor.to(CommandPhase);
} else {
await game.phaseInterceptor.to(MysteryEncounterRewardsPhase);
@ -60,7 +70,7 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
}, () => game.isCurrentPhase(MysteryEncounterOptionSelectedPhase));
if (game.isCurrentPhase(MessagePhase)) {
await game.phaseInterceptor.run(MessagePhase);
@ -70,7 +80,7 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN
game.onNextPrompt("MysteryEncounterPhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
uiHandler.processInput(Button.ACTION);
});
}, () => game.isCurrentPhase(MysteryEncounterOptionSelectedPhase));
await game.phaseInterceptor.to(MysteryEncounterPhase, true);

View File

@ -0,0 +1,266 @@
import { Biome } from "#app/enums/biome";
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
import { Species } from "#app/enums/species";
import GameManager from "#app/test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
import BattleScene from "#app/battle-scene";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import { BerryModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { BerryType } from "#enums/berry-type";
import { AbsoluteAvariceEncounter } from "#app/data/mystery-encounters/encounters/absolute-avarice-encounter";
import { CommandPhase, MovePhase, SelectModifierPhase } from "#app/phases";
import { Moves } from "#enums/moves";
const namespace = "mysteryEncounter:absoluteAvarice";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
const defaultBiome = Biome.PLAINS;
const defaultWave = 45;
describe("Absolute Avarice - Mystery Encounter", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let scene: BattleScene;
beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
});
beforeEach(async () => {
game = new GameManager(phaserGame);
scene = game.scene;
game.override.mysteryEncounterChance(100);
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
[Biome.PLAINS, [MysteryEncounterType.ABSOLUTE_AVARICE]],
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
])
);
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should have the correct properties", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
expect(AbsoluteAvariceEncounter.encounterType).toBe(MysteryEncounterType.ABSOLUTE_AVARICE);
expect(AbsoluteAvariceEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT);
expect(AbsoluteAvariceEncounter.dialogue).toBeDefined();
expect(AbsoluteAvariceEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}:intro` }]);
expect(AbsoluteAvariceEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}:title`);
expect(AbsoluteAvariceEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}:description`);
expect(AbsoluteAvariceEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}:query`);
expect(AbsoluteAvariceEncounter.options.length).toBe(3);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.ABSOLUTE_AVARICE);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should not spawn outside of proper biomes", async () => {
game.override.startingBiome(Biome.VOLCANO);
await game.runToMysteryEncounter();
expect(game.scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.ABSOLUTE_AVARICE);
});
it("should not spawn if player does not have enough berries", async () => {
scene.modifiers = [];
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.ABSOLUTE_AVARICE);
});
it("should spawn if player has enough berries", async () => {
game.override.starterHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}]);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).toBe(MysteryEncounterType.ABSOLUTE_AVARICE);
});
it("should remove all player's berries at the start of the encounter", async () => {
game.override.starterHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}]);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).toBe(MysteryEncounterType.ABSOLUTE_AVARICE);
expect(scene.modifiers?.length).toBe(0);
});
describe("Option 1 - Fight the Greedent", () => {
it("should have the correct properties", () => {
const option1 = AbsoluteAvariceEncounter.options[0];
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`,
selected: [
{
text: `${namespace}:option:1:selected`,
},
],
});
});
it("should start battle against Greedent", async () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(Species.GREEDENT);
const moveset = enemyField[0].moveset.map(m => m.moveId);
expect(moveset?.length).toBe(4);
expect(moveset).toEqual([Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF]);
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]);
expect(movePhases.length).toBe(1);
expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.STUFF_CHEEKS).length).toBe(1); // Stuff Cheeks used before battle
});
it("should give reviver seed to each pokemon after battle", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
for (const partyPokemon of scene.getParty()) {
const pokemonId = partyPokemon.id;
const pokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).pokemonId === pokemonId, true) as PokemonHeldItemModifier[];
const revSeed = pokemonItems.find(i => i.type.name === "Reviver Seed");
expect(revSeed).toBeDefined;
expect(revSeed.stackCount).toBe(1);
}
});
});
describe("Option 2 - Reason with It", () => {
it("should have the correct properties", () => {
const option = AbsoluteAvariceEncounter.options[1];
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:2:label`,
buttonTooltip: `${namespace}:option:2:tooltip`,
selected: [
{
text: `${namespace}:option:2:selected`,
},
],
});
});
it("Should return 3 (2/5ths floored) berries if 8 were stolen", async () => {
game.override.starterHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}, {name: "BERRY", count: 3, type: BerryType.APICOT}]);
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
expect(scene.currentBattle?.mysteryEncounter?.encounterType).toBe(MysteryEncounterType.ABSOLUTE_AVARICE);
expect(scene.modifiers?.length).toBe(0);
await runMysteryEncounterToEnd(game, 2);
const berriesAfter = scene.findModifiers(m => m instanceof BerryModifier);
const berryCountAfter = berriesAfter.reduce((a, b) => a + b.stackCount, 0);
expect(berriesAfter).toBeDefined();
expect(berryCountAfter).toBe(3);
});
it("Should return 2 (2/5ths floored) berries if 7 were stolen", async () => {
game.override.starterHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}, {name: "BERRY", count: 2, type: BerryType.APICOT}]);
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
expect(scene.currentBattle?.mysteryEncounter?.encounterType).toBe(MysteryEncounterType.ABSOLUTE_AVARICE);
expect(scene.modifiers?.length).toBe(0);
await runMysteryEncounterToEnd(game, 2);
const berriesAfter = scene.findModifiers(m => m instanceof BerryModifier);
const berryCountAfter = berriesAfter.reduce((a, b) => a + b.stackCount, 0);
expect(berriesAfter).toBeDefined();
expect(berryCountAfter).toBe(2);
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
describe("Option 3 - Let it have the food", () => {
it("should have the correct properties", () => {
const option = AbsoluteAvariceEncounter.options[2];
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:3:label`,
buttonTooltip: `${namespace}:option:3:tooltip`,
selected: [
{
text: `${namespace}:option:3:selected`,
},
],
});
});
it("should add Greedent to the party", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
const partyCountBefore = scene.getParty().length;
await runMysteryEncounterToEnd(game, 3);
const partyCountAfter = scene.getParty().length;
expect(partyCountBefore + 1).toBe(partyCountAfter);
const greedent = scene.getParty()[scene.getParty().length - 1];
expect(greedent.species.speciesId).toBe(Species.GREEDENT);
const moveset = greedent.moveset.map(m => m.moveId);
expect(moveset?.length).toBe(4);
expect(moveset).toEqual([Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF]);
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
await runMysteryEncounterToEnd(game, 3);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
});

View File

@ -11,7 +11,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { DelibirdyEncounter } from "#app/data/mystery-encounters/encounters/delibirdy-encounter";
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { BerryModifier, HealingBoosterModifier, HiddenAbilityRateBoosterModifier, LevelIncrementBoosterModifier, PokemonInstantReviveModifier, PokemonNatureWeightModifier, PreserveBerryModifier } from "#app/modifier/modifier";
import { BerryModifier, HealingBoosterModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, LevelIncrementBoosterModifier, PokemonInstantReviveModifier, PokemonNatureWeightModifier, PreserveBerryModifier } from "#app/modifier/modifier";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { generateModifierTypeOption } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes } from "#app/modifier/modifier-type";
@ -131,6 +131,28 @@ describe("Delibird-y - Mystery Encounter", () => {
expect(itemModifier.stackCount).toBe(1);
});
it("Should give the player a Shell Bell if they have max stacks of Berry Pouches", async () => {
scene.money = 200000;
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
// 5 Healing Charms
scene.modifiers = [];
const abilityCharm = generateModifierTypeOption(scene, modifierTypes.ABILITY_CHARM).type.newModifier() as HiddenAbilityRateBoosterModifier;
abilityCharm.stackCount = 4;
await scene.addModifier(abilityCharm, true, false, false, true);
await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 1);
const abilityCharmAfter = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier);
const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier);
expect(abilityCharmAfter).toBeDefined();
expect(abilityCharmAfter.stackCount).toBe(4);
expect(shellBellAfter).toBeDefined();
expect(shellBellAfter.stackCount).toBe(1);
});
it("should be disabled if player does not have enough money", async () => {
scene.money = 0;
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
@ -221,6 +243,64 @@ describe("Delibird-y - Mystery Encounter", () => {
expect(healingCharmAfter.stackCount).toBe(1);
});
it("Should give the player a Shell Bell if they have max stacks of Candy Jars", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
// 99 Candy Jars
scene.modifiers = [];
const candyJar = generateModifierTypeOption(scene, modifierTypes.CANDY_JAR).type.newModifier() as LevelIncrementBoosterModifier;
candyJar.stackCount = 99;
await scene.addModifier(candyJar, true, false, false, true);
const sitrus = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type;
// Sitrus berries on party
const sitrusMod = sitrus.newModifier(scene.getParty()[0]) as BerryModifier;
sitrusMod.stackCount = 2;
await scene.addModifier(sitrusMod, true, false, false, true);
await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1});
const sitrusAfter = scene.findModifier(m => m instanceof BerryModifier);
const candyJarAfter = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier);
const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier);
expect(sitrusAfter.stackCount).toBe(1);
expect(candyJarAfter).toBeDefined();
expect(candyJarAfter.stackCount).toBe(99);
expect(shellBellAfter).toBeDefined();
expect(shellBellAfter.stackCount).toBe(1);
});
it("Should give the player a Shell Bell if they have max stacks of Healing Charms", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
// 5 Healing Charms
scene.modifiers = [];
const healingCharm = generateModifierTypeOption(scene, modifierTypes.HEALING_CHARM).type.newModifier() as HealingBoosterModifier;
healingCharm.stackCount = 5;
await scene.addModifier(healingCharm, true, false, false, true);
// Set 1 Reviver Seed on party lead
const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type;
const modifier = revSeed.newModifier(scene.getParty()[0]) as PokemonInstantReviveModifier;
modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1});
const reviverSeedAfter = scene.findModifier(m => m instanceof PokemonInstantReviveModifier);
const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier);
const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier);
expect(reviverSeedAfter).toBeUndefined();
expect(healingCharmAfter).toBeDefined();
expect(healingCharmAfter.stackCount).toBe(5);
expect(shellBellAfter).toBeDefined();
expect(shellBellAfter.stackCount).toBe(1);
});
it("should be disabled if player does not have any proper items", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
@ -325,6 +405,35 @@ describe("Delibird-y - Mystery Encounter", () => {
expect(berryPouchAfter.stackCount).toBe(1);
});
it("Should give the player a Shell Bell if they have max stacks of Berry Pouches", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
// 5 Healing Charms
scene.modifiers = [];
const healingCharm = generateModifierTypeOption(scene, modifierTypes.BERRY_POUCH).type.newModifier() as PreserveBerryModifier;
healingCharm.stackCount = 3;
await scene.addModifier(healingCharm, true, false, false, true);
// Set 1 Soul Dew on party lead
const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type;
const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1});
const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier);
const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier);
const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier);
expect(soulDewAfter).toBeUndefined();
expect(berryPouchAfter).toBeDefined();
expect(berryPouchAfter.stackCount).toBe(3);
expect(shellBellAfter).toBeDefined();
expect(shellBellAfter.stackCount).toBe(1);
});
it("should be disabled if player does not have any proper items", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);

View File

@ -8,6 +8,7 @@ import * as overrides from "#app/overrides";
import * as GameMode from "#app/game-mode";
import { GameModes, getGameMode } from "#app/game-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { ModifierOverride } from "#app/overrides";
/**
* Helper to handle overrides in tests
@ -117,6 +118,12 @@ export class OverridesHelper {
return spy;
}
starterHeldItems(modifiers: ModifierOverride[]) {
const spy = vi.spyOn(Overrides, "STARTING_MODIFIER_OVERRIDE", "get").mockReturnValue(modifiers);
this.log(`Starting modifiers set to ${modifiers.map(m => JSON.stringify(m)).join(", ")}!`);
return spy;
}
private log(...params: any[]) {
console.log("Overrides:", ...params);
}

View File

@ -48,6 +48,14 @@ import {
PostMysteryEncounterPhase
} from "#app/phases/mystery-encounter-phases";
export interface PromptHandler {
phaseTarget?;
mode?;
callback?;
expireFn?;
awaitingActionInput?;
}
export default class PhaseInterceptor {
public scene;
public phases = {};
@ -56,7 +64,7 @@ export default class PhaseInterceptor {
private interval;
private promptInterval;
private intervalRun;
private prompts;
private prompts: PromptHandler[];
private phaseFrom;
private inProgress;
private originalSetMode;
@ -337,6 +345,7 @@ export default class PhaseInterceptor {
* @param mode - The mode of the UI.
* @param callback - The callback function to execute.
* @param expireFn - The function to determine if the prompt has expired.
* @param awaitingActionInput
*/
addToNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn: () => void, awaitingActionInput: boolean = false) {
this.prompts.push({