Skip to content

Commit

Permalink
Merge pull request #124 from AsdarDevelops/trast-to-treasure
Browse files Browse the repository at this point in the history
Trash to Treasure encounter
  • Loading branch information
ben-lear authored Aug 1, 2024
2 parents 8e6b680 + 6c07435 commit 93de483
Show file tree
Hide file tree
Showing 16 changed files with 2,519 additions and 2,002 deletions.
4,011 changes: 2,016 additions & 1,995 deletions public/images/items.json

Large diffs are not rendered by default.

Binary file modified public/images/items.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/items/black_sludge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
219 changes: 219 additions & 0 deletions src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts
Original file line number Diff line number Diff line change
@@ -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");
});
}
5 changes: 4 additions & 1 deletion src/data/mystery-encounters/mystery-encounters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
];

/**
Expand Down Expand Up @@ -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 => {
Expand Down
1 change: 0 additions & 1 deletion src/data/mystery-encounters/utils/encounter-phase-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!`);
});
}

Expand Down
3 changes: 2 additions & 1 deletion src/enums/mystery-encounter-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export enum MysteryEncounterType {
AN_OFFER_YOU_CANT_REFUSE,
DELIBIRDY,
ABSOLUTE_AVARICE,
A_TRAINERS_TEST
A_TRAINERS_TEST,
TRASH_TO_TREASURE
}
1 change: 1 addition & 0 deletions src/locales/en/modifier-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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." },
Expand Down
2 changes: 2 additions & 0 deletions src/locales/en/mystery-encounter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -57,4 +58,5 @@ export const mysteryEncounter = {
delibirdy: delibirdyDialogue,
absoluteAvarice: absoluteAvariceDialogue,
aTrainersTest: aTrainersTestDialogue,
trashToTreasure: trashToTreasureDialogue
} as const;
22 changes: 22 additions & 0 deletions src/locales/en/mystery-encounters/trash-to-treasure-dialogue.ts
Original file line number Diff line number Diff line change
@@ -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!"
},
},
};
2 changes: 2 additions & 0 deletions src/modifier/modifier-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
22 changes: 22 additions & 0 deletions src/modifier/modifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 6 additions & 1 deletion src/phases/mystery-encounter-phases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 93de483

Please sign in to comment.