From 841b059f6e0bffa3886d23e2fbc124f51c3af23f Mon Sep 17 00:00:00 2001 From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com> Date: Fri, 4 Nov 2022 14:34:39 +0530 Subject: [PATCH] do more work on replays --- src/CameraController.hx | 4 +- src/Marble.hx | 20 ++++-- src/MarbleWorld.hx | 52 +++++++++++--- src/Replay.hx | 150 +++++++++++++++++++++++++++++++++++++++- src/shapes/Trapdoor.hx | 2 +- 5 files changed, 211 insertions(+), 17 deletions(-) diff --git a/src/CameraController.hx b/src/CameraController.hx index 308001b2..0f87b04f 100644 --- a/src/CameraController.hx +++ b/src/CameraController.hx @@ -214,7 +214,9 @@ class CameraController extends Object { } if (!this.level.isWatching) { - this.level.replay.recordCameraState(CameraPitch, CameraYaw); + if (this.level.isRecording) { + this.level.replay.recordCameraState(CameraPitch, CameraYaw); + } } else { CameraPitch = this.level.replay.currentPlaybackFrame.cameraPitch; CameraYaw = this.level.replay.currentPlaybackFrame.cameraYaw; diff --git a/src/Marble.hx b/src/Marble.hx index c9efc2bc..3f1b07dc 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -1335,7 +1335,11 @@ class Marble extends GameObject { public function update(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array) { var move = new Move(); move.d = new Vector(); - if (this.controllable && this.mode != Finish && !MarbleGame.instance.paused && !this.level.isWatching) { + if (this.controllable + && this.mode != Finish + && !MarbleGame.instance.paused + && !this.level.isWatching + && this.level.isRecording) { if (Key.isDown(Settings.controlsSettings.forward)) { move.d.x -= 1; } @@ -1367,16 +1371,20 @@ class Marble extends GameObject { move.powerup = true; move.d = new Vector(this.level.replay.currentPlaybackFrame.marbleX, this.level.replay.currentPlaybackFrame.marbleY, 0); } else { - this.level.replay.recordMarbleStateFlags(move.jump, move.powerup, false); - this.level.replay.recordMarbleInput(move.d.x, move.d.y); + if (this.level.isRecording) { + this.level.replay.recordMarbleStateFlags(move.jump, move.powerup, false); + this.level.replay.recordMarbleInput(move.d.x, move.d.y); + } } playedSounds = []; advancePhysics(timeState, move, collisionWorld, pathedInteriors); - if (!this.level.isWatching) - this.level.replay.recordMarbleState(this.getAbsPos().getPosition(), this.velocity, this.getRotationQuat(), this.omega); - else { + if (!this.level.isWatching) { + if (this.level.isRecording) { + this.level.replay.recordMarbleState(this.getAbsPos().getPosition(), this.velocity, this.getRotationQuat(), this.omega); + } + } else { var expectedPos = this.level.replay.currentPlaybackFrame.marblePosition.clone(); var expectedVel = this.level.replay.currentPlaybackFrame.marbleVelocity.clone(); var expectedOmega = this.level.replay.currentPlaybackFrame.marbleAngularVelocity.clone(); diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index d40fa024..5eb6f4bf 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -131,6 +131,7 @@ class MarbleWorld extends Scheduler { // Replay public var replay:Replay; public var isWatching:Bool = false; + public var isRecording:Bool = true; // Loading var resourceLoadFuncs:Array<(() -> Void)->Void> = []; @@ -320,9 +321,10 @@ class MarbleWorld extends Scheduler { } public function restart() { - if (!this.isWatching) + if (!this.isWatching) { this.replay.clear(); - else + this.isRecording = true; + } else this.replay.rewind(); this.timeState.currentAttemptTime = 0; this.timeState.gameplayClock = 0; @@ -339,6 +341,33 @@ class MarbleWorld extends Scheduler { this.playGui.formatGemCounter(this.gemCount, this.totalGems); } + // Record/Playback trapdoor and landmine states + var tidx = 0; + var lidx = 0; + for (dtss in this.dtsObjects) { + if (dtss is Trapdoor) { + var trapdoor:Trapdoor = cast dtss; + if (!this.isWatching) { + this.replay.recordTrapdoorState(trapdoor.lastContactTime - this.timeState.timeSinceLoad, trapdoor.lastDirection, trapdoor.lastCompletion); + } else { + var state = this.replay.getTrapdoorState(tidx); + trapdoor.lastContactTime = state.lastContactTime + this.timeState.timeSinceLoad; + trapdoor.lastDirection = state.lastDirection; + trapdoor.lastCompletion = state.lastCompletion; + } + tidx++; + } + if (dtss is LandMine) { + var landmine:LandMine = cast dtss; + if (!this.isWatching) { + this.replay.recordLandMineState(landmine.disappearTime - this.timeState.timeSinceLoad); + } else { + landmine.disappearTime = this.replay.getLandMineState(lidx) + this.timeState.timeSinceLoad; + } + lidx++; + } + } + var startquat = this.getStartPositionAndOrientation(); this.marble.setPosition(startquat.position.x, startquat.position.y, startquat.position.z + 3); @@ -819,9 +848,11 @@ class MarbleWorld extends Scheduler { if (!_ready) { return; } - if (!this.isWatching) - this.replay.startFrame(); - else { + if (!this.isWatching) { + if (this.isRecording) { + this.replay.startFrame(); + } + } else { if (!this.replay.advance(dt)) { if (Util.isTouchDevice()) { MarbleGame.instance.touchInput.hideControls(@:privateAccess this.playGui.playGuiCtrl); @@ -834,6 +865,7 @@ class MarbleWorld extends Scheduler { #if js pointercontainer.hidden = false; #end + return; } } @@ -859,8 +891,11 @@ class MarbleWorld extends Scheduler { ProfilerUI.measure("updateAudio"); AudioManager.update(this.scene); - if (!this.isWatching) - this.replay.endFrame(); + if (!this.isWatching) { + if (this.isRecording) { + this.replay.endFrame(); + } + } if (this.outOfBounds && this.finishTime == null && Key.isDown(Settings.controlsSettings.powerup)) { this.clearSchedule(); @@ -958,7 +993,7 @@ class MarbleWorld extends Scheduler { this.timeState.gameplayClock = finishTime.gameplayClock; playGui.formatTimer(this.timeState.gameplayClock); - if (!this.isWatching) + if (!this.isWatching && this.isRecording) this.replay.recordTimeState(timeState.currentAttemptTime, timeState.gameplayClock, this.bonusTime); } @@ -1160,6 +1195,7 @@ class MarbleWorld extends Scheduler { var pointercontainer = js.Browser.document.querySelector("#pointercontainer"); pointercontainer.hidden = false; #end + this.isRecording = false; // Stop recording here if (Util.isTouchDevice()) { MarbleGame.instance.touchInput.setControlsEnabled(false); } diff --git a/src/Replay.hx b/src/Replay.hx index 697af383..ab9d4464 100644 --- a/src/Replay.hx +++ b/src/Replay.hx @@ -1,5 +1,9 @@ package src; +import haxe.io.Bytes; +import haxe.io.BytesBuffer; +import dif.io.BytesReader; +import dif.io.BytesWriter; import haxe.EnumFlags; import h3d.Quat; import h3d.Vector; @@ -94,13 +98,96 @@ class ReplayFrame { return interpFrame; } + + public function write(bw:BytesWriter) { + bw.writeFloat(this.time); + bw.writeFloat(this.clockTime); + bw.writeFloat(this.bonusTime); + bw.writeFloat(this.marblePosition.x); + bw.writeFloat(this.marblePosition.y); + bw.writeFloat(this.marblePosition.z); + bw.writeFloat(this.marbleVelocity.x); + bw.writeFloat(this.marbleVelocity.y); + bw.writeFloat(this.marbleVelocity.z); + bw.writeFloat(this.marbleOrientation.x); + bw.writeFloat(this.marbleOrientation.y); + bw.writeFloat(this.marbleOrientation.z); + bw.writeFloat(this.marbleOrientation.w); + bw.writeFloat(this.marbleAngularVelocity.x); + bw.writeFloat(this.marbleAngularVelocity.y); + bw.writeFloat(this.marbleAngularVelocity.z); + bw.writeByte(this.marbleStateFlags.toInt()); + bw.writeFloat(this.cameraPitch); + bw.writeFloat(this.cameraYaw); + bw.writeFloat(this.marbleX); + bw.writeFloat(this.marbleY); + } + + public function read(br:BytesReader) { + this.time = br.readFloat(); + this.clockTime = br.readFloat(); + this.bonusTime = br.readFloat(); + this.marblePosition = new Vector(br.readFloat(), br.readFloat(), br.readFloat()); + this.marbleVelocity = new Vector(br.readFloat(), br.readFloat(), br.readFloat()); + this.marbleOrientation = new Quat(br.readFloat(), br.readFloat(), br.readFloat(), br.readFloat()); + this.marbleAngularVelocity = new Vector(br.readFloat(), br.readFloat(), br.readFloat()); + this.marbleStateFlags = EnumFlags.ofInt(br.readByte()); + this.cameraPitch = br.readFloat(); + this.cameraYaw = br.readFloat(); + this.marbleX = br.readFloat(); + this.marbleY = br.readFloat(); + } +} + +@:publicFields +class ReplayInitialState { + var trapdoorLastContactTimes:Array = []; + var trapdoorLastDirections:Array = []; + var trapdoorLastCompletions:Array = []; + var landMineDisappearTimes:Array = []; + + public function new() {} + + public function write(bw:BytesWriter) { + bw.writeInt16(this.trapdoorLastContactTimes.length); + for (time in this.trapdoorLastContactTimes) { + bw.writeFloat(time); + } + for (dir in this.trapdoorLastDirections) { + bw.writeByte(dir); + } + for (completion in this.trapdoorLastCompletions) { + bw.writeFloat(completion); + } + bw.writeInt16(this.landMineDisappearTimes.length); + for (time in this.landMineDisappearTimes) { + bw.writeFloat(time); + } + } + + public function read(br:BytesReader) { + var trapdoorCount = br.readInt16(); + for (i in 0...trapdoorCount) { + this.trapdoorLastContactTimes.push(br.readFloat()); + } + for (i in 0...trapdoorCount) { + this.trapdoorLastDirections.push(br.readByte()); + } + for (i in 0...trapdoorCount) { + this.trapdoorLastCompletions.push(br.readFloat()); + } + var landMineCount = br.readInt16(); + for (i in 0...landMineCount) { + this.landMineDisappearTimes.push(br.readFloat()); + } + } } class Replay { public var mission:String; var frames:Array; - + var initialState:ReplayInitialState; var currentRecordFrame:ReplayFrame; public var currentPlaybackFrame:ReplayFrame; @@ -110,6 +197,7 @@ class Replay { public function new(mission:String) { this.mission = mission; + this.initialState = new ReplayInitialState(); } public function startFrame() { @@ -153,6 +241,28 @@ class Replay { currentRecordFrame.cameraYaw = yaw; } + public function recordTrapdoorState(lastContactTime:Float, lastDirection:Int, lastCompletion:Float) { + initialState.trapdoorLastContactTimes.push(lastContactTime); + initialState.trapdoorLastDirections.push(lastDirection); + initialState.trapdoorLastCompletions.push(lastCompletion); + } + + public function recordLandMineState(disappearTime:Float) { + initialState.landMineDisappearTimes.push(disappearTime); + } + + public function getTrapdoorState(idx:Int) { + return { + lastContactTime: initialState.trapdoorLastContactTimes[idx], + lastDirection: initialState.trapdoorLastDirections[idx], + lastCompletion: initialState.trapdoorLastCompletions[idx] + }; + } + + public function getLandMineState(idx:Int) { + return initialState.landMineDisappearTimes[idx]; + } + public function clear() { this.frames = []; currentRecordFrame = null; @@ -188,4 +298,42 @@ class Replay { this.currentPlaybackFrame = null; this.currentPlaybackFrameIdx = 0; } + + public function write() { + var bw = new BytesWriter(); + + bw.writeStr(this.mission); + this.initialState.write(bw); + bw.writeInt32(this.frames.length); + for (frame in this.frames) { + frame.write(bw); + } + + var buf = bw.getBuffer(); + var bufsize = buf.length; + var compressed = haxe.zip.Compress.run(bw.getBuffer(), 7); + + var finalB = new BytesBuffer(); + finalB.addInt32(bufsize); + finalB.addBytes(compressed, 4, compressed.length); + + return finalB.getBytes(); + } + + public function read(data:Bytes) { + var uncompressedLength = data.getInt32(0); + var compressedData = data.sub(4, data.length - 4); + + var uncompressed = haxe.zip.Uncompress.run(compressedData, uncompressedLength); + var br = new BytesReader(uncompressed); + this.mission = br.readStr(); + this.initialState.read(br); + var frameCount = br.readInt32(); + this.frames = []; + for (i in 0...frameCount) { + var frame = new ReplayFrame(); + frame.read(br); + this.frames.push(frame); + } + } } diff --git a/src/shapes/Trapdoor.hx b/src/shapes/Trapdoor.hx index 1102ab3a..d56f0db7 100644 --- a/src/shapes/Trapdoor.hx +++ b/src/shapes/Trapdoor.hx @@ -14,7 +14,7 @@ import src.MarbleWorld; class Trapdoor extends DtsObject { var lastContactTime = -1e8; var timeout:Float = 0.2; - var lastDirection:Float; + var lastDirection:Int; var lastCompletion:Float = 0; public function new() {