various ME bug fixes and balance changes

This commit is contained in:
ImperialSympathizer 2024-09-05 15:24:57 -04:00
parent e23d40618f
commit b032a3d6a5
17 changed files with 218 additions and 1011 deletions

File diff suppressed because it is too large Load Diff

View File

@ -95,10 +95,9 @@ import { ToggleDoublePositionPhase } from "./phases/toggle-double-position-phase
import { TurnInitPhase } from "./phases/turn-init-phase";
import { ShopCursorTarget } from "./enums/shop-cursor-target";
import MysteryEncounter from "./data/mystery-encounters/mystery-encounter";
import { allMysteryEncounters, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "./data/mystery-encounters/mystery-encounters";
import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "./data/mystery-encounters/mystery-encounters";
import { MysteryEncounterData } from "#app/data/mystery-encounters/mystery-encounter-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
@ -1151,33 +1150,36 @@ export default class BattleScene extends SceneBase {
}
// TODO: remove these once ME spawn rates are finalized
// let testStartingWeight = 0;
// while (testStartingWeight < 3) {
// let testStartingWeight = 3;
// while (testStartingWeight < 4) {
// calculateMEAggregateStats(this, testStartingWeight);
// testStartingWeight += 2;
// testStartingWeight += 1;
// }
// calculateRareSpawnAggregateStats(this, 14);
// Check for mystery encounter
// Can only occur in place of a standard wild battle, waves 10-180
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180
const highestMysteryEncounterWave = 180;
const lowestMysteryEncounterWave = 10;
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(newWaveIndex) && newWaveIndex < highestMysteryEncounterWave && newWaveIndex > lowestMysteryEncounterWave) {
const roll = Utils.randSeedInt(256);
// Base spawn weight is 1/256, and increases by 5/256 for each missed attempt at spawning an encounter on a valid floor
const sessionEncounterRate = !isNullOrUndefined(this.mysteryEncounterData?.encounterSpawnChance) ? this.mysteryEncounterData.encounterSpawnChance : BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
// Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor
const sessionEncounterRate = this.mysteryEncounterData.encounterSpawnChance;
const encounteredEvents = this.mysteryEncounterData.encounteredEvents;
// If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn
// Do the reverse as well
// Reduces occurrence of runs with very few (<6) and a ton (>10) of encounters
// If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn (reverse as well)
// Reduces occurrence of runs with total encounters significantly different from AVERAGE_ENCOUNTERS_PER_RUN_TARGET
const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (highestMysteryEncounterWave - lowestMysteryEncounterWave) * (newWaveIndex - lowestMysteryEncounterWave);
const currentRunDiffFromAvg = expectedEncountersByFloor - (this.mysteryEncounterData?.encounteredEvents?.length || 0);
const favoredEncounterRate = sessionEncounterRate + currentRunDiffFromAvg * 5;
const currentRunDiffFromAvg = expectedEncountersByFloor - encounteredEvents.length;
const favoredEncounterRate = sessionEncounterRate + currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER;
const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) ? favoredEncounterRate : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!;
if (roll < successRate) {
// If the most recent ME was 3 or fewer waves ago, can never spawn a ME
const canSpawn = encounteredEvents.length === 0 || (newWaveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex) > 3 || !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE);
if (canSpawn && roll < successRate) {
newBattleType = BattleType.MYSTERY_ENCOUNTER;
// Reset base spawn weight
this.mysteryEncounterData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
@ -1240,7 +1242,7 @@ export default class BattleScene extends SceneBase {
const isEndlessFifthWave = this.gameMode.hasShortBiomes && (lastBattle.waveIndex % 5) === 0;
const isWaveIndexMultipleOfFiftyMinusOne = (lastBattle.waveIndex % 50) === 49;
const isNewBiome = isWaveIndexMultipleOfTen || isEndlessFifthWave || (isEndlessOrDaily && isWaveIndexMultipleOfFiftyMinusOne);
const resetArenaState = isNewBiome || this.currentBattle.battleType === BattleType.TRAINER || this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS;
const resetArenaState = isNewBiome || this.currentBattle.battleType === BattleType.TRAINER || this.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER || this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS;
this.getEnemyParty().forEach(enemyPokemon => enemyPokemon.destroy());
this.trySpreadPokerus();
if (!isNewBiome && (newWaveIndex % 10) === 5) {
@ -1248,14 +1250,19 @@ export default class BattleScene extends SceneBase {
}
if (resetArenaState) {
this.arena.resetArenaEffects();
if (lastBattle?.mysteryEncounter?.encounterMode !== MysteryEncounterMode.NO_BATTLE) {
playerField.forEach((_, p) => this.pushPhase(new ReturnPhase(this, p)));
for (const pokemon of this.getParty()) {
pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
playerField.forEach((pokemon, p) => {
if (pokemon.isOnField()) {
this.pushPhase(new ReturnPhase(this, p));
}
});
for (const pokemon of this.getParty()) {
pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
}
if (!this.trainer.visible) {
this.pushPhase(new ShowTrainerPhase(this));
}
}
@ -2960,8 +2967,8 @@ export default class BattleScene extends SceneBase {
}
let availableEncounters: MysteryEncounter[] = [];
// New encounter will never be the same as the most recent encounter
const previousEncounter = this.mysteryEncounterData.encounteredEvents?.length > 0 ? this.mysteryEncounterData.encounteredEvents[this.mysteryEncounterData.encounteredEvents.length - 1][0] : null;
// New encounter should never be the same as the most recent encounter
const previousEncounter = this.mysteryEncounterData.encounteredEvents.length > 0 ? this.mysteryEncounterData.encounteredEvents[this.mysteryEncounterData.encounteredEvents.length - 1].type : null;
const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType) ?? [];
// If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available
while (availableEncounters.length === 0 && tier !== null) {
@ -2977,10 +2984,10 @@ export default class BattleScene extends SceneBase {
if (!encounterCandidate.meetsRequirements!(this)) { // Meets encounter requirements
return false;
}
if (!isNullOrUndefined(previousEncounter) && encounterType === previousEncounter) { // Previous encounter was not this one
if (previousEncounter !== null && encounterType === previousEncounter) { // Previous encounter was not this one
return false;
}
if (this.mysteryEncounterData.encounteredEvents?.length > 0 && // Encounter has not exceeded max allowed encounters
if (this.mysteryEncounterData.encounteredEvents.length > 0 && // Encounter has not exceeded max allowed encounters
(encounterCandidate.maxAllowedEncounters && encounterCandidate.maxAllowedEncounters > 0)
&& this.mysteryEncounterData.encounteredEvents.filter(e => e.type === encounterType).length >= encounterCandidate.maxAllowedEncounters) {
return false;

View File

@ -194,7 +194,11 @@ export default class Battle {
getBgmOverride(scene: BattleScene): string | null {
const battlers = this.enemyParty.slice(0, this.getBattlerCount());
if (this.battleType === BattleType.TRAINER || this.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
if (this.battleType === BattleType.MYSTERY_ENCOUNTER && this.mysteryEncounter?.encounterMode === MysteryEncounterMode.DEFAULT) {
// Music is overridden MEs during ME onInit()
// Should not use BGM overrides before swapping from DEFAULT mode
return null;
} else if (this.battleType === BattleType.TRAINER || this.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) {
return `encounter_${this.trainer?.getEncounterBgm()}`;
}

View File

@ -891,7 +891,7 @@ export abstract class BattleAnim {
const isUser = frame.target === AnimFrameTarget.USER;
if (isUser && target === user) {
continue;
} else if (this.playOnEmptyField && frame.target === AnimFrameTarget.TARGET) {
} else if (this.playOnEmptyField && frame.target === AnimFrameTarget.TARGET && !target.isOnField()) {
continue;
}
const sprites = spriteCache[isUser ? AnimFrameTarget.USER : AnimFrameTarget.TARGET];

View File

@ -294,7 +294,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 230, 40, 2);
await transitionMysteryEncounterIntroVisuals(scene);
await transitionMysteryEncounterIntroVisuals(scene, true, true, 200);
})
.build()
)
@ -358,7 +358,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 230, 40, 2);
await transitionMysteryEncounterIntroVisuals(scene);
await transitionMysteryEncounterIntroVisuals(scene, true, true, 200);
})
.build()
)

View File

@ -86,8 +86,8 @@ export const FightOrFlightEncounter: MysteryEncounter =
: ModifierTier.GREAT;
regenerateModifierPoolThresholds(scene.getParty(), ModifierPoolType.PLAYER, 0);
let item: ModifierTypeOption | null = null;
// TMs excluded from possible rewards as they're too swingy in value for a singular item reward
while (!item || item.type.id.includes("TM_")) {
// TMs and Candy Jar excluded from possible rewards as they're too swingy in value for a singular item reward
while (!item || item.type.id.includes("TM_") || item.type.id === "CANDY_JAR") {
item = getPlayerModifierTypeOptions(1, scene.getParty(), [], { guaranteedModifierTiers: [tier], allowLuckUpgrades: false })[0];
}
encounter.setDialogueToken("itemName", item.type.name);

View File

@ -32,7 +32,7 @@ const BST_INCREASE_VALUE = 10;
*/
export const TheStrongStuffEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party
.withHideWildIntroMessage(true)
@ -43,7 +43,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
fileRoot: "items",
hasShadow: true,
isItem: true,
scale: 1.5,
scale: 1.25,
x: -15,
y: 3,
disableAnimation: true
@ -53,7 +53,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
fileRoot: "pokemon",
hasShadow: true,
repeat: true,
scale: 1.5,
scale: 1.25,
x: 20,
y: 10,
yShadow: 7
@ -76,7 +76,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
species: getPokemonSpecies(Species.SHUCKLE),
isBoss: true,
bossSegments: 5,
mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
mysteryEncounterData: new MysteryEncounterPokemonData(1.25),
nature: Nature.BOLD,
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
modifierConfigs: [
@ -142,7 +142,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
if (index < 2) {
// -15 to the two highest BST mons
modifyPlayerPokemonBST(pokemon, -HIGH_BST_REDUCTION_VALUE);
encounter.setDialogueToken("highBstPokemon" + index, pokemon.getNameToRender());
encounter.setDialogueToken("highBstPokemon" + (index + 1), pokemon.getNameToRender());
} else {
// +10 for the rest
modifyPlayerPokemonBST(pokemon, BST_INCREASE_VALUE);

View File

@ -20,9 +20,9 @@ export class MysteryEncounterData {
encounterSpawnChance: number = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
nextEncounterQueue: [MysteryEncounterType, integer][] = [];
constructor(flags: MysteryEncounterData | null) {
if (!isNullOrUndefined(flags)) {
Object.assign(this, flags);
constructor(data: MysteryEncounterData | null) {
if (!isNullOrUndefined(data)) {
Object.assign(this, data);
}
}
}

View File

@ -31,10 +31,36 @@ import { BugTypeSuperfanEncounter } from "#app/data/mystery-encounters/encounter
import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fun-and-games-encounter";
import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-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;
export const WEIGHT_INCREMENT_ON_SPAWN_MISS = 5;
export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 15;
/**
* Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
*/
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 3;
/**
* When an ME spawn roll fails, WEIGHT_INCREMENT_ON_SPAWN_MISS is added to future rolls for ME spawn checks.
* These values are cleared whenever the next ME spawns, and spawn weight returns to BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT
*/
export const WEIGHT_INCREMENT_ON_SPAWN_MISS = 3;
/**
* Specifies the target average for total ME spawns in a single Classic run.
* Used by anti-variance mechanic to check whether a run is above or below the target on a given wave.
*/
export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 12;
/**
* Will increase/decrease the chance of spawning a ME based on the current run's total MEs encountered vs AVERAGE_ENCOUNTERS_PER_RUN_TARGET
* Example:
* AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 17 (expects avg 1 ME every 10 floors)
* ANTI_VARIANCE_WEIGHT_MODIFIER = 15
*
* On wave 20, if 1 ME has been encountered, the difference from expected average is 0 MEs.
* So anti-variance adds 0/256 to the spawn weight check for ME spawn.
*
* On wave 20, if 0 MEs have been encountered, the difference from expected average is 1 ME.
* So anti-variance adds 15/256 to the spawn weight check for ME spawn.
*
* On wave 20, if 2 MEs have been encountered, the difference from expected average is -1 ME.
* So anti-variance adds -15/256 to the spawn weight check for ME spawn.
*/
export const ANTI_VARIANCE_WEIGHT_MODIFIER = 15;
export const EXTREME_ENCOUNTER_BIOMES = [
Biome.SEA,

View File

@ -1,7 +1,7 @@
import Battle, { BattlerIndex, BattleType } from "#app/battle";
import { biomeLinks, BiomePoolTier } from "#app/data/biomes";
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import Pokemon, { FieldPosition, PlayerPokemon, PokemonMove, PokemonSummonData } from "#app/field/pokemon";
import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier";
@ -908,13 +908,13 @@ export function handleMysteryEncounterTurnStartEffects(scene: BattleScene): bool
export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: number) {
const numRuns = 1000;
let run = 0;
const targetEncountersPerRun = 15; // AVERAGE_ENCOUNTERS_PER_RUN_TARGET
const biomes = Object.keys(Biome).filter(key => isNaN(Number(key)));
const alwaysPickTheseBiomes = [Biome.ISLAND, Biome.ABYSS, Biome.WASTELAND, Biome.FAIRY_CAVE, Biome.TEMPLE, Biome.LABORATORY, Biome.SPACE, Biome.WASTELAND];
const calculateNumEncounters = (): any[] => {
let encounterRate = baseSpawnWeight; // BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT
const numEncounters = [0, 0, 0, 0];
let mostRecentEncounterWave = 0;
const encountersByBiome = new Map<string, number>(biomes.map(b => [b, 0]));
const validMEfloorsByBiome = new Map<string, number>(biomes.map(b => [b, 0]));
let currentBiome = Biome.TOWN;
@ -976,16 +976,20 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
// If total number of encounters is lower than expected for the run, slightly favor a new encounter
// Do the reverse as well
const expectedEncountersByFloor = targetEncountersPerRun / (180 - 10) * i;
const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (180 - 10) * (i - 10);
const currentRunDiffFromAvg = expectedEncountersByFloor - numEncounters.reduce((a, b) => a + b);
const favoredEncounterRate = encounterRate + currentRunDiffFromAvg * 5;
const favoredEncounterRate = encounterRate + currentRunDiffFromAvg * 15;
if (roll < favoredEncounterRate) {
// If the most recent ME was 3 or fewer waves ago, can never spawn a ME
const canSpawn = (i - mostRecentEncounterWave) > 3;
if (canSpawn && roll < favoredEncounterRate) {
mostRecentEncounterWave = i;
encounterRate = baseSpawnWeight;
// Calculate encounter rarity
// Common / Uncommon / Rare / Super Rare (base is out of 128)
const tierWeights = [64, 40, 21, 3];
const tierWeights = [66, 40, 19, 3];
// Adjust tier weights by currently encountered events (pity system that lowers odds of multiple common/uncommons)
tierWeights[0] = tierWeights[0] - 6 * numEncounters[0];

View File

@ -1,8 +1,9 @@
export enum MysteryEncounterMode {
/** MysteryEncounter will always begin in this mode, but will always swap modes when an option is selected */
DEFAULT,
TRAINER_BATTLE,
WILD_BATTLE,
/** Enables wild boss music during encounter */
/** Enables special boss music during encounter */
BOSS_BATTLE,
NO_BATTLE
}

View File

@ -1,10 +1,11 @@
/**
* Enum values are base spawn weights of each tier
* Enum values are base spawn weights of each tier.
* The weights aim for 46.25/31.25/18.5/4% spawn ratios, AFTER accounting for anti-variance and pity mechanisms
*/
export enum MysteryEncounterTier {
COMMON = 64,
COMMON = 66,
GREAT = 40,
ULTRA = 21,
ULTRA = 19,
ROGUE = 3,
MASTER = 0 // Not currently used
}

View File

@ -5,7 +5,7 @@ import { variantData } from "#app/data/variant";
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info";
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr } from "../data/move";
import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
import { Constructor } from "#app/utils";
import { Constructor, isNullOrUndefined } from "#app/utils";
import * as Utils from "../utils";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
import { getLevelTotalExp } from "../data/exp";
@ -577,7 +577,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const formKey = this.getFormKey();
if (formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1 || formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1) {
return 1.5;
} else if (this?.mysteryEncounterData?.spriteScale) {
} else if (!isNullOrUndefined(this.mysteryEncounterData?.spriteScale)) {
return this.mysteryEncounterData.spriteScale;
}
return 1;

View File

@ -5,10 +5,10 @@
"query": "What will you do?",
"option": {
"1": {
"label": "Touch the Shuckle",
"label": "Approach the Shuckle",
"tooltip": "(?) Something awful or amazing might happen",
"selected": "You black out.",
"selected_2": "@f{150}When you awaken, the Shuckle is gone\nand juice stash completely drained.${{highBstPokemon1}} and {{highBstPokemon2}} feel a terrible lethargy come over them!\nTheir base stats were reduced by {{reductionValue}}!$Your remaining Pokémon feel an incredible vigor, though!\nTheir base stats are increased by {{increaseValue}}!"
"selected_2": "@f{150}When you awaken, the Shuckle is gone\nand juice stash completely drained.${{highBstPokemon1}} and {{highBstPokemon2}}\nfeel a terrible lethargy come over them!$Their base stats were reduced by {{reductionValue}}!$Your remaining Pokémon feel an incredible vigor, though!\nTheir base stats are increased by {{increaseValue}}!"
},
"2": {
"label": "Battle the Shuckle",

View File

@ -141,9 +141,9 @@ class DefaultOverrides {
// -------------------------
/** 1 to 256, set to null to ignore */
readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number | null = null;
readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number | null = 256;
readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier | null = null;
readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType | null = null;
readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType | null = MysteryEncounterType.CLOWNING_AROUND;
// -------------------------
// MODIFIER / ITEM OVERRIDES

View File

@ -6,6 +6,7 @@ import { StatusEffect } from "#app/enums/status-effect.js";
import { PokemonPhase } from "./pokemon-phase";
import { MysteryEncounterPostSummonTag } from "#app/data/battler-tags";
import { BattlerTagType } from "#enums/battler-tag-type";
import { BattleType } from "#app/battle";
export class PostSummonPhase extends PokemonPhase {
constructor(scene: BattleScene, battlerIndex: BattlerIndex) {
@ -23,7 +24,7 @@ export class PostSummonPhase extends PokemonPhase {
this.scene.arena.applyTags(ArenaTrapTag, pokemon);
// If this is mystery encounter and has post summon phase tag, apply post summon effects
if (pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag)) {
if (this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag).length > 0) {
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
}

View File

@ -15,12 +15,14 @@ import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-d
import i18next from "i18next";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
export default class MysteryEncounterUiHandler extends UiHandler {
private cursorContainer: Phaser.GameObjects.Container;
private cursorObj?: Phaser.GameObjects.Image;
private optionsContainer: Phaser.GameObjects.Container;
private optionScrollTweens: (Phaser.Tweens.Tween | null)[] = [null, null, null, null];
private tooltipWindow: Phaser.GameObjects.NineSlice;
private tooltipContainer: Phaser.GameObjects.Container;
@ -81,7 +83,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
this.rarityBall.setScale(0.75);
this.descriptionContainer.add(this.rarityBall);
const dexProgressIndicator = this.scene.add.sprite(12, 10, "encounter_radar");
const dexProgressIndicator = this.scene.add.sprite(12, 10, "encounter_radar");
dexProgressIndicator.setScale(0.80);
this.dexProgressContainer.add(dexProgressIndicator);
this.dexProgressContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, 24, 28), Phaser.Geom.Rectangle.Contains);
@ -341,16 +343,17 @@ export default class MysteryEncounterUiHandler extends UiHandler {
for (let i = 0; i < this.encounterOptions.length; i++) {
const option = this.encounterOptions[i];
let optionText;
let optionText: BBCodeText;
switch (this.encounterOptions.length) {
default:
case 2:
optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 });
optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 });
break;
case 3:
optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 });
optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 });
break;
case 4:
optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 });
optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 });
break;
}
@ -375,6 +378,38 @@ export default class MysteryEncounterUiHandler extends UiHandler {
if (this.blockInput) {
optionText.setAlpha(0.5);
}
// Sets up the mask that hides the option text to give an illusion of scrolling
const nonScrollWidth = 90;
const optionTextMaskRect = this.scene.make.graphics({});
optionTextMaskRect.setScale(6);
optionTextMaskRect.fillStyle(0xFFFFFF);
optionTextMaskRect.beginPath();
optionTextMaskRect.fillRect(optionText.x + 11, optionText.y + 140, nonScrollWidth, 18);
const optionTextMask = optionTextMaskRect.createGeometryMask();
optionText.setMask(optionTextMask);
const optionTextWidth = optionText.displayWidth;
const tween = this.optionScrollTweens[i];
if (tween) {
tween.remove();
this.optionScrollTweens[i] = null;
}
// Animates the option text scrolling sideways
if (optionTextWidth > nonScrollWidth) {
this.optionScrollTweens[i] = this.scene.tweens.add({
targets: optionText,
delay: Utils.fixedInt(2000),
loop: -1,
hold: Utils.fixedInt(2000),
duration: Utils.fixedInt((optionTextWidth - nonScrollWidth) / 15 * 2000),
x: `-=${(optionTextWidth - nonScrollWidth)}`
});
}
this.optionsContainer.add(optionText);
}