mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-29 02:06:07 +00:00
[Bug] Fix eggs having exploitable RNG (#3913)
* [Bug] Fix eggs having exploitable RNG * Fix Wind Rider test having random chance to fail * Revert egg's ID back to its own unseeded generation * Remove change from wind rider test --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Mumble <171087428+frutescens@users.noreply.github.com>
This commit is contained in:
parent
deb4e9dd24
commit
587360c8da
@ -974,6 +974,7 @@ export default class BattleScene extends SceneBase {
|
||||
|
||||
this.setSeed(Overrides.SEED_OVERRIDE || Utils.randomString(24));
|
||||
console.log("Seed:", this.seed);
|
||||
this.resetSeed(); // Properly resets RNG after saving and quitting a session
|
||||
|
||||
this.disableMenu = false;
|
||||
|
||||
|
@ -139,6 +139,7 @@ export class Egg {
|
||||
////
|
||||
|
||||
constructor(eggOptions?: IEggOptions) {
|
||||
const generateEggProperties = (eggOptions?: IEggOptions) => {
|
||||
//if (eggOptions.tier && eggOptions.species) throw Error("Error egg can't have species and tier as option. only choose one of them.")
|
||||
|
||||
this._sourceType = eggOptions?.sourceType!; // TODO: is this bang correct?
|
||||
@ -180,6 +181,16 @@ export class Egg {
|
||||
this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct?
|
||||
this.addEggToGameData(eggOptions.scene!); // TODO: is this bang correct?
|
||||
}
|
||||
};
|
||||
|
||||
if (eggOptions?.scene) {
|
||||
const seedOverride = Utils.randomString(24);
|
||||
eggOptions?.scene.executeWithSeedOffset(() => {
|
||||
generateEggProperties(eggOptions);
|
||||
}, 0, seedOverride);
|
||||
} else { // For legacy eggs without scene
|
||||
generateEggProperties(eggOptions);
|
||||
}
|
||||
}
|
||||
|
||||
////
|
||||
@ -200,6 +211,9 @@ export class Egg {
|
||||
|
||||
// Generates a PlayerPokemon from an egg
|
||||
public generatePlayerPokemon(scene: BattleScene): PlayerPokemon {
|
||||
let ret: PlayerPokemon;
|
||||
|
||||
const generatePlayerPokemonHelper = (scene: BattleScene) => {
|
||||
// Legacy egg wants to hatch. Generate missing properties
|
||||
if (!this._species) {
|
||||
this._isShiny = this.rollShiny();
|
||||
@ -222,7 +236,7 @@ export class Egg {
|
||||
}
|
||||
|
||||
// This function has way to many optional parameters
|
||||
const ret: PlayerPokemon = scene.addPlayerPokemon(pokemonSpecies, 1, abilityIndex, undefined, undefined, false);
|
||||
ret = scene.addPlayerPokemon(pokemonSpecies, 1, abilityIndex, undefined, undefined, false);
|
||||
ret.shiny = this._isShiny;
|
||||
ret.variant = this._variantTier;
|
||||
|
||||
@ -231,6 +245,12 @@ export class Egg {
|
||||
for (let s = 0; s < ret.ivs.length; s++) {
|
||||
ret.ivs[s] = Math.max(ret.ivs[s], secondaryIvs[s]);
|
||||
}
|
||||
};
|
||||
|
||||
ret = ret!; // Tell TS compiler it's defined now
|
||||
scene.executeWithSeedOffset(() => {
|
||||
generatePlayerPokemonHelper(scene);
|
||||
}, this._id, EGG_SEED.toString());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { LoadingScene } from "#app/loading-scene";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import GameManager from "./utils/gameManager";
|
||||
|
||||
describe("BattleScene", () => {
|
||||
@ -24,4 +24,12 @@ describe("BattleScene", () => {
|
||||
// `BattleScene.create()` is called during the `new GameManager()` call
|
||||
expect(game.scene.scene.remove).toHaveBeenCalledWith(LoadingScene.KEY);
|
||||
});
|
||||
|
||||
it("should also reset RNG on reset", () => {
|
||||
vi.spyOn(game.scene, "resetSeed");
|
||||
|
||||
game.scene.reset();
|
||||
|
||||
expect(game.scene.resetSeed).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -12,6 +12,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite
|
||||
describe("Egg Generation Tests", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
const EGG_HATCH_COUNT: integer = 1000;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
@ -47,14 +48,21 @@ describe("Egg Generation Tests", () => {
|
||||
|
||||
expect(result).toBe(expectedSpecies);
|
||||
});
|
||||
it("should hatch an Arceus. Set from legendary gacha", async () => {
|
||||
it("should hatch an Arceus around half the time. Set from legendary gacha", async () => {
|
||||
const scene = game.scene;
|
||||
const timestamp = new Date(2024, 6, 10, 15, 0, 0, 0).getTime();
|
||||
const expectedSpecies = Species.ARCEUS;
|
||||
let gachaSpeciesCount = 0;
|
||||
|
||||
for (let i = 0; i < EGG_HATCH_COUNT; i++) {
|
||||
const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.MASTER }).generatePlayerPokemon(scene).species.speciesId;
|
||||
if (result === expectedSpecies) {
|
||||
gachaSpeciesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
expect(result).toBe(expectedSpecies);
|
||||
expect(gachaSpeciesCount).toBeGreaterThan(0.4 * EGG_HATCH_COUNT);
|
||||
expect(gachaSpeciesCount).toBeLessThan(0.6 * EGG_HATCH_COUNT);
|
||||
});
|
||||
it("should hatch an Arceus. Set from species", () => {
|
||||
const scene = game.scene;
|
||||
@ -156,7 +164,7 @@ describe("Egg Generation Tests", () => {
|
||||
const scene = game.scene;
|
||||
|
||||
const eggMoveIndex = new Egg({ scene }).eggMoveIndex;
|
||||
const result = eggMoveIndex && eggMoveIndex >= 0 && eggMoveIndex <= 3;
|
||||
const result = !Utils.isNullOrUndefined(eggMoveIndex) && eggMoveIndex >= 0 && eggMoveIndex <= 3;
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
@ -309,4 +317,63 @@ describe("Egg Generation Tests", () => {
|
||||
|
||||
expect(egg.variantTier).toBe(VariantTier.EPIC);
|
||||
});
|
||||
|
||||
it("should generate egg moves, species, shinyness, and ability unpredictably for the egg gacha", () => {
|
||||
const scene = game.scene;
|
||||
scene.setSeed("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
scene.resetSeed();
|
||||
|
||||
const firstEgg = new Egg({scene, sourceType: EggSourceType.GACHA_SHINY, tier: EggTier.COMMON});
|
||||
const firstHatch = firstEgg.generatePlayerPokemon(scene);
|
||||
let diffEggMove = false;
|
||||
let diffSpecies = false;
|
||||
let diffShiny = false;
|
||||
let diffAbility = false;
|
||||
for (let i = 0; i < EGG_HATCH_COUNT; i++) {
|
||||
scene.gameData.unlockPity[EggTier.COMMON] = 0;
|
||||
scene.setSeed("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
scene.resetSeed(); // Make sure that eggs are unpredictable even if using same seed
|
||||
|
||||
const newEgg = new Egg({scene, sourceType: EggSourceType.GACHA_SHINY, tier: EggTier.COMMON});
|
||||
const newHatch = newEgg.generatePlayerPokemon(scene);
|
||||
diffEggMove = diffEggMove || (newEgg.eggMoveIndex !== firstEgg.eggMoveIndex);
|
||||
diffSpecies = diffSpecies || (newHatch.species.speciesId !== firstHatch.species.speciesId);
|
||||
diffShiny = diffShiny || (newHatch.shiny !== firstHatch.shiny);
|
||||
diffAbility = diffAbility || (newHatch.abilityIndex !== firstHatch.abilityIndex);
|
||||
}
|
||||
|
||||
expect(diffEggMove).toBe(true);
|
||||
expect(diffSpecies).toBe(true);
|
||||
expect(diffShiny).toBe(true);
|
||||
expect(diffAbility).toBe(true);
|
||||
});
|
||||
|
||||
it("should generate egg moves, shinyness, and ability unpredictably for species eggs", () => {
|
||||
const scene = game.scene;
|
||||
scene.setSeed("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
scene.resetSeed();
|
||||
|
||||
const firstEgg = new Egg({scene, species: Species.BULBASAUR});
|
||||
const firstHatch = firstEgg.generatePlayerPokemon(scene);
|
||||
let diffEggMove = false;
|
||||
let diffSpecies = false;
|
||||
let diffShiny = false;
|
||||
let diffAbility = false;
|
||||
for (let i = 0; i < EGG_HATCH_COUNT; i++) {
|
||||
scene.setSeed("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
scene.resetSeed(); // Make sure that eggs are unpredictable even if using same seed
|
||||
|
||||
const newEgg = new Egg({scene, species: Species.BULBASAUR});
|
||||
const newHatch = newEgg.generatePlayerPokemon(scene);
|
||||
diffEggMove = diffEggMove || (newEgg.eggMoveIndex !== firstEgg.eggMoveIndex);
|
||||
diffSpecies = diffSpecies || (newHatch.species.speciesId !== firstHatch.species.speciesId);
|
||||
diffShiny = diffShiny || (newHatch.shiny !== firstHatch.shiny);
|
||||
diffAbility = diffAbility || (newHatch.abilityIndex !== firstHatch.abilityIndex);
|
||||
}
|
||||
|
||||
expect(diffEggMove).toBe(true);
|
||||
expect(diffSpecies).toBe(false);
|
||||
expect(diffShiny).toBe(true);
|
||||
expect(diffAbility).toBe(true);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user