safari encounter and sprite offset positioning updates

This commit is contained in:
ImperialSympathizer 2024-07-14 17:37:03 -04:00
parent 43ebff59a4
commit da0aea0d1e
15 changed files with 1048 additions and 288 deletions

View File

@ -4,177 +4,198 @@
"image": "chest_blue.png",
"format": "RGBA8888",
"size": {
"w": 300,
"h": 75
"w": 58,
"h": 528
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"filename": "0000.png",
"rotated": false,
"trimmed": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"x": 14,
"y": 30,
"w": 48,
"h": 41
},
"frame": {
"x": 1,
"y": 1,
"w": 48,
"h": 41
}
},
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 14,
"y": 34,
"w": 49,
"h": 37
},
"frame": {
"x": -15,
"y": 0,
"w": 75,
"h": 75
"x": 1,
"y": 44,
"w": 49,
"h": 37
}
},
{
"filename": "0002.png",
"rotated": false,
"trimmed": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 75,
"h": 75
"x": 14,
"y": 30,
"w": 48,
"h": 41
},
"frame": {
"x": -15,
"y": 0,
"w": 75,
"h": 75
"x": 1,
"y": 83,
"w": 48,
"h": 41
}
},
{
"filename": "0003.png",
"rotated": false,
"trimmed": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 75,
"h": 75
"x": 14,
"y": 23,
"w": 48,
"h": 48
},
"frame": {
"x": 57,
"y": 0,
"w": 75,
"h": 75
"x": 1,
"y": 126,
"w": 48,
"h": 48
}
},
{
"filename": "0004.png",
"rotated": false,
"trimmed": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 75,
"h": 75
"x": 13,
"y": 4,
"w": 55,
"h": 67
},
"frame": {
"x": 57,
"y": 0,
"w": 75,
"h": 75
"x": 1,
"y": 176,
"w": 55,
"h": 67
}
},
{
"filename": "0005.png",
"rotated": false,
"trimmed": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 75,
"h": 75
"x": 15,
"y": 2,
"w": 56,
"h": 69
},
"frame": {
"x": 129,
"y": 0,
"w": 75,
"h": 75
"x": 1,
"y": 245,
"w": 56,
"h": 69
}
},
{
"filename": "0006.png",
"rotated": false,
"trimmed": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 75,
"h": 75
"x": 15,
"y": 2,
"w": 56,
"h": 69
},
"frame": {
"x": 129,
"y": 0,
"w": 75,
"h": 75
"x": 1,
"y": 316,
"w": 56,
"h": 69
}
},
{
"filename": "0007.png",
"rotated": false,
"trimmed": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 75,
"h": 75
"x": 13,
"y": 2,
"w": 56,
"h": 69
},
"frame": {
"x": 201,
"y": 0,
"w": 75,
"h": 75
"x": 1,
"y": 387,
"w": 56,
"h": 69
}
},
{
"filename": "0008.png",
"rotated": false,
"trimmed": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 75,
"h": 75
"x": 13,
"y": 2,
"w": 56,
"h": 69
},
"frame": {
"x": 201,
"y": 0,
"w": 75,
"h": 75
"x": 1,
"y": 458,
"w": 56,
"h": 69
}
}
]
@ -183,6 +204,6 @@
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:895f0a79b89fa0fb44167f4584fd9a22:357b46953b7e17c6b2f43a62d52855d8:cc1ed0e4f90aaa9dcf1b39a0af1283b0$"
"smartupdate": "$TexturePacker:SmartUpdate:5f36000f6160ee6f397afe5a6fd60b73:cf6f4b08e23400447813583c322eb6c7:f4f3c064e6c93b8d1290f93bee927f60$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -30,7 +30,8 @@ export const MysteriousChestEncounter: IMysteryEncounter =
fileRoot: "mystery-encounters",
hasShadow: true,
x: 4,
y: 8,
y: 10,
yShadowOffset: 3,
disableAnimation: true, // Re-enabled after option select
},
])
@ -75,10 +76,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
],
});
// Display result message then proceed to rewards
queueEncounterMessage(
scene,
"mysteryEncounter:mysterious_chest_option_1_normal_result"
);
queueEncounterMessage(scene, "mysteryEncounter:mysterious_chest_option_1_normal_result");
leaveEncounterWithoutBattle(scene);
} else if (roll > 40) {
// Choose between 3 ULTRA tier items (20%)
@ -90,10 +88,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
],
});
// Display result message then proceed to rewards
queueEncounterMessage(
scene,
"mysteryEncounter:mysterious_chest_option_1_good_result"
);
queueEncounterMessage(scene, "mysteryEncounter:mysterious_chest_option_1_good_result");
leaveEncounterWithoutBattle(scene);
} else if (roll > 36) {
// Choose between 2 ROGUE tier items (4%)
@ -101,10 +96,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
});
// Display result message then proceed to rewards
queueEncounterMessage(
scene,
"mysteryEncounter:mysterious_chest_option_1_great_result"
);
queueEncounterMessage(scene, "mysteryEncounter:mysterious_chest_option_1_great_result");
leaveEncounterWithoutBattle(scene);
} else if (roll > 35) {
// Choose 1 MASTER tier item (1%)
@ -112,10 +104,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
guaranteedModifierTiers: [ModifierTier.MASTER],
});
// Display result message then proceed to rewards
queueEncounterMessage(
scene,
"mysteryEncounter:mysterious_chest_option_1_amazing_result"
);
queueEncounterMessage(scene, "mysteryEncounter:mysterious_chest_option_1_amazing_result");
leaveEncounterWithoutBattle(scene);
} else {
// Your highest level unfainted Pok<6F>mon gets OHKO. Progress with no rewards (35%)
@ -125,27 +114,22 @@ export const MysteriousChestEncounter: IMysteryEncounter =
);
koPlayerPokemon(highestLevelPokemon);
scene.currentBattle.mysteryEncounter.setDialogueToken(
"pokeName",
highestLevelPokemon.name
);
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.name);
// Show which Pokemon was KOed, then leave encounter with no rewards
// Does this synchronously so that game over doesn't happen over result message
await showEncounterText(
scene,
"mysteryEncounter:mysterious_chest_option_1_bad_result"
).then(() => {
if (
scene.getParty().filter((p) => p.isAllowedInBattle()).length ===
await showEncounterText(scene, "mysteryEncounter:mysterious_chest_option_1_bad_result")
.then(() => {
if (
scene.getParty().filter((p) => p.isAllowedInBattle()).length ===
0
) {
) {
// All pokemon fainted, game over
scene.clearPhaseQueue();
scene.unshiftPhase(new GameOverPhase(scene));
} else {
leaveEncounterWithoutBattle(scene);
}
});
scene.clearPhaseQueue();
scene.unshiftPhase(new GameOverPhase(scene));
} else {
leaveEncounterWithoutBattle(scene);
}
});
}
})
.build()

View File

