mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-27 17:26:11 +00:00
Merge pull request #124 from AsdarDevelops/trast-to-treasure
Trash to Treasure encounter
This commit is contained in:
commit
93de483b0b
10625
public/images/items.json
10625
public/images/items.json
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 |
BIN
public/images/items/black_sludge.png
Normal file
BIN
public/images/items/black_sludge.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
@ -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");
|
||||
});
|
||||
}
|
@ -19,6 +19,7 @@ import { AnOfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/enco
|
||||
import { DelibirdyEncounter } from "#app/data/mystery-encounters/encounters/delibirdy-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 { 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
|
||||
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
|
||||
@ -155,7 +156,8 @@ const anyBiomeEncounters: MysteryEncounterType[] = [
|
||||
MysteryEncounterType.MYSTERIOUS_CHEST,
|
||||
MysteryEncounterType.TRAINING_SESSION,
|
||||
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.ABSOLUTE_AVARICE] = AbsoluteAvariceEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.A_TRAINERS_TEST] = ATrainersTestEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.TRASH_TO_TREASURE] = TrashToTreasureEncounter;
|
||||
|
||||
// Add extreme encounters to biome map
|
||||
extremeBiomeEncounters.forEach(encounter => {
|
||||
|
@ -456,7 +456,6 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust
|
||||
eggRewards.forEach(eggOptions => {
|
||||
const egg = new Egg(eggOptions);
|
||||
egg.addEggToGameData(scene);
|
||||
// queueEncounterMessage(scene, `You gained a ${egg.getEggTypeDescriptor(scene)} Egg!`);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -16,5 +16,6 @@ export enum MysteryEncounterType {
|
||||
AN_OFFER_YOU_CANT_REFUSE,
|
||||
DELIBIRDY,
|
||||
ABSOLUTE_AVARICE,
|
||||
A_TRAINERS_TEST
|
||||
A_TRAINERS_TEST,
|
||||
TRASH_TO_TREASURE
|
||||
}
|
||||
|
@ -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." },
|
||||
|
||||
"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: {
|
||||
"LIGHT_BALL": { name: "Light Ball", description: "It's a mysterious orb that boosts Pikachu's Attack and Sp. Atk stats." },
|
||||
|
@ -16,6 +16,7 @@ import { anOfferYouCantRefuseDialogue } from "#app/locales/en/mystery-encounters
|
||||
import { delibirdyDialogue } from "#app/locales/en/mystery-encounters/delibirdy-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 { trashToTreasureDialogue } from "#app/locales/en/mystery-encounters/trash-to-treasure-dialogue";
|
||||
|
||||
/**
|
||||
* Patterns that can be used:
|
||||
@ -57,4 +58,5 @@ export const mysteryEncounter = {
|
||||
delibirdy: delibirdyDialogue,
|
||||
absoluteAvarice: absoluteAvariceDialogue,
|
||||
aTrainersTest: aTrainersTestDialogue,
|
||||
trashToTreasure: trashToTreasureDialogue
|
||||
} as const;
|
||||
|
@ -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!"
|
||||
},
|
||||
},
|
||||
};
|
@ -1474,6 +1474,8 @@ export const modifierTypes = {
|
||||
}
|
||||
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 {
|
||||
|
@ -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 {
|
||||
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
||||
super(type, pokemonId, stackCount);
|
||||
|
@ -3,7 +3,7 @@ import BattleScene from "../battle-scene";
|
||||
import { Phase } from "../phase";
|
||||
import { Mode } from "../ui/ui";
|
||||
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 { getCharVariantFromDialogue } from "../data/dialogue";
|
||||
import { TrainerSlot } from "../data/trainer-config";
|
||||
@ -183,6 +183,11 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
||||
it("should initialize fully ", async () => {
|
||||
initSceneWithoutEncounterPhase(scene, defaultParty);
|
||||
scene.currentBattle.mysteryEncounter = TheStrongStuffEncounter;
|
||||
const moveInitSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
|
||||
const moveInitSpy = vi.spyOn(BattleAnims, "initMoveAnim");
|
||||
const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
|
||||
|
||||
const { onInit } = TheStrongStuffEncounter;
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@ -4,7 +4,7 @@ import { getPokeballAtlasKey, PokeballType } from "../data/pokeball";
|
||||
import { addTextObject, getTextStyleOptions, getModifierTierTextTint, getTextColor, TextStyle } from "./text";
|
||||
import AwaitableUiHandler from "./awaitable-ui-handler";
|
||||
import { Mode } from "./ui";
|
||||
import { LockModifierTiersModifier, PokemonHeldItemModifier } from "../modifier/modifier";
|
||||
import { LockModifierTiersModifier, PokemonHeldItemModifier, RemoveHealShopModifier } from "../modifier/modifier";
|
||||
import { handleTutorial, Tutorial } from "../tutorial";
|
||||
import { Button } from "#enums/buttons";
|
||||
import MoveInfoOverlay from "./move-info-overlay";
|
||||
@ -166,7 +166,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
||||
this.updateRerollCostText();
|
||||
|
||||
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))
|
||||
: [];
|
||||
const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24;
|
||||
|
Loading…
Reference in New Issue
Block a user