do more work on replays

This commit is contained in:
RandomityGuy 2022-11-04 14:34:39 +05:30
parent 24c87da4fd
commit 841b059f6e
5 changed files with 211 additions and 17 deletions

View file

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

View file

@ -1335,7 +1335,11 @@ class Marble extends GameObject {
public function update(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array<PathedInterior>) {
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();

View file

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

View file

@ -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<Float> = [];
var trapdoorLastDirections:Array<Int> = [];
var trapdoorLastCompletions:Array<Float> = [];
var landMineDisappearTimes:Array<Float> = [];
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<ReplayFrame>;
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);
}
}
}

View file

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