@ -1,31 +1,167 @@
import {
getHighestLevelPlayerPokemon,
koPlayerPokemon,
leaveEncounterWithoutBattle,
queueEncounterMessage,
setEncounterRewards,
showEncounterText,
} from "#app/data/mystery-encounters/mystery-encounter-utils";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { GameOverPhase } from "#app/phases";
import { randSeedInt } from "#app/utils";
import { getRandomSpeciesByStarterTier, initFollowupOptionSelect, leaveEncounterWithoutBattle, updatePlayerMoney, } from "#app/data/mystery-encounters/mystery-encounter-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene";
import IMysteryEncounter, {
MysteryEncounterBuilder,
MysteryEncounterTier,
} from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, MysteryEncounterVariant } from "../mystery-encounter";
import MysteryEncounterOption, { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { TrainerSlot } from "#app/data/trainer-config";
import { ScanIvsPhase, SummonPhase, VictoryPhase } from "#app/phases";
import i18next from "i18next";
import { HiddenAbilityRateBoosterModifier, IvScannerModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { EnemyPokemon } from "#app/field/pokemon";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballTintColor, PokeballType } from "#app/data/pokeball";
import { StatusEffect } from "#app/data/status-effect";
import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims";
import { achvs } from "#app/system/achv";
import { Mode } from "#app/ui/ui";
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
import { BattlerIndex } from "#app/battle";
import { PlayerGender } from "#enums/player-gender";
import { IntegerHolder, randSeedInt } from "#app/utils";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:safari_zone";
/**
* SAFARI ZONE OPTIONS
*
* Catch and flee rate **multipliers** are calculated in the same way stat changes are (they range from -6/+6)
* https://bulbapedia.bulbagarden.net/wiki/Catch_rate#Great_Marsh_and_Johto_Safari_Zone
*
* Catch Rate calculation:
* catchRate = speciesCatchRate [1 to 255] * catchStageMultiplier [2/8 to 8/2] * ballCatchRate [1.5]
*
* Flee calculation:
* The harder a species is to catch, the higher its flee rate is
* (Caps at 50% base chance to flee for the hardest to catch Pokemon, before factoring in flee stage)
* fleeRate = ((255^2 - speciesCatchRate^2) / 255 / 2) [0 to 127.5] * fleeStageMultiplier [2/8 to 8/2]
* Flee chance = fleeRate / 255
*/
const safariZoneOptions: MysteryEncounterOption[] = [
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}_pokeball_option_label`,
buttonTooltip: `${namespace}_pokeball_option_tooltip`,
selected: [
{
text: `${namespace}_pokeball_option_selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Throw a ball option
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
const catchResult = await throwPokeball(scene, pokemon);
if (catchResult) {
// You caught pokemon
scene.unshiftPhase(new VictoryPhase(scene, 0));
// Check how many safari pokemon left
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene);
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, startingCursorIndex: 0, hideDescription: true });
} else {
// End safari mode
leaveEncounterWithoutBattle(scene, true);
}
} else {
// Pokemon failed to catch, end turn
await doEndTurn(scene, 0);
}
return true;
})
.build(),
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}_bait_option_label`,
buttonTooltip: `${namespace}_bait_option_tooltip`,
selected: [
{
text: `${namespace}_bait_option_selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Throw bait option
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
await throwBait(scene, pokemon);
// 100% chance to increase catch stage +2
tryChangeCatchStage(scene, 2);
// 80% chance to increase flee stage +1
const fleeChangeResult = tryChangeFleeStage(scene, 1, 8);
if (!fleeChangeResult) {
scene.queueMessage(i18next.t(`${namespace}_pokemon_busy_eating`, { pokemonName: pokemon.name }), 0, null, 500);
} else {
scene.queueMessage(i18next.t(`${namespace}_pokemon_eating`, { pokemonName: pokemon.name }), 0, null, 500);
}
// TODO: throw bait with eat animation
// TODO: play bug bite sfx, maybe spike cannon?
await doEndTurn(scene, 1);
return true;
})
.build(),
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}_mud_option_label`,
buttonTooltip: `${namespace}_mud_option_tooltip`,
selected: [
{
text: `${namespace}_mud_option_selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Throw mud option
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
await throwMud(scene, pokemon);
// 100% chance to decrease flee stage -2
tryChangeFleeStage(scene, -2);
// 80% chance to decrease catch stage -1
const catchChangeResult = tryChangeCatchStage(scene, -1, 8);
if (!catchChangeResult) {
scene.queueMessage(i18next.t(`${namespace}_pokemon_beside_itself_angry`, { pokemonName: pokemon.name }), 0, null, 500);
} else {
scene.queueMessage(i18next.t(`${namespace}_pokemon_angry`, { pokemonName: pokemon.name }), 0, null, 500);
}
await doEndTurn(scene, 2);
return true;
})
.build(),
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}_flee_option_label`,
buttonTooltip: `${namespace}_flee_option_tooltip`,
})
.withOptionPhase(async (scene: BattleScene) => {
// Flee option
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
await doPlayerFlee(scene, pokemon);
// Check how many safari pokemon left
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene);
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, startingCursorIndex: 3, hideDescription: true });
} else {
// End safari mode
leaveEncounterWithoutBattle(scene, true);
}
return true;
})
.build()
];
export const SafariZoneEncounter: IMysteryEncounter =
MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.SAFARI_ZONE
)
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 2 to 180
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
.withHideIntroVisuals(false)
.withIntroSpriteConfigs([
{
@ -33,133 +169,57 @@ export const SafariZoneEncounter: IMysteryEncounter =
fileRoot: "mystery-encounters",
hasShadow: true,
x: 4,
y: 8,
y: 10,
yShadowOffset: 3,
disableAnimation: true, // Re-enabled after option select
},
])
.withIntroDialogue([
{
text: `mysteryEncounter:${namespace}_intro_message`,
text: `${namespace}_intro_message`,
},
])
.withTitle(`mysteryEncounter:${namespace}_title`)
.withDescription(`mysteryEncounter:${namespace}_description`)
.withQuery(`mysteryEncounter:${namespace}_query`)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: "mysteryEncounter:${namespace}_option_1_label",
buttonTooltip: "mysteryEncounter:${namespace}_option_1_tooltip",
selected: [
{
text: "mysteryEncounter:${namespace}_option_1_selected_message",
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Play animation
const introVisuals =
scene.currentBattle.mysteryEncounter.introVisuals;
introVisuals.spriteConfigs[0].disableAnimation = false;
introVisuals.playAnim();
})
.withOptionPhase(async (scene: BattleScene) => {
// Open the chest
const roll = randSeedInt(100);
if (roll > 60) {
// Choose between 2 COMMON / 2 GREAT tier items (40%)
setEncounterRewards(scene, {
guaranteedModifierTiers: [
ModifierTier.COMMON,
ModifierTier.COMMON,
ModifierTier.GREAT,
ModifierTier.GREAT,
],
});
// Display result message then proceed to rewards
queueEncounterMessage(
scene,
"mysteryEncounter:${namespace}_option_1_normal_result"
);
leaveEncounterWithoutBattle(scene);
} else if (roll > 40) {
// Choose between 3 ULTRA tier items (20%)
setEncounterRewards(scene, {
guaranteedModifierTiers: [
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
});
// Display result message then proceed to rewards
queueEncounterMessage(
scene,
"mysteryEncounter:${namespace}_option_1_good_result"
);
leaveEncounterWithoutBattle(scene);
} else if (roll > 36) {
// Choose between 2 ROGUE tier items (4%)
setEncounterRewards(scene, {
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
});
// Display result message then proceed to rewards
queueEncounterMessage(
scene,
"mysteryEncounter:${namespace}_option_1_great_result"
);
leaveEncounterWithoutBattle(scene);
} else if (roll > 35) {
// Choose 1 MASTER tier item (1%)
setEncounterRewards(scene, {
guaranteedModifierTiers: [ModifierTier.MASTER],
});
// Display result message then proceed to rewards
queueEncounterMessage(
scene,
"mysteryEncounter:${namespace}_option_1_amazing_result"
);
leaveEncounterWithoutBattle(scene);
} else {
// Your highest level unfainted Pok<6F>mon gets OHKO. Progress with no rewards (35%)
const highestLevelPokemon = getHighestLevelPlayerPokemon(
scene,
true
);
koPlayerPokemon(highestLevelPokemon);
scene.currentBattle.mysteryEncounter.setDialogueToken(
"pokeName",
highestLevelPokemon.name
);
// Show which Pokemon was KOed, then leave encounter with no rewards
// Does this synchronously so that game over doesn't happen over result message
await showEncounterText(
scene,
"mysteryEncounter:${namespace}_option_1_bad_result"
).then(() => {
if (
scene.getParty().filter((p) => p.isAllowedInBattle()).length ===
0
) {
// All pokemon fainted, game over
scene.clearPhaseQueue();
scene.unshiftPhase(new GameOverPhase(scene));
} else {
leaveEncounterWithoutBattle(scene);
}
});
}
})
.build()
.withTitle(`${namespace}_title`)
.withDescription(`${namespace}_description`)
.withQuery(`${namespace}_query`)
.withOption(new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
// TODO: update
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
.withDialogue({
buttonLabel: `${namespace}_option_1_label`,
buttonTooltip: `${namespace}_option_1_tooltip`,
selected: [
{
text: `${namespace}_option_1_selected_message`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Start safari encounter
const encounter = scene.currentBattle.mysteryEncounter;
encounter.encounterVariant = MysteryEncounterVariant.SAFARI_BATTLE;
encounter.misc = {
safariPokemonRemaining: 3
};
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
scene.loadSe("PRSFX- Bug Bite", "battle_anims");
scene.loadSe("PRSFX- Sludge Bomb2", "battle_anims");
scene.loadSe("PRSFX- Taunt2", "battle_anims");
await hideMysteryEncounterIntroVisuals(scene);
await summonSafariPokemon(scene);
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, hideDescription: true });
return true;
})
.build()
)
.withSimpleOption(
{
buttonLabel: "mysteryEncounter:${namespace}_option_2_label",
buttonTooltip: "mysteryEncounter:${namespace}_option_2_tooltip",
buttonLabel: `${namespace}_option_2_label`,
buttonTooltip: `${namespace}_option_2_tooltip`,
selected: [
{
text: "mysteryEncounter:${namespace}_option_2_selected_message",
text: `${namespace}_option_2_selected_message`,
},
],
},
@ -170,3 +230,588 @@ export const SafariZoneEncounter: IMysteryEncounter =
}
)
.build();
function hideMysteryEncounterIntroVisuals(scene: BattleScene): Promise<boolean> {
return new Promise(resolve => {
const introVisuals = scene.currentBattle.mysteryEncounter.introVisuals;
if (introVisuals) {
// Hide
scene.tweens.add({
targets: introVisuals,
x: "+=16",
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 750,
onComplete: () => {
scene.field.remove(introVisuals);
introVisuals.setVisible(false);
introVisuals.destroy();
scene.currentBattle.mysteryEncounter.introVisuals = null;
resolve(true);
}
});
} else {
resolve(true);
}
});
}
async function summonSafariPokemon(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter;
// Message pokemon remaining
scene.queueMessage(i18next.t(`${namespace}_remaining_count`, { remainingCount: encounter.misc.safariPokemonRemaining}), null, true);
// Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken
// Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
let enemySpecies;
let pokemon;
scene.executeWithSeedOffset(() => {
enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(scene.currentBattle.waveIndex, true, false, scene.gameMode));
scene.currentBattle.enemyParty = [];
pokemon = scene.addEnemyPokemon(enemySpecies, scene.currentBattle.waveIndex, TrainerSlot.NONE, false);
// Roll shiny twice
if (!pokemon.shiny) {
pokemon.trySetShiny();
}
// Roll HA twice
if (pokemon.species.abilityHidden) {
const hiddenIndex = pokemon.species.ability2 ? 2 : 1;
if (pokemon.abilityIndex < hiddenIndex) {
const hiddenAbilityChance = new IntegerHolder(256);
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
if (hasHiddenAbility) {
pokemon.abilityIndex = hiddenIndex;
}
}
}
pokemon.calculateStats();
scene.currentBattle.enemyParty[0] = pokemon;
}, scene.currentBattle.waveIndex + encounter.misc.safariPokemonRemaining);
scene.gameData.setPokemonSeen(pokemon, true);
await pokemon.loadAssets();
// Reset safari catch and flee rates
encounter.misc.catchStage = 0;
encounter.misc.fleeStage = 0;
encounter.misc.pokemon = pokemon;
encounter.misc.safariPokemonRemaining -= 1;
scene.unshiftPhase(new SummonPhase(scene, 0, false));
scene.ui.showText(i18next.t("battle:singleWildAppeared", { pokemonName: pokemon.name }), null, () => {
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) {
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
}
}, 1500);
}
async function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
const pokeballType: PokeballType = PokeballType.POKEBALL;
const originalY: number = pokemon.y;
const baseCatchRate = pokemon.species.catchRate;
// Catch stage ranges from -6 to +6 (like stat boost stages)
const safariCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage;
// Catch modifier ranges from 2/8 (-6 stage) to 8/2 (+6)
const safariModifier = (2 + Math.min(Math.max(safariCatchStage, 0), 6)) / (2 - Math.max(Math.min(safariCatchStage, 0), -6));
// Catch rate same as safari ball
const pokeballMultiplier = 1.5;
const catchRate = Math.round(baseCatchRate * pokeballMultiplier * safariModifier);
const ballTwitchRate = Math.round(1048560 / Math.sqrt(Math.sqrt(16711680 / catchRate)));
const fpOffset = pokemon.getFieldPositionOffset();
const catchSuccess = (ballTwitchRate / 65536) * (ballTwitchRate / 65536) * (ballTwitchRate / 65536);
console.log("Catch success rate: " + catchSuccess);
const pokeballAtlasKey = getPokeballAtlasKey(pokeballType);
const pokeball: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "pb", pokeballAtlasKey);
pokeball.setOrigin(0.5, 0.625);
scene.field.add(pokeball);
scene.playSound("pb_throw");
scene.time.delayedCall(300, () => {
scene.field.moveBelow(pokeball as Phaser.GameObjects.GameObject, pokemon);
});
return new Promise(resolve => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
scene.time.delayedCall(512, () => {
// Trainer throw frames
scene.trainer.setFrame("2");
scene.time.delayedCall(256, () => {
scene.trainer.setFrame("3");
scene.time.delayedCall(768, () => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
});
});
// Pokeball move and catch logic
scene.tweens.add({
targets: pokeball,
x: { value: 236 + fpOffset[0], ease: "Linear" },
y: { value: 16 + fpOffset[1], ease: "Cubic.easeOut" },
duration: 500,
onComplete: () => {
pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`);
scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}_open`));
scene.playSound("pb_rel");
pokemon.tint(getPokeballTintColor(pokeballType));
addPokeballOpenParticles(scene, pokeball.x, pokeball.y, pokeballType);
scene.tweens.add({
targets: pokemon,
duration: 500,
ease: "Sine.easeIn",
scale: 0.25,
y: 20,
onComplete: () => {
pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`);
pokemon.setVisible(false);
scene.playSound("pb_catch");
scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}`));
const doShake = () => {
let shakeCount = 0;
const pbX = pokeball.x;
const shakeCounter = scene.tweens.addCounter({
from: 0,
to: 1,
repeat: 4,
yoyo: true,
ease: "Cubic.easeOut",
duration: 250,
repeatDelay: 500,
onUpdate: t => {
if (shakeCount && shakeCount < 4) {
const value = t.getValue();
const directionMultiplier = shakeCount % 2 === 1 ? 1 : -1;
pokeball.setX(pbX + value * 4 * directionMultiplier);
pokeball.setAngle(value * 27.5 * directionMultiplier);
}
},
onRepeat: () => {
if (!pokemon.species.isObtainable()) {
shakeCounter.stop();
failCatch(scene, pokemon, originalY, pokeball, pokeballType).then(() => resolve(false));
} else if (shakeCount++ < 3) {
if (randSeedInt(65536) < ballTwitchRate) {
scene.playSound("pb_move");
} else {
shakeCounter.stop();
failCatch(scene, pokemon, originalY, pokeball, pokeballType).then(() => resolve(false));
}
} else {
scene.playSound("pb_lock");
addPokeballCaptureStars(scene, pokeball);
const pbTint = scene.add.sprite(pokeball.x, pokeball.y, "pb", "pb");
pbTint.setOrigin(pokeball.originX, pokeball.originY);
pbTint.setTintFill(0);
pbTint.setAlpha(0);
scene.field.add(pbTint);
scene.tweens.add({
targets: pbTint,
alpha: 0.375,
duration: 200,
easing: "Sine.easeOut",
onComplete: () => {
scene.tweens.add({
targets: pbTint,
alpha: 0,
duration: 200,
easing: "Sine.easeIn",
onComplete: () => pbTint.destroy()
});
}
});
}
},
onComplete: () => {
catchPokemon(scene, pokemon, pokeball, pokeballType).then(() => resolve(true));
}
});
};
scene.time.delayedCall(250, () => doPokeballBounceAnim(scene, pokeball, 16, 72, 350, doShake));
}
});
}
});
});
});
}
async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
// TODO: replace with bait
const pokeballType: PokeballType = PokeballType.POKEBALL;
const originalY: number = pokemon.y;
const fpOffset = pokemon.getFieldPositionOffset();
const pokeballAtlasKey = getPokeballAtlasKey(pokeballType);
const bait: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "pb", pokeballAtlasKey);
bait.setOrigin(0.5, 0.625);
scene.field.add(bait);
scene.playSound("pb_throw");
// scene.time.delayedCall(300, () => {
// scene.field.moveBelow(pokemon, pokeball as Phaser.GameObjects.GameObject);
// });
return new Promise(resolve => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
scene.time.delayedCall(512, () => {
// Trainer throw frames
scene.trainer.setFrame("2");
scene.time.delayedCall(256, () => {
scene.trainer.setFrame("3");
scene.time.delayedCall(768, () => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
});
});
// Pokeball move and catch logic
scene.tweens.add({
targets: bait,
x: { value: 210 + fpOffset[0], ease: "Linear" },
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
duration: 500,
onComplete: () => {
// Bait frame 2
bait.setTexture("pb", `${pokeballAtlasKey}_opening`);
// Bait frame 3
scene.time.delayedCall(17, () => bait.setTexture("pb", `${pokeballAtlasKey}_open`));
// scene.playSound("pb_rel");
// pokemon.tint(getPokeballTintColor(pokeballType));
// addPokeballOpenParticles(scene, pokeball.x, pokeball.y, pokeballType);
scene.time.delayedCall(512, () => {
scene.tweens.add({
targets: pokemon,
duration: 200,
ease: "Cubic.easeOut",
yoyo: true,
y: originalY - 30,
loop: 2,
onStart: () => {
scene.playSound("PRSFX- Bug Bite");
},
onLoop: () => {
scene.playSound("PRSFX- Bug Bite");
},
onComplete: () => {
resolve(true);
bait.destroy();
}
});
});
}
});
});
});
}
async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
// TODO: replace with mud
const pokeballType: PokeballType = PokeballType.POKEBALL;
const originalY: number = pokemon.y;
const fpOffset = pokemon.getFieldPositionOffset();
const pokeballAtlasKey = getPokeballAtlasKey(pokeballType);
const mud: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "pb", pokeballAtlasKey);
mud.setOrigin(0.5, 0.625);
scene.field.add(mud);
scene.playSound("pb_throw");
return new Promise(resolve => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
scene.time.delayedCall(512, () => {
// Trainer throw frames
scene.trainer.setFrame("2");
scene.time.delayedCall(256, () => {
scene.trainer.setFrame("3");
scene.time.delayedCall(768, () => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
});
});
// Pokeball move and catch logic
scene.tweens.add({
targets: mud,
x: { value: 230 + fpOffset[0], ease: "Linear" },
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
duration: 500,
onComplete: () => {
// Bait frame 2
mud.setTexture("pb", `${pokeballAtlasKey}_opening`);
// Bait frame 3
scene.time.delayedCall(17, () => mud.setTexture("pb", `${pokeballAtlasKey}_open`));
scene.playSound("PRSFX- Sludge Bomb2");
// pokemon.tint(getPokeballTintColor(pokeballType));
// addPokeballOpenParticles(scene, pokeball.x, pokeball.y, pokeballType);
scene.time.delayedCall(1536, () => {
mud.destroy();
scene.tweens.add({
targets: pokemon,
duration: 300,
ease: "Cubic.easeOut",
yoyo: true,
y: originalY - 20,
loop: 1,
onStart: () => {
scene.playSound("PRSFX- Taunt2");
},
onLoop: () => {
scene.playSound("PRSFX- Taunt2");
},
onComplete: () => {
resolve(true);
}
});
});
}
});
});
});
}
async function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType) {
return new Promise<void>(resolve => {
scene.playSound("pb_rel");
pokemon.setY(originalY);
if (pokemon.status?.effect !== StatusEffect.SLEEP) {
pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 });
}
pokemon.tint(getPokeballTintColor(pokeballType));
pokemon.setVisible(true);
pokemon.untint(250, "Sine.easeOut");
const pokeballAtlasKey = getPokeballAtlasKey(pokeballType);
pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`);
scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}_open`));
scene.tweens.add({
targets: pokemon,
duration: 250,
ease: "Sine.easeOut",
scale: 1
});
scene.currentBattle.lastUsedPokeball = pokeballType;
removePb(scene, pokeball);
scene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.name }), null, () => resolve(), null, true);
});
}
async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType): Promise<void> {
scene.unshiftPhase(new VictoryPhase(scene, BattlerIndex.ENEMY));
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) {
scene.validateAchv(achvs.HIDDEN_ABILITY);
}
if (pokemon.species.subLegendary) {
scene.validateAchv(achvs.CATCH_SUB_LEGENDARY);
}
if (pokemon.species.legendary) {
scene.validateAchv(achvs.CATCH_LEGENDARY);
}
if (pokemon.species.mythical) {
scene.validateAchv(achvs.CATCH_MYTHICAL);
}
scene.pokemonInfoContainer.show(pokemon, true);
scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
return new Promise(resolve => {
scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: pokemon.name }), null, () => {
const end = () => {
scene.pokemonInfoContainer.hide();
removePb(scene, pokeball);
resolve();
};
const removePokemon = () => {
scene.field.remove(pokemon, true);
};
const addToParty = () => {
const newPokemon = pokemon.addToParty(pokeballType);
const modifiers = scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false);
if (scene.getParty().filter(p => p.isShiny()).length === 6) {
scene.validateAchv(achvs.SHINY_PARTY);
}
Promise.all(modifiers.map(m => scene.addModifier(m, true))).then(() => {
scene.updateModifiers(true);
removePokemon();
if (newPokemon) {
newPokemon.loadAssets().then(end);
} else {
end();
}
});
};
Promise.all([pokemon.hideInfo(), scene.gameData.setPokemonCaught(pokemon)]).then(() => {
if (scene.getParty().length === 6) {
const promptRelease = () => {
scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.name }), null, () => {
scene.pokemonInfoContainer.makeRoomForConfirmUi();
scene.ui.setMode(Mode.CONFIRM, () => {
scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => {
scene.ui.setMode(Mode.MESSAGE).then(() => {
if (slotIndex < 6) {
addToParty();
} else {
promptRelease();
}
});
});
}, () => {
scene.ui.setMode(Mode.MESSAGE).then(() => {
removePokemon();
end();
});
});
});
};
promptRelease();
} else {
addToParty();
}
});
}, 0, true);
});
}
function removePb(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite) {
scene.tweens.add({
targets: pokeball,
duration: 250,
delay: 250,
ease: "Sine.easeIn",
alpha: 0,
onComplete: () => pokeball.destroy()
});
}
function isPokemonFlee(pokemon: EnemyPokemon, fleeStage: number): boolean {
const speciesCatchRate = pokemon.species.catchRate;
const fleeModifier = (2 + Math.min(Math.max(fleeStage, 0), 6)) / (2 - Math.max(Math.min(fleeStage, 0), -6));
const fleeRate = (255 * 255 - speciesCatchRate * speciesCatchRate) / 255 / 2 * fleeModifier;
console.log("Flee rate: " + fleeRate);
const roll = randSeedInt(256);
console.log("Roll: " + roll);
return roll < fleeRate;
}
async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise<void> {
const fleeAnimation = new Promise<void>(resolve => {
// Ease pokemon out
scene.tweens.add({
targets: pokemon,
x: "+=16",
y: "-=16",
alpha: 0,
duration: 1000,
ease: "Sine.easeIn",
scale: pokemon.getSpriteScale(),
onComplete: () => {
pokemon.setVisible(false);
scene.field.remove(pokemon, true);
resolve();
}
});
});
const prompt = new Promise<void>(resolve => {
scene.ui.showText(i18next.t("battle:pokemonFled", { pokemonName: pokemon.name }), 0, () => resolve(), 500);
});
await Promise.all([fleeAnimation, prompt]);
}
async function doPlayerFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise<void> {
const fleeAnimation = new Promise<void>(resolve => {
// Ease pokemon out
scene.tweens.add({
targets: pokemon,
x: "+=16",
y: "-=16",
alpha: 0,
duration: 1000,
ease: "Sine.easeIn",
scale: pokemon.getSpriteScale(),
onComplete: () => {
pokemon.setVisible(false);
scene.field.remove(pokemon, true);
resolve();
}
});
});
const prompt = new Promise<void>(resolve => {
scene.ui.showText(i18next.t("battle:playerFled", { pokemonName: pokemon.name }), 0, () => resolve(), 500);
});
await Promise.all([fleeAnimation, prompt]);
}
function tryChangeFleeStage(scene: BattleScene, change: number, chance?: number): boolean {
if (chance && randSeedInt(10) >= chance) {
console.log("Failed to change flee stage");
return false;
}
const currentFleeStage = scene.currentBattle.mysteryEncounter.misc.fleeStage ?? 0;
// console.log("currentFleeStage: " + currentFleeStage);
scene.currentBattle.mysteryEncounter.misc.fleeStage = Math.min(Math.max(currentFleeStage + change, -6), 6);
return true;
}
function tryChangeCatchStage(scene: BattleScene, change: number, chance?: number): boolean {
if (chance && randSeedInt(10) >= chance) {
console.log("Failed to change catch stage");
return false;
}
const currentCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage ?? 0;
// console.log("currentCatchStage: " + currentCatchStage);
scene.currentBattle.mysteryEncounter.misc.catchStage = Math.min(Math.max(currentCatchStage + change, -6), 6);
return true;
}
async function doEndTurn(scene: BattleScene, cursorIndex: number, message?: string) {
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
console.log("fleeStage: " + scene.currentBattle.mysteryEncounter.misc.fleeStage);
console.log("catchStage: " + scene.currentBattle.mysteryEncounter.misc.catchStage);
const isFlee = isPokemonFlee(pokemon, scene.currentBattle.mysteryEncounter.misc.fleeStage);
if (isFlee) {
// Pokemon flees!
await doPokemonFlee(scene, pokemon);
// Check how many safari pokemon left
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene);
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, startingCursorIndex: cursorIndex, hideDescription: true });
} else {
// End safari mode
leaveEncounterWithoutBattle(scene, true);
}
} else {
scene.queueMessage(i18next.t(`${namespace}_pokemon_watching`, { pokemonName: pokemon.name }), 0, null, 500);
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, startingCursorIndex: cursorIndex, hideDescription: true });
}
}

View File

@ -28,15 +28,17 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
fileRoot: "pokemon",
hasShadow: true,
repeat: true,
x: 10,
y: -1,
x: 12,
y: -5,
yShadowOffset: -5
},
{
spriteKey: "b2w2_veteran_m",
fileRoot: "mystery-encounters",
hasShadow: true,
x: -10,
y: 2,
x: -12,
y: 3,
yShadowOffset: 3
},
])
.withIntroDialogue([

View File

@ -10,7 +10,7 @@ import Trainer, { TrainerVariant } from "../../field/trainer";
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 { BattleEndPhase, EggLapsePhase, ExpPhase, ModifierRewardPhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases";
import { MysteryEncounterBattlePhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase";
import { MysteryEncounterBattlePhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase";
import * as Utils from "../../utils";
import { isNullOrUndefined } from "#app/utils";
import { TrainerType } from "#enums/trainer-type";
@ -27,6 +27,7 @@ import { WIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/myst
import { getTextWithColors, TextStyle } from "#app/ui/text";
import * as Overrides from "#app/overrides";
import { UiTheme } from "#enums/ui-theme";
import { MysteryEncounterUiSettings } from "#app/ui/mystery-encounter-ui-handler";
/**
*
@ -427,6 +428,11 @@ export function updatePlayerMoney(scene: BattleScene, changeValue: number, playS
if (playSound) {
scene.playSound("buy");
}
if (changeValue < 0) {
scene.queueMessage(i18next.t("mysteryEncounter:paid_money", { amount: -changeValue }), null, true);
} else {
scene.queueMessage(i18next.t("mysteryEncounter:receive_money", { amount: changeValue }), null, true);
}
}
/**
@ -459,7 +465,7 @@ export function generateModifierTypeOption(scene: BattleScene, modifier: () => M
}
/**
*
* This function is intended for use inside onPreOptionPhase() of an encounter option
* @param scene
* @param onPokemonSelected - Any logic that needs to be performed when Pokemon is chosen
* If a second option needs to be selected, onPokemonSelected should return a OptionSelectItem[] object
@ -674,6 +680,16 @@ export function setEncounterExp(scene: BattleScene, participantIds: integer[], b
};
}
/**
* Can be used to exit an encounter without any battles or followup
* Will skip any shops and rewards, and queue the next encounter phase as normal
* @param scene
* @param followupOptionSelectSettings
*/
export function initFollowupOptionSelect(scene: BattleScene, followupOptionSelectSettings: MysteryEncounterUiSettings) {
scene.pushPhase(new MysteryEncounterPhase(scene, followupOptionSelectSettings));
}
/**
* Can be used to exit an encounter without any battles or followup
* Will skip any shops and rewards, and queue the next encounter phase as normal
@ -688,7 +704,9 @@ export function leaveEncounterWithoutBattle(scene: BattleScene, addHealPhase: bo
}
export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase: boolean = false) {
if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.NO_BATTLE) {
if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.SAFARI_BATTLE) {
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
} else if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.NO_BATTLE) {
scene.pushPhase(new EggLapsePhase(scene));
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
} else if (!scene.getEnemyParty().find(p => scene.currentBattle.mysteryEncounter.encounterVariant !== MysteryEncounterVariant.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {

View File

@ -24,7 +24,8 @@ export enum MysteryEncounterVariant {
TRAINER_BATTLE,
WILD_BATTLE,
BOSS_BATTLE,
NO_BATTLE
NO_BATTLE,
SAFARI_BATTLE
}
export enum MysteryEncounterTier {
@ -118,6 +119,18 @@ export default interface IMysteryEncounter {
*/
expMultiplier?: number;
/**
* When true, will never queue PostSummon phases from a SummonPhase
* Defaults to false
*/
disableAllPostSummon?: boolean;
/**
* Used for keeping RNG consistent on session resets, but increments when cycling through multiple "Encounters" on the same wave
* You should never need to modify this
*/
seedOffset?: any;
/**
* Generic property to set any custom data required for the encounter
* Extremely useful for carrying state/data between onPreOptionPhase/onOptionPhase/onPostOptionPhase

View File

@ -10,6 +10,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { DepartmentStoreSaleEncounter } from "./encounters/department-store-sale-encounter";
import { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-encounter";
import { FieldTripEncounter } from "./encounters/field-trip-encounter";
import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-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;
@ -213,6 +214,7 @@ export function initMysteryEncounters() {
allMysteryEncounters[MysteryEncounterType.DEPARTMENT_STORE_SALE] = DepartmentStoreSaleEncounter;
allMysteryEncounters[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerEncounter;
allMysteryEncounters[MysteryEncounterType.FIELD_TRIP] = FieldTripEncounter;
allMysteryEncounters[MysteryEncounterType.SAFARI_ZONE] = SafariZoneEncounter;
// Add extreme encounters to biome map
extremeBiomeEncounters.forEach(encounter => {

View File

@ -11,6 +11,7 @@ export class MysteryEncounterSpriteConfig {
tint?: number;
x?: number; // X offset
y?: number; // Y offset
yShadowOffset?: number;
scale?: number;
isItem?: boolean; // For item sprites, set to true
}
@ -37,10 +38,10 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
return;
}
const getSprite = (spriteKey: string, hasShadow?: boolean) => {
const getSprite = (spriteKey: string, hasShadow?: boolean, yShadowOffset?: number) => {
const ret = this.scene.addFieldSprite(0, 0, spriteKey);
ret.setOrigin(0.5, 1);
ret.setPipeline(this.scene.spritePipeline, { tone: [0.0, 0.0, 0.0, 0.0], hasShadow: !!hasShadow });
ret.setPipeline(this.scene.spritePipeline, { tone: [0.0, 0.0, 0.0, 0.0], hasShadow: !!hasShadow, yShadowOffset: yShadowOffset ?? 0 });
return ret;
};
@ -62,7 +63,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
let sprite: GameObjects.Sprite;
let tintSprite: GameObjects.Sprite;
if (!config.isItem) {
sprite = getSprite(config.spriteKey, config.hasShadow);
sprite = getSprite(config.spriteKey, config.hasShadow, config.yShadowOffset);
tintSprite = getSprite(config.spriteKey);
} else {
sprite = getItemSprite(config.spriteKey);
@ -83,8 +84,8 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
tintSprite.setPosition(origin + config.x, tintSprite.y);
}
if (config.y) {
sprite.setPosition(sprite.x, config.y);
tintSprite.setPosition(tintSprite.x, config.y);
sprite.setPosition(sprite.x, sprite.y + config.y);
tintSprite.setPosition(tintSprite.x, tintSprite.y + config.y);
}
} else {
// Single sprite

View File

@ -16,6 +16,9 @@ export const battle: SimpleTranslationEntries = {
"moneyWon": "You got\n₽{{moneyAmount}} for winning!",
"moneyPickedUp": "You picked up ₽{{moneyAmount}}!",
"pokemonCaught": "{{pokemonName}} was caught!",
"pokemonBrokeFree": "Oh no!\nThe Pokémon broke free!",
"pokemonFled": "The wild {{pokemonName}} fled!",
"playerFled": "You fled from the {{pokemonName}}!",
"addedAsAStarter": "{{pokemonName}} has been\nadded as a starter!",
"partyFull": "Your party is full.\nRelease a Pokémon to make room for {{pokemonName}}?",
"pokemon": "Pokémon",

View File

@ -17,6 +17,10 @@ export const mysteryEncounter: SimpleTranslationEntries = {
// DO NOT REMOVE
"unit_test_dialogue": "{{test}}{{test}} {{test{{test}}}} {{test1}} {{test\}} {{test\\}} {{test\\\}} {test}}",
// General use content
"paid_money": "You paid ₽{{amount, number}}.",
"receive_money": "You received ₽{{amount, number}}!",
// Mystery Encounters -- Common Tier
"mysterious_chest_intro_message": "You found...@d{32} a chest?",
@ -125,7 +129,7 @@ export const mysteryEncounter: SimpleTranslationEntries = {
"field_trip_outro_good": "Thank you so much for your kindness!\nI hope the items I had were helpful!",
"field_trip_outro_bad": "Come along children, we'll\nfind a better demonstration elsewhere.",
// Mystery Encounters -- Uncommon Tier
// Mystery Encounters -- Great Tier
"mysterious_challengers_intro_message": "Mysterious challengers have appeared!",
"mysterious_challengers_title": "Mysterious Challengers",
@ -140,7 +144,35 @@ export const mysteryEncounter: SimpleTranslationEntries = {
"mysterious_challengers_option_selected_message": "The trainer steps forward...",
"mysterious_challengers_outro_win": "The mysterious challenger was defeated!",
// Mystery Encounters -- Rare Tier
"safari_zone_intro_message": "It's a safari zone!",
"safari_zone_title": "The Safari Zone",
"safari_zone_description": "There are all kinds of rare and special Pokémon that can be found here!\nIf you choose to enter, you'll have a time limit of 3 wild encounters where you can try to catch these special Pokémon.\nBeware, though. These Pokémon may flee before you're able to catch them!",
"safari_zone_query": "Would you like to enter?",
"safari_zone_option_1_label": "Enter",
"safari_zone_option_1_tooltip": "(-) Pay {{option1Money, money}}\n@[SUMMARY_GREEN]{(?) Safari Zone}",
"safari_zone_option_2_label": "Leave",
"safari_zone_option_2_tooltip": "(-) No Rewards",
"safari_zone_option_1_selected_message": "Time to test your luck.",
"safari_zone_option_2_selected_message": "You hurry along your way,\nwith a slight feeling of regret.",
"safari_zone_pokeball_option_label": "Throw a Pokéball",
"safari_zone_pokeball_option_tooltip": "(+) Throw a Pokéball",
"safari_zone_pokeball_option_selected": "You throw a Pokéball!",
"safari_zone_bait_option_label": "Throw bait",
"safari_zone_bait_option_tooltip": "(+) Increases Capture Rate\n(-) Chance to Increase Flee Rate",
"safari_zone_bait_option_selected": "You throw some bait!",
"safari_zone_mud_option_label": "Throw mud",
"safari_zone_mud_option_tooltip": "(+) Decreases Flee Rate\n(-) Chance to Decrease Capture Rate",
"safari_zone_mud_option_selected": "You throw some mud!",
"safari_zone_flee_option_label": "Flee",
"safari_zone_flee_option_tooltip": "(?) Flee from this Pokémon",
"safari_zone_pokemon_watching": "{{pokemonName}} is watching carefully!",
"safari_zone_pokemon_eating": "{{pokemonName}} is eating!",
"safari_zone_pokemon_busy_eating": "{{pokemonName}} is busy eating!",
"safari_zone_pokemon_angry": "{{pokemonName}} is angry!",
"safari_zone_pokemon_beside_itself_angry": "{{pokemonName}} is beside itself with anger!",
"safari_zone_remaining_count": "{{remainingCount}} Pokémon remaining!",
// Mystery Encounters -- Ultra Tier
"training_session_intro_message": "You've come across some\ntraining tools and supplies.",
"training_session_title": "Training Session",
@ -163,7 +195,7 @@ export const mysteryEncounter: SimpleTranslationEntries = {
$Its ability was changed to {{ability}}!`,
"training_session_outro_win": "That was a successful training session!",
// Mystery Encounters -- Super Rare Tier
// Mystery Encounters -- Rogue Tier
"dark_deal_intro_message": "A strange man in a tattered coat\nstands in your way...",
"dark_deal_speaker": "Shady Guy",

