Skip to content

Commit

Permalink
small cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
ImperialSympathizer committed Jul 3, 2024
1 parent b4a6302 commit 5048560
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 125 deletions.
128 changes: 10 additions & 118 deletions src/battle-scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler"
import CharSprite from "./ui/char-sprite";
import DamageNumberHandler from "./field/damage-number-handler";
import PokemonInfoContainer from "./ui/pokemon-info-container";
import {biomeDepths, biomeLinks, getBiomeName} from "./data/biomes";
import {biomeDepths, getBiomeName} from "./data/biomes";
import { SceneBase } from "./scene-base";
import CandyBar from "./ui/candy-bar";
import { Variant, variantData } from "./data/variant";
Expand Down Expand Up @@ -1046,120 +1046,12 @@ export default class BattleScene extends SceneBase {
}
}

// TODO: remove once encounter spawn rate is finalized
// Just a helper function to calculate stats on MEs per run
aggregateEncountersPerRun(baseSpawnWeight: number) {
const numRuns = 1000;
let run = 0;

const calculateNumEncounters = (): number[] => {
let encounterRate = baseSpawnWeight;
const numEncounters = [0, 0, 0, 0];
let currentBiome = Biome.TOWN;
let currentArena = this.newArena(currentBiome);
for (let i = 10; i < 180; i++) {
// Boss
if (i % 10 === 0) {
continue;
}

// New biome
if (i % 10 === 1) {
if (Array.isArray(biomeLinks[currentBiome])) {
let biomes: Biome[];
this.executeWithSeedOffset(() => {
biomes = (biomeLinks[currentBiome] as (Biome | [Biome, integer])[])
.filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1]))
.map(b => !Array.isArray(b) ? b : b[0]);
}, i);
currentBiome = biomes[Utils.randSeedInt(biomes.length)];
} else if (biomeLinks.hasOwnProperty(currentBiome)) {
currentBiome = (biomeLinks[currentBiome] as Biome);
} else {
if (!(i % 50)) {
currentBiome = Biome.END;
} else {
currentBiome = this.generateRandomBiome(i);
}
}

currentArena = this.newArena(currentBiome);
}

// Fixed battle
if (this.gameMode.isFixedBattle(i)) {
continue;
}

// Trainer
if (this.gameMode.isWaveTrainer(i, currentArena)) {
continue;
}

// Otherwise, roll encounter

const roll = Utils.randSeedInt(256);

// If total number of encounters is lower than expected for the run, slightly favor a new encounter
// Do the reverse as well
const expectedEncountersByFloor = 8 / (180 - 10) * i;
const currentRunDiffFromAvg = expectedEncountersByFloor - numEncounters.reduce((a, b) => a + b);
const favoredEncounterRate = encounterRate + currentRunDiffFromAvg * 5;

if (roll < favoredEncounterRate) {
encounterRate = baseSpawnWeight;

// Calculate encounter rarity
// Common / Uncommon / Rare / Super Rare
const tierWeights = [34, 16, 11, 3];

// Adjust tier weights by currently encountered events (pity system that lowers odds of multiple common/uncommons)
tierWeights[0] = tierWeights[0] - 3 * numEncounters[0];
tierWeights[1] = tierWeights[1] - 2 * numEncounters[1];

const totalWeight = tierWeights.reduce((a, b) => a + b);
const tierValue = Utils.randSeedInt(totalWeight);
const commonThreshold = totalWeight - tierWeights[0]; // 64 - 32 = 32
const uncommonThreshold = totalWeight - tierWeights[0] - tierWeights[1]; // 64 - 32 - 16 = 16
const rareThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2]; // 64 - 32 - 16 - 10 = 6

tierValue > commonThreshold ? ++numEncounters[0] : tierValue > uncommonThreshold ? ++numEncounters[1] : tierValue > rareThreshold ? ++numEncounters[2] : ++numEncounters[3];
} else {
encounterRate++;
}
}

return numEncounters;
};

