From 366e3e5120292ea2a96009113f78c95254dda9df Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Mon, 1 Apr 2024 12:48:35 -0400 Subject: [PATCH] Fix battle RNG varying when loading a game --- src/battle-scene.ts | 59 ++++++++++++++++++++++++++++++----------- src/battle.ts | 14 ++++++++-- src/field/arena.ts | 44 ++++++++++++++++++------------ src/field/pokemon.ts | 12 ++++----- src/phases.ts | 10 ++++--- src/system/game-data.ts | 4 ++- 6 files changed, 99 insertions(+), 44 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index b3bb1eb0bf8..9f017db5e00 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -67,6 +67,7 @@ export const STARTING_LEVEL_OVERRIDE = 0; export const STARTING_WAVE_OVERRIDE = 0; export const STARTING_BIOME_OVERRIDE = Biome.TOWN; export const STARTING_MONEY_OVERRIDE = 0; +const DEBUG_RNG = false; export const startingWave = STARTING_WAVE_OVERRIDE || 1; @@ -178,6 +179,10 @@ export default class BattleScene extends Phaser.Scene { private blockInput: boolean; + public rngCounter: integer = 0; + public rngSeedOverride: string = ''; + public rngOffset: integer = 0; + constructor() { super('battle'); @@ -276,6 +281,20 @@ export default class BattleScene extends Phaser.Scene { 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 this.loadAtlas('bg', 'ui'); this.loadImage('command_fight_labels', 'ui'); @@ -805,10 +824,15 @@ export default class BattleScene extends Phaser.Scene { setSeed(seed: string): void { this.seed = seed; + this.rngCounter = 0; this.waveCycleOffset = this.getGeneratedWaveCycleOffset(); 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 { this.gameMode = gameModes[GameModes.CLASSIC]; @@ -843,7 +867,7 @@ export default class BattleScene extends Phaser.Scene { this.updateScoreText(); 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.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.arenaNextEnemy.setVisible(false); + this.arena.init(); + this.trainer.setTexture(`trainer_${this.gameData.gender === PlayerGender.FEMALE ? 'f' : 'm'}_back`); this.trainer.setPosition(406, 186); this.trainer.setVisible(true) @@ -933,7 +959,9 @@ export default class BattleScene extends Phaser.Scene { 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.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; } - newArena(biome: Biome, init?: boolean): Arena { + newArena(biome: Biome): Arena { 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() }; return this.arena; @@ -1139,17 +1156,29 @@ export default class BattleScene extends Phaser.Scene { } 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 ]); + console.log('Wave Seed:', this.waveSeed, wave); + this.rngCounter = 0; } executeWithSeedOffset(func: Function, offset: integer, seedOverride?: string): void { if (!func) return; + const tempRngCounter = this.rngCounter; + const tempRngOffset = this.rngOffset; + const tempRngSeedOverride = this.rngSeedOverride; const state = Phaser.Math.RND.state(); Phaser.Math.RND.sow([ Utils.shiftCharCodes(seedOverride || this.seed, offset) ]); + this.rngCounter = 0; + this.rngOffset = offset; + this.rngSeedOverride = seedOverride || ''; func(); 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 { diff --git a/src/battle.ts b/src/battle.ts index 1f29d7f6781..b7c7090955a 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -59,6 +59,8 @@ export default class Battle { public battleSeed: string; private battleSeedState: string; + private rngCounter: integer = 0; + constructor(gameMode: GameMode, waveIndex: integer, battleType: BattleType, trainer: Trainer, double: boolean) { this.gameMode = gameMode; this.waveIndex = waveIndex; @@ -191,16 +193,24 @@ export default class Battle { return null; } - randSeedInt(range: integer, min: integer = 0): integer { + randSeedInt(scene: BattleScene, range: integer, min: integer = 0): integer { let ret: integer; + const tempRngCounter = scene.rngCounter; + const tempSeedOverride = scene.rngSeedOverride; const state = Phaser.Math.RND.state(); if (this.battleSeedState) Phaser.Math.RND.state(this.battleSeedState); - else + else { 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); this.battleSeedState = Phaser.Math.RND.state(); Phaser.Math.RND.state(state); + scene.rngCounter = tempRngCounter; + scene.rngSeedOverride = tempSeedOverride; return ret; } } diff --git a/src/field/arena.ts b/src/field/arena.ts index 60d08e01bae..5fe8b175e81 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -44,6 +44,17 @@ export class Arena { 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 { const timeOfDay = this.getTimeOfDay(); if (timeOfDay !== this.lastTimeOfDay) { @@ -664,28 +675,27 @@ export class ArenaBase extends Phaser.GameObjects.Container { } setBiome(biome: Biome, propValue?: integer): void { - if (this.biome === biome) - return; - const hasProps = getBiomeHasProps(biome); const biomeKey = getBiomeKey(biome); 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) { - const baseFrameNames = this.scene.anims.generateFrameNames(baseKey, { zeroPad: 4, suffix: ".png", start: 1, end: this.base.texture.frameTotal - 1 }); - this.scene.anims.create({ - key: baseKey, - frames: baseFrameNames, - frameRate: 12, - repeat: -1 - }); - this.base.play(baseKey); - } else - this.base.stop(); + 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 }); + this.scene.anims.create({ + key: baseKey, + frames: baseFrameNames, + frameRate: 12, + repeat: -1 + }); + this.base.play(baseKey); + } else + this.base.stop(); - this.add(this.base); + this.add(this.base); + } if (!this.player) { (this.scene as BattleScene).executeWithSeedOffset(() => { @@ -711,7 +721,7 @@ export class ArenaBase extends Phaser.GameObjects.Container { prop.setVisible(hasProps && !!(this.propValue & (1 << p))); this.add(prop); }); - }, (this.scene as BattleScene).currentBattle?.waveIndex || 0); + }, (this.scene as BattleScene).currentBattle?.waveIndex || 0, (this.scene as BattleScene).waveSeed); } } } \ No newline at end of file diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e95cc3c65a2..3240fc32f1f 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1092,7 +1092,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (source.getTag(BattlerTagType.CRIT_BOOST)) critLevel.value += 2; 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) { const blockCrit = new Utils.BooleanHolder(false); applyAbAttrs(BlockCritAbAttr, this, null, blockCrit); @@ -1121,7 +1121,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(VariableDefAttr, source, this, move, targetDef); 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) { const burnDamageReductionCancelled = new Utils.BooleanHolder(false); applyAbAttrs(BypassBurnDamageReductionAbAttr, this, burnDamageReductionCancelled); @@ -1971,7 +1971,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { randSeedInt(range: integer, min: integer = 0): integer { return this.scene.currentBattle - ? this.scene.currentBattle.randSeedInt(range, min) + ? this.scene.randBattleSeedInt(range, min) : Utils.randSeedInt(range, min); } @@ -2326,7 +2326,7 @@ export class EnemyPokemon extends Pokemon { } switch (this.aiType) { 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) }; case AiType.SMART_RANDOM: case AiType.SMART: @@ -2373,7 +2373,7 @@ export class EnemyPokemon extends Pokemon { }); let r = 0; 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++; } 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; }, 0); - const randValue = this.scene.currentBattle.randSeedInt(totalWeight); + const randValue = this.scene.randBattleSeedInt(totalWeight); let targetIndex: integer; thresholds.every((t, i) => { diff --git a/src/phases.ts b/src/phases.ts index 7c3f16c796b..71961de1bb3 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -258,8 +258,9 @@ export class TitlePhase extends Phase { Promise.all(loadPokemonAssets).then(() => { this.scene.time.delayedCall(500, () => this.scene.playBgm()); 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.arena.init(); this.scene.sessionPlayTime = 0; this.end(); }); @@ -271,7 +272,7 @@ export class TitlePhase extends Phase { if (!this.loaded && !this.scene.gameMode.isDaily) { this.scene.arena.preloadBgm(); 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 this.scene.playBgm(); @@ -435,6 +436,7 @@ export class SelectStarterPhase extends Phase { else this.scene.gameData.gameStats.endlessSessionsPlayed++; this.scene.newBattle(); + this.scene.arena.init(); this.scene.sessionPlayTime = 0; this.end(); }); @@ -497,7 +499,7 @@ export abstract class FieldPhase extends BattlePhase { const aSpeed = a?.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); @@ -874,6 +876,7 @@ export class NextEncounterPhase extends EncounterPhase { pokemon.resetBattleData(); } + this.scene.arenaNextEnemy.setBiome(this.scene.arena.biomeType); this.scene.arenaNextEnemy.setVisible(true); const enemyField = this.scene.getEnemyField(); @@ -882,6 +885,7 @@ export class NextEncounterPhase extends EncounterPhase { x: '+=300', duration: 2000, onComplete: () => { + this.scene.arenaEnemy.setBiome(this.scene.arena.biomeType); this.scene.arenaEnemy.setX(this.scene.arenaNextEnemy.x); this.scene.arenaEnemy.setAlpha(1); this.scene.arenaNextEnemy.setX(this.scene.arenaNextEnemy.x - 300); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 764d4376d62..0142b197d4b 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -577,12 +577,14 @@ export class GameData { scene.score = sessionData.score; scene.updateScoreText(); + scene.newArena(sessionData.arena.biome); + const battleType = sessionData.battleType || 0; 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); battle.enemyLevels = sessionData.enemyParty.map(p => p.level); - scene.newArena(sessionData.arena.biome, true); + scene.arena.init(); sessionData.enemyParty.forEach((enemyData, e) => { const enemyPokemon = enemyData.toPokemon(scene, battleType, e, sessionData.trainer?.variant === TrainerVariant.DOUBLE) as EnemyPokemon;