View File

@ -1650,7 +1650,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
pokemon.untint(250, "Sine.easeIn");
this.scene.updateFieldScale();
pokemon.x += 16;
pokemon.y -= 16;
pokemon.y -= 20;
pokemon.alpha = 0;
// Ease pokemon in
@ -1680,7 +1680,9 @@ export class SummonPhase extends PartyMemberPokemonPhase {
pokemon.resetTurnData();
if (!this.loaded || this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER || (this.scene.currentBattle.waveIndex % 10) === 1) {
const addPostSummonForEncounter = this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && !this.scene.currentBattle.mysteryEncounter?.disableAllPostSummon;
if (!this.loaded || this.scene.currentBattle.battleType === BattleType.TRAINER || (this.scene.currentBattle.waveIndex % 10) === 1 || addPostSummonForEncounter) {
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
this.queuePostSummon();
}

View File

@ -15,6 +15,7 @@ import { Tutorial, handleTutorial } from "../tutorial";
import { IvScannerModifier } from "../modifier/modifier";
import * as Utils from "../utils";
import { isNullOrUndefined } from "../utils";
import { MysteryEncounterUiSettings } from "#app/ui/mystery-encounter-ui-handler";
/**
* Will handle (in order):
@ -26,8 +27,11 @@ import { isNullOrUndefined } from "../utils";
* - Queuing of the MysteryEncounterOptionSelectedPhase
*/
export class MysteryEncounterPhase extends Phase {
constructor(scene: BattleScene) {
followupOptionSelectSettings: MysteryEncounterUiSettings;
constructor(scene: BattleScene, followupOptionSelectSettings?: MysteryEncounterUiSettings) {
super(scene);
this.followupOptionSelectSettings = followupOptionSelectSettings;
}
start() {
@ -37,12 +41,18 @@ export class MysteryEncounterPhase extends Phase {
this.scene.clearPhaseQueue();
this.scene.clearPhaseQueueSplice();
// Sets flag that ME was encountered
// Can be used in later MEs to check for requirements to spawn, etc.
this.scene.mysteryEncounterData.encounteredEvents.push([this.scene.currentBattle.mysteryEncounter.encounterType, this.scene.currentBattle.mysteryEncounter.encounterTier]);
// Generates seed offset for RNG consistency, but incremented if the same MysteryEncounter has multiple option select cycles
const offset = this.scene.currentBattle.mysteryEncounter.seedOffset ?? this.scene.currentBattle.waveIndex * 1000;
this.scene.currentBattle.mysteryEncounter.seedOffset = offset + 512;
if (!this.followupOptionSelectSettings) {
// Sets flag that ME was encountered, only if this is not a followup option select phase
// Can be used in later MEs to check for requirements to spawn, etc.
this.scene.mysteryEncounterData.encounteredEvents.push([this.scene.currentBattle.mysteryEncounter.encounterType, this.scene.currentBattle.mysteryEncounter.encounterTier]);
}
// Initiates encounter dialogue window and option select
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER);
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, this.followupOptionSelectSettings);
}
handleOptionSelect(option: MysteryEncounterOption, index: number): boolean {
@ -61,24 +71,24 @@ export class MysteryEncounterPhase extends Phase {
return await option.onPreOptionPhase(this.scene)
.then((result) => {
if (isNullOrUndefined(result) || result) {
this.continueEncounter(index);
this.continueEncounter();
}
});
}, this.scene.currentBattle.waveIndex * 1000);
}, this.scene.currentBattle.mysteryEncounter.seedOffset);
} else {
this.continueEncounter(index);
this.continueEncounter();
}
return true;
}
continueEncounter(optionIndex: number) {
continueEncounter() {
const endDialogueAndContinueEncounter = () => {
this.scene.pushPhase(new MysteryEncounterOptionSelectedPhase(this.scene));
this.end();
};
const optionSelectDialogue = this.scene.currentBattle?.mysteryEncounter?.options?.[optionIndex]?.dialogue;
const optionSelectDialogue = this.scene.currentBattle?.mysteryEncounter?.selectedOption?.dialogue;
if (optionSelectDialogue?.selected?.length > 0) {
// Handle intermediate dialogue (between player selection event and the onOptionSelect logic)
this.scene.ui.setMode(Mode.MESSAGE);
@ -139,14 +149,14 @@ export class MysteryEncounterOptionSelectedPhase extends Phase {
this.onOptionSelect(this.scene).finally(() => {
this.end();
});
}, this.scene.currentBattle.waveIndex * 1000);
}, this.scene.currentBattle.mysteryEncounter.seedOffset);
});
} else {
this.scene.executeWithSeedOffset(() => {
this.onOptionSelect(this.scene).finally(() => {
this.end();
});
}, this.scene.currentBattle.waveIndex * 1000);
}, this.scene.currentBattle.mysteryEncounter.seedOffset);
}
}
@ -265,7 +275,7 @@ export class MysteryEncounterBattlePhase extends Phase {
} else {
const trainer = this.scene.currentBattle.trainer;
let message: string;
scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), scene.currentBattle.waveIndex * 1000);
scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.mysteryEncounter.seedOffset);
const showDialogueAndSummon = () => {
scene.ui.showDialogue(message, trainer.getName(TrainerSlot.NONE, true), null, () => {
@ -390,6 +400,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
this.scene.tryRemovePhase(p => p instanceof SelectModifierPhase);
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, 0, null, { fillRemaining: false, rerollMultiplier: 0 }));
}
// Do not use ME's seedOffset for rewards, these should always be consistent with waveIndex (once per wave)
}, this.scene.currentBattle.waveIndex * 1000);
this.scene.pushPhase(new PostMysteryEncounterPhase(this.scene));
@ -423,7 +434,7 @@ export class PostMysteryEncounterPhase extends Phase {
this.continueEncounter();
}
});
}, this.scene.currentBattle.waveIndex * 1000);
}, this.scene.currentBattle.mysteryEncounter.seedOffset);
} else {
this.continueEncounter();
}

