Merge pull request #124 from AsdarDevelops/trast-to-treasure

Trash to Treasure encounter
This commit is contained in:
ImperialSympathizer 2024-08-01 14:04:21 -04:00 committed by GitHub
commit 93de483b0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 5826 additions and 5309 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,219 @@
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { ModifierRewardPhase } from "#app/phases";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Species } from "#enums/species";
import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#app/modifier/modifier";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import i18next from "#app/plugins/i18n";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
import { PokemonMove } from "#app/field/pokemon";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:trashToTreasure";
const SOUND_EFFECT_WAIT_TIME = 700;
/**
* Trash to Treasure encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/74 | GitHub Issue #74}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TrashToTreasureEncounter: IMysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRASH_TO_TREASURE)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180)
.withMaxAllowedEncounters(1)
.withIntroSpriteConfigs([
{
spriteKey: Species.GARBODOR.toString() + "-gigantamax",
fileRoot: "pokemon",
hasShadow: false,
disableAnimation: true,
scale: 1.5,
y: 8
}
])
.withAutoHideIntroVisuals(false)
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Calculate boss mon
const bossSpecies = getPokemonSpecies(Species.GARBODOR);
const pokemonConfig: EnemyPokemonConfig = {
species: bossSpecies,
isBoss: true,
formIndex: 1, // Gmax
bossSegmentModifier: 1, // +1 Segment from normal
moveSet: [Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH]
};
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 1,
pokemonConfigs: [pokemonConfig],
disableSwitch: true
};
encounter.enemyPartyConfigs = [config];
// Load animations/sfx for Garbodor fight start moves
loadCustomMovesForEncounter(scene, [Moves.TOXIC, Moves.AMNESIA]);
scene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav");
scene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav");
return true;
})
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Play Dig2 and then Venom Drench sfx
doGarbageDig(scene);
})
.withOptionPhase(async (scene: BattleScene) => {
// Gain 2 Leftovers and 2 Shell Bell
transitionMysteryEncounterIntroVisuals(scene);
await tryApplyDigRewardItems(scene);
// Give the player the Black Sludge curse
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE));
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Investigate garbage, battle Gmax Garbodor
scene.setFieldScale(0.75);
await showEncounterText(scene, `${namespace}.option.2.selected_2`);
transitionMysteryEncounterIntroVisuals(scene);
const encounter = scene.currentBattle.mysteryEncounter;
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.TOXIC),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY],
move: new PokemonMove(Moves.AMNESIA),
ignorePp: true
});
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
})
.build()
)
.build();
async function tryApplyDigRewardItems(scene: BattleScene) {
const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType;
const leftovers = generateModifierTypeOption(scene, modifierTypes.LEFTOVERS).type as PokemonHeldItemModifierType;
const party = scene.getParty();
// Iterate over the party until an item was successfully given
// First leftovers
for (const pokemon of party) {
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, leftovers);
break;
}
}
// Second leftovers
for (const pokemon of party) {
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, leftovers);
break;
}
}
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + leftovers.name }), null, true);
// First Shell bell
for (const pokemon of party) {
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, shellBell);
break;
}
}
// Second Shell bell
for (const pokemon of party) {
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, shellBell);
break;
}
}
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + shellBell.name }), null, true);
}
async function doGarbageDig(scene: BattleScene) {
scene.playSound("PRSFX- Dig2");
scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => {
scene.playSound("PRSFX- Dig2");
scene.playSound("PRSFX- Venom Drench", { volume: 2 });
});
scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME * 2, () => {
scene.playSound("PRSFX- Dig2");
});
}

View File

@ -19,6 +19,7 @@ import { AnOfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/enco
import { DelibirdyEncounter } from "#app/data/mystery-encounters/encounters/delibirdy-encounter"; import { DelibirdyEncounter } from "#app/data/mystery-encounters/encounters/delibirdy-encounter";
import { AbsoluteAvariceEncounter } from "#app/data/mystery-encounters/encounters/absolute-avarice-encounter"; import { AbsoluteAvariceEncounter } from "#app/data/mystery-encounters/encounters/absolute-avarice-encounter";
import { ATrainersTestEncounter } from "#app/data/mystery-encounters/encounters/a-trainers-test-encounter"; import { ATrainersTestEncounter } from "#app/data/mystery-encounters/encounters/a-trainers-test-encounter";
import { TrashToTreasureEncounter } from "#app/data/mystery-encounters/encounters/trash-to-treasure-encounter";
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256 // Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1; export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
@ -155,7 +156,8 @@ const anyBiomeEncounters: MysteryEncounterType[] = [
MysteryEncounterType.MYSTERIOUS_CHEST, MysteryEncounterType.MYSTERIOUS_CHEST,
MysteryEncounterType.TRAINING_SESSION, MysteryEncounterType.TRAINING_SESSION,
MysteryEncounterType.DELIBIRDY, MysteryEncounterType.DELIBIRDY,
MysteryEncounterType.A_TRAINERS_TEST MysteryEncounterType.A_TRAINERS_TEST,
MysteryEncounterType.TRASH_TO_TREASURE
]; ];
/** /**
@ -243,6 +245,7 @@ export function initMysteryEncounters() {
allMysteryEncounters[MysteryEncounterType.DELIBIRDY] = DelibirdyEncounter; allMysteryEncounters[MysteryEncounterType.DELIBIRDY] = DelibirdyEncounter;
allMysteryEncounters[MysteryEncounterType.ABSOLUTE_AVARICE] = AbsoluteAvariceEncounter; allMysteryEncounters[MysteryEncounterType.ABSOLUTE_AVARICE] = AbsoluteAvariceEncounter;
allMysteryEncounters[MysteryEncounterType.A_TRAINERS_TEST] = ATrainersTestEncounter; allMysteryEncounters[MysteryEncounterType.A_TRAINERS_TEST] = ATrainersTestEncounter;
allMysteryEncounters[MysteryEncounterType.TRASH_TO_TREASURE] = TrashToTreasureEncounter;
// Add extreme encounters to biome map // Add extreme encounters to biome map
extremeBiomeEncounters.forEach(encounter => { extremeBiomeEncounters.forEach(encounter => {

View File

@ -456,7 +456,6 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust
eggRewards.forEach(eggOptions => { eggRewards.forEach(eggOptions => {
const egg = new Egg(eggOptions); const egg = new Egg(eggOptions);
egg.addEggToGameData(scene); egg.addEggToGameData(scene);
// queueEncounterMessage(scene, `You gained a ${egg.getEggTypeDescriptor(scene)} Egg!`);
}); });
} }

View File

@ -16,5 +16,6 @@ export enum MysteryEncounterType {
AN_OFFER_YOU_CANT_REFUSE, AN_OFFER_YOU_CANT_REFUSE,
DELIBIRDY, DELIBIRDY,
ABSOLUTE_AVARICE, ABSOLUTE_AVARICE,
A_TRAINERS_TEST A_TRAINERS_TEST,
TRASH_TO_TREASURE
} }

View File

@ -257,6 +257,7 @@ export const modifierType: ModifierTypeTranslationEntries = {
"ENEMY_FUSED_CHANCE": { name: "Fusion Token", description: "Adds a 1% chance that a wild Pokémon will be a fusion." }, "ENEMY_FUSED_CHANCE": { name: "Fusion Token", description: "Adds a 1% chance that a wild Pokémon will be a fusion." },
"MYSTERY_ENCOUNTER_SHUCKLE_JUICE": { name: "Shuckle Juice" }, "MYSTERY_ENCOUNTER_SHUCKLE_JUICE": { name: "Shuckle Juice" },
"MYSTERY_ENCOUNTER_BLACK_SLUDGE": { name: "Black Sludge", description: "The stench is so powerful that healing items are no longer available to purchase in shops." },
}, },
SpeciesBoosterItem: { SpeciesBoosterItem: {
"LIGHT_BALL": { name: "Light Ball", description: "It's a mysterious orb that boosts Pikachu's Attack and Sp. Atk stats." }, "LIGHT_BALL": { name: "Light Ball", description: "It's a mysterious orb that boosts Pikachu's Attack and Sp. Atk stats." },

View File

@ -16,6 +16,7 @@ import { anOfferYouCantRefuseDialogue } from "#app/locales/en/mystery-encounters
import { delibirdyDialogue } from "#app/locales/en/mystery-encounters/delibirdy-dialogue"; import { delibirdyDialogue } from "#app/locales/en/mystery-encounters/delibirdy-dialogue";
import { absoluteAvariceDialogue } from "#app/locales/en/mystery-encounters/absolute-avarice-dialogue"; import { absoluteAvariceDialogue } from "#app/locales/en/mystery-encounters/absolute-avarice-dialogue";
import { aTrainersTestDialogue } from "#app/locales/en/mystery-encounters/a-trainers-test-dialogue"; import { aTrainersTestDialogue } from "#app/locales/en/mystery-encounters/a-trainers-test-dialogue";
import { trashToTreasureDialogue } from "#app/locales/en/mystery-encounters/trash-to-treasure-dialogue";
/** /**
* Patterns that can be used: * Patterns that can be used:
@ -57,4 +58,5 @@ export const mysteryEncounter = {
delibirdy: delibirdyDialogue, delibirdy: delibirdyDialogue,
absoluteAvarice: absoluteAvariceDialogue, absoluteAvarice: absoluteAvariceDialogue,
aTrainersTest: aTrainersTestDialogue, aTrainersTest: aTrainersTestDialogue,
trashToTreasure: trashToTreasureDialogue
} as const; } as const;

View File

@ -0,0 +1,22 @@
export const trashToTreasureDialogue = {
intro: "It's a massive pile of garbage!\nWhere did this come from?",
title: "Trash to Treasure",
description: "The garbage heap looms over you, and you can spot some items of value buried amidst the refuse. Are you sure you want to get covered in filth to get them, though?",
query: "What will you do?",
option: {
1: {
label: "Dig for Valuables",
tooltip: "(-) Become Covered in Filth\n(+) Gain Amazing Items",
selected: `You wade through the garbage pile, becoming mired in filth.
$There's no way any respectable shopkeepers\nwill sell you anything in your grimy state!
$You'll just have to make do without shop healing items.
$However, you found some incredible items in the garbage!`,
},
2: {
label: "Investigate Further",
tooltip: "(?) Find the Source of the Garbage",
selected: "You wander around the heap, searching for any indication as to how this might have appeared here...",
selected_2: "Suddenly, the garbage shifts! It wasn't just garbage, it's a Pokémon!"
},
},
};

View File

@ -1474,6 +1474,8 @@ export const modifierTypes = {
} }
return new PokemonBaseStatTotalModifierType(Utils.randSeedInt(20)); return new PokemonBaseStatTotalModifierType(Utils.randSeedInt(20));
}), }),
MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new Modifiers.RemoveHealShopModifier(type)),
}; };
interface ModifierPool { interface ModifierPool {

View File

@ -2268,6 +2268,28 @@ export class LockModifierTiersModifier extends PersistentModifier {
} }
} }
export class RemoveHealShopModifier extends PersistentModifier {
constructor(type: ModifierType, stackCount?: integer) {
super(type, stackCount);
}
match(modifier: Modifier): boolean {
return modifier instanceof RemoveHealShopModifier;
}
clone(): RemoveHealShopModifier {
return new RemoveHealShopModifier(this.type, this.stackCount);
}
apply(args: any[]): boolean {
return true;
}
getMaxStackCount(scene: BattleScene): integer {
return 1;
}
}
export class SwitchEffectTransferModifier extends PokemonHeldItemModifier { export class SwitchEffectTransferModifier extends PokemonHeldItemModifier {
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
super(type, pokemonId, stackCount); super(type, pokemonId, stackCount);

View File

@ -3,7 +3,7 @@ import BattleScene from "../battle-scene";
import { Phase } from "../phase"; import { Phase } from "../phase";
import { Mode } from "../ui/ui"; import { Mode } from "../ui/ui";
import { transitionMysteryEncounterIntroVisuals, OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils"; import { transitionMysteryEncounterIntroVisuals, OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils";
import { CheckSwitchPhase, NewBattlePhase, ReturnPhase, ScanIvsPhase, SelectModifierPhase, SummonPhase, ToggleDoublePositionPhase } from "../phases"; import { CheckSwitchPhase, NewBattlePhase, PostTurnStatusEffectPhase, ReturnPhase, ScanIvsPhase, SelectModifierPhase, SummonPhase, ToggleDoublePositionPhase } from "../phases";
import MysteryEncounterOption, { OptionPhaseCallback } from "../data/mystery-encounters/mystery-encounter-option"; import MysteryEncounterOption, { OptionPhaseCallback } from "../data/mystery-encounters/mystery-encounter-option";
import { getCharVariantFromDialogue } from "../data/dialogue"; import { getCharVariantFromDialogue } from "../data/dialogue";
import { TrainerSlot } from "../data/trainer-config"; import { TrainerSlot } from "../data/trainer-config";
@ -183,6 +183,11 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
pokemon.lapseTags(BattlerTagLapseType.TURN_END); pokemon.lapseTags(BattlerTagLapseType.TURN_END);
}); });
// Remove any status tick phases
while (!!this.scene.findPhase(p => p instanceof PostTurnStatusEffectPhase)) {
this.scene.tryRemovePhase(p => p instanceof PostTurnStatusEffectPhase);
}
super.end(); super.end();
} }
} }

View File

@ -99,7 +99,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
it("should initialize fully ", async () => { it("should initialize fully ", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty); initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = TheStrongStuffEncounter; scene.currentBattle.mysteryEncounter = TheStrongStuffEncounter;
const moveInitSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets"); const moveInitSpy = vi.spyOn(BattleAnims, "initMoveAnim");
const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets"); const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
const { onInit } = TheStrongStuffEncounter; const { onInit } = TheStrongStuffEncounter;

View File

@ -0,0 +1,219 @@
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
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 { getPokemonSpecies } from "#app/data/pokemon-species";
import * as BattleAnims from "#app/data/battle-anims";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
import { CommandPhase, MovePhase, SelectModifierPhase } from "#app/phases";
import { Moves } from "#enums/moves";
import BattleScene from "#app/battle-scene";
import { PokemonMove } from "#app/field/pokemon";
import { Mode } from "#app/ui/ui";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import { HitHealModifier, RemoveHealShopModifier, TurnHealModifier } from "#app/modifier/modifier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
import { TrashToTreasureEncounter } from "#app/data/mystery-encounters/encounters/trash-to-treasure-encounter";
import { ModifierTier } from "#app/modifier/modifier-tier";
const namespace = "mysteryEncounter:trashToTreasure";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
const defaultBiome = Biome.CAVE;
const defaultWave = 45;
describe("Trash to Treasure - 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.CAVE, [MysteryEncounterType.TRASH_TO_TREASURE]],
[Biome.MOUNTAIN, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
])
);
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should have the correct properties", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty);
expect(TrashToTreasureEncounter.encounterType).toBe(MysteryEncounterType.TRASH_TO_TREASURE);
expect(TrashToTreasureEncounter.encounterTier).toBe(MysteryEncounterTier.ULTRA);
expect(TrashToTreasureEncounter.dialogue).toBeDefined();
expect(TrashToTreasureEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
expect(TrashToTreasureEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(TrashToTreasureEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(TrashToTreasureEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(TrashToTreasureEncounter.options.length).toBe(2);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.TRASH_TO_TREASURE);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should initialize fully", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = TrashToTreasureEncounter;
const moveInitSpy = vi.spyOn(BattleAnims, "initMoveAnim");
const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
const { onInit } = TrashToTreasureEncounter;
expect(TrashToTreasureEncounter.onInit).toBeDefined();
TrashToTreasureEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
expect(TrashToTreasureEncounter.enemyPartyConfigs).toEqual([
{
levelAdditiveMultiplier: 1,
disableSwitch: true,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.GARBODOR),
isBoss: true,
formIndex: 1,
bossSegmentModifier: 1,
moveSet: [Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH],
}
],
}
]);
await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled());
await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled());
expect(onInitResult).toBe(true);
});
describe("Option 1 - Dig for Valuables", () => {
it("should have the correct properties", () => {
const option1 = TrashToTreasureEncounter.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 give 2 Leftovers, 2 Shell Bell, and Black Sludge", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty);
await runMysteryEncounterToEnd(game, 1);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
const leftovers = scene.findModifier(m => m instanceof TurnHealModifier) as TurnHealModifier;
expect(leftovers).toBeDefined();
expect(leftovers.stackCount).toBe(2);
const shellBell = scene.findModifier(m => m instanceof HitHealModifier) as HitHealModifier;
expect(shellBell).toBeDefined();
expect(shellBell.stackCount).toBe(2);
const blackSludge = scene.findModifier(m => m instanceof RemoveHealShopModifier) as RemoveHealShopModifier;
expect(blackSludge).toBeDefined();
expect(blackSludge.stackCount).toBe(1);
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty);
await runMysteryEncounterToEnd(game, 1);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
describe("Option 2 - Battle Garbodor", () => {
it("should have the correct properties", () => {
const option1 = TrashToTreasureEncounter.options[1];
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
});
});
it("should start battle against Garbodor", async () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty);
await runMysteryEncounterToEnd(game, 2, 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.GARBODOR);
expect(enemyField[0].moveset).toEqual([new PokemonMove(Moves.PAYBACK), new PokemonMove(Moves.GUNK_SHOT), new PokemonMove(Moves.STOMPING_TANTRUM), new PokemonMove(Moves.DRAIN_PUNCH)]);
// Should have used moves pre-battle
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]);
expect(movePhases.length).toBe(2);
expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.TOXIC).length).toBe(1);
expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.AMNESIA).length).toBe(1);
});
it("should have 2 Rogue, 1 Ultra, 1 Great in rewards", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty);
await runMysteryEncounterToEnd(game, 2, null, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(4);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ROGUE);
expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ROGUE);
expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ULTRA);
expect(modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount).toEqual(ModifierTier.GREAT);
});
});
});

View File

@ -4,7 +4,7 @@ import { getPokeballAtlasKey, PokeballType } from "../data/pokeball";
import { addTextObject, getTextStyleOptions, getModifierTierTextTint, getTextColor, TextStyle } from "./text"; import { addTextObject, getTextStyleOptions, getModifierTierTextTint, getTextColor, TextStyle } from "./text";
import AwaitableUiHandler from "./awaitable-ui-handler"; import AwaitableUiHandler from "./awaitable-ui-handler";
import { Mode } from "./ui"; import { Mode } from "./ui";
import { LockModifierTiersModifier, PokemonHeldItemModifier } from "../modifier/modifier"; import { LockModifierTiersModifier, PokemonHeldItemModifier, RemoveHealShopModifier } from "../modifier/modifier";
import { handleTutorial, Tutorial } from "../tutorial"; import { handleTutorial, Tutorial } from "../tutorial";
import { Button } from "#enums/buttons"; import { Button } from "#enums/buttons";
import MoveInfoOverlay from "./move-info-overlay"; import MoveInfoOverlay from "./move-info-overlay";
@ -166,7 +166,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.updateRerollCostText(); this.updateRerollCostText();
const typeOptions = args[1] as ModifierTypeOption[]; const typeOptions = args[1] as ModifierTypeOption[];
const shopTypeOptions = !this.scene.gameMode.hasNoShop const removeHealShop = this.scene.gameMode.hasNoShop || !!this.scene.findModifier(m => m instanceof RemoveHealShopModifier);
const shopTypeOptions = !removeHealShop
? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)) ? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1))
: []; : [];
const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24; const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24;