mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-30 18:56:10 +00:00
add unit tests and enhancements for item overrides in tests
This commit is contained in:
parent
7b2f21dc8c
commit
132f5ac343
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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> = [];
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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({
|
||||
|
Loading…
Reference in New Issue
Block a user