From cc84f4d30e722a5c005e6fcea109034f66597017 Mon Sep 17 00:00:00 2001 From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com> Date: Wed, 19 Jul 2023 01:59:41 +0530 Subject: [PATCH] deterministic gem hunt spawn --- src/MarbleWorld.hx | 1 + src/RandomLCG.hx | 53 +++++++++++++++++++++++++++++++++++++++++ src/modes/GameMode.hx | 1 + src/modes/HuntMode.hx | 55 +++++++++++++++++++++++++++++++++++++++++-- src/modes/NullMode.hx | 2 ++ 5 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 src/RandomLCG.hx diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index e4154845..2f662eb1 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -581,6 +581,7 @@ class MarbleWorld extends Scheduler { this.playGui.setCenterText(''); this.clearSchedule(); this.outOfBounds = false; + this.gameMode.onRespawn(); AudioManager.playSound(ResourceLoader.getResource('data/sound/spawn_alternate.wav', ResourceLoader.getAudio, this.soundResources)); } diff --git a/src/RandomLCG.hx b/src/RandomLCG.hx new file mode 100644 index 00000000..962564cf --- /dev/null +++ b/src/RandomLCG.hx @@ -0,0 +1,53 @@ +class RandomLCG { + var seed:Int; + + static var msSeed:Int = 1376312589; + + static var quotient = 127773; + static var remainder = 2836; + + public function new(seed = -1) { + this.seed = (seed == -1) ? generateSeed() : seed; + } + + inline function generateSeed() { + // A very, VERY crude LCG but good enough to generate + // a nice range of seed values + msSeed = (msSeed * 0x015a4e35) + 1; + msSeed = (msSeed >> 16) & 0x7fff; + return (msSeed); + } + + public function setSeed(seed:Int) { + this.seed = seed; + } + + public function randInt() { + if (seed <= quotient) + seed = (seed * 16807) % 2147483647; + else { + var high_part:Int = Std.int(seed / quotient); + var low_part = seed % quotient; + + var test:Int = (16807 * low_part) - (remainder * high_part); + + if (test > 0) + seed = test; + else + seed = test + 2147483647; + } + return seed; + } + + public function randFloat() { + return randInt() / 2147483647.0; + } + + public function randRange(i:Int, n:Int) { + return (i + (randInt() % (n - i + 1))); + } + + public function randRangeF(i:Float, n:Int) { + return (i + (n - i) * randFloat()); + } +} diff --git a/src/modes/GameMode.hx b/src/modes/GameMode.hx index 606be47a..c1f3415a 100644 --- a/src/modes/GameMode.hx +++ b/src/modes/GameMode.hx @@ -24,6 +24,7 @@ interface GameMode { public function applyRewindState(state:RewindableState):Void; public function onTimeExpire():Void; public function onRestart():Void; + public function onRespawn():Void; public function onGemPickup(gem:Gem):Void; public function getPreloadFiles():Array; diff --git a/src/modes/HuntMode.hx b/src/modes/HuntMode.hx index 0f04aef8..9dcee72e 100644 --- a/src/modes/HuntMode.hx +++ b/src/modes/HuntMode.hx @@ -1,5 +1,6 @@ package modes; +import hxd.Rand; import rewind.RewindableState; import gui.AchievementsGui; import modes.GameMode.ScoreType; @@ -81,6 +82,8 @@ class HuntState implements RewindableState { var activeGemSpawnGroup:Array; var activeGems:Array; var points:Int; + var rngState:Int; + var rngState2:Int; public function new() {} @@ -94,6 +97,8 @@ class HuntState implements RewindableState { c.activeGemSpawnGroup = activeGemSpawnGroup.copy(); c.points = points; c.activeGems = activeGems.copy(); + c.rngState = rngState; + c.rngState2 = rngState2; return c; } } @@ -109,6 +114,8 @@ class HuntMode extends NullMode { var activeGems:Array = []; var gemBeams:Array = []; var gemToBeamMap:Map = []; + var rng:RandomLCG = new RandomLCG(100); + var rng2:RandomLCG = new RandomLCG(100); var points:Int = 0; @@ -131,7 +138,7 @@ class HuntMode extends NullMode { }; override function getSpawnTransform() { - var randomSpawn = playerSpawnPoints[Math.floor(Math.random() * playerSpawnPoints.length)]; + var randomSpawn = playerSpawnPoints[Math.floor(rng2.randRange(0, playerSpawnPoints.length - 1))]; var spawnPos = MisParser.parseVector3(randomSpawn.position); spawnPos.x *= -1; var spawnRot = MisParser.parseRotation(randomSpawn.rotation); @@ -170,6 +177,7 @@ class HuntMode extends NullMode { var spawnMat = spawnRot.toMatrix(); var up = spawnMat.up(); spawnPos = spawnPos.add(up.multiply(0.727843 / 3)); // 1.5 -> 0.5 + return { position: spawnPos, orientation: spawnRot, @@ -179,6 +187,26 @@ class HuntMode extends NullMode { return null; } + override function onRespawn() { + if (activeGemSpawnGroup.length != 0) { + var gemAvg = new Vector(); + for (g in activeGemSpawnGroup) { + gemAvg = gemAvg.add(g.position); + } + gemAvg.scale(1 / activeGemSpawnGroup.length); + var delta = gemAvg.sub(level.marble.getAbsPos().getPosition()); + var gravFrame = level.getOrientationQuat(0).toMatrix(); + var v1 = gravFrame.front(); + var v2 = gravFrame.right(); + var deltaRot = new Vector(delta.dot(v2), delta.dot(v1)); + if (deltaRot.length() >= 0.001) { + var ang = Math.atan2(deltaRot.x, deltaRot.y); + level.marble.camera.CameraYaw = ang; + level.marble.camera.nextCameraYaw = ang; + } + } + } + override public function getStartTime() { return level.mission.qualifyTime; } @@ -188,6 +216,8 @@ class HuntMode extends NullMode { } override function onRestart() { + rng.setSeed(100); + rng2.setSeed(100); setupGems(); points = 0; @:privateAccess level.playGui.formatGemHuntCounter(points); @@ -242,9 +272,26 @@ class HuntMode extends NullMode { } } } + activeGemSpawnGroup = []; activeGems = []; refillGemGroups(); + + var gemAvg = new Vector(); + for (g in activeGemSpawnGroup) { + gemAvg = gemAvg.add(g.position); + } + gemAvg.scale(1 / activeGemSpawnGroup.length); + var delta = gemAvg.sub(level.marble.getAbsPos().getPosition()); + var gravFrame = level.getOrientationQuat(0).toMatrix(); + var v1 = gravFrame.front(); + var v2 = gravFrame.right(); + var deltaRot = new Vector(delta.dot(v2), delta.dot(v1)); + if (deltaRot.length() >= 0.001) { + var ang = Math.atan2(deltaRot.x, deltaRot.y); + level.marble.camera.CameraYaw = ang; + level.marble.camera.nextCameraYaw = ang; + } } function refillGemGroups() { @@ -320,7 +367,7 @@ class HuntMode extends NullMode { function findGemSpawnGroup(outSpawnPoint:Vector) { // Pick random spawn point - var rnd:Int = Std.int(Math.random() * gemSpawnPoints.length); + var rnd:Int = Std.int(rng.randRange(0, gemSpawnPoints.length - 1)); if (level.isRecording) level.replay.recordRandomGenState(rnd); if (level.isWatching) @@ -394,6 +441,8 @@ class HuntMode extends NullMode { s.points = points; s.activeGemSpawnGroup = activeGemSpawnGroup; s.activeGems = activeGems.copy(); + s.rngState = @:privateAccess rng.seed; + s.rngState2 = @:privateAccess rng2.seed; return s; } @@ -415,5 +464,7 @@ class HuntMode extends NullMode { var gemBeam = gemToBeamMap.get(gem); gemBeam.setHide(false); } + rng.setSeed(s.rngState); + rng2.setSeed(s.rngState2); } } diff --git a/src/modes/NullMode.hx b/src/modes/NullMode.hx index 902c840e..490796b9 100644 --- a/src/modes/NullMode.hx +++ b/src/modes/NullMode.hx @@ -56,6 +56,8 @@ class NullMode implements GameMode { public function onRestart() {} + public function onRespawn() {} + public function onGemPickup(gem:Gem) { this.level.gemCount++; var string:String;