View File

@ -38,6 +38,7 @@ uniform vec2 texFrameUv;
uniform vec2 size;
uniform vec2 texSize;
uniform float yOffset;
uniform float yShadowOffset;
uniform vec4 tone;
uniform ivec4 baseVariantColors[32];
uniform vec4 variantColors[32];
@ -252,7 +253,7 @@ void main() {
float width = size.x - (yOffset / 2.0);
float spriteX = ((floor(outPosition.x / fieldScale) - relPosition.x) / width) + 0.5;
float spriteY = ((floor(outPosition.y / fieldScale) - relPosition.y) / size.y);
float spriteY = ((floor(outPosition.y / fieldScale) - relPosition.y - yShadowOffset) / size.y);
if (yCenter == 1) {
spriteY += 0.5;
@ -339,6 +340,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
this.set2f("size", 0, 0);
this.set2f("texSize", 0, 0);
this.set1f("yOffset", 0);
this.set1f("yShadowOffset", 0);
this.set4fv("tone", this._tone);
}
@ -351,6 +353,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
const tone = data["tone"] as number[];
const teraColor = data["teraColor"] as integer[] ?? [ 0, 0, 0 ];
const hasShadow = data["hasShadow"] as boolean;
const yShadowOffset = data["yShadowOffset"] as number;
const ignoreFieldPos = data["ignoreFieldPos"] as boolean;
const ignoreOverride = data["ignoreOverride"] as boolean;
@ -377,6 +380,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
this.set2f("size", sprite.frame.width, sprite.height);
this.set2f("texSize", sprite.texture.source[0].width, sprite.texture.source[0].height);
this.set1f("yOffset", sprite.height - sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale));
this.set1f("yShadowOffset", yShadowOffset ?? 0);
this.set4fv("tone", tone);
this.bindTexture(this.game.textures.get("tera").source[0].glTexture, 1);
@ -448,6 +452,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
this.set1f("vCutoff", v1);
const hasShadow = sprite.pipelineData["hasShadow"] as boolean;
const yShadowOffset = sprite.pipelineData["yShadowOffset"] as number;
if (hasShadow) {
const isEntityObj = sprite.parentContainer instanceof Pokemon || sprite.parentContainer instanceof Trainer || sprite.parentContainer instanceof MysteryEncounterIntroVisuals;
const field = isEntityObj ? sprite.parentContainer.parentContainer : sprite.parentContainer;
@ -455,7 +460,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
const baseY = (isEntityObj
? sprite.parentContainer.y
: sprite.y + sprite.height) * 6 / fieldScaleRatio;
const bottomPadding = Math.ceil(sprite.height * 0.05) * 6 / fieldScaleRatio;
const bottomPadding = Math.ceil(sprite.height * 0.05 + Math.max(yShadowOffset, 0)) * 6 / fieldScaleRatio;
const yDelta = (baseY - y1) / field.scale;
y2 = y1 = baseY + bottomPadding;
const pixelHeight = (v1 - v0) / (sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale));

