rewind barebones

This commit is contained in:
RandomityGuy 2023-05-06 23:21:13 +05:30
parent c6c1449a84
commit e6b5dbbb25
9 changed files with 317 additions and 10 deletions

View file

@ -1,5 +1,6 @@
package src;
import rewind.RewindManager;
import Macros.MarbleWorldMacros;
import shapes.PushButton;
#if js
@ -179,6 +180,10 @@ class MarbleWorld extends Scheduler {
public var isWatching:Bool = false;
public var isRecording:Bool = false;
// Rewind
public var rewindManager:RewindManager;
public var rewinding:Bool = false;
// Loading
var resourceLoadFuncs:Array<(() -> Void)->Void> = [];
@ -205,6 +210,7 @@ class MarbleWorld extends Scheduler {
this.game = mission.game.toLowerCase();
this.replay = new Replay(mission.path, mission.isClaMission ? mission.id : 0);
this.isRecording = record;
this.rewindManager = new RewindManager(this);
}
public function init() {
@ -448,6 +454,8 @@ class MarbleWorld extends Scheduler {
} else
this.replay.rewind();
this.rewindManager.clear();
this.timeState.currentAttemptTime = 0;
this.timeState.gameplayClock = 0;
this.bonusTime = 0;
@ -997,6 +1005,23 @@ class MarbleWorld extends Scheduler {
}
}
if (Key.isDown(Key.R)) {
this.rewinding = true;
} else {
this.rewinding = false;
}
if (this.rewinding) {
var rframe = rewindManager.getNextRewindFrame(timeState.currentAttemptTime - dt);
if (rframe != null) {
var actualDt = timeState.currentAttemptTime - rframe.timeState.currentAttemptTime - dt;
dt = actualDt;
rewindManager.applyFrame(rframe);
}
}
if (dt < 0)
return;
ProfilerUI.measure("updateTimer");
this.updateTimer(dt);
@ -1080,6 +1105,9 @@ class MarbleWorld extends Scheduler {
}
}
if (!this.rewinding)
this.rewindManager.recordFrame();
this.updateTexts();
}

83
src/rewind/RewindFrame.hx Normal file
View file

@ -0,0 +1,83 @@
package rewind;
import src.PathedInterior.PIState;
import shapes.PowerUp;
import h3d.Vector;
import h3d.Quat;
import src.TimeState;
@:publicFields
class RewindFrame {
var timeState:TimeState;
var marblePosition:Vector;
var marbleOrientation:Quat;
var marbleVelocity:Vector;
var marbleAngularVelocity:Vector;
var marblePowerup:PowerUp;
var bonusTime:Float;
var mpStates:Array<{
curState:PIState,
prevState:PIState,
stopped:Bool,
stopTime:Float
}>;
var gemCount:Int;
var gemStates:Array<Bool>;
var powerupStates:Array<Float>;
var landMineStates:Array<Float>;
var activePowerupStates:Array<Float>;
var currentUp:Vector;
var trapdoorStates:Array<{lastContactTime:Float, lastDirection:Int, lastCompletion:Float}>;
var lastContactNormal:Vector;
public function new() {}
public function clone() {
var c = new RewindFrame();
c.timeState = timeState.clone();
c.marblePosition = marblePosition.clone();
c.marbleOrientation = marbleOrientation.clone();
c.marbleVelocity = marbleVelocity.clone();
c.marbleAngularVelocity = marbleAngularVelocity.clone();
c.marblePowerup = marblePowerup;
c.bonusTime = bonusTime;
c.gemCount = gemCount;
c.gemStates = gemStates.copy();
c.powerupStates = powerupStates.copy();
c.landMineStates = landMineStates.copy();
c.activePowerupStates = activePowerupStates.copy();
c.currentUp = currentUp.clone();
c.lastContactNormal = lastContactNormal.clone();
c.mpStates = [];
for (s in mpStates) {
c.mpStates.push({
curState: {
currentTime: s.curState.currentTime,
targetTime: s.curState.targetTime,
velocity: s.curState.velocity.clone(),
currentPosition: s.curState.currentPosition.clone(),
prevPosition: s.curState.prevPosition.clone(),
changeTime: s.curState.changeTime,
},
stopTime: s.stopTime,
stopped: s.stopped,
prevState: s.prevState != null ? {
currentTime: s.prevState.currentTime,
targetTime: s.prevState.targetTime,
velocity: s.prevState.velocity.clone(),
currentPosition: s.prevState.currentPosition.clone(),
prevPosition: s.prevState.prevPosition.clone(),
changeTime: s.prevState.changeTime,
} : null,
});
}
c.trapdoorStates = [];
for (s in trapdoorStates) {
c.trapdoorStates.push({
lastContactTime: s.lastContactTime,
lastDirection: s.lastDirection,
lastCompletion: s.lastCompletion,
});
}
}
}

