Fix battle RNG varying when loading a game

pull/24/head
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_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 {

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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) => {

View File

@ -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);

View File

@ -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;