const runs = [];
while (run < numRuns) {
this.executeWithSeedOffset(() => {
const numEncounters = calculateNumEncounters();
runs.push(numEncounters);
}, 1000 * run);
run++;
}

const n = runs.length;
const totalEncountersInRun = runs.map(run => run.reduce((a, b) => a + b));
const totalMean = totalEncountersInRun.reduce((a, b) => a + b) / n;
const totalStd = Math.sqrt(totalEncountersInRun.map(x => Math.pow(x - totalMean, 2)).reduce((a, b) => a + b) / n);
const commonMean = runs.reduce((a, b) => a + b[0], 0) / n;
const uncommonMean = runs.reduce((a, b) => a + b[1], 0) / n;
const rareMean = runs.reduce((a, b) => a + b[2], 0) / n;
const superRareMean = runs.reduce((a, b) => a + b[3], 0) / n;

console.log(`Starting weight: ${baseSpawnWeight}\nAverage MEs per run: ${totalMean}\nStandard Deviation: ${totalStd}\nAvg Commons: ${commonMean}\nAvg Uncommons: ${uncommonMean}\nAvg Rares: ${rareMean}\nAvg Super Rares: ${superRareMean}`);
}

newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounter?: MysteryEncounter): Battle {
// TODO: remove once encounter spawn rate is finalized
// Useful for calculating spawns per run
// TODO: can remove once encounter spawn rates are finalized
// let baseSpawnWeight = 2;
// while (baseSpawnWeight < 6) {
// this.aggregateEncountersPerRun(baseSpawnWeight);
// baseSpawnWeight += 2;
// calculateMEAggregateStats(this, baseSpawnWeight);
// baseSpawnWeight += 1;
// }

const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave;
Expand Down Expand Up @@ -1211,8 +1103,8 @@ export default class BattleScene extends SceneBase {
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(newWaveIndex) && !(this.gameMode.isClassic && (newWaveIndex > 180 || newWaveIndex < 10))) {
const roll = Utils.randSeedInt(256);

// Base spawn weight is 4/256, and increases by 1/256 for each missed attempt at spawning an encounter on a valid floor
// TODO: reset BASE_MYSTYERY_ENCOUNTER_WEIGHT to 4, 90 is for test branch
// Base spawn weight is 3/256, and increases by 1/256 for each missed attempt at spawning an encounter on a valid floor
// TODO: reset BASE_MYSTYERY_ENCOUNTER_WEIGHT to 3, 90 is for test branch
const sessionEncounterRate = !isNullOrUndefined(this.mysteryEncounterFlags?.encounterSpawnChance) ? this.mysteryEncounterFlags.encounterSpawnChance : BASE_MYSTYERY_ENCOUNTER_WEIGHT;

// If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn
Expand Down Expand Up @@ -2765,16 +2657,16 @@ export default class BattleScene extends SceneBase {
return encounter;
}

// Common / Uncommon / Rare / Super Rare
const tierWeights = [34, 16, 11, 3];
// Common / Uncommon / Rare / Super Rare (base out of 128)
const tierWeights = [61, 40, 21, 6];

// Adjust tier weights by previously encountered events to lower odds of only common/uncommons in run
this.mysteryEncounterFlags.encounteredEvents.forEach(val => {
const tier = val[1];
if (tier === MysteryEncounterTier.COMMON) {
tierWeights[0] = tierWeights[0] - 3;
tierWeights[0] = tierWeights[0] - 6;
} else if (tier === MysteryEncounterTier.UNCOMMON) {
tierWeights[1] = tierWeights[1] - 2;
tierWeights[1] = tierWeights[1] - 4;
}
});

Expand Down
4 changes: 2 additions & 2 deletions src/data/mystery-encounters/fight-or-flight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export const FightOrFlightEncounter: MysteryEncounter = new MysteryEncounterBuil
instance.enemyPartyConfigs = [config];

// Calculate item
// 1-50 GREAT, 50-100 ULTRA, 100-150 ROGUE, 150+ MASTER
const tier = scene.currentBattle.waveIndex > 150 ? ModifierTier.MASTER : scene.currentBattle.waveIndex > 100 ? ModifierTier.ROGUE : scene.currentBattle.waveIndex > 50 ? ModifierTier.ULTRA : ModifierTier.GREAT;
// 10-60 GREAT, 60-110 ULTRA, 110-160 ROGUE, 160-180 MASTER
const tier = scene.currentBattle.waveIndex > 160 ? ModifierTier.MASTER : scene.currentBattle.waveIndex > 110 ? ModifierTier.ROGUE : scene.currentBattle.waveIndex > 60 ? ModifierTier.ULTRA : ModifierTier.GREAT;
regenerateModifierPoolThresholds(scene.getParty(), ModifierPoolType.PLAYER, 0); // refresh player item pool
const item = getPlayerModifierTypeOptions(1, scene.getParty(), [], { guaranteedModifierTiers: [tier]})[0];
scene.currentBattle.mysteryEncounter.dialogueTokens.push([/@ec\{itemName\}/gi, item.type.name]);
Expand Down
4 changes: 2 additions & 2 deletions src/data/mystery-encounters/mysterious-challengers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = new MysteryEncou
// Spawn hard fight with ULTRA/GREAT reward (can improve with luck)
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];

setCustomEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });
setCustomEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], fillRemaining: true });