193
src/rewind/RewindManager.hx Normal file
View file

@ -0,0 +1,193 @@
package rewind;
import shapes.PowerUp;
import shapes.LandMine;
import src.MarbleWorld;
import shapes.Trapdoor;
import src.Util;
class RewindManager {
var frames:Array<RewindFrame> = [];
var level:MarbleWorld;
public function new(level:MarbleWorld) {
this.level = level;
}
public function recordFrame() {
var rf = new RewindFrame();
rf.timeState = level.timeState.clone();
rf.marblePosition = level.marble.getAbsPos().getPosition().clone();
rf.marbleOrientation = level.marble.getRotationQuat().clone();
rf.marbleVelocity = level.marble.velocity.clone();
rf.marbleAngularVelocity = level.marble.omega.clone();
rf.marblePowerup = level.marble.heldPowerup;
rf.bonusTime = level.bonusTime;
rf.gemCount = level.gemCount;
rf.gemStates = level.gems.map(x -> x.pickedUp);
rf.activePowerupStates = [@:privateAccess
level.marble.superBounceEnableTime, @:privateAccess
level.marble.shockAbsorberEnableTime, @:privateAccess
level.marble.helicopterEnableTime, @:privateAccess
level.marble.megaMarbleEnableTime
];
rf.currentUp = level.currentUp.clone();
rf.lastContactNormal = level.marble.lastContactNormal.clone();
rf.mpStates = level.pathedInteriors.map(x -> {
return {
curState: {
currentTime: x.currentTime,
targetTime: x.targetTime,
velocity: x.velocity.clone(),
currentPosition: x.currentPosition.clone(),
prevPosition: x.prevPosition.clone(),
changeTime: x.changeTime,
},
stopTime: @:privateAccess x.stopTime,
stopped: @:privateAccess x.stopped,
prevState: @:privateAccess x.previousState != null ? {
currentTime: @:privateAccess x.previousState.currentTime,
targetTime: @:privateAccess x.previousState.targetTime,
velocity: @:privateAccess x.previousState.velocity.clone(),
currentPosition: @:privateAccess x.previousState.currentPosition.clone(),
prevPosition: @:privateAccess x.previousState.prevPosition.clone(),
changeTime: @:privateAccess x.previousState.changeTime,
} : null,
}
});
rf.powerupStates = [];
rf.landMineStates = [];
rf.trapdoorStates = [];
for (dts in level.dtsObjects) {
if (dts is PowerUp) {
var pow:PowerUp = cast dts;
rf.powerupStates.push(pow.lastPickUpTime);
}
if (dts is LandMine) {
var lm:LandMine = cast dts;
rf.landMineStates.push(lm.disappearTime);
}
if (dts is Trapdoor) {
var td:Trapdoor = cast dts;
rf.trapdoorStates.push({
lastCompletion: td.lastCompletion,
lastDirection: td.lastDirection,
lastContactTime: td.lastContactTime
});
}
}
frames.push(rf);
}
public function applyFrame(rf:RewindFrame) {
level.timeState = rf.timeState.clone();
level.marble.setPosition(rf.marblePosition.x, rf.marblePosition.y, rf.marblePosition.z);
level.marble.setRotationQuat(rf.marbleOrientation.clone());
level.marble.velocity.set(rf.marbleVelocity.x, rf.marbleVelocity.y, rf.marbleVelocity.z);
level.marble.omega.set(rf.marbleAngularVelocity.x, rf.marbleAngularVelocity.y, rf.marbleAngularVelocity.z);
if (level.marble.heldPowerup == null) {
if (rf.marblePowerup != null) {
level.pickUpPowerUp(rf.marblePowerup);
}
} else {
if (rf.marblePowerup == null) {
level.deselectPowerUp();
} else {
level.pickUpPowerUp(rf.marblePowerup);
}
}
level.bonusTime = rf.bonusTime;
level.gemCount = rf.gemCount;
@:privateAccess level.playGui.formatGemCounter(level.gemCount, level.totalGems);
for (i in 0...rf.gemStates.length) {
level.gems[i].setHide(rf.gemStates[i]);
}
@:privateAccess level.marble.superBounceEnableTime = rf.activePowerupStates[0];
@:privateAccess level.marble.shockAbsorberEnableTime = rf.activePowerupStates[1];
@:privateAccess level.marble.helicopterEnableTime = rf.activePowerupStates[2];
@:privateAccess level.marble.megaMarbleEnableTime = rf.activePowerupStates[3];
if (level.currentUp.x != rf.currentUp.x || level.currentUp.y != rf.currentUp.y || level.currentUp.z != rf.currentUp.z) {
level.setUp(rf.currentUp, level.timeState);
// Hacky things
@:privateAccess level.orientationChangeTime = level.timeState.currentAttemptTime - 300;
var oldorient = level.newOrientationQuat;
level.newOrientationQuat = @:privateAccess level.oldOrientationQuat;
@:privateAccess level.oldOrientationQuat = oldorient;
}
var gravitycompletion = Util.clamp((level.timeState.currentAttemptTime - @:privateAccess level.orientationChangeTime) / 300, 0, 1);
if (gravitycompletion == 0) {
level.newOrientationQuat = @:privateAccess level.oldOrientationQuat;
@:privateAccess level.orientationChangeTime = -1e8;
}
level.currentUp.set(rf.currentUp.x, rf.currentUp.y, rf.currentUp.z);
level.marble.lastContactNormal.set(rf.lastContactNormal.x, rf.lastContactNormal.y, rf.lastContactNormal.z);
for (i in 0...rf.mpStates.length) {
level.pathedInteriors[i].currentTime = rf.mpStates[i].curState.currentTime;
level.pathedInteriors[i].targetTime = rf.mpStates[i].curState.targetTime;
level.pathedInteriors[i].velocity.set(rf.mpStates[i].curState.velocity.x, rf.mpStates[i].curState.velocity.y, rf.mpStates[i].curState.velocity.z);
level.pathedInteriors[i].currentPosition.set(rf.mpStates[i].curState.currentPosition.x, rf.mpStates[i].curState.currentPosition.y,
rf.mpStates[i].curState.currentPosition.z);
level.pathedInteriors[i].prevPosition.set(rf.mpStates[i].curState.prevPosition.x, rf.mpStates[i].curState.prevPosition.y,
rf.mpStates[i].curState.prevPosition.z);
level.pathedInteriors[i].changeTime = rf.mpStates[i].curState.changeTime;
@:privateAccess level.pathedInteriors[i].stopTime = rf.mpStates[i].stopTime;
@:privateAccess level.pathedInteriors[i].stopped = rf.mpStates[i].stopped;
if (rf.mpStates[i].prevState != null) {
@:privateAccess level.pathedInteriors[i].previousState.currentTime = rf.mpStates[i].prevState.currentTime;
@:privateAccess level.pathedInteriors[i].previousState.targetTime = rf.mpStates[i].prevState.targetTime;
@:privateAccess level.pathedInteriors[i].previousState.velocity.set(rf.mpStates[i].prevState.velocity.x, rf.mpStates[i].prevState.velocity.y,
rf.mpStates[i].prevState.velocity.z);
@:privateAccess level.pathedInteriors[i].previousState.currentPosition.set(rf.mpStates[i].prevState.currentPosition.x,
rf.mpStates[i].prevState.currentPosition.y, rf.mpStates[i].prevState.currentPosition.z);
@:privateAccess level.pathedInteriors[i].previousState.prevPosition.set(rf.mpStates[i].prevState.prevPosition.x,
rf.mpStates[i].prevState.prevPosition.y, rf.mpStates[i].prevState.prevPosition.z);
@:privateAccess level.pathedInteriors[i].previousState.changeTime = rf.mpStates[i].prevState.changeTime;
} else {
@:privateAccess level.pathedInteriors[i].previousState = null;
}
}
var pstates = rf.powerupStates.copy();
var lmstates = rf.landMineStates.copy();
var tstates = rf.trapdoorStates.copy();
for (dts in level.dtsObjects) {
if (dts is PowerUp) {
var pow:PowerUp = cast dts;
pow.lastPickUpTime = pstates.shift();
}
if (dts is LandMine) {
var lm:LandMine = cast dts;
lm.disappearTime = lmstates.shift();
}
if (dts is Trapdoor) {
var td:Trapdoor = cast dts;
var tdState = tstates.shift();
td.lastCompletion = tdState.lastCompletion;
td.lastDirection = tdState.lastDirection;
td.lastContactTime = tdState.lastContactTime;
}
}
}
public function getNextRewindFrame(absTime:Float):RewindFrame {
if (frames.length == 0)
return null;
var topFrame = frames[frames.length - 1];
while (topFrame.timeState.currentAttemptTime > absTime) {
frames.pop();
if (frames.length == 0)
return null;
topFrame = frames[frames.length - 1];
}
return topFrame;
}
public function clear() {
frames = [];
}
}

