Fix battle RNG varying when loading a game

This commit is contained in:
Flashfyre 2024-04-01 12:48:35 -04:00
parent dbff672469
commit 366e3e5120
6 changed files with 99 additions and 44 deletions

View File

@ -67,6 +67,7 @@ export const STARTING_LEVEL_OVERRIDE = 0;
export const STARTING_WAVE_OVERRIDE = 0; export const STARTING_WAVE_OVERRIDE = 0;
export const STARTING_BIOME_OVERRIDE = Biome.TOWN; export const STARTING_BIOME_OVERRIDE = Biome.TOWN;
export const STARTING_MONEY_OVERRIDE = 0; export const STARTING_MONEY_OVERRIDE = 0;
const DEBUG_RNG = false;
export const startingWave = STARTING_WAVE_OVERRIDE || 1; export const startingWave = STARTING_WAVE_OVERRIDE || 1;
@ -178,6 +179,10 @@ export default class BattleScene extends Phaser.Scene {
private blockInput: boolean; private blockInput: boolean;
public rngCounter: integer = 0;
public rngSeedOverride: string = '';
public rngOffset: integer = 0;
constructor() { constructor() {
super('battle'); super('battle');
@ -276,6 +281,20 @@ export default class BattleScene extends Phaser.Scene {
this.load['cacheBuster'] = buildIdMatch[1]; this.load['cacheBuster'] = buildIdMatch[1];
} }
if (DEBUG_RNG) {
const scene = this;
const originalRealInRange = Phaser.Math.RND.realInRange;
Phaser.Math.RND.realInRange = function (min: number, max: number): number {
const ret = originalRealInRange.apply(this, [ min, max ]);
const args = [ 'RNG', ++scene.rngCounter, ret / (max - min), `min: ${min} / max: ${max}` ];
args.push(`seed: ${scene.rngSeedOverride || scene.waveSeed || scene.seed}`);
if (scene.rngOffset)
args.push(`offset: ${scene.rngOffset}`);
console.log(...args);
return ret;
};
}
// Load menu images // Load menu images
this.loadAtlas('bg', 'ui'); this.loadAtlas('bg', 'ui');
this.loadImage('command_fight_labels', 'ui'); this.loadImage('command_fight_labels', 'ui');
@ -805,10 +824,15 @@ export default class BattleScene extends Phaser.Scene {
setSeed(seed: string): void { setSeed(seed: string): void {
this.seed = seed; this.seed = seed;
this.rngCounter = 0;
this.waveCycleOffset = this.getGeneratedWaveCycleOffset(); this.waveCycleOffset = this.getGeneratedWaveCycleOffset();
this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym(); this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym();
} }
randBattleSeedInt(range: integer, min: integer = 0): integer {
return this.currentBattle.randSeedInt(this, range, min);
}
reset(clearScene?: boolean): void { reset(clearScene?: boolean): void {
this.gameMode = gameModes[GameModes.CLASSIC]; this.gameMode = gameModes[GameModes.CLASSIC];
@ -843,7 +867,7 @@ export default class BattleScene extends Phaser.Scene {
this.updateScoreText(); this.updateScoreText();
this.scoreText.setVisible(false); this.scoreText.setVisible(false);
this.newArena(STARTING_BIOME_OVERRIDE || Biome.TOWN, true); this.newArena(STARTING_BIOME_OVERRIDE || Biome.TOWN);
this.arenaBgTransition.setPosition(0, 0); this.arenaBgTransition.setPosition(0, 0);
this.arenaPlayer.setPosition(300, 0); this.arenaPlayer.setPosition(300, 0);
@ -851,6 +875,8 @@ export default class BattleScene extends Phaser.Scene {
[ this.arenaEnemy, this.arenaNextEnemy ].forEach(a => a.setPosition(-280, 0)); [ this.arenaEnemy, this.arenaNextEnemy ].forEach(a => a.setPosition(-280, 0));
this.arenaNextEnemy.setVisible(false); this.arenaNextEnemy.setVisible(false);
this.arena.init();
this.trainer.setTexture(`trainer_${this.gameData.gender === PlayerGender.FEMALE ? 'f' : 'm'}_back`); this.trainer.setTexture(`trainer_${this.gameData.gender === PlayerGender.FEMALE ? 'f' : 'm'}_back`);
this.trainer.setPosition(406, 186); this.trainer.setPosition(406, 186);
this.trainer.setVisible(true) this.trainer.setVisible(true)
@ -933,7 +959,9 @@ export default class BattleScene extends Phaser.Scene {
this.lastEnemyTrainer = lastBattle?.trainer ?? null; this.lastEnemyTrainer = lastBattle?.trainer ?? null;
this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble); this.executeWithSeedOffset(() => {
this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble);
}, newWaveIndex << 3, this.waveSeed);
this.currentBattle.incrementTurn(this); this.currentBattle.incrementTurn(this);
//this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6)); //this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6));
@ -972,20 +1000,9 @@ export default class BattleScene extends Phaser.Scene {
return this.currentBattle; return this.currentBattle;
} }
newArena(biome: Biome, init?: boolean): Arena { newArena(biome: Biome): Arena {
this.arena = new Arena(this, biome, Biome[biome].toLowerCase()); this.arena = new Arena(this, biome, Biome[biome].toLowerCase());
if (init) {
const biomeKey = getBiomeKey(biome);
this.arenaPlayer.setBiome(biome);
this.arenaPlayerTransition.setBiome(biome);
this.arenaEnemy.setBiome(biome);
this.arenaNextEnemy.setBiome(biome);
this.arenaBg.setTexture(`${biomeKey}_bg`);
this.arenaBgTransition.setTexture(`${biomeKey}_bg`);
}
this.arenaBg.pipelineData = { terrainColorRatio: this.arena.getBgTerrainColorRatioForBiome() }; this.arenaBg.pipelineData = { terrainColorRatio: this.arena.getBgTerrainColorRatioForBiome() };
return this.arena; return this.arena;
@ -1139,17 +1156,29 @@ export default class BattleScene extends Phaser.Scene {
} }
resetSeed(waveIndex?: integer): void { resetSeed(waveIndex?: integer): void {
this.waveSeed = Utils.shiftCharCodes(this.seed, waveIndex || this.currentBattle?.waveIndex || 0); const wave = waveIndex || this.currentBattle?.waveIndex || 0;
this.waveSeed = Utils.shiftCharCodes(this.seed, wave);
Phaser.Math.RND.sow([ this.waveSeed ]); Phaser.Math.RND.sow([ this.waveSeed ]);
console.log('Wave Seed:', this.waveSeed, wave);
this.rngCounter = 0;
} }
executeWithSeedOffset(func: Function, offset: integer, seedOverride?: string): void { executeWithSeedOffset(func: Function, offset: integer, seedOverride?: string): void {
if (!func) if (!func)
return; return;
const tempRngCounter = this.rngCounter;
const tempRngOffset = this.rngOffset;
const tempRngSeedOverride = this.rngSeedOverride;
const state = Phaser.Math.RND.state(); const state = Phaser.Math.RND.state();
Phaser.Math.RND.sow([ Utils.shiftCharCodes(seedOverride || this.seed, offset) ]); Phaser.Math.RND.sow([ Utils.shiftCharCodes(seedOverride || this.seed, offset) ]);
this.rngCounter = 0;
this.rngOffset = offset;
this.rngSeedOverride = seedOverride || '';
func(); func();
Phaser.Math.RND.state(state); Phaser.Math.RND.state(state);
this.rngCounter = tempRngCounter;
this.rngOffset = tempRngOffset;
this.rngSeedOverride = tempRngSeedOverride;
} }
addFieldSprite(x: number, y: number, texture: string | Phaser.Textures.Texture, frame?: string | number, terrainColorRatio: number = 0): Phaser.GameObjects.Sprite { addFieldSprite(x: number, y: number, texture: string | Phaser.Textures.Texture, frame?: string | number, terrainColorRatio: number = 0): Phaser.GameObjects.Sprite {

View File

@ -59,6 +59,8 @@ export default class Battle {
public battleSeed: string; public battleSeed: string;
private battleSeedState: string; private battleSeedState: string;
private rngCounter: integer = 0;
constructor(gameMode: GameMode, waveIndex: integer, battleType: BattleType, trainer: Trainer, double: boolean) { constructor(gameMode: GameMode, waveIndex: integer, battleType: BattleType, trainer: Trainer, double: boolean) {
this.gameMode = gameMode; this.gameMode = gameMode;
this.waveIndex = waveIndex; this.waveIndex = waveIndex;
@ -191,16 +193,24 @@ export default class Battle {
return null; return null;
} }
randSeedInt(range: integer, min: integer = 0): integer { randSeedInt(scene: BattleScene, range: integer, min: integer = 0): integer {
let ret: integer; let ret: integer;
const tempRngCounter = scene.rngCounter;
const tempSeedOverride = scene.rngSeedOverride;
const state = Phaser.Math.RND.state(); const state = Phaser.Math.RND.state();
if (this.battleSeedState) if (this.battleSeedState)
Phaser.Math.RND.state(this.battleSeedState); Phaser.Math.RND.state(this.battleSeedState);
else else {
Phaser.Math.RND.sow([ Utils.shiftCharCodes(this.battleSeed, this.turn << 6) ]); Phaser.Math.RND.sow([ Utils.shiftCharCodes(this.battleSeed, this.turn << 6) ]);
console.log('Battle Seed:', this.battleSeed);
}
scene.rngCounter = this.rngCounter++;
scene.rngSeedOverride = this.battleSeed;
ret = Utils.randSeedInt(range, min); ret = Utils.randSeedInt(range, min);
this.battleSeedState = Phaser.Math.RND.state(); this.battleSeedState = Phaser.Math.RND.state();
Phaser.Math.RND.state(state); Phaser.Math.RND.state(state);
scene.rngCounter = tempRngCounter;
scene.rngSeedOverride = tempSeedOverride;
return ret; return ret;
} }
} }

View File

@ -44,6 +44,17 @@ export class Arena {
this.updatePoolsForTimeOfDay(); this.updatePoolsForTimeOfDay();
} }
init() {
const biomeKey = getBiomeKey(this.biomeType);
this.scene.arenaPlayer.setBiome(this.biomeType);
this.scene.arenaPlayerTransition.setBiome(this.biomeType);
this.scene.arenaEnemy.setBiome(this.biomeType);
this.scene.arenaNextEnemy.setBiome(this.biomeType);
this.scene.arenaBg.setTexture(`${biomeKey}_bg`);
this.scene.arenaBgTransition.setTexture(`${biomeKey}_bg`);
}
updatePoolsForTimeOfDay(): void { updatePoolsForTimeOfDay(): void {
const timeOfDay = this.getTimeOfDay(); const timeOfDay = this.getTimeOfDay();
if (timeOfDay !== this.lastTimeOfDay) { if (timeOfDay !== this.lastTimeOfDay) {
@ -664,28 +675,27 @@ export class ArenaBase extends Phaser.GameObjects.Container {
} }
setBiome(biome: Biome, propValue?: integer): void { setBiome(biome: Biome, propValue?: integer): void {
if (this.biome === biome)
return;
const hasProps = getBiomeHasProps(biome); const hasProps = getBiomeHasProps(biome);
const biomeKey = getBiomeKey(biome); const biomeKey = getBiomeKey(biome);
const baseKey = `${biomeKey}_${this.player ? 'a' : 'b'}`; const baseKey = `${biomeKey}_${this.player ? 'a' : 'b'}`;
this.base.setTexture(baseKey); if (biome !== this.biome) {
this.base.setTexture(baseKey);
if (this.base.texture.frameTotal > 1) { if (this.base.texture.frameTotal > 1) {
const baseFrameNames = this.scene.anims.generateFrameNames(baseKey, { zeroPad: 4, suffix: ".png", start: 1, end: this.base.texture.frameTotal - 1 }); const baseFrameNames = this.scene.anims.generateFrameNames(baseKey, { zeroPad: 4, suffix: ".png", start: 1, end: this.base.texture.frameTotal - 1 });
this.scene.anims.create({ this.scene.anims.create({
key: baseKey, key: baseKey,
frames: baseFrameNames, frames: baseFrameNames,
frameRate: 12, frameRate: 12,
repeat: -1 repeat: -1
}); });
this.base.play(baseKey); this.base.play(baseKey);
} else } else
this.base.stop(); this.base.stop();
this.add(this.base); this.add(this.base);
}
if (!this.player) { if (!this.player) {
(this.scene as BattleScene).executeWithSeedOffset(() => { (this.scene as BattleScene).executeWithSeedOffset(() => {
@ -711,7 +721,7 @@ export class ArenaBase extends Phaser.GameObjects.Container {
prop.setVisible(hasProps && !!(this.propValue & (1 << p))); prop.setVisible(hasProps && !!(this.propValue & (1 << p)));
this.add(prop); this.add(prop);
}); });
}, (this.scene as BattleScene).currentBattle?.waveIndex || 0); }, (this.scene as BattleScene).currentBattle?.waveIndex || 0, (this.scene as BattleScene).waveSeed);
} }
} }
} }

View File

@ -1092,7 +1092,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (source.getTag(BattlerTagType.CRIT_BOOST)) if (source.getTag(BattlerTagType.CRIT_BOOST))
critLevel.value += 2; critLevel.value += 2;
const critChance = Math.ceil(16 / Math.pow(2, critLevel.value)); const critChance = Math.ceil(16 / Math.pow(2, critLevel.value));
isCritical = !source.getTag(BattlerTagType.NO_CRIT) && (critChance === 1 || !this.scene.currentBattle.randSeedInt(critChance)); isCritical = !source.getTag(BattlerTagType.NO_CRIT) && (critChance === 1 || !this.scene.randBattleSeedInt(critChance));
if (isCritical) { if (isCritical) {
const blockCrit = new Utils.BooleanHolder(false); const blockCrit = new Utils.BooleanHolder(false);
applyAbAttrs(BlockCritAbAttr, this, null, blockCrit); applyAbAttrs(BlockCritAbAttr, this, null, blockCrit);
@ -1121,7 +1121,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs(VariableDefAttr, source, this, move, targetDef); applyMoveAttrs(VariableDefAttr, source, this, move, targetDef);
if (!isTypeImmune) { if (!isTypeImmune) {
damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier * ((this.scene.currentBattle.randSeedInt(15) + 85) / 100)) * criticalMultiplier; damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier * ((this.scene.randBattleSeedInt(15) + 85) / 100)) * criticalMultiplier;
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) { if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) {
const burnDamageReductionCancelled = new Utils.BooleanHolder(false); const burnDamageReductionCancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BypassBurnDamageReductionAbAttr, this, burnDamageReductionCancelled); applyAbAttrs(BypassBurnDamageReductionAbAttr, this, burnDamageReductionCancelled);
@ -1971,7 +1971,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
randSeedInt(range: integer, min: integer = 0): integer { randSeedInt(range: integer, min: integer = 0): integer {
return this.scene.currentBattle return this.scene.currentBattle
? this.scene.currentBattle.randSeedInt(range, min) ? this.scene.randBattleSeedInt(range, min)
: Utils.randSeedInt(range, min); : Utils.randSeedInt(range, min);
} }
@ -2326,7 +2326,7 @@ export class EnemyPokemon extends Pokemon {
} }
switch (this.aiType) { switch (this.aiType) {
case AiType.RANDOM: case AiType.RANDOM:
const moveId = movePool[this.scene.currentBattle.randSeedInt(movePool.length)].moveId; const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)].moveId;
return { move: moveId, targets: this.getNextTargets(moveId) }; return { move: moveId, targets: this.getNextTargets(moveId) };
case AiType.SMART_RANDOM: case AiType.SMART_RANDOM:
case AiType.SMART: case AiType.SMART:
@ -2373,7 +2373,7 @@ export class EnemyPokemon extends Pokemon {
}); });
let r = 0; let r = 0;
if (this.aiType === AiType.SMART_RANDOM) { if (this.aiType === AiType.SMART_RANDOM) {
while (r < sortedMovePool.length - 1 && this.scene.currentBattle.randSeedInt(8) >= 5) while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5)
r++; r++;
} }
console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName())); console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName()));
@ -2426,7 +2426,7 @@ export class EnemyPokemon extends Pokemon {
return total; return total;
}, 0); }, 0);
const randValue = this.scene.currentBattle.randSeedInt(totalWeight); const randValue = this.scene.randBattleSeedInt(totalWeight);
let targetIndex: integer; let targetIndex: integer;
thresholds.every((t, i) => { thresholds.every((t, i) => {

View File

@ -258,8 +258,9 @@ export class TitlePhase extends Phase {
Promise.all(loadPokemonAssets).then(() => { Promise.all(loadPokemonAssets).then(() => {
this.scene.time.delayedCall(500, () => this.scene.playBgm()); this.scene.time.delayedCall(500, () => this.scene.playBgm());
this.scene.gameData.gameStats.dailyRunSessionsPlayed++; this.scene.gameData.gameStats.dailyRunSessionsPlayed++;
this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene), true); this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene));
this.scene.newBattle(); this.scene.newBattle();
this.scene.arena.init();
this.scene.sessionPlayTime = 0; this.scene.sessionPlayTime = 0;
this.end(); this.end();
}); });
@ -271,7 +272,7 @@ export class TitlePhase extends Phase {
if (!this.loaded && !this.scene.gameMode.isDaily) { if (!this.loaded && !this.scene.gameMode.isDaily) {
this.scene.arena.preloadBgm(); this.scene.arena.preloadBgm();
this.scene.pushPhase(new SelectStarterPhase(this.scene)); this.scene.pushPhase(new SelectStarterPhase(this.scene));
this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene), true); this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene));
} else } else
this.scene.playBgm(); this.scene.playBgm();
@ -435,6 +436,7 @@ export class SelectStarterPhase extends Phase {
else else
this.scene.gameData.gameStats.endlessSessionsPlayed++; this.scene.gameData.gameStats.endlessSessionsPlayed++;
this.scene.newBattle(); this.scene.newBattle();
this.scene.arena.init();
this.scene.sessionPlayTime = 0; this.scene.sessionPlayTime = 0;
this.end(); this.end();
}); });
@ -497,7 +499,7 @@ export abstract class FieldPhase extends BattlePhase {
const aSpeed = a?.getBattleStat(Stat.SPD) || 0; const aSpeed = a?.getBattleStat(Stat.SPD) || 0;
const bSpeed = b?.getBattleStat(Stat.SPD) || 0; const bSpeed = b?.getBattleStat(Stat.SPD) || 0;
return aSpeed < bSpeed ? 1 : aSpeed > bSpeed ? -1 : !this.scene.currentBattle.randSeedInt(2) ? -1 : 1; return aSpeed < bSpeed ? 1 : aSpeed > bSpeed ? -1 : !this.scene.randBattleSeedInt(2) ? -1 : 1;
}); });
const speedReversed = new Utils.BooleanHolder(false); const speedReversed = new Utils.BooleanHolder(false);
@ -874,6 +876,7 @@ export class NextEncounterPhase extends EncounterPhase {
pokemon.resetBattleData(); pokemon.resetBattleData();
} }
this.scene.arenaNextEnemy.setBiome(this.scene.arena.biomeType);
this.scene.arenaNextEnemy.setVisible(true); this.scene.arenaNextEnemy.setVisible(true);
const enemyField = this.scene.getEnemyField(); const enemyField = this.scene.getEnemyField();
@ -882,6 +885,7 @@ export class NextEncounterPhase extends EncounterPhase {
x: '+=300', x: '+=300',
duration: 2000, duration: 2000,
onComplete: () => { onComplete: () => {
this.scene.arenaEnemy.setBiome(this.scene.arena.biomeType);
this.scene.arenaEnemy.setX(this.scene.arenaNextEnemy.x); this.scene.arenaEnemy.setX(this.scene.arenaNextEnemy.x);
this.scene.arenaEnemy.setAlpha(1); this.scene.arenaEnemy.setAlpha(1);
this.scene.arenaNextEnemy.setX(this.scene.arenaNextEnemy.x - 300); this.scene.arenaNextEnemy.setX(this.scene.arenaNextEnemy.x - 300);

View File

@ -577,12 +577,14 @@ export class GameData {
scene.score = sessionData.score; scene.score = sessionData.score;
scene.updateScoreText(); scene.updateScoreText();
scene.newArena(sessionData.arena.biome);
const battleType = sessionData.battleType || 0; const battleType = sessionData.battleType || 0;
const trainerConfig = sessionData.trainer ? trainerConfigs[sessionData.trainer.trainerType] : null; const trainerConfig = sessionData.trainer ? trainerConfigs[sessionData.trainer.trainerType] : null;
const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfig?.doubleOnly || sessionData.trainer?.variant === TrainerVariant.DOUBLE : sessionData.enemyParty.length > 1); const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfig?.doubleOnly || sessionData.trainer?.variant === TrainerVariant.DOUBLE : sessionData.enemyParty.length > 1);
battle.enemyLevels = sessionData.enemyParty.map(p => p.level); battle.enemyLevels = sessionData.enemyParty.map(p => p.level);
scene.newArena(sessionData.arena.biome, true); scene.arena.init();
sessionData.enemyParty.forEach((enemyData, e) => { sessionData.enemyParty.forEach((enemyData, e) => {
const enemyPokemon = enemyData.toPokemon(scene, battleType, e, sessionData.trainer?.variant === TrainerVariant.DOUBLE) as EnemyPokemon; const enemyPokemon = enemyData.toPokemon(scene, battleType, e, sessionData.trainer?.variant === TrainerVariant.DOUBLE) as EnemyPokemon;