View File

@ -12,6 +12,16 @@ import { isNullOrUndefined } from "../utils";
import { getPokeballAtlasKey } from "../data/pokeball";
import { getEncounterText } from "#app/data/mystery-encounters/mystery-encounter-utils";
export class MysteryEncounterUiSettings {
hideDescription?: boolean;
slideInDescription?: boolean;
overrideTitle?: string;
overrideDescription?: string;
overrideQuery?: string;
overrideOptions?: MysteryEncounterOption[];
startingCursorIndex?: number;
}
export default class MysteryEncounterUiHandler extends UiHandler {
private cursorContainer: Phaser.GameObjects.Container;
private cursorObj: Phaser.GameObjects.Image;
@ -27,7 +37,8 @@ export default class MysteryEncounterUiHandler extends UiHandler {
private descriptionScrollTween: Phaser.Tweens.Tween;
private rarityBall: Phaser.GameObjects.Sprite;
private filteredEncounterOptions: MysteryEncounterOption[] = [];
private overrideSettings: MysteryEncounterUiSettings;
private encounterOptions: MysteryEncounterOption[] = [];
private optionsMeetsReqs: boolean[];
protected viewPartyIndex: integer = 0;
@ -70,16 +81,21 @@ export default class MysteryEncounterUiHandler extends UiHandler {
show(args: any[]): boolean {
super.show(args);
this.overrideSettings = args[0] as MysteryEncounterUiSettings ?? {};
const showDescriptionContainer = isNullOrUndefined(this.overrideSettings?.hideDescription) ? true : !this.overrideSettings?.hideDescription;
const slideInDescription = isNullOrUndefined(this.overrideSettings?.slideInDescription) ? true : this.overrideSettings?.slideInDescription;
const startingCursorIndex = this.overrideSettings?.startingCursorIndex ?? 0;
this.cursorContainer.setVisible(true);
this.descriptionContainer.setVisible(true);
this.descriptionContainer.setVisible(showDescriptionContainer);
this.optionsContainer.setVisible(true);
this.displayEncounterOptions(!(args[0] as boolean || false));
this.displayEncounterOptions(slideInDescription);
const cursor = this.getCursor();
if (cursor === (this?.optionsContainer?.length || 0) - 1) {
// Always resets cursor on view party button if it was last there
this.setCursor(cursor);
} else {
this.setCursor(0);
this.setCursor(startingCursorIndex);
}
if (this.blockInput) {
setTimeout(() => {
@ -100,12 +116,16 @@ export default class MysteryEncounterUiHandler extends UiHandler {
if (button === Button.CANCEL || button === Button.ACTION) {
if (button === Button.ACTION) {
const selected = this.filteredEncounterOptions[cursor];
const selected = this.encounterOptions[cursor];
if (cursor === this.viewPartyIndex) {
// Handle view party
success = true;
const overrideSettings: MysteryEncounterUiSettings = {
...this.overrideSettings,
slideInDescription: false
};
this.scene.ui.setMode(Mode.PARTY, PartyUiMode.CHECK, -1, () => {
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, true);
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, overrideSettings);
setTimeout(() => {
this.setCursor(this.viewPartyIndex);
this.unblockInput();
@ -253,7 +273,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
if (this.blockInput) {
this.blockInput = false;
for (let i = 0; i < this.optionsContainer.length - 1; i++) {
const optionMode = this.filteredEncounterOptions[i].optionMode;
const optionMode = this.encounterOptions[i].optionMode;
if (!this.optionsMeetsReqs[i] && (optionMode === EncounterOptionMode.DISABLED_OR_DEFAULT || optionMode === EncounterOptionMode.DISABLED_OR_SPECIAL)) {
continue;
}
@ -296,7 +316,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
displayEncounterOptions(slideInDescription: boolean = true): void {
this.getUi().clearText();
const mysteryEncounter = this.scene.currentBattle.mysteryEncounter;
this.filteredEncounterOptions = mysteryEncounter.options;
this.encounterOptions = this.overrideSettings?.overrideOptions ?? mysteryEncounter.options;
this.optionsMeetsReqs = [];
const titleText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.title, TextStyle.TOOLTIP_TITLE);
@ -307,11 +327,11 @@ export default class MysteryEncounterUiHandler extends UiHandler {
this.optionsContainer.removeAll();
// Options Window
for (let i = 0; i < this.filteredEncounterOptions.length; i++) {
const option = this.filteredEncounterOptions[i];
for (let i = 0; i < this.encounterOptions.length; i++) {
const option = this.encounterOptions[i];
let optionText;
switch (this.filteredEncounterOptions.length) {
switch (this.encounterOptions.length) {
case 2:
optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 });
break;
@ -424,7 +444,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
}
let text: string;
const cursorOption = this.filteredEncounterOptions[cursor];
const cursorOption = this.encounterOptions[cursor];
const optionDialogue = cursorOption.dialogue;
if (!this.optionsMeetsReqs[cursor] && (cursorOption.optionMode === EncounterOptionMode.DISABLED_OR_DEFAULT || cursorOption.optionMode === EncounterOptionMode.DISABLED_OR_SPECIAL) && optionDialogue.disabledButtonTooltip) {
text = getEncounterText(this.scene, optionDialogue.disabledButtonTooltip, TextStyle.TOOLTIP_CONTENT);
@ -474,6 +494,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
clear(): void {
super.clear();
this.overrideSettings = null;
this.optionsContainer.setVisible(false);
this.optionsContainer.removeAll(true);
this.descriptionContainer.setVisible(false);