View file

@ -27,9 +27,11 @@ class AntiGravity extends PowerUp {
}
public function use(timeState:TimeState) {
var direction = new Vector(0, 0, -1);
direction.transform(this.getRotationQuat().toMatrix());
this.level.setUp(direction, timeState);
if (!this.level.rewinding) {
var direction = new Vector(0, 0, -1);
direction.transform(this.getRotationQuat().toMatrix());
this.level.setUp(direction, timeState);
}
// marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity
// if (!this.level.rewinding)
// AudioManager.play(this.sounds[1]);

View file

@ -53,7 +53,7 @@ class Gem extends DtsObject {
override function onMarbleInside(timeState:TimeState) {
super.onMarbleInside(timeState);
if (this.pickedUp)
if (this.pickedUp || this.level.rewinding)
return;
this.pickedUp = true;
this.setOpacity(0); // Hide the gem

View file

@ -122,13 +122,13 @@ class LandMine extends DtsObject {
}
override function onMarbleContact(timeState:TimeState, ?contact:CollisionInfo) {
if (this.isCollideable) {
if (this.isCollideable && !this.level.rewinding) {
// marble.velocity = marble.velocity.add(vec);
this.disappearTime = timeState.timeSinceLoad;
this.setCollisionEnabled(false);
// if (!this.level.rewinding)
AudioManager.playSound(ResourceLoader.getResource("data/sound/explode1.wav", ResourceLoader.getAudio, this.soundResources));
if (!this.level.rewinding)
AudioManager.playSound(ResourceLoader.getResource("data/sound/explode1.wav", ResourceLoader.getAudio, this.soundResources));
this.level.particleManager.createEmitter(landMineParticle, landMineParticleData, this.getAbsPos().getPosition());
this.level.particleManager.createEmitter(landMineSmokeParticle, landMineSmokeParticleData, this.getAbsPos().getPosition());
this.level.particleManager.createEmitter(landMineSparksParticle, landMineSparkParticleData, this.getAbsPos().getPosition());

View file

@ -122,7 +122,7 @@ class Nuke extends DtsObject {
}
override function onMarbleContact(timeState:TimeState, ?contact:CollisionInfo) {
if (this.isCollideable) {
if (this.isCollideable && !this.level.rewinding) {
// marble.velocity = marble.velocity.add(vec);
this.disappearTime = timeState.timeSinceLoad;
this.setCollisionEnabled(false);

View file

@ -45,7 +45,7 @@ abstract class PowerUp extends DtsObject {
if (this.element.showhelponpickup == "1" && !this.autoUse)
this.level.displayHelp('Press <func:bind mousefire> to use the ${this.pickUpName}!');
if (pickupSound != null) {
if (pickupSound != null && !this.level.rewinding) {
AudioManager.playSound(pickupSound);
}
}

View file

@ -43,6 +43,7 @@ class TimeTravel extends PowerUp {
}
public function use(time:TimeState) {
level.addBonusTime(this.timeBonus);
if (!this.level.rewinding)
level.addBonusTime(this.timeBonus);
}
}