some more work on replays, almost finish up it

This commit is contained in:
RandomityGuy 2022-11-04 22:15:32 +05:30
parent 841b059f6e
commit bd748af198
11 changed files with 177 additions and 15 deletions

View file

@ -1,6 +1,7 @@
-cp src
-lib heaps
-lib stb_ogg_sound
-lib zip
--js marblegame.js
-D windowSize=1280x720
-D js-es=6

BIN
data/ui/play/playback.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

BIN
data/ui/play/record.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

View file

@ -1335,11 +1335,7 @@ 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
&& this.level.isRecording) {
if (this.controllable && this.mode != Finish && !MarbleGame.instance.paused && !this.level.isWatching) {
if (Key.isDown(Settings.controlsSettings.forward)) {
move.d.x -= 1;
}

View file

@ -1,5 +1,6 @@
package src;
import src.Replay;
import touch.TouchInput;
import src.ResourceLoader;
import src.AudioManager;
@ -28,6 +29,7 @@ class MarbleGame {
var scene:h3d.scene.Scene;
var paused:Bool;
var toRecord:Bool = false;
var exitGameDlg:ExitGameDlg;
@ -212,14 +214,26 @@ class MarbleGame {
paused = false;
var pmg = new PlayMissionGui();
PlayMissionGui.currentSelectionStatic = world.mission.index;
if (world.isRecording) {
world.saveReplay();
}
world.dispose();
world = null;
canvas.setContent(pmg);
}
public function playMission(mission:Mission) {
canvas.clearContent();
world = new MarbleWorld(scene, scene2d, mission, toRecord);
toRecord = false;
world.init();
}
public function watchMissionReplay(mission:Mission, replay:Replay) {
canvas.clearContent();
world = new MarbleWorld(scene, scene2d, mission);
world.replay = replay;
world.isWatching = true;
world.init();
}

View file

@ -131,7 +131,7 @@ class MarbleWorld extends Scheduler {
// Replay
public var replay:Replay;
public var isWatching:Bool = false;
public var isRecording:Bool = true;
public var isRecording:Bool = false;
// Loading
var resourceLoadFuncs:Array<(() -> Void)->Void> = [];
@ -149,11 +149,12 @@ class MarbleWorld extends Scheduler {
var lock:Bool = false;
public function new(scene:Scene, scene2d:h2d.Scene, mission:Mission) {
public function new(scene:Scene, scene2d:h2d.Scene, mission:Mission, record:Bool = false) {
this.scene = scene;
this.scene2d = scene2d;
this.mission = mission;
this.replay = new Replay(mission.path);
this.isRecording = record;
}
public function init() {
@ -323,7 +324,6 @@ class MarbleWorld extends Scheduler {
public function restart() {
if (!this.isWatching) {
this.replay.clear();
this.isRecording = true;
} else
this.replay.rewind();
this.timeState.currentAttemptTime = 0;
@ -863,6 +863,7 @@ class MarbleWorld extends Scheduler {
PlayMissionGui.currentSelectionStatic = mission.index + 1;
MarbleGame.canvas.setContent(pmg);
#if js
var pointercontainer = js.Browser.document.querySelector("#pointercontainer");
pointercontainer.hidden = false;
#end
return;
@ -1195,7 +1196,10 @@ class MarbleWorld extends Scheduler {
var pointercontainer = js.Browser.document.querySelector("#pointercontainer");
pointercontainer.hidden = false;
#end
this.isRecording = false; // Stop recording here
if (this.isRecording) {
this.isRecording = false; // Stop recording here
this.saveReplay();
}
if (Util.isTouchDevice()) {
MarbleGame.instance.touchInput.setControlsEnabled(false);
}
@ -1323,6 +1327,20 @@ class MarbleWorld extends Scheduler {
}
}
public function saveReplay() {
var replayBytes = this.replay.write();
hxd.File.saveAs(replayBytes, {
title: 'Save Replay',
fileTypes: [
{
name: "Replay (*.mbr)",
extensions: ["mbr"]
}
],
defaultPath: '${this.mission.title}${this.timeState.gameplayClock}.mbr'
});
}
public function dispose() {
this.playGui.dispose();
scene.removeChildren();

View file

@ -10,6 +10,8 @@ class MissionList {
static var advancedMissions:Array<Mission>;
static var customMissions:Array<Mission>;
static var missions:Map<String, Mission>;
static var _build:Bool = false;
public function new() {}
@ -17,6 +19,9 @@ class MissionList {
public static function buildMissionList() {
if (_build)
return;
missions = new Map<String, Mission>();
function parseDifficulty(difficulty:String) {
#if (hl && !android)
var difficultyFiles = ResourceLoader.fileSystem.dir("data/missions/" + difficulty);
@ -30,6 +35,7 @@ class MissionList {
var misParser = new MisParser(file.getText());
var mInfo = misParser.parseMissionInfo();
var mission = Mission.fromMissionInfo(file.path, mInfo);
missions.set(file.path, mission);
difficultyMissions.push(mission);
}
}

View file

@ -1,5 +1,7 @@
package src;
import haxe.io.BytesInput;
import haxe.zip.Huffman;
import haxe.io.Bytes;
import haxe.io.BytesBuffer;
import dif.io.BytesReader;
@ -195,6 +197,8 @@ class Replay {
var currentPlaybackFrameIdx:Int;
var currentPlaybackTime:Float;
var version:Int = 1;
public function new(mission:String) {
this.mission = mission;
this.initialState = new ReplayInitialState();
@ -311,20 +315,37 @@ class Replay {
var buf = bw.getBuffer();
var bufsize = buf.length;
var compressed = haxe.zip.Compress.run(bw.getBuffer(), 7);
#if hl
var compressed = haxe.zip.Compress.run(bw.getBuffer(), 9);
#end
#if js
var stream = zip.DeflateStream.create(zip.DeflateStream.CompressionLevel.GOOD, false);
stream.write(new BytesInput(bw.getBuffer()));
var compressed = stream.finalize();
#end
var finalB = new BytesBuffer();
finalB.addByte(version);
finalB.addInt32(bufsize);
finalB.addBytes(compressed, 4, compressed.length);
finalB.addBytes(compressed, 0, compressed.length);
return finalB.getBytes();
}
public function read(data:Bytes) {
var uncompressedLength = data.getInt32(0);
var compressedData = data.sub(4, data.length - 4);
var replayVersion = data.get(0);
if (replayVersion > version) {
return false;
}
var uncompressedLength = data.getInt32(1);
var compressedData = data.sub(5, data.length - 5);
#if hl
var uncompressed = haxe.zip.Uncompress.run(compressedData, uncompressedLength);
#end
#if js
var uncompressed = haxe.zip.InflateImpl.run(new BytesInput(compressedData), uncompressedLength);
#end
var br = new BytesReader(uncompressed);
this.mission = br.readStr();
this.initialState.read(br);
@ -335,5 +356,6 @@ class Replay {
frame.read(br);
this.frames.push(frame);
}
return true;
}
}

View file

@ -19,15 +19,15 @@ class BytesWriter {
public function writeUInt16(int:Int) {
var h = int >> 8;
var l = int & 0xFF;
this.bytes.addByte(h);
this.bytes.addByte(l);
this.bytes.addByte(h);
}
public function writeInt16(int:Int) {
var h = int >> 8;
var l = int & 0xFF;
this.bytes.addByte(h);
this.bytes.addByte(l);
this.bytes.addByte(h);
}
public function writeByte(int:Int) {

View file

@ -0,0 +1,60 @@
package gui;
import src.MarbleGame;
import hxd.res.BitmapFont;
import h3d.Vector;
import src.ResourceLoader;
import src.Settings;
class MessageBoxOkDlg extends GuiControl {
public function new(text:String) {
super();
this.horizSizing = Width;
this.vertSizing = Height;
this.position = new Vector();
this.extent = new Vector(640, 480);
var domcasual24fontdata = ResourceLoader.getFileEntry("data/font/DomCasualD.fnt");
var domcasual24b = new BitmapFont(domcasual24fontdata.entry);
@:privateAccess domcasual24b.loader = ResourceLoader.loader;
var domcasual24 = domcasual24b.toSdfFont(cast 20 * Settings.uiScale, MultiChannel);
function loadButtonImages(path:String) {
var normal = ResourceLoader.getResource('${path}_n.png', ResourceLoader.getImage, this.imageResources).toTile();
var hover = ResourceLoader.getResource('${path}_h.png', ResourceLoader.getImage, this.imageResources).toTile();
var pressed = ResourceLoader.getResource('${path}_d.png', ResourceLoader.getImage, this.imageResources).toTile();
return [normal, hover, pressed];
}
var yesNoFrame = new GuiImage(ResourceLoader.getResource("data/ui/common/dialog.png", ResourceLoader.getImage, this.imageResources).toTile());
yesNoFrame.horizSizing = Center;
yesNoFrame.vertSizing = Center;
yesNoFrame.position = new Vector(187, 156);
yesNoFrame.extent = new Vector(300, 161);
this.addChild(yesNoFrame);
var yesNoText = new GuiMLText(domcasual24, null);
yesNoText.position = new Vector(33, 46);
yesNoText.horizSizing = Center;
yesNoText.extent = new Vector(198, 23);
yesNoText.text.text = text;
yesNoText.text.textColor = 0;
yesNoText.text.maxWidth = 198;
yesNoFrame.addChild(yesNoText);
var okButton = new GuiButton(loadButtonImages("data/ui/common/ok"));
okButton.position = new Vector(117, 85);
okButton.extent = new Vector(78, 59);
okButton.vertSizing = Top;
okButton.pressedAction = (sender) -> {
MarbleGame.canvas.popDialog(this);
}
yesNoFrame.addChild(okButton);
if (yesNoText.text.getBounds().yMax > yesNoText.extent.y) {
var diff = yesNoText.text.getBounds().yMax - yesNoText.extent.y;
yesNoFrame.extent.y += diff;
okButton.position.y += diff;
}
}
}

View file

@ -1,5 +1,6 @@
package gui;
import src.Replay;
import haxe.ds.Option;
import hxd.Key;
import gui.GuiControl.MouseState;
@ -128,6 +129,50 @@ class PlayMissionGui extends GuiImage {
var filt = new ColorMatrix(Matrix.I());
pmPreview.bmp.filter = filt;
var replayPlayButton = new GuiImage(ResourceLoader.getResource("data/ui/play/playback.png", ResourceLoader.getImage, this.imageResources).toTile());
replayPlayButton.position = new Vector(38, 315);
replayPlayButton.extent = new Vector(18, 18);
replayPlayButton.pressedAction = (sender) -> {
hxd.File.browse((replayToLoad) -> {
replayToLoad.load((replayData) -> {
var replay = new Replay("");
if (!replay.read(replayData)) {
cast(this.parent, Canvas).pushDialog(new MessageBoxOkDlg("Cannot load replay."));
// Idk do something to notify the user here
} else {
var repmis = replay.mission;
#if js
repmis = StringTools.replace(repmis, "data/", "");
#end
var playMis = MissionList.missions.get(repmis);
if (playMis != null) {
cast(this.parent, Canvas).marbleGame.watchMissionReplay(playMis, replay);
} else {
cast(this.parent, Canvas).pushDialog(new MessageBoxOkDlg("Cannot load replay."));
}
}
});
}, {
title: "Select replay file",
fileTypes: [
{
name: "Replay (*.mbr)",
extensions: ["mbr"]
}
],
});
};
pmBox.addChild(replayPlayButton);
var replayRecordButton = new GuiImage(ResourceLoader.getResource("data/ui/play/record.png", ResourceLoader.getImage, this.imageResources).toTile());
replayRecordButton.position = new Vector(56, 315);
replayRecordButton.extent = new Vector(18, 18);
replayRecordButton.pressedAction = (sender) -> {
cast(this.parent, Canvas).marbleGame.toRecord = true;
cast(this.parent, Canvas).pushDialog(new MessageBoxOkDlg("The next mission you play will be recorded."));
};
pmBox.addChild(replayRecordButton);
var levelWnd = new GuiImage(ResourceLoader.getResource("data/ui/play/level_window.png", ResourceLoader.getImage, this.imageResources).toTile());
levelWnd.position = new Vector();
levelWnd.extent = new Vector(258, 194);