return initBattleWithEnemyConfig(scene, config);
})
Expand All @@ -126,7 +126,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = new MysteryEncou
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[2];

// To avoid player level snowballing from picking this option
encounter.expMultiplier = 0.8;
encounter.expMultiplier = 0.9;

setCustomEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });

Expand Down
109 changes: 109 additions & 0 deletions src/data/mystery-encounters/mystery-encounter-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {Species} from "#enums/species";
import {Type} from "#app/data/type";
import {BattlerTagType} from "#enums/battler-tag-type";
import PokemonData from "#app/system/pokemon-data";
import {Biome} from "#enums/biome";
import {biomeLinks} from "#app/data/biomes";

/**
* Util file for functions used in mystery encounters
Expand Down Expand Up @@ -561,3 +563,110 @@ export function applyEncounterDialogueTokens(scene: BattleScene, text: string):

return text;
}

// TODO: remove once encounter spawn rate is finalized
// Just a helper function to calculate stats on MEs per run
export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: number) {
const numRuns = 1000;
let run = 0;

const calculateNumEncounters = (): number[] => {
let encounterRate = baseSpawnWeight;
const numEncounters = [0, 0, 0, 0];
let currentBiome = Biome.TOWN;
let currentArena = scene.newArena(currentBiome);
for (let i = 10; i < 180; i++) {
// Boss
if (i % 10 === 0) {
continue;
}

// New biome
if (i % 10 === 1) {
if (Array.isArray(biomeLinks[currentBiome])) {
let biomes: Biome[];
scene.executeWithSeedOffset(() => {
biomes = (biomeLinks[currentBiome] as (Biome | [Biome, integer])[])
.filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1]))
.map(b => !Array.isArray(b) ? b : b[0]);
}, i);
currentBiome = biomes[Utils.randSeedInt(biomes.length)];
} else if (biomeLinks.hasOwnProperty(currentBiome)) {
currentBiome = (biomeLinks[currentBiome] as Biome);
} else {
if (!(i % 50)) {
currentBiome = Biome.END;
} else {
currentBiome = scene.generateRandomBiome(i);
}
}

currentArena = scene.newArena(currentBiome);
}

// Fixed battle
if (scene.gameMode.isFixedBattle(i)) {
continue;
}

// Trainer
if (scene.gameMode.isWaveTrainer(i, currentArena)) {
continue;
}

// Otherwise, roll encounter

const roll = Utils.randSeedInt(256);

// If total number of encounters is lower than expected for the run, slightly favor a new encounter
// Do the reverse as well
const expectedEncountersByFloor = 8 / (180 - 10) * i;
const currentRunDiffFromAvg = expectedEncountersByFloor - numEncounters.reduce((a, b) => a + b);
const favoredEncounterRate = encounterRate + currentRunDiffFromAvg * 5;

if (roll < favoredEncounterRate) {
encounterRate = baseSpawnWeight;

// Calculate encounter rarity
// Common / Uncommon / Rare / Super Rare (base is out of 128)
const tierWeights = [61, 40, 21, 6];

// Adjust tier weights by currently encountered events (pity system that lowers odds of multiple common/uncommons)
tierWeights[0] = tierWeights[0] - 6 * numEncounters[0];
tierWeights[1] = tierWeights[1] - 4 * numEncounters[1];

const totalWeight = tierWeights.reduce((a, b) => a + b);
const tierValue = Utils.randSeedInt(totalWeight);
const commonThreshold = totalWeight - tierWeights[0]; // 64 - 32 = 32
const uncommonThreshold = totalWeight - tierWeights[0] - tierWeights[1]; // 64 - 32 - 16 = 16
const rareThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2]; // 64 - 32 - 16 - 10 = 6

tierValue > commonThreshold ? ++numEncounters[0] : tierValue > uncommonThreshold ? ++numEncounters[1] : tierValue > rareThreshold ? ++numEncounters[2] : ++numEncounters[3];
} else {
encounterRate++;
}
}

return numEncounters;
};

const runs = [];
while (run < numRuns) {
scene.executeWithSeedOffset(() => {
const numEncounters = calculateNumEncounters();
runs.push(numEncounters);
}, 1000 * run);
run++;
}

const n = runs.length;
const totalEncountersInRun = runs.map(run => run.reduce((a, b) => a + b));
const totalMean = totalEncountersInRun.reduce((a, b) => a + b) / n;
const totalStd = Math.sqrt(totalEncountersInRun.map(x => Math.pow(x - totalMean, 2)).reduce((a, b) => a + b) / n);
const commonMean = runs.reduce((a, b) => a + b[0], 0) / n;
const uncommonMean = runs.reduce((a, b) => a + b[1], 0) / n;
const rareMean = runs.reduce((a, b) => a + b[2], 0) / n;
const superRareMean = runs.reduce((a, b) => a + b[3], 0) / n;

console.log(`Starting weight: ${baseSpawnWeight}\nAverage MEs per run: ${totalMean}\nStandard Deviation: ${totalStd}\nAvg Commons: ${commonMean}\nAvg Uncommons: ${uncommonMean}\nAvg Rares: ${rareMean}\nAvg Super Rares: ${superRareMean}`);
}
2 changes: 1 addition & 1 deletion src/data/mystery-encounters/mystery-encounters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {MysteriousChestEncounter} from "./mysterious-chest";
import {FightOrFlightEncounter} from "#app/data/mystery-encounters/fight-or-flight";
import {TrainingSessionEncounter} from "#app/data/mystery-encounters/training-session";

// TODO: reset BASE_MYSTYERY_ENCOUNTER_WEIGHT to 4, 90 is for test branch
// TODO: reset BASE_MYSTYERY_ENCOUNTER_WEIGHT to 3, 90 is for test branch
export const BASE_MYSTYERY_ENCOUNTER_WEIGHT = 90;

export const allMysteryEncounters: MysteryEncounter[] = [];
Expand Down
2 changes: 0 additions & 2 deletions src/phases/mystery-encounter-phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ export class MysteryEncounterPhase extends Phase {
this.scene.clearPhaseQueue();
this.scene.clearPhaseQueueSplice();

this.scene.getParty()[0].ivs = new Array(6).fill(0);

// Sets flag that ME was encountered
// Can be used in later MEs to check for requirements to spawn, etc.
this.scene.mysteryEncounterFlags.encounteredEvents.push([this.scene.currentBattle.mysteryEncounter.encounterType, this.scene.currentBattle.mysteryEncounter.encounterTier]);
Expand Down

0 comments on commit 5048560

Please sign in to comment.