various ME bug fixes and balance changes
This commit is contained in:
parent
e23d40618f
commit
b032a3d6a5
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
|
|
|
@ -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()}`;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue