(directory + File.separator + "temp");
-
- /**
- * A map of control bytes. Used to change settings during playback via the
- * playback file.
- *
- * A full list of changes can be found in {@link ControlByteHandler}
- *
- * The values are as follows:
- *
- * Map(int playbackLine, List(Pair(String controlCommand, String[] arguments))
"
- */
- private Map>> controlBytes = new HashMap>>(); // TODO Replace with TASFile extension
-
- /**
- * The comments in the file, used to store them again later
- */
- private Map> comments = new HashMap<>(); // TODO Replace with TASFile extension
-
- public DesyncMonitoring desyncMonitor = new DesyncMonitoring(this); // TODO Replace with TASFile extension
+ private BigArrayList inputs = new BigArrayList(directory + File.separator + "temp");
- private long startSeed = TASmod.ktrngHandler.getGlobalSeedClient(); // TODO Replace with Metadata extension
+// private long startSeed = TASmod.ktrngHandler.getGlobalSeedClient(); // TODO Replace with Metadata extension
// =====================================================================================================
@@ -166,8 +150,7 @@ public String setTASStateClient(TASstate stateIn) {
*/
public String setTASStateClient(TASstate stateIn, boolean verbose) {
EventListenerRegistry.fireEvent(EventControllerStateChange.class, stateIn, state);
- ControlByteHandler.reset(); // FIXME Controlbytes are resetting when loading a world, due to "Paused" state
- // being active during loading... Fix Paused state shenanigans?
+
if (state == stateIn) {
switch (stateIn) {
case PLAYBACK:
@@ -204,7 +187,7 @@ public String setTASStateClient(TASstate stateIn, boolean verbose) {
case PAUSED:
LOGGER.debug(LoggerMarkers.Playback, "Pausing a recording");
state = TASstate.PAUSED;
- tempPause = TASstate.RECORDING;
+ stateAfterPause = TASstate.RECORDING;
return verbose ? TextFormatting.GREEN + "Pausing a recording" : "";
case NONE:
stopRecording();
@@ -220,7 +203,7 @@ public String setTASStateClient(TASstate stateIn, boolean verbose) {
case PAUSED:
LOGGER.debug(LoggerMarkers.Playback, "Pausing a playback");
state = TASstate.PAUSED;
- tempPause = TASstate.PLAYBACK;
+ stateAfterPause = TASstate.PLAYBACK;
TASmodClient.virtual.clear();
return verbose ? TextFormatting.GREEN + "Pausing a playback" : "";
case NONE:
@@ -233,20 +216,20 @@ public String setTASStateClient(TASstate stateIn, boolean verbose) {
case PLAYBACK:
LOGGER.debug(LoggerMarkers.Playback, "Resuming a playback");
state = TASstate.PLAYBACK;
- tempPause = TASstate.NONE;
+ stateAfterPause = TASstate.NONE;
return verbose ? TextFormatting.GREEN + "Resuming a playback" : "";
case RECORDING:
LOGGER.debug(LoggerMarkers.Playback, "Resuming a recording");
state = TASstate.RECORDING;
- tempPause = TASstate.NONE;
+ stateAfterPause = TASstate.NONE;
return verbose ? TextFormatting.GREEN + "Resuming a recording" : "";
case PAUSED:
return TextFormatting.RED + "Please report this message to the mod author, because you should never be able to see this (Error: Paused)";
case NONE:
LOGGER.debug(LoggerMarkers.Playback, "Aborting pausing");
state = TASstate.NONE;
- TASstate statey = tempPause;
- tempPause = TASstate.NONE;
+ TASstate statey = stateAfterPause;
+ stateAfterPause = TASstate.NONE;
return TextFormatting.GREEN + "Aborting a " + statey.toString().toLowerCase() + " that was paused";
}
}
@@ -255,9 +238,13 @@ public String setTASStateClient(TASstate stateIn, boolean verbose) {
private void startRecording() {
LOGGER.debug(LoggerMarkers.Playback, "Starting recording");
- if (this.inputs.isEmpty()) {
- inputs.add(new TickInputContainer(index));
- desyncMonitor.recordNull(index);
+ if(this.inputs.isEmpty()) {
+ VirtualCameraAngleInput CAMERA_ANGLE = TASmodClient.virtual.CAMERA_ANGLE;
+ Float pitch = CAMERA_ANGLE.getCurrentPitch();
+ Float yaw = CAMERA_ANGLE.getCurrentYaw();
+ this.camera.set(pitch, yaw);
+
+ inputs.add(new TickContainer());
}
}
@@ -270,7 +257,7 @@ private void startPlayback() {
LOGGER.debug(LoggerMarkers.Playback, "Starting playback");
Minecraft.getMinecraft().gameSettings.chatLinks = false; // #119
index = 0;
- TASmod.ktrngHandler.setInitialSeed(startSeed);
+// TASmod.ktrngHandler.setInitialSeed(startSeed);
}
private void stopPlayback() {
@@ -288,7 +275,7 @@ public TASstate togglePause() {
if (state != TASstate.PAUSED) {
setTASStateClient(TASstate.PAUSED);
} else {
- setTASStateClient(tempPause);
+ setTASStateClient(stateAfterPause);
}
return state;
}
@@ -306,7 +293,7 @@ public void pause(boolean pause) {
}
} else {
if (state == TASstate.PAUSED) {
- setTASStateClient(tempPause, false);
+ setTASStateClient(stateAfterPause, false);
}
}
}
@@ -333,6 +320,10 @@ public boolean isNothingPlaying() {
public TASstate getState() {
return state;
}
+
+ public TASstate getStateAfterPause() {
+ return stateAfterPause;
+ }
// =====================================================================================================
// Methods to update the temporary variables of the container.
@@ -390,8 +381,8 @@ public void onClientTickPost(Minecraft mc) {
EntityPlayerSP player = mc.player;
if (player != null && player.addedToChunk) {
- if (isPaused() && tempPause != TASstate.NONE) {
- setTASState(tempPause); // The recording is paused in LoadWorldEvents#startLaunchServer
+ if (isPaused() && stateAfterPause != TASstate.NONE) {
+ setTASState(stateAfterPause); // The recording is paused in LoadWorldEvents#startLaunchServer
pause(false);
EventListenerRegistry.fireEvent(EventPlaybackJoinedWorld.class, state);
}
@@ -407,15 +398,17 @@ public void onClientTickPost(Minecraft mc) {
private void recordNextTick() {
index++;
+ TickContainer container = new TickContainer(keyboard.clone(), mouse.clone(), camera.clone());
if (inputs.size() <= index) {
if (inputs.size() < index) {
LOGGER.warn("Index is {} inputs bigger than the container!", index - inputs.size());
}
- inputs.add(new TickInputContainer(index, keyboard.clone(), mouse.clone(), camera.clone()));
+ inputs.add(container);
} else {
- inputs.set(index, new TickInputContainer(index, keyboard.clone(), mouse.clone(), camera.clone()));
+ inputs.set(index, container);
}
- desyncMonitor.recordMonitor(index); // Capturing monitor values
+
+ EventListenerRegistry.fireEvent(EventRecordTick.class, index, container);
}
private void playbackNextTick() {
@@ -448,14 +441,13 @@ private void playbackNextTick() {
}
/* Continue condition */
else {
- TickInputContainer tickcontainer = inputs.get(index); // Loads the new inputs from the container
- this.keyboard = tickcontainer.getKeyboard().clone();
- this.mouse = tickcontainer.getMouse().clone();
- this.camera = tickcontainer.getCameraAngle().clone();
- // check for control bytes
- ControlByteHandler.readCotrolByte(controlBytes.get(index));
+ TickContainer container = inputs.get(index); // Loads the new inputs from the container
+ this.keyboard = container.getKeyboard().clone();
+ this.mouse = container.getMouse().clone();
+ this.camera = container.getCameraAngle().clone();
+ EventListenerRegistry.fireEvent(EventPlaybackTick.class, index, container);
}
- desyncMonitor.playMonitor(index);
+
}
// =====================================================================================================
// Methods to manipulate inputs
@@ -468,27 +460,34 @@ public boolean isEmpty() {
return inputs.isEmpty();
}
- public int index() {
+ public long index() {
return index;
}
- public BigArrayList getInputs() {
+ public BigArrayList getInputs() {
return inputs;
}
-
- public Map>> getControlBytes() { // TODO Replace with TASFile extension
- return controlBytes;
+
+ public void setInputs(BigArrayList inputs) {
+ this.setInputs(inputs, 0);
}
-
- public Map> getComments() { // TODO Replace with TASFile extension
- return comments;
+
+ public void setInputs(BigArrayList inputs, long index) {
+ try {
+ this.inputs.clearMemory();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ this.inputs = new BigArrayList(directory + File.separator + "temp");
+ SerialiserFlavorBase.addAll(this.inputs, inputs);
+ setIndex(index);
}
- public void setIndex(int index) throws IndexOutOfBoundsException {
+ public void setIndex(long index) throws IndexOutOfBoundsException {
if (index <= size()) {
this.index = index;
if (state == TASstate.PLAYBACK) {
- TickInputContainer tickcontainer = inputs.get(index);
+ TickContainer tickcontainer = inputs.get(index);
this.keyboard = tickcontainer.getKeyboard();
this.mouse = tickcontainer.getMouse();
this.camera = tickcontainer.getCameraAngle();
@@ -498,8 +497,8 @@ public void setIndex(int index) throws IndexOutOfBoundsException {
}
}
- public TickInputContainer get(int index) {
- TickInputContainer tickcontainer = null;
+ public TickContainer get(long index) {
+ TickContainer tickcontainer = null;
try {
tickcontainer = inputs.get(index);
} catch (IndexOutOfBoundsException e) {
@@ -509,20 +508,22 @@ public TickInputContainer get(int index) {
}
/**
- * @return The {@link TickInputContainer} at the current index
+ * @return The {@link TickContainer} at the current index
*/
- public TickInputContainer get() {
+ public TickContainer get() {
return get(index);
}
public void clear() {
- LOGGER.debug(LoggerMarkers.Playback, "Clearing playback controller");
- inputs = new BigArrayList(directory + File.separator + "temp");
- controlBytes.clear();
- comments.clear();
+ LOGGER.info(LoggerMarkers.Playback, "Clearing playback controller");
+ EventListenerRegistry.fireEvent(EventPlaybackClient.EventRecordClear.class);
+ try {
+ inputs.clearMemory();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ inputs = new BigArrayList(directory + File.separator + "temp");
index = 0;
- desyncMonitor.clear();
- PlaybackMetadataRegistry.handleOnClear();
}
/**
@@ -540,21 +541,15 @@ public String toString() {
return out;
}
- public void fixTicks() { // TODO Remove and use Serializer to list ticks
- for (int i = 0; i < inputs.size(); i++) {
- inputs.get(i).setTick(i + 1);
- }
- }
-
// ==============================================================
/**
* Clears {@link #keyboard} and {@link #mouse}
*/
public void unpressContainer() {
-// LOGGER.trace(LoggerMarkers.Playback, "Unpressing container");
-// keyboard.clear();
-// mouse.clear();
+ LOGGER.trace(LoggerMarkers.Playback, "Unpressing container");
+ keyboard.clear();
+ mouse.clear();
}
// ==============================================================
@@ -571,35 +566,35 @@ public void setPlayUntil(int until) {
* @author Scribble
*
*/
- public static class TickInputContainer implements Serializable {
-
- private static final long serialVersionUID = -3420565284438152474L;
-
- private int tick;
+ public static class TickContainer implements Serializable {
private VirtualKeyboard keyboard;
private VirtualMouse mouse;
- private VirtualCameraAngle subticks;
+ private VirtualCameraAngle cameraAngle;
+
+ private CommentContainer comments;
- public TickInputContainer(int tick, VirtualKeyboard keyboard, VirtualMouse mouse, VirtualCameraAngle subticks) {
- this.tick = tick;
+ public TickContainer(VirtualKeyboard keyboard, VirtualMouse mouse, VirtualCameraAngle subticks) {
+ this(keyboard, mouse, subticks, new CommentContainer());
+ }
+
+ public TickContainer(VirtualKeyboard keyboard, VirtualMouse mouse, VirtualCameraAngle camera, CommentContainer comments) {
this.keyboard = keyboard;
this.mouse = mouse;
- this.subticks = subticks;
+ this.cameraAngle = camera;
+ this.comments = comments;
}
- public TickInputContainer(int tick) {
- this.tick = tick;
- this.keyboard = new VirtualKeyboard();
- this.mouse = new VirtualMouse();
- this.subticks = new VirtualCameraAngle();
+ public TickContainer() {
+ this(new VirtualKeyboard(), new VirtualMouse(), new VirtualCameraAngle());
}
@Override
public String toString() {
- return tick + "|" + keyboard.toString() + "|" + mouse.toString() + "|" + subticks.toString();
+ String.join("\n// ", comments.inlineComments);
+ return keyboard.toString() + "|" + mouse.toString() + "|" + cameraAngle.toString() + "\t\t// " + comments.endlineComments;
}
public VirtualKeyboard getKeyboard() {
@@ -611,20 +606,103 @@ public VirtualMouse getMouse() {
}
public VirtualCameraAngle getCameraAngle() {
- return subticks;
+ return cameraAngle;
}
- public int getTick() {
- return tick;
+ public CommentContainer getComments() {
+ return comments;
+ }
+
+ @Override
+ public TickContainer clone() {
+ return new TickContainer(keyboard, mouse, cameraAngle);
}
- public void setTick(int tick) {
- this.tick = tick;
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof TickContainer) {
+ TickContainer container = (TickContainer) other;
+ return keyboard.equals(container.keyboard) && mouse.equals(container.mouse) && cameraAngle.equals(container.cameraAngle) && comments.equals(container.comments);
+ }
+ return super.equals(other);
+ }
+ }
+
+ public static class CommentContainer implements Serializable{
+
+ /**
+ * List of all inline comments in a tick.
+ * These comments take the form:
+ *
+ *
+ * // This is an inline comment
+ * // This is a second inline comment
+ * 1|W;w|;0;0;0|0.0;0.0
+ * 1|||1.0;1.0
+ *
+ *
+ * Inline comments are supposed to describe the tick as a whole and therefore
+ * can not be attached to subticks.
+ * like so:
+ *
+ *
+ * 1|W;w|;0;0;0|0.0;0.0
+ * // This is not allowed. This comment won't be saved
+ * 1|||1.0;1.0
+ *
+ */
+ private List inlineComments;
+
+ /**
+ * List of all endline comments.
+ * These comments take the form:
+ *
+ *
+ * 1|W;w|;0;0;0|0.0;0.0 // This is an endline comment
+ * 1|||1.0;1.0 // This is a second endline comment
+ *
+ *
+ * Endline comments are supposed to describe individual subticks.
+ */
+ private List endlineComments;
+
+ public CommentContainer() {
+ this(new ArrayList<>(), new ArrayList<>());
+ }
+
+ public CommentContainer(List inlineComments, List endlineComments) {
+ this.inlineComments=inlineComments;
+ this.endlineComments=endlineComments;
+ }
+
+ public void addInlineComment(String inlineComment) {
+ inlineComments.add(inlineComment);
+ }
+
+ public void addEndlineComment(String endlineComment) {
+ endlineComments.add(endlineComment);
+ }
+
+ public List getInlineComments() {
+ return inlineComments;
}
+ public List getEndlineComments() {
+ return endlineComments;
+ }
+
@Override
- public TickInputContainer clone() {
- return new TickInputContainer(tick, keyboard, mouse, subticks);
+ public boolean equals(Object obj) {
+ if(obj instanceof CommentContainer) {
+ CommentContainer other = (CommentContainer) obj;
+ return inlineComments.equals(other.inlineComments) && endlineComments.equals(other.endlineComments);
+ }
+ return super.equals(obj);
+ }
+
+ @Override
+ public String toString() {
+ return inlineComments.toString()+"\n\n"+endlineComments.toString();
}
}
@@ -688,38 +766,57 @@ public PacketID[] getAcceptedPacketIDs() {
public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception {
TASmodPackets packet = (TASmodPackets) id;
String name = null;
+ String flavor = null;
Minecraft mc = Minecraft.getMinecraft();
switch (packet) {
case PLAYBACK_SAVE:
name = TASmodBufferBuilder.readString(buf);
-// try {
-// TASmodClient.virtual.saveInputs(name); TODO Move to PlaybackController
-// } catch (IOException e) {
-// if (mc.world != null)
-// mc.ingameGUI.getChatGUI().printChatMessage(new TextComponentString(TextFormatting.RED + e.getMessage()));
-// else
-// e.printStackTrace();
-// return;
-// }
- if (mc.world != null)
- mc.ingameGUI.getChatGUI().printChatMessage(new TextComponentString(TextFormatting.GREEN + "Saved inputs to " + name + ".mctas"));
+ flavor = TASmodBufferBuilder.readString(buf);
+
+ try {
+ PlaybackSerialiser.saveToFile(new File(directory, name + ".mctas"), this, flavor);
+ } catch (PlaybackSaveException e) {
+ if (mc.world != null)
+ mc.ingameGUI.getChatGUI().printChatMessage(new TextComponentString(TextFormatting.RED + e.getMessage()));
+ LOGGER.catching(e);
+ return;
+ } catch (Exception e) {
+ if (mc.world != null)
+ mc.ingameGUI.getChatGUI().printChatMessage(new TextComponentString(TextFormatting.RED + "Saving failed, something went very wrong"));
+ LOGGER.catching(e);
+ return;
+ }
+
+ if (mc.world != null) {
+ TextComponentString confirm = new TextComponentString(TextFormatting.GREEN + "Saved inputs to " + name + ".mctas" + TextFormatting.RESET + " [" + TextFormatting.YELLOW + "Open folder" + TextFormatting.RESET + "]");
+ confirm.getStyle().setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/folder tasfiles"));
+ mc.ingameGUI.getChatGUI().printChatMessage(confirm);
+ }
else
LOGGER.debug(LoggerMarkers.Playback, "Saved inputs to " + name + ".mctas");
break;
case PLAYBACK_LOAD:
name = TASmodBufferBuilder.readString(buf);
-// try {
-// TASmodClient.virtual.loadInputs(name); TODO Move to PlaybackController
-// } catch (IOException e) {
-// if (mc.world != null)
-// mc.ingameGUI.getChatGUI().printChatMessage(new TextComponentString(TextFormatting.RED + e.getMessage()));
-// else
-// e.printStackTrace();
-// return;
-// }
+ flavor = TASmodBufferBuilder.readString(buf);
+
+ try {
+ TASmodClient.controller.setInputs(PlaybackSerialiser.loadFromFile(new File(directory, name + ".mctas"), flavor));
+ } catch (PlaybackLoadException e) {
+ if (mc.world != null) {
+ TextComponentString textComponent = new TextComponentString(e.getMessage());
+ mc.ingameGUI.getChatGUI().printChatMessage(textComponent);
+ }
+ LOGGER.catching(e);
+ return;
+ } catch (Exception e) {
+ if (mc.world != null)
+ mc.ingameGUI.getChatGUI().printChatMessage(new TextComponentString(TextFormatting.RED + "Loading failed, something went very wrong"));
+ LOGGER.catching(e);
+ }
+
if (mc.world != null)
mc.ingameGUI.getChatGUI().printChatMessage(new TextComponentString(TextFormatting.GREEN + "Loaded inputs from " + name + ".mctas"));
else
@@ -763,7 +860,7 @@ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws
e.printStackTrace();
}
Minecraft.getMinecraft().addScheduledTask(() -> {
- TASmodClient.config.set(ConfigOptions.FileToOpen, finalname);
+ TASmodClient.config.set(TASmodConfig.FileToOpen, finalname);
System.exit(0);
});
break;
@@ -810,4 +907,18 @@ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws
throw new PacketNotImplementedException(packet, this.getClass(), Side.CLIENT);
}
}
+
+ /**
+ * Runs on client initialization, used for loading the TASfile after /restartandplay
+ */
+ @Override
+ public void onClientInit(Minecraft mc) {
+ // Execute /restartandplay. Load the file to start from the config. If it exists load the playback file on start.
+ String fileOnStart = TASmodClient.config.get(TASmodConfig.FileToOpen);
+ if (fileOnStart.isEmpty()) {
+ fileOnStart = null;
+ } else {
+ TASmodClient.config.reset(TASmodConfig.FileToOpen);
+ }
+ }
}
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerServer.java b/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerServer.java
index a834fc40..889a5647 100644
--- a/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerServer.java
+++ b/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerServer.java
@@ -1,27 +1,27 @@
package com.minecrafttas.tasmod.playback;
import static com.minecrafttas.tasmod.TASmod.LOGGER;
-import static com.minecrafttas.tasmod.networking.TASmodPackets.PLAYBACK_CLEAR_INPUTS;
-import static com.minecrafttas.tasmod.networking.TASmodPackets.PLAYBACK_FULLPLAY;
-import static com.minecrafttas.tasmod.networking.TASmodPackets.PLAYBACK_FULLRECORD;
-import static com.minecrafttas.tasmod.networking.TASmodPackets.PLAYBACK_LOAD;
-import static com.minecrafttas.tasmod.networking.TASmodPackets.PLAYBACK_PLAYUNTIL;
-import static com.minecrafttas.tasmod.networking.TASmodPackets.PLAYBACK_RESTARTANDPLAY;
-import static com.minecrafttas.tasmod.networking.TASmodPackets.PLAYBACK_SAVE;
-import static com.minecrafttas.tasmod.networking.TASmodPackets.PLAYBACK_STATE;
+import static com.minecrafttas.tasmod.registries.TASmodPackets.PLAYBACK_CLEAR_INPUTS;
+import static com.minecrafttas.tasmod.registries.TASmodPackets.PLAYBACK_FULLPLAY;
+import static com.minecrafttas.tasmod.registries.TASmodPackets.PLAYBACK_FULLRECORD;
+import static com.minecrafttas.tasmod.registries.TASmodPackets.PLAYBACK_LOAD;
+import static com.minecrafttas.tasmod.registries.TASmodPackets.PLAYBACK_PLAYUNTIL;
+import static com.minecrafttas.tasmod.registries.TASmodPackets.PLAYBACK_RESTARTANDPLAY;
+import static com.minecrafttas.tasmod.registries.TASmodPackets.PLAYBACK_SAVE;
+import static com.minecrafttas.tasmod.registries.TASmodPackets.PLAYBACK_STATE;
import static com.minecrafttas.tasmod.util.LoggerMarkers.Playback;
import java.nio.ByteBuffer;
-import com.minecrafttas.mctcommon.server.Client.Side;
-import com.minecrafttas.mctcommon.server.exception.PacketNotImplementedException;
-import com.minecrafttas.mctcommon.server.exception.WrongSideException;
-import com.minecrafttas.mctcommon.server.interfaces.PacketID;
-import com.minecrafttas.mctcommon.server.interfaces.ServerPacketHandler;
+import com.minecrafttas.mctcommon.networking.Client.Side;
+import com.minecrafttas.mctcommon.networking.exception.PacketNotImplementedException;
+import com.minecrafttas.mctcommon.networking.exception.WrongSideException;
+import com.minecrafttas.mctcommon.networking.interfaces.PacketID;
+import com.minecrafttas.mctcommon.networking.interfaces.ServerPacketHandler;
import com.minecrafttas.tasmod.TASmod;
import com.minecrafttas.tasmod.networking.TASmodBufferBuilder;
-import com.minecrafttas.tasmod.networking.TASmodPackets;
import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate;
+import com.minecrafttas.tasmod.registries.TASmodPackets;
/**
* The playback controller on the server side.
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommand.java b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommand.java
new file mode 100644
index 00000000..9aed7f28
--- /dev/null
+++ b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommand.java
@@ -0,0 +1,207 @@
+package com.minecrafttas.tasmod.playback.filecommands;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.minecrafttas.mctcommon.registry.Registerable;
+import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TickContainer;
+
+public class PlaybackFileCommand {
+
+ private String name;
+
+ private String[] args;
+
+ public PlaybackFileCommand(String name) {
+ this(name, (String[]) null);
+ }
+
+ public PlaybackFileCommand(String name, String... args) {
+ if (args == null) {
+ args = new String[] {};
+ }
+ this.name = name;
+ this.args = args;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String[] getArgs() {
+ return args;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof PlaybackFileCommand) {
+ PlaybackFileCommand other = (PlaybackFileCommand) obj;
+ return this.name.equals(other.name) && Arrays.equals(this.args, other.args);
+ }
+ return super.equals(obj);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("$%s(%s);", name, String.join(", ", args));
+ }
+
+ public static abstract class PlaybackFileCommandExtension implements Registerable{
+
+ protected boolean enabled = false;
+
+ public abstract String[] getFileCommandNames();
+
+ public void onEnable() {
+ };
+
+ public void onDisable() {
+ };
+
+ public void onClear() {
+ };
+
+ public void onRecord(long tick, TickContainer tickContainer) {
+ };
+
+ public void onPlayback(long tick, TickContainer tickContainer) {
+ };
+
+ public PlaybackFileCommandContainer onSerialiseInlineComment(long tick, TickContainer tickContainer) {
+ return null;
+ }
+
+ public PlaybackFileCommandContainer onSerialiseEndlineComment(long currentTick, TickContainer tickContainer) {
+ return null;
+ }
+
+ public void onDeserialiseInlineComment(long tick, TickContainer container, PlaybackFileCommandContainer fileCommandContainer) {
+ }
+
+ public void onDeserialiseEndlineComment(long tick, TickContainer container, PlaybackFileCommandContainer fileCommandContainer) {
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ if (enabled)
+ onEnable();
+ else
+ onDisable();
+ this.enabled = enabled;
+ }
+
+ @Override
+ public String toString() {
+ return getExtensionName();
+ }
+ }
+
+ public static class PlaybackFileCommandContainer extends LinkedHashMap {
+
+ public PlaybackFileCommandContainer() {
+ }
+
+ public PlaybackFileCommandContainer(List> list) {
+ for (List lists : list) {
+ if (lists != null) {
+ for (PlaybackFileCommand command : lists) {
+ this.put(command.getName(), new PlaybackFileCommandLine());
+ }
+ }
+ }
+
+ for (List lists : list) {
+ for (Map.Entry entry : this.entrySet()) {
+ String key = entry.getKey();
+ List val = entry.getValue();
+
+ boolean valuePresent = false;
+ if (lists != null) {
+ for (PlaybackFileCommand command : lists) {
+ if (key.equals(command.getName())) {
+ valuePresent = true;
+ val.add(command);
+ }
+ }
+ }
+ if (!valuePresent) {
+ val.add(null);
+ }
+ }
+ }
+ }
+
+ public void add(String key, PlaybackFileCommand fileCommand) {
+ PlaybackFileCommandLine toAdd = getOrDefault(key, new PlaybackFileCommandLine());
+ if (toAdd.isEmpty()) {
+ put(key, toAdd);
+ }
+
+ toAdd.add(fileCommand);
+ }
+
+ public PlaybackFileCommandContainer split(String... keys) {
+ return split(Arrays.asList(keys));
+ }
+
+ public PlaybackFileCommandContainer split(Iterable keys) {
+ PlaybackFileCommandContainer out = new PlaybackFileCommandContainer();
+ for (String key : keys) {
+ out.put(key, this.get(key));
+ }
+ return out;
+ }
+
+ public List> valuesBySubtick() {
+ List> out = new ArrayList<>();
+
+ int biggestSize = 0;
+ for (PlaybackFileCommandLine list : values()) {
+ if (list.size() > biggestSize) {
+ biggestSize = list.size();
+ }
+ }
+
+ for (int i = 0; i < biggestSize; i++) {
+ List commandListForOneLine = new ArrayList<>();
+ for (PlaybackFileCommandLine list : values()) {
+ if (i < list.size()) {
+ PlaybackFileCommand fc = list.get(i);
+ commandListForOneLine.add(fc);
+ } else {
+ commandListForOneLine.add(null);
+ }
+ }
+ out.add(commandListForOneLine);
+ }
+
+ return out;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof PlaybackFileCommandContainer) {
+ PlaybackFileCommandContainer other = (PlaybackFileCommandContainer) o;
+ for (java.util.Map.Entry entry : other.entrySet()) {
+ String key = entry.getKey();
+ PlaybackFileCommandLine val = entry.getValue();
+
+ if (!this.containsKey(key) && !this.get(key).equals(val))
+ return false;
+ }
+ return true;
+ }
+ return super.equals(o);
+ }
+ }
+
+ public static class PlaybackFileCommandLine extends ArrayList {
+
+ }
+}
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommandsRegistry.java b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommandsRegistry.java
new file mode 100644
index 00000000..d70e4f5e
--- /dev/null
+++ b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommandsRegistry.java
@@ -0,0 +1,179 @@
+package com.minecrafttas.tasmod.playback.filecommands;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import com.minecrafttas.mctcommon.Configuration;
+import com.minecrafttas.mctcommon.registry.AbstractRegistry;
+import com.minecrafttas.tasmod.events.EventPlaybackClient;
+import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TickContainer;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandContainer;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandExtension;
+import com.minecrafttas.tasmod.registries.TASmodConfig;
+
+public class PlaybackFileCommandsRegistry extends AbstractRegistry implements EventPlaybackClient.EventRecordTick, EventPlaybackClient.EventPlaybackTick, EventPlaybackClient.EventRecordClear {
+
+ private List enabledExtensions = new ArrayList<>();
+
+ private Configuration config = null;
+
+ public PlaybackFileCommandsRegistry() {
+ super("FILECOMMAND_REGISTRY", new LinkedHashMap<>());
+ }
+
+ @Override
+ public void register(PlaybackFileCommandExtension extension) {
+ super.register(extension);
+ enabledExtensions = getEnabled();
+ }
+
+ @Override
+ public void unregister(PlaybackFileCommandExtension extension) {
+ super.unregister(extension);
+ enabledExtensions = getEnabled();
+ }
+
+ public boolean setEnabled(String extensionName, boolean enabled) {
+ return setEnabled(extensionName, enabled, true);
+ }
+
+ public boolean setEnabled(String extensionName, boolean enabled, boolean saveToConfig) {
+ PlaybackFileCommandExtension extension = REGISTRY.get(extensionName);
+ if(extension == null) {
+ return false;
+ }
+ extension.setEnabled(enabled);
+ enabledExtensions = getEnabled();
+
+ if(saveToConfig) {
+ saveConfig();
+ }
+ return true;
+ }
+
+ private void disableAll() {
+ REGISTRY.forEach((name, value) -> {
+ value.setEnabled(false);
+ });
+ }
+
+ public void setEnabled(List extensionNames) {
+ setEnabled(extensionNames, false);
+ }
+
+ public void setEnabled(List extensionNames, boolean saveToConfig) {
+ disableAll();
+ for (String name : extensionNames) {
+ setEnabled(name, true, false);
+ }
+ if(saveToConfig)
+ saveConfig();
+ }
+
+ public List getEnabled() {
+ List out = new ArrayList<>();
+
+ for (PlaybackFileCommandExtension element : REGISTRY.values()) {
+ if (element.isEnabled()) {
+ out.add(element);
+ }
+ }
+
+ return out;
+ }
+
+ public List getAll(){
+ return new ArrayList<>(REGISTRY.values());
+ }
+
+ @Override
+ public void onRecordTick(long index, TickContainer container) {
+ enabledExtensions.forEach(extension -> {
+ if(extension.isEnabled()) {
+ extension.onRecord(index, container);
+ }
+ });
+ }
+
+ @Override
+ public void onPlaybackTick(long index, TickContainer container) {
+ enabledExtensions.forEach(extension -> {
+ if(extension.isEnabled()) {
+ extension.onPlayback(index, container);
+ }
+ });
+ }
+
+ public PlaybackFileCommandContainer handleOnSerialiseInline(long currentTick, TickContainer container) {
+ PlaybackFileCommandContainer out = new PlaybackFileCommandContainer();
+ for (PlaybackFileCommandExtension extension : enabledExtensions) {
+ PlaybackFileCommandContainer extensionContainer=extension.onSerialiseInlineComment(currentTick, container);
+ if(extensionContainer!=null) {
+ out.putAll(extensionContainer);
+ }
+ }
+ return out;
+ }
+
+ public PlaybackFileCommandContainer handleOnSerialiseEndline(long currentTick, TickContainer container) {
+ PlaybackFileCommandContainer out = new PlaybackFileCommandContainer();
+ for (PlaybackFileCommandExtension extension : enabledExtensions) {
+ PlaybackFileCommandContainer extensionContainer=extension.onSerialiseEndlineComment(currentTick, container);
+ if(extensionContainer!=null) {
+ out.putAll(extensionContainer);
+ }
+ }
+ return out;
+ }
+
+ public void handleOnDeserialiseInline(long currentTick, TickContainer deserialisedContainer, List> inlineFileCommands) {
+ PlaybackFileCommandContainer fileCommandContainer = new PlaybackFileCommandContainer(inlineFileCommands);
+ for (PlaybackFileCommandExtension extension : enabledExtensions) {
+ String[] fileCommandNames = extension.getFileCommandNames();
+ extension.onDeserialiseInlineComment(currentTick, deserialisedContainer, fileCommandContainer.split(fileCommandNames));
+ }
+ }
+
+ public void handleOnDeserialiseEndline(long currentTick, TickContainer deserialisedContainer, List> endlineFileCommands) {
+ PlaybackFileCommandContainer fileCommandContainer = new PlaybackFileCommandContainer(endlineFileCommands);
+ for (PlaybackFileCommandExtension extension : enabledExtensions) {
+ String[] fileCommandNames = extension.getFileCommandNames();
+ extension.onDeserialiseEndlineComment(currentTick, deserialisedContainer, fileCommandContainer.split(fileCommandNames));
+ }
+ }
+
+ @Override
+ public void onClear() {
+ REGISTRY.values().forEach(fc -> {
+ fc.onClear();
+ });
+ }
+
+ public void setConfig(Configuration config) {
+ this.config = config;
+ loadConfig();
+ }
+
+ private void loadConfig() {
+ if (config == null) {
+ return;
+ }
+ String enabled = config.get(TASmodConfig.EnabledFileCommands);
+ setEnabled(Arrays.asList(enabled.split(", ")));
+ }
+
+ private void saveConfig() {
+ if (config == null) {
+ return;
+ }
+ List nameList = new ArrayList<>();
+
+ enabledExtensions.forEach(element ->{
+ nameList.add(element.getExtensionName());
+ });
+ config.set(TASmodConfig.EnabledFileCommands, String.join(", ", nameList));
+ config.save();
+ }
+}
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/DesyncMonitorFileCommandExtension.java b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/DesyncMonitorFileCommandExtension.java
new file mode 100644
index 00000000..a89fef45
--- /dev/null
+++ b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/DesyncMonitorFileCommandExtension.java
@@ -0,0 +1,353 @@
+package com.minecrafttas.tasmod.playback.filecommands.integrated;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.List;
+import java.util.Locale;
+
+import com.dselent.bigarraylist.BigArrayList;
+import com.minecrafttas.tasmod.TASmodClient;
+import com.minecrafttas.tasmod.events.EventPlaybackClient;
+import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate;
+import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TickContainer;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandContainer;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandExtension;
+import com.minecrafttas.tasmod.playback.tasfile.exception.PlaybackLoadException;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.entity.EntityPlayerSP;
+import net.minecraft.util.text.TextFormatting;
+
+/**
+ * Stores the players position during recording and compares it with the
+ * position during playback
+ *
+ * @author Scribble
+ */
+public class DesyncMonitorFileCommandExtension extends PlaybackFileCommandExtension implements EventPlaybackClient.EventControllerStateChange {
+
+ private File tempDir = new File(Minecraft.getMinecraft().mcDataDir.getAbsolutePath() + File.separator + "saves" + File.separator + "tasfiles" + File.separator + "temp" + File.separator + "monitoring");
+
+ private BigArrayList monitorContainer = new BigArrayList(tempDir.toString());
+
+ private MonitorContainer currentValues;
+
+ public DesyncMonitorFileCommandExtension() {
+ enabled = true;
+ }
+
+ @Override
+ public String getExtensionName() {
+ return "tasmod_desyncMonitor@v1";
+ }
+
+ @Override
+ public String[] getFileCommandNames() {
+ return new String[] { "desyncMonitor" };
+ }
+
+ @Override
+ public void onControllerStateChange(TASstate newstate, TASstate oldstate) {
+ if(newstate==TASstate.RECORDING && monitorContainer.isEmpty()) {
+ recordNull(0);
+ }
+ }
+
+ @Override
+ public void onRecord(long tick, TickContainer tickContainer) {
+ EntityPlayerSP player = Minecraft.getMinecraft().player;
+ MonitorContainer values = null;
+ if (player != null) {
+ values = new MonitorContainer(tick, player.posX, player.posY, player.posZ, player.motionX, player.motionY, player.motionZ);
+ } else {
+ values = new MonitorContainer(tick);
+ }
+
+ if (monitorContainer.size() <= tick) {
+ monitorContainer.add(values);
+ } else {
+ monitorContainer.set(tick, values);
+ }
+ }
+
+ @Override
+ public void onDisable() {
+ this.onClear();
+ }
+
+ @Override
+ public PlaybackFileCommandContainer onSerialiseEndlineComment(long currentTick, TickContainer tickContainer) {
+ PlaybackFileCommandContainer out = new PlaybackFileCommandContainer();
+ MonitorContainer monitoredValues = monitorContainer.get(currentTick);
+ PlaybackFileCommand command = new PlaybackFileCommand("desyncMonitor", monitoredValues.toStringArray());
+
+ out.add("desyncMonitor", command);
+
+ return out;
+ }
+
+ @Override
+ public void onDeserialiseEndlineComment(long tick, TickContainer container, PlaybackFileCommandContainer fileCommandContainer) {
+ List commandsEndline = fileCommandContainer.get("desyncMonitor");
+ if (commandsEndline == null || commandsEndline.isEmpty()) {
+ recordNull(tick);
+ return;
+ }
+
+ PlaybackFileCommand command = commandsEndline.get(0);
+ this.monitorContainer.add(loadFromFile(tick, command.getArgs()));
+ }
+
+ public void recordNull(long tick) {
+ if (monitorContainer.size() <= tick) {
+ monitorContainer.add(new MonitorContainer(tick));
+ } else {
+ monitorContainer.set(tick, new MonitorContainer(tick));
+ }
+ }
+
+ @Override
+ public void onPlayback(long tick, TickContainer tickContainer) {
+ currentValues = get(tick - 1);
+ }
+
+ private MonitorContainer loadFromFile(long tick, String[] args) throws PlaybackLoadException {
+
+ if (args.length != 6)
+ throw new PlaybackLoadException("Tick %s: desyncMonitorArgsLength ");
+
+ double x = 0;
+ double y = 0;
+ double z = 0;
+ double mx = 0;
+ double my = 0;
+ double mz = 0;
+ try {
+ x = parseDouble(args[0]);
+ y = parseDouble(args[1]);
+ z = parseDouble(args[2]);
+ mx = parseDouble(args[3]);
+ my = parseDouble(args[4]);
+ mz = parseDouble(args[5]);
+ } catch (ParseException e) {
+ throw new PlaybackLoadException(e);
+ }
+
+ return new MonitorContainer(tick, x, y, z, mx, my, mz);
+ }
+
+ public MonitorContainer get(long l) {
+ try {
+ return monitorContainer.get(l);
+ } catch (IndexOutOfBoundsException e) {
+ return null;
+ }
+ }
+
+ private String lastStatus = TextFormatting.GRAY + "Empty";
+
+ public String getStatus(EntityPlayerSP player) {
+ if (!TASmodClient.controller.isNothingPlaying()) {
+ if (currentValues != null) {
+ double[] playervalues = new double[6];
+ playervalues[0] = player.posX;
+ playervalues[1] = player.posY;
+ playervalues[2] = player.posZ;
+ playervalues[3] = player.motionX;
+ playervalues[4] = player.motionY;
+ playervalues[5] = player.motionZ;
+ DesyncStatus status = currentValues.getSeverity(TASmodClient.controller.index(), playervalues);
+ lastStatus = status.getFormat() + status.getText();
+ } else {
+ lastStatus = TextFormatting.GRAY + "Empty";
+ }
+ }
+ return lastStatus;
+ }
+
+ private String lastPos = "";
+
+ public String getPos() {
+ if (currentValues != null && !TASmodClient.controller.isNothingPlaying()) {
+ EntityPlayerSP player = Minecraft.getMinecraft().player;
+ String[] values = new String[3];
+ values[0] = getFormattedString(player.posX - currentValues.values[0]);
+ values[1] = getFormattedString(player.posY - currentValues.values[1]);
+ values[2] = getFormattedString(player.posZ - currentValues.values[2]);
+
+ String out = "";
+ for (String val : values) {
+ if (val != null) {
+ out += val + " ";
+ }
+ }
+ lastPos = out;
+ }
+ return lastPos;
+ }
+
+ private String lastMotion = "";
+
+ public String getMotion() {
+ if (currentValues != null && !TASmodClient.controller.isNothingPlaying()) {
+ EntityPlayerSP player = Minecraft.getMinecraft().player;
+ String[] values = new String[3];
+ values[0] = getFormattedString(player.motionX - currentValues.values[3]);
+ values[1] = getFormattedString(player.motionY - currentValues.values[4]);
+ values[2] = getFormattedString(player.motionZ - currentValues.values[5]);
+
+ String out = "";
+ for (String val : values) {
+ if (val != null) {
+ out += val + " ";
+ }
+ }
+ lastMotion = out;
+ }
+ return lastMotion;
+ }
+
+ private String getFormattedString(double delta) {
+ String out = "";
+ if (delta != 0D) {
+ DesyncStatus status = DesyncStatus.fromDelta(delta);
+ if (status == DesyncStatus.EQUAL) {
+ return "";
+ }
+ out = status.getFormat() + Double.toString(delta);
+ }
+ return out;
+ }
+
+ public class MonitorContainer implements Serializable {
+ private static final long serialVersionUID = -3138791930493647885L;
+
+ long index;
+
+ double[] values = new double[6];
+
+ public MonitorContainer(long index, double posx, double posy, double posz, double velx, double vely, double velz) {
+ this.index = index;
+ this.values[0] = posx;
+ this.values[1] = posy;
+ this.values[2] = posz;
+ this.values[3] = velx;
+ this.values[4] = vely;
+ this.values[5] = velz;
+ }
+
+ public MonitorContainer(long index) {
+ this(index, 0, 0, 0, 0, 0, 0);
+ }
+
+ public String[] toStringArray() {
+ String[] out = new String[values.length];
+ for (int i = 0; i < values.length; i++) {
+ out[i] = String.format(Locale.ENGLISH, "%.5f", values[i]);
+ }
+ return out;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(Locale.US, "%d, %d, %d, %d, %d, %d", values[0], values[1], values[2], values[3], values[4], values[5]);
+ }
+
+ public DesyncStatus getSeverity(long index, double[] playerValues) {
+
+ DesyncStatus out = null;
+
+ for (int i = 0; i < values.length; i++) {
+ double delta = 0;
+ try {
+ delta = playerValues[i] - values[i];
+ } catch (Exception e) {
+ return DesyncStatus.ERROR;
+ }
+ DesyncStatus status = DesyncStatus.fromDelta(delta);
+ if (out == null || status.getSeverity() > out.getSeverity()) {
+ out = status;
+ }
+ }
+
+ return out;
+ }
+ }
+
+ public enum DesyncStatus {
+ EQUAL(0, TextFormatting.GREEN, "In sync", 0D),
+ WARNING(1, TextFormatting.YELLOW, "Slight desync", 0.00001D),
+ MODERATE(2, TextFormatting.RED, "Moderate desync", 0.01D),
+ TOTAL(3, TextFormatting.DARK_RED, "Total desync"),
+ ERROR(3, TextFormatting.DARK_PURPLE, "ERROR");
+
+ private Double tolerance;
+ private int severity;
+ private String text;
+ private TextFormatting format;
+
+ private DesyncStatus(int severity, TextFormatting color, String text) {
+ this.severity = severity;
+ this.format = color;
+ this.text = text;
+ tolerance = null;
+ }
+
+ private DesyncStatus(int severity, TextFormatting color, String text, double tolerance) {
+ this(severity, color, text);
+ this.tolerance = tolerance;
+ }
+
+ public static DesyncStatus fromDelta(double delta) {
+ DesyncStatus out = TOTAL;
+ for (DesyncStatus status : values()) {
+ if (status.tolerance == null) {
+ return status;
+ }
+ if (Math.abs(delta) < status.tolerance) {
+ break;
+ }
+ if (Math.abs(delta) >= status.tolerance) {
+ out = status;
+ }
+ }
+ return out;
+ }
+
+ public TextFormatting getFormat() {
+ return format;
+ }
+
+ public int getSeverity() {
+ return severity;
+ }
+
+ public String getText() {
+ return text;
+ }
+ }
+
+ private double parseDouble(String doublestring) throws ParseException {
+ NumberFormat format = NumberFormat.getInstance(Locale.ENGLISH);
+ Number number = format.parse(doublestring);
+ return number.doubleValue();
+ }
+
+ @Override
+ public void onClear() {
+ currentValues = null;
+ try {
+ monitorContainer.clearMemory();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ monitorContainer = new BigArrayList(tempDir.toString());
+ lastStatus = TextFormatting.GRAY + "Empty";
+ lastPos = "";
+ lastMotion = "";
+ }
+}
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/LabelFileCommandExtension.java b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/LabelFileCommandExtension.java
new file mode 100644
index 00000000..e0e11548
--- /dev/null
+++ b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/LabelFileCommandExtension.java
@@ -0,0 +1,71 @@
+package com.minecrafttas.tasmod.playback.filecommands.integrated;
+
+import java.io.IOException;
+
+import com.dselent.bigarraylist.BigArrayList;
+import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TickContainer;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandContainer;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandExtension;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandLine;
+
+public class LabelFileCommandExtension extends PlaybackFileCommandExtension {
+
+ private String labelText = "";
+
+ BigArrayList label = new BigArrayList<>();
+
+ public LabelFileCommandExtension() {
+ enabled = true;
+ }
+
+ @Override
+ public String getExtensionName() {
+ return "tasmod_label@v1";
+ }
+
+ @Override
+ public String[] getFileCommandNames() {
+ return new String[] { "label" };
+ }
+
+ @Override
+ public void onDeserialiseInlineComment(long tick, TickContainer container, PlaybackFileCommandContainer fileCommandContainer) {
+ if (fileCommandContainer.containsKey("label")) {
+ label.add(fileCommandContainer.split("label"));
+ }
+ }
+
+ @Override
+ public void onPlayback(long tick, TickContainer tickContainer) {
+ PlaybackFileCommandContainer containerInTick = label.get(tick - 1);
+ if (containerInTick == null) {
+ return;
+ }
+
+ PlaybackFileCommandLine line = containerInTick.get("label");
+ if (line == null) {
+ return;
+ }
+
+ for (PlaybackFileCommand command : line) {
+ labelText = String.join(", ", command.getArgs());
+ }
+ }
+
+ @Override
+ public void onClear() {
+ try {
+ label.clearMemory();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ label = new BigArrayList<>();
+ labelText = "";
+ }
+
+ public String getLabelText() {
+ return labelText;
+ }
+}
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/OptionsFileCommandExtension.java b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/OptionsFileCommandExtension.java
new file mode 100644
index 00000000..af0390ee
--- /dev/null
+++ b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/OptionsFileCommandExtension.java
@@ -0,0 +1,90 @@
+package com.minecrafttas.tasmod.playback.filecommands.integrated;
+
+import java.io.IOException;
+
+import com.dselent.bigarraylist.BigArrayList;
+import com.minecrafttas.tasmod.TASmod;
+import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TickContainer;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandContainer;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandExtension;
+import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandLine;
+import com.minecrafttas.tasmod.util.LoggerMarkers;
+
+public class OptionsFileCommandExtension extends PlaybackFileCommandExtension {
+
+ private boolean shouldRenderHud = true;
+
+ BigArrayList hud = new BigArrayList<>();
+
+ public OptionsFileCommandExtension() {
+ enabled = true;
+ }
+
+ @Override
+ public String getExtensionName() {
+ return "tasmod_options@v1";
+ }
+
+ @Override
+ public String[] getFileCommandNames() {
+ return new String[] { "hud" };
+ }
+
+ @Override
+ public void onDeserialiseInlineComment(long tick, TickContainer container, PlaybackFileCommandContainer fileCommandContainer) {
+ if (fileCommandContainer.containsKey("hud")) {
+ hud.add(fileCommandContainer.split("hud"));
+ }
+ }
+
+ @Override
+ public void onPlayback(long tick, TickContainer tickContainer) {
+ PlaybackFileCommandContainer containerInTick = hud.get(tick);
+ if(containerInTick == null) {
+ return;
+ }
+
+ PlaybackFileCommandLine line = containerInTick.get("hud");
+ if(line == null) {
+ return;
+ }
+
+ for (PlaybackFileCommand command : line) {
+ String[] args = command.getArgs();
+ if (args.length == 1) {
+ switch (args[0]) {
+ case "true":
+ shouldRenderHud = true;
+ break;
+
+ case "false":
+ shouldRenderHud = false;
+ break;
+
+ default:
+ TASmod.LOGGER.warn(LoggerMarkers.Playback, "FileCommand hud has the wrong argument in tick {}: {} (Must be true or false)", tick, args[0]);
+ break;
+ }
+ } else {
+ TASmod.LOGGER.warn(LoggerMarkers.Playback, "FileCommand hud has the wrong number of arguments in tick {}: {}", tick, args.length);
+ }
+ }
+ }
+
+ @Override
+ public void onClear() {
+ try {
+ hud.clearMemory();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ hud = new BigArrayList<>();
+ shouldRenderHud = true;
+ }
+
+ public boolean shouldRenderHud() {
+ return shouldRenderHud;
+ }
+}
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/metadata/PlaybackMetadata.java b/src/main/java/com/minecrafttas/tasmod/playback/metadata/PlaybackMetadata.java
index c2a00c47..d20e2c4a 100644
--- a/src/main/java/com/minecrafttas/tasmod/playback/metadata/PlaybackMetadata.java
+++ b/src/main/java/com/minecrafttas/tasmod/playback/metadata/PlaybackMetadata.java
@@ -1,21 +1,21 @@
package com.minecrafttas.tasmod.playback.metadata;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import com.minecrafttas.tasmod.playback.metadata.PlaybackMetadataRegistry.PlaybackMetadataExtension;
+import com.minecrafttas.mctcommon.registry.Registerable;
/**
* Stores a section of
*
*/
public class PlaybackMetadata {
+
private String extensionName;
- private LinkedHashMap metadata;
+ private LinkedHashMap data;
private static String SEPERATOR = ":";
@@ -25,24 +25,29 @@ public PlaybackMetadata(PlaybackMetadataExtension extension) {
private PlaybackMetadata(String extensionName) {
this.extensionName = extensionName;
- this.metadata = new LinkedHashMap();
+ this.data = new LinkedHashMap();
+ }
+
+ private PlaybackMetadata(String extensionName, LinkedHashMap data) {
+ this.extensionName = extensionName;
+ this.data = data;
}
public void setValue(String key, String value) {
if (key.contains(SEPERATOR)) {
throw new IllegalArgumentException(String.format("%sKeyname %s can't contain %s", extensionName != null ? extensionName + ": " : "", key, SEPERATOR));
}
- metadata.put(key, value);
+ data.put(key, value);
}
public String getValue(String key) {
- return metadata.get(key);
+ return data.get(key);
}
@Override
public String toString() {
String out = "";
- for (String key : metadata.keySet()) {
+ for (String key : data.keySet()) {
String value = getValue(key);
out += (String.format("%s%s%s\n", key, SEPERATOR, value));
}
@@ -51,7 +56,7 @@ public String toString() {
public List toStringList() {
List out = new ArrayList<>();
- for (Object keyObj : metadata.keySet()) {
+ for (Object keyObj : data.keySet()) {
String key = (String) keyObj;
String value = getValue(key);
out.add(String.format("%s%s%s\n", key, SEPERATOR, value));
@@ -63,15 +68,15 @@ public String getExtensionName() {
return extensionName;
}
- public HashMap getMetadata() {
- return metadata;
+ public LinkedHashMap getData() {
+ return data;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof PlaybackMetadata) {
PlaybackMetadata other = (PlaybackMetadata) obj;
- return other.metadata.equals(metadata) && other.extensionName.equals(extensionName);
+ return other.data.equals(data) && other.extensionName.equals(extensionName);
}
return super.equals(obj);
}
@@ -92,4 +97,38 @@ public static PlaybackMetadata fromStringList(String extensionName, List
return out;
}
+
+ public static PlaybackMetadata fromHashMap(String extensionName, LinkedHashMap data) {
+ return new PlaybackMetadata(extensionName, new LinkedHashMap<>(data));
+ }
+
+ public static abstract class PlaybackMetadataExtension implements Registerable {
+
+ /**
+ * Currently unused.
+ * Maybe in the future, TASes have to be created with /create, then you can interactively set the values...
+ */
+ public void onCreate() {};
+
+ /**
+ * Runs, when the TASfile is being stored to a file.
+ * Create a new {@link PlaybackMetadata} with PlaybackMetadata metadata = new PlaybackMetadata(this);
.
+ * This will ensure, that the metadata is linked to this extension by using the {@link PlaybackMetadataExtension#getExtensionName()}.
+ *
+ * @return The {@link PlaybackMetadata} to be saved in the TASfile
+ */
+ public abstract PlaybackMetadata onStore();
+
+ /**
+ * Runs when the TASfile is being loaded from a file
+ *
+ * @param metadata The metadata for this extension to read from
+ */
+ public abstract void onLoad(PlaybackMetadata metadata);
+
+ /**
+ * Runs when the PlaybackController is cleared
+ */
+ public abstract void onClear();
+ }
}
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/metadata/PlaybackMetadataRegistry.java b/src/main/java/com/minecrafttas/tasmod/playback/metadata/PlaybackMetadataRegistry.java
index 4c604261..658638f4 100644
--- a/src/main/java/com/minecrafttas/tasmod/playback/metadata/PlaybackMetadataRegistry.java
+++ b/src/main/java/com/minecrafttas/tasmod/playback/metadata/PlaybackMetadataRegistry.java
@@ -3,131 +3,58 @@
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
+import com.minecrafttas.mctcommon.registry.AbstractRegistry;
import com.minecrafttas.tasmod.TASmod;
+import com.minecrafttas.tasmod.events.EventPlaybackClient;
+import com.minecrafttas.tasmod.playback.metadata.PlaybackMetadata.PlaybackMetadataExtension;
/**
- * Registry for registering custom metadata that is stored in the TASFile.
+ * Registry for registering custom metadata that is stored in the TASfile.
*
* The default metadata includes general information such as author name,
* savestate/rerecord count and category.
*
* Any custom class has to extend PlaybackMetadataExtension
*
+ * @author Scribble
*/
-public class PlaybackMetadataRegistry {
+public class PlaybackMetadataRegistry extends AbstractRegistry implements EventPlaybackClient.EventRecordClear {
- private static final Map METADATA_EXTENSION = new LinkedHashMap<>();
-
- /**
- * Registers a new class as a metadata extension
- *
- * @param extension
- */
- public static void register(PlaybackMetadataExtension extension) {
- if (extension == null) {
- throw new NullPointerException("Tried to register a playback extension with value null");
- }
-
- if (containsClass(extension)) {
- TASmod.LOGGER.warn("Trying to register the playback extension {}, but another instance of this class is already registered!", extension.getClass().getName());
- return;
- }
-
- if(METADATA_EXTENSION.containsKey(extension.getExtensionName())) {
- TASmod.LOGGER.warn("Trying to register the playback extension {}, but an extension with the same name is already registered!", extension.getExtensionName());
- return;
- }
-
- METADATA_EXTENSION.put(extension.getExtensionName(), extension);
- }
-
- public static void unregister(PlaybackMetadataExtension extension) {
- if (extension == null) {
- throw new NullPointerException("Tried to unregister an extension with value null");
- }
- if (METADATA_EXTENSION.containsKey(extension.getExtensionName())) {
- METADATA_EXTENSION.remove(extension.getExtensionName());
- } else {
- TASmod.LOGGER.warn("Trying to unregister the playback extension {}, but it was not registered!", extension.getClass().getName());
- }
+ public PlaybackMetadataRegistry() {
+ super("METADATA_REGISTRY", new LinkedHashMap<>());
}
public static void handleOnCreate() {
}
- public static List handleOnStore() {
+ public List handleOnStore() {
List metadataList = new ArrayList<>();
- for(PlaybackMetadataExtension extension : METADATA_EXTENSION.values()) {
+ for(PlaybackMetadataExtension extension : REGISTRY.values()) {
metadataList.add(extension.onStore());
}
return metadataList;
}
- public static void handleOnLoad(List meta) {
+ public void handleOnLoad(List meta) {
+ if(meta.isEmpty())
+ return;
for(PlaybackMetadata metadata : meta) {
- if(METADATA_EXTENSION.containsKey(metadata.getExtensionName())) {
- PlaybackMetadataExtension extension = METADATA_EXTENSION.get(metadata.getExtensionName());
+ if(REGISTRY.containsKey(metadata.getExtensionName())) {
+ PlaybackMetadataExtension extension = REGISTRY.get(metadata.getExtensionName());
extension.onLoad(metadata);
} else {
- TASmod.LOGGER.warn("The metadata extension {} was not found while loading the TASFile. Things might not be correctly loaded!", metadata.getExtensionName());
+ TASmod.LOGGER.warn("The metadata extension {} was not found while loading the TASfile. Things might not be correctly loaded!", metadata.getExtensionName());
}
}
}
- public static void handleOnClear() {
- METADATA_EXTENSION.forEach((key, extension) ->{
+ @Override
+ public void onClear() {
+ REGISTRY.forEach((key, extension) ->{
extension.onClear();
});
}
-
- private static boolean containsClass(PlaybackMetadataExtension newExtension) {
- for (PlaybackMetadataExtension extension : METADATA_EXTENSION.values()) {
- if (extension.getClass().equals(newExtension.getClass())) {
- return true;
- }
- }
- return false;
- }
-
- public static interface PlaybackMetadataExtension {
-
- /**
- * The name of this playback metadata extension.
- * The name is printed in the playback file and declares this "section".
- * It is also used in the {@link PlaybackMetadata} itself to link the metadata to the extension.
- * @return The name of this playback metadata extension.
- */
- public String getExtensionName();
-
- /**
- * Currently unused.
- * Maybe in the future, TASes have to be created with /create, then you can interactively set the values...
- */
- public void onCreate();
-
- /**
- * Runs, when the TASfile is being stored to a file.
- * Create a new {@link PlaybackMetadata} with PlaybackMetadata metadata = new PlaybackMetadata(this);
.
- * This will ensure, that the metadata is linked to this extension by using the {@link PlaybackMetadataExtension#getExtensionName()}.
- *
- * @return The {@link PlaybackMetadata} to be saved in the TASfile
- */
- public PlaybackMetadata onStore();
-
- /**
- * Runs when the TASfile is being loaded from a file
- *
- * @param metadata The metadata for this extension to read from
- */
- public void onLoad(PlaybackMetadata metadata);
-
- /**
- * Runs when the PlaybackController is cleared
- */
- public void onClear();
- }
}
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/CreditsMetadataExtension.java b/src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/CreditsMetadataExtension.java
index ddeb0a9d..a7eec12d 100644
--- a/src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/CreditsMetadataExtension.java
+++ b/src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/CreditsMetadataExtension.java
@@ -6,7 +6,7 @@
import com.minecrafttas.tasmod.events.EventPlaybackClient.EventPlaybackJoinedWorld;
import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate;
import com.minecrafttas.tasmod.playback.metadata.PlaybackMetadata;
-import com.minecrafttas.tasmod.playback.metadata.PlaybackMetadataRegistry.PlaybackMetadataExtension;
+import com.minecrafttas.tasmod.playback.metadata.PlaybackMetadata.PlaybackMetadataExtension;
import com.minecrafttas.tasmod.playback.tasfile.exception.PlaybackLoadException;
import com.minecrafttas.tasmod.util.LoggerMarkers;
@@ -20,7 +20,7 @@
* Credits can be changed in the file and will be printed in chat, when the
* player joins a world after /fullplay
*/
-public class CreditsMetadataExtension implements PlaybackMetadataExtension, EventPlaybackJoinedWorld, EventControllerStateChange {
+public class CreditsMetadataExtension extends PlaybackMetadataExtension implements EventPlaybackJoinedWorld, EventControllerStateChange {
/**
* The title/category of the TAS (e.g. KillSquid - Any% Glitched)
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/StartpositionMetadataExtension.java b/src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/StartpositionMetadataExtension.java
index 319588e0..81954595 100644
--- a/src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/StartpositionMetadataExtension.java
+++ b/src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/StartpositionMetadataExtension.java
@@ -4,18 +4,19 @@
import java.nio.ByteBuffer;
-import com.minecrafttas.mctcommon.server.exception.PacketNotImplementedException;
-import com.minecrafttas.mctcommon.server.exception.WrongSideException;
-import com.minecrafttas.mctcommon.server.interfaces.PacketID;
-import com.minecrafttas.mctcommon.server.interfaces.ServerPacketHandler;
+import com.minecrafttas.mctcommon.networking.exception.PacketNotImplementedException;
+import com.minecrafttas.mctcommon.networking.exception.WrongSideException;
+import com.minecrafttas.mctcommon.networking.interfaces.PacketID;
+import com.minecrafttas.mctcommon.networking.interfaces.ServerPacketHandler;
import com.minecrafttas.tasmod.TASmod;
import com.minecrafttas.tasmod.TASmodClient;
import com.minecrafttas.tasmod.events.EventPlaybackClient.EventControllerStateChange;
import com.minecrafttas.tasmod.networking.TASmodBufferBuilder;
-import com.minecrafttas.tasmod.networking.TASmodPackets;
import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate;
import com.minecrafttas.tasmod.playback.metadata.PlaybackMetadata;
-import com.minecrafttas.tasmod.playback.metadata.PlaybackMetadataRegistry.PlaybackMetadataExtension;
+import com.minecrafttas.tasmod.playback.metadata.PlaybackMetadata.PlaybackMetadataExtension;
+import com.minecrafttas.tasmod.playback.tasfile.exception.PlaybackLoadException;
+import com.minecrafttas.tasmod.registries.TASmodPackets;
import com.minecrafttas.tasmod.util.LoggerMarkers;
import net.minecraft.client.Minecraft;
@@ -30,7 +31,7 @@
*
* @author Scribble
*/
-public class StartpositionMetadataExtension implements PlaybackMetadataExtension, EventControllerStateChange, ServerPacketHandler {
+public class StartpositionMetadataExtension extends PlaybackMetadataExtension implements EventControllerStateChange, ServerPacketHandler {
/**
* The startposition of the playback
@@ -75,21 +76,47 @@ public PlaybackMetadata onStore() {
metadata.setValue("x", Double.toString(startPosition.x));
metadata.setValue("y", Double.toString(startPosition.y));
metadata.setValue("z", Double.toString(startPosition.z));
- metadata.setValue("pitch", Double.toString(startPosition.pitch));
- metadata.setValue("yaw", Double.toString(startPosition.yaw));
+ metadata.setValue("pitch", Float.toString(startPosition.pitch));
+ metadata.setValue("yaw", Float.toString(startPosition.yaw));
return metadata;
}
@Override
public void onLoad(PlaybackMetadata metadata) {
- double x = Double.parseDouble(metadata.getValue("x"));
- double y = Double.parseDouble(metadata.getValue("y"));
- double z = Double.parseDouble(metadata.getValue("z"));
- float pitch = Float.parseFloat(metadata.getValue("pitch"));
- float yaw = Float.parseFloat(metadata.getValue("yaw"));
+ double x = getDouble("x", metadata);
+ double y = getDouble("y", metadata);
+ double z = getDouble("z", metadata);
+ float pitch = getFloat("pitch", metadata);
+ float yaw = getFloat("yaw", metadata);
this.startPosition = new StartPosition(x, y, z, pitch, yaw);
}
+
+ private double getDouble(String key, PlaybackMetadata metadata) {
+ String out = metadata.getValue(key);
+ if(out != null) {
+ try {
+ return Double.parseDouble(out);
+ } catch (NumberFormatException e) {
+ throw new PlaybackLoadException(e);
+ }
+ } else {
+ throw new PlaybackLoadException(String.format("Missing key %s in Start Position metadata", key));
+ }
+ }
+
+ private float getFloat(String key, PlaybackMetadata metadata) {
+ String out = metadata.getValue(key);
+ if(out != null) {
+ try {
+ return Float.parseFloat(out);
+ } catch (NumberFormatException e) {
+ throw new PlaybackLoadException(e);
+ }
+ } else {
+ throw new PlaybackLoadException(String.format("Missing key %s in Start Position metadata", key));
+ }
+ }
@Override
public void onClear() {
@@ -127,7 +154,6 @@ public void onControllerStateChange(TASstate newstate, TASstate oldstate) {
}
}
}
-
}
@Override
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiser.java b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiser.java
index 29b0db5e..e8945a7f 100644
--- a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiser.java
+++ b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiser.java
@@ -1,472 +1,263 @@
package com.minecrafttas.tasmod.playback.tasfile;
-import com.dselent.bigarraylist.BigArrayList;
-import com.minecrafttas.tasmod.TASmod;
-import com.minecrafttas.tasmod.monitoring.DesyncMonitoring;
-import com.minecrafttas.tasmod.playback.ControlByteHandler;
-import com.minecrafttas.tasmod.playback.PlaybackControllerClient;
-import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TickInputContainer;
-import com.minecrafttas.tasmod.util.FileThread;
-import com.minecrafttas.tasmod.util.LoggerMarkers;
-import com.minecrafttas.tasmod.virtual.VirtualCameraAngle;
-import com.minecrafttas.tasmod.virtual.VirtualKeyboard;
-import com.minecrafttas.tasmod.virtual.VirtualMouse;
-import com.mojang.realmsclient.util.Pair;
-import org.apache.commons.io.FileUtils;
-
+import java.io.BufferedReader;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
import java.io.IOException;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
-import static com.minecrafttas.tasmod.TASmod.LOGGER;
+import com.dselent.bigarraylist.BigArrayList;
+import com.minecrafttas.tasmod.playback.PlaybackControllerClient;
+import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TickContainer;
+import com.minecrafttas.tasmod.playback.tasfile.exception.PlaybackLoadException;
+import com.minecrafttas.tasmod.playback.tasfile.exception.PlaybackSaveException;
+import com.minecrafttas.tasmod.playback.tasfile.flavor.SerialiserFlavorBase;
+import com.minecrafttas.tasmod.registries.TASmodAPIRegistry;
+import com.minecrafttas.tasmod.util.FileThread;
/**
- * Saves a given {@linkplain PlaybackControllerClient} to a file. Is also able to read an input container from a file.
- *
- * I plan to be backwards compatible so all the save functions have a V1 in their name by the time of writing this
- *
- * It also serializes the {@linkplain DesyncMonitoring} from the input container
- *
- * Side: Client
+ * Loads and stores the {@link PlaybackControllerClient} to/from a file.
*
- * @author ScribbleLP
- *
+ * @author Scribble
*/
public class PlaybackSerialiser {
-
+
+ private static String defaultFlavor = "beta1";
+
/**
- * A list of sections to check for in the playback file
- * @author ScribbleLP
- *
+ * Saves the {@link PlaybackControllerClient} to a file
+ *
+ * @param file The file to save the serialised inputs to
+ * @param controller The {@link PlaybackControllerClient} to use. Uses the {@link PlaybackControllerClient#getInputs() getInputs()} method, to extract the ticks.
+ * @param flavorName The name of the {@link SerialiserFlavorBase flavor} to use for the tasfile
+ * @throws PlaybackSaveException When a saving operation fails
*/
- public enum SectionsV1{
- TICKS("Ticks", ""),
- KEYBOARD("Keyboard", "(\\|Keyboard:)"),
- MOUSE("Mouse", "(\\|Mouse:)"),
- CAMERA("Camera", "(\\|Camera:)");
-
- private String name;
- private String regex;
-
- private SectionsV1(String nameIn, String regexIn) {
- name=nameIn;
- regex=regexIn;
- }
-
- public String getName() {
- return name;
- }
-
- public String getRegex() {
- return regex;
- }
-
- public static String getRegexString() {
- String out="";
- for(SectionsV1 section : values()) {
- if(!section.getRegex().isEmpty()) {
- String seperator="|";
- if(values().length-1==section.ordinal()) {
- seperator="";
- }
- out=out.concat(section.getRegex()+seperator);
- }
- }
- return out;
+ public static void saveToFile(File file, PlaybackControllerClient controller, String flavorName) throws PlaybackSaveException {
+ saveToFile(file, controller, flavorName, -1L);
+ }
+
+ /**
+ * Saves the {@link PlaybackControllerClient} partially to a file
+ *
+ * @param file The file to save the serialised inputs to
+ * @param controller The {@link PlaybackControllerClient} to use. Uses the {@link PlaybackControllerClient#getInputs() getInputs()} method, to extract the ticks.
+ * @param flavorName The name of the {@link SerialiserFlavorBase flavor} to use for the tasfile
+ * @param stopIndex The index at which the serialiser stops. Use -1L to parse the entire file
+ * @throws PlaybackSaveException When a saving operation fails
+ */
+ public static void saveToFile(File file, PlaybackControllerClient controller, String flavorName, long stopIndex) throws PlaybackSaveException {
+ if (controller == null) {
+ throw new PlaybackSaveException("Save to file failed. No controller specified");
}
+ saveToFile(file, controller.getInputs(), flavorName, stopIndex);
}
-
+
/**
- * Saves all inputs of the input container
- * @param file Where to save the container
- * @param container The container to save
- * @throws IOException When the input container is empty
+ * Saves a BigArrayList of {@link TickContainer TickContainers} to a file
+ *
+ * @param file The file to save the serialised inputs to
+ * @param container The list of {@link TickContainer TickContainers} to use
+ * @param flavorName The name of the {@link SerialiserFlavorBase flavor} to use for the tasfile
+ * @throws PlaybackSaveException When a saving operation fails
*/
- public void saveToFileV1(File file, PlaybackControllerClient container) throws IOException {
- saveToFileV1Until(file, container, -1);
+ public static void saveToFile(File file, BigArrayList container, String flavorName) throws PlaybackSaveException {
+ saveToFile(file, container, flavorName, -1);
}
-
+
/**
- * Saves inputs up to a certain index of the input container
- * @param file Where to save the container
- * @param container The container to save
- * @param index index until the inputs get saved
- * @throws IOException When the input container is empty
+ * Saves a BigArrayList of {@link TickContainer TickContainers} partially to a file
+ * @param file The file to save the serialised inputs to
+ * @param container The list of {@link TickContainer TickContainers} to use
+ * @param flavorName The name of the {@link SerialiserFlavorBase flavor} to use for the tasfile
+ * @param stopIndex The index at which the serialiser stops. Use -1L to parse the entire file
+ * @throws PlaybackSaveException When a saving operation fails
*/
- public void saveToFileV1Until(File file, PlaybackControllerClient container, int index) throws IOException{
- LOGGER.debug(LoggerMarkers.Playback, "Saving playback controller to file {}", file);
- if (container.size() == 0) {
- throw new IOException("There are no inputs to save to a file");
+ public static void saveToFile(File file, BigArrayList container, String flavorName, long stopIndex) throws PlaybackSaveException {
+ if (file == null) {
+ throw new PlaybackSaveException("Save to file failed. No file specified");
}
- FileThread fileThread = new FileThread(file, false);
-// FileThread monitorThread= new FileThread(new File(file, "../"+file.getName().replace(".mctas", "")+".mon"), false);
- fileThread.start();
-// monitorThread.start();
-
-// fileThread.addLine("################################################# TASFile ###################################################\n"
-// + "# Version:1 #\n"
-// + "# This file was generated using the Minecraft TASMod #\n"
-// + "# #\n"
-// + "# Any errors while reading this file will be printed out in the console and the chat #\n"
-// + "# #\n"
-// + "#------------------------------------------------ Header ---------------------------------------------------#\n"
-// + "#Author:" + container.getAuthors() + "\n"
-// + "# #\n"
-// + "#Title:" + container.getTitle() + "\n"
-// + "# #\n"
-// + "#Playing Time:" + container.getPlaytime() + "\n"
-// + "# #\n"
-// + "#Rerecords:"+container.getRerecords() + "\n"
-// + "# #\n"
-// + "#----------------------------------------------- Settings --------------------------------------------------#\n"
-// + "#StartPosition:"+container.getStartLocation()+"\n"
-// + "# #\n"
-// + "#StartSeed:" + container.getStartSeed() + "\n"
-// + "#############################################################################################################\n"
-// + "#Comments start with \"//\" at the start of the line, comments with # will not be saved\n");
-
- BigArrayList ticks = container.getInputs();
- Map>> cbytes= container.getControlBytes();
- Map> comments = container.getComments();
-
- for (int i = 0; i < ticks.size(); i++) {
- if(i==index) {
- break;
- }
-
- // Add comments
- if(comments.containsKey(i)) {
- List multiLine=comments.get(i);
- multiLine.forEach(comment -> {
- fileThread.addLine("//"+comment+"\n");
- });
- }
-
- // Add controlbytes
- if(cbytes.containsKey(i)) {
- List> cbytelist= cbytes.get(i);
- String cbyteString= ControlByteHandler.toString(cbytelist);
- if(!cbyteString.isEmpty()) {
- fileThread.addLine(cbyteString);
- }
- }
-
- // Add a data line
- TickInputContainer tickInput = ticks.get(i);
- fileThread.addLine(tickInput.toString() + "~&\t\t\t\t//Monitoring:"+container.desyncMonitor.get(i)+"\n");
+ if (container == null) {
+ throw new PlaybackSaveException("Save to file failed. No tickcontainer list specified");
}
- fileThread.close();
- }
- public int getFileVersion(File file) throws IOException {
- LOGGER.trace(LoggerMarkers.Playback, "Retrieving file version from {}", file);
- List lines = FileUtils.readLines(file, Charset.defaultCharset());
- for (String line : lines) {
- if (line.contains("Version")) {
- String trimmed = line.replaceAll("#|\t", "");
- int tick=0;
- try {
- tick=Integer.parseInt(trimmed.split(":")[1]);
- } catch (NumberFormatException e) {
- throw new IOException("Can't read the file version: "+trimmed);
- }
- return tick;
- }
+ if (flavorName == null || flavorName.isEmpty()) {
+ if (defaultFlavor == null || defaultFlavor.isEmpty())
+ throw new PlaybackSaveException("No default flavor specified... Please specify a flavor name first");
+ flavorName = defaultFlavor;
+ } else {
+ defaultFlavor = flavorName;
}
- return 0;
- }
- public PlaybackControllerClient fromEntireFileV1(File file) throws IOException {
- LOGGER.debug(LoggerMarkers.Playback, "Loading playback controller to file {}", file);
- List lines = FileUtils.readLines(file, StandardCharsets.UTF_8);
-
- File monitorFile=new File(file, "../"+file.getName().replace(".mctas", "")+".mon");
-
- List monitorLines=new ArrayList<>();
-
- // Read the legacy monitoring file system. Still reads the file but deletes it afterwards
- if(monitorFile.exists()) {
- monitorLines = FileUtils.readLines(monitorFile, StandardCharsets.UTF_8);
- monitorFile.delete();
+ FileThread writerThread;
+ try {
+ writerThread = new FileThread(file, false);
+ } catch (FileNotFoundException e) {
+ throw new PlaybackSaveException(e, "Trying to save the file %s, but the file can't be created", file.getName());
}
- boolean oldmonfileLoaded=!monitorLines.isEmpty();
+ writerThread.start();
- PlaybackControllerClient controller = new PlaybackControllerClient();
+ SerialiserFlavorBase flavor = TASmodAPIRegistry.SERIALISER_FLAVOR.getFlavor(flavorName);
- String author = "Insert author here";
+ List header = flavor.serialiseHeader();
+ for (String line : header) {
+ writerThread.addLine(line);
+ }
- String title = "Insert TAS category here";
+ BigArrayList tickLines = flavor.serialise(container, stopIndex);
+ for (long i = 0; i < tickLines.size(); i++) {
+ writerThread.addLine(tickLines.get(i));
+ }
- String playtime = "00:00.0";
+ writerThread.close();
+ }
- int rerecords = 0;
-
- // No default start location
- String startLocation="";
-
- // Default the start seed to the current global ktrng seed. If KTRNG is not loaded, defaults to 0
- long startSeed=TASmod.ktrngHandler.getGlobalSeedClient();
+ /**
+ * Loads a BigArrayList of {@link TickContainer TickContainers} from a file.
+ * Tries to determine the {@link SerialiserFlavorBase flavor} by reading the header of the TASfile
+ *
+ * @param file The file to load from
+ * @return The loaded BigArrayList of {@link TickContainer TickContainers}
+ * @throws PlaybackLoadException If the file contains errors
+ * @throws IOException If the file could not be read
+ */
+ public static BigArrayList loadFromFile(File file) throws PlaybackLoadException, IOException {
+ if (file == null) {
+ throw new PlaybackLoadException("Load from file failed. No file specified");
+ }
+ if (!file.exists()) {
+ throw new PlaybackLoadException("Trying to load %s but the file doesn't exist", file.getName());
+ }
- // Clear the current container before reading new data
- controller.clear();
+ SerialiserFlavorBase flavor = readFlavor(file);
- int linenumber = 0; //The current line number
+ return loadFromFile(file, flavor);
+ }
+
+ /**
+ * Loads a BigArrayList of {@link TickContainer TickContainers} from a file, with a specific flavor
+ *
+ * @param file The file to load from
+ * @param flavorName The name of the {@link SerialiserFlavorBase flavor} to use. If the detected flavor in the TASfile mismatches, a {@link PlaybackLoadException} is thrown
+ * @return The loaded BigArrayList of {@link TickContainer TickContainers}
+ * @throws PlaybackLoadException If the file contains errors
+ * @throws IOException If the file could not be read
+ */
+ public static BigArrayList loadFromFile(File file, String flavorName) throws PlaybackLoadException, IOException {
- for (String line : lines) {
- linenumber++;
- int tickcount=(int) controller.getInputs().size();
- // Read out header
- if (line.startsWith("#")) {
- // Read author tag
- if (line.startsWith("#Author:")) {
- author = line.split(":")[1];
- // Read title tag
- } else if (line.startsWith("#Title:")) {
- title = line.split(":")[1];
- // Read playtime
- } else if (line.startsWith("#Playing Time:")) {
- playtime = line.split("Playing Time:")[1];
- // Read rerecords
- } else if (line.startsWith("#Rerecords:")) {
- rerecords = Integer.parseInt(line.split(":")[1]);
- // Read start position
- } else if (line.startsWith("#StartPosition:")) {
- startLocation = line.replace("#StartPosition:", "");
- // Read start seed
- } else if (line.startsWith("#StartSeed:")) {
- startSeed = Long.parseLong(line.replace("#StartSeed:", ""));
- }
- // Read control bytes
- } else if (line.startsWith("$") && line.replace('$', ' ').trim().contains(" ")) {
- String[] sections = line.replace('$', ' ').trim().split(" ", 2);
- if (sections.length == 0)
- continue;
- String control = sections[0];
- String[] params = sections[1].split(" ");
- List> cbytes = controller.getControlBytes().getOrDefault(tickcount, new ArrayList<>());
- cbytes.add(Pair.of(control, params));
- controller.getControlBytes().put(tickcount, cbytes);
- //Read comments
- } else if (line.startsWith("//")) {
- List commentList = controller.getComments().getOrDefault(tickcount, new ArrayList<>());
- commentList.add(line.replace("//", ""));
- controller.getComments().put(tickcount, commentList);
- //Read data
- } else {
-
- // Splitting the line into a data- and commentPart, the comment part will most likely contain the Monitoring
- String dataPart=line;
- String commentPart="";
- if(line.contains("~&")) {
- String[] splitComments=line.split("~&");
- dataPart=splitComments[0];
- commentPart=splitComments[1];
- }
- String[] sections = dataPart.split(SectionsV1.getRegexString());
-
- if (sections.length != SectionsV1.values().length) {
- throw new IOException("Error in line " + linenumber + ". Cannot read the line correctly");
- }
-
- controller.getInputs().add(new TickInputContainer(readTicks(sections[0], linenumber), readKeyboard(sections[1], linenumber), readMouse(sections[2], linenumber), readSubtick(sections[3], linenumber)));
-
- if(!oldmonfileLoaded) {
- String[] commentData = commentPart.split("Monitoring:");
- if(commentData.length==2) {
- monitorLines.add(commentData[1]);
- }
- }
- }
- }
-// controller.setAuthors(author);
-// controller.setTitle(title);
-// controller.setPlaytime(playtime);
-// controller.setRerecords(rerecords);
-// controller.setStartLocation(startLocation);
-// controller.setStartSeed(startSeed);
- if(!monitorLines.isEmpty()) {
- controller.desyncMonitor = new DesyncMonitoring(controller, monitorLines);
+ // If the flavor is null or empty, try to determine the flavor by reading the header
+ if (flavorName == null || flavorName.isEmpty()) {
+ return loadFromFile(file);
}
-
- //If an old monitoring file is loaded, save the file immediately to not loose any data.
- if(oldmonfileLoaded) {
- saveToFileV1(file, controller);
+
+ // Try to get the flavor from the registry via its name
+ SerialiserFlavorBase flavor = TASmodAPIRegistry.SERIALISER_FLAVOR.getFlavor(flavorName);
+
+ if (flavor == null) {
+ throw new PlaybackLoadException("Flavor name %s doesn't exist.", flavorName);
}
-
- return controller;
- }
- private int readTicks(String section, int linenumber) throws IOException {
- int ticks = 0;
- try {
- ticks = Integer.parseInt(section);
- } catch (NumberFormatException e) {
- throw new IOException(section + " is not a recognised number in line " + linenumber);
+ // Read the head of the TASfile to check if the flavors match
+ SerialiserFlavorBase flavorInFile = readFlavor(file);
+ if (!flavor.equals(flavorInFile)) {
+ throw new PlaybackLoadException("Detected flavor %s in the TASfile, which does not match the specified flavor: %s");
}
- return ticks;
+
+ return loadFromFile(file, flavor);
}
-
- private VirtualKeyboard readKeyboard(String section, int linenumber) throws IOException {
- VirtualKeyboard keyboard = new VirtualKeyboard();
- // Remove the prefix
- section = section.replace("Keyboard:", "");
+ /**
+ * Loads a BigArrayList of {@link TickContainer TickContainers} from a file, with a specific flavor
+ *
+ * @param file The file to load from
+ * @param flavor The {@link SerialiserFlavorBase flavor} to use. If the detected flavor in the TASfile mismatches, a {@link PlaybackLoadException} is thrown
+ * @return The loaded BigArrayList of {@link TickContainer TickContainers}
+ * @throws PlaybackLoadException If the file contains errors
+ * @throws IOException If the file could not be read
+ */
+ public static BigArrayList loadFromFile(File file, SerialiserFlavorBase flavor) throws PlaybackLoadException, IOException {
+ if (file == null) {
+ throw new PlaybackLoadException("Load from file failed. No file specified");
+ }
- // Split in keys and characters
- String[] keys = section.split(";");
+ // Read file
+ BufferedReader reader = null;
- // If there is nothing, return the empty keyboard
- if (keys.length == 0) {
- return keyboard;
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ } catch (FileNotFoundException e) {
+ throw new PlaybackLoadException("Trying to load %s, but the file doesn't exist", file.getName());
}
- // Check if the keylist is empty
- if (!keys[0].isEmpty()) {
-
- // Split multiple keys
- String[] splitKeys = keys[0].split(",");
-
- for (String key : splitKeys) {
-
-// VirtualKey vkey = null;
-// // Check if the key is a keycode
-// if (isNumeric(key)) {
-// vkey = keyboard.get(Integer.parseInt(key));
-// } else {
-// vkey = keyboard.get(key);
-// }
-//
-// if (vkey == null) {
-// throw new IOException(key + " is not a recognised keyboard key in line " + linenumber);
-// }
-//
-// vkey.setPressed(true);
- }
+ BigArrayList lines = new BigArrayList<>();
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ lines.add(line);
}
-
- char[] chars = {};
- //Check if the characterlist is empty
- if (keys.length == 2) {
- chars = keys[1].replace("\\n", "\n").toCharArray(); //Replacing the "\n" in lines to the character \n
- }
-
- for (char onechar : chars) {
-// keyboard.addChar(onechar);
- }
- return keyboard;
+ reader.close();
+
+ // Deserialise Header
+ List headerLines = flavor.extractHeader(lines);
+ flavor.deserialiseHeader(headerLines);
+
+ // Deserialise main data
+ BigArrayList deserialisedContainers = flavor.deserialise(lines, headerLines.size());
+
+ return deserialisedContainers;
}
- private VirtualMouse readMouse(String section, int linenumber) throws IOException {
- VirtualMouse mouse = new VirtualMouse();
-
- // Remove the prefix
- section = section.replace("Mouse:", "");
-
- //Split into buttons and paths...
- String buttons = section.split(";")[0];
- String path = section.split(";")[1];
-
- //Check whether the button is empty
- if(!buttons.isEmpty()) {
-
- //Splitting multiple buttons
- String[] splitButtons=buttons.split(",");
- for (String button : splitButtons) {
-
-// VirtualKey vkey = null;
-// // Check if the key is a keycode
-// if (isNumeric(button)) {
-// vkey = mouse.get(Integer.parseInt(button));
-// } else {
-// vkey = mouse.get(button);
-// }
-// if (vkey == null) {
-// throw new IOException(button + " is not a recognised mouse key in line " + linenumber);
-// }
-// mouse.get(button).setPressed(true);
+ /**
+ * Searches in a list of lines if one of the {@link SerialiserFlavorBase flavors} matches
+ * @param lines The lines to search through
+ * @param flavorList The list of {@link SerialiserFlavorBase flavor} to check for
+ * @return A copy of the {@link SerialiserFlavorBase flavor} that was found
+ * @throws PlaybackLoadException If no {@link SerialiserFlavorBase flavor} was found
+ */
+ public static SerialiserFlavorBase searchForFlavor(List lines, List flavorList) {
+ for (SerialiserFlavorBase flavor : flavorList) {
+ if (flavor.deserialiseFlavorName(lines)) {
+ return flavor.clone();
}
}
-// mouse.setPath(readPath(path, linenumber, mouse));
-
- return mouse;
+ throw new PlaybackLoadException("Couldn't find a flavorname in the file. TASfile is missing a flavor-extension or the file is broken");
}
-// private List readPath(String section, int linenumber, VirtualMouse mouse) throws IOException {
-// List path = new ArrayList();
-//
-// section = section.replace("[", "").replace("]", "");
-// String[] pathNodes = section.split("->");
-//
-// for (String pathNode : pathNodes) {
-// String[] split = pathNode.split(",");
-//
-// int length=split.length;
-// int scrollWheel = 0;
-// int cursorX = 0;
-// int cursorY = 0;
-// try {
-// scrollWheel = Integer.parseInt(split[length-3]);
-// cursorX = Integer.parseInt(split[length-2]);
-// cursorY = Integer.parseInt(split[length-1]);
-// } catch (NumberFormatException e) {
-// throw new IOException("'" + pathNode + "' couldn't be read in line " + linenumber+": Something is not a number");
-// } catch (ArrayIndexOutOfBoundsException e) {
-// throw new IOException("'" + pathNode + "' couldn't be read in line " + linenumber+": Something is missing or is too much");
-// }
-// PathNode node = mouse.new PathNode();
-// for (int i=0; i lines = new ArrayList<>();
+ String line = null;
+
+ // Reads the first 100 lines
+ for (int i = 0; i < 100; i++) {
+
+ line = reader.readLine();
+
+ if (line != null) {
+ lines.add(line);
+ }
}
- return true;
+ reader.close();
+
+ SerialiserFlavorBase flavor = null;
+
+ flavor = searchForFlavor(lines, TASmodAPIRegistry.SERIALISER_FLAVOR.getFlavors());
+ return flavor;
}
}
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiserBase.java b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiserBase.java
deleted file mode 100644
index 305b3a74..00000000
--- a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiserBase.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.minecrafttas.tasmod.playback.tasfile;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import com.minecrafttas.tasmod.playback.PlaybackControllerClient;
-
-public abstract class PlaybackSerialiserBase {
-
- public PlaybackSerialiserBase(PlaybackControllerClient controller) {
- if(controller == null) {
- throw new NullPointerException("Parameter controller can't be null");
- }
-
-
- }
-
- public void onSave() {
-
- }
-
- public void onLoad() {
-
- }
-
- public List serialize() {
- List out = new ArrayList<>();
- return out;
- }
-
- public void deserialize(List in) {
-
- }
-
- public List serializeMetadata(){
- return null;
- }
-
- public void deserializeMetadata(List metadataString) {
-
- }
-}
diff --git a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiserOld.java b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiserOld.java
new file mode 100644
index 00000000..9a36dcf2
--- /dev/null
+++ b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiserOld.java
@@ -0,0 +1,471 @@
+package com.minecrafttas.tasmod.playback.tasfile;
+
+import static com.minecrafttas.tasmod.TASmod.LOGGER;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+
+import com.dselent.bigarraylist.BigArrayList;
+import com.minecrafttas.tasmod.TASmod;
+import com.minecrafttas.tasmod.playback.PlaybackControllerClient;
+import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TickContainer;
+import com.minecrafttas.tasmod.playback.filecommands.integrated.DesyncMonitorFileCommandExtension;
+import com.minecrafttas.tasmod.util.FileThread;
+import com.minecrafttas.tasmod.util.LoggerMarkers;
+import com.minecrafttas.tasmod.virtual.VirtualCameraAngle;
+import com.minecrafttas.tasmod.virtual.VirtualKeyboard;
+import com.minecrafttas.tasmod.virtual.VirtualMouse;
+
+/**
+ * Saves a given {@linkplain PlaybackControllerClient} to a file. Is also able to read an input container from a file.
+ *
+ * I plan to be backwards compatible so all the save functions have a V1 in their name by the time of writing this
+ *
+ * It also serializes the {@linkplain DesyncMonitorFileCommandExtension} from the input container
+ *
+ * Side: Client
+ *
+ * @author ScribbleLP
+ *
+ */
+@Deprecated
+public class PlaybackSerialiserOld {
+
+ /**
+ * A list of sections to check for in the playback file
+ * @author ScribbleLP
+ *
+ */
+ public enum SectionsV1{
+ TICKS("Ticks", ""),
+ KEYBOARD("Keyboard", "(\\|Keyboard:)"),
+ MOUSE("Mouse", "(\\|Mouse:)"),
+ CAMERA("Camera", "(\\|Camera:)");
+
+ private String name;
+ private String regex;
+
+ private SectionsV1(String nameIn, String regexIn) {
+ name=nameIn;
+ regex=regexIn;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getRegex() {
+ return regex;
+ }
+
+ public static String getRegexString() {
+ String out="";
+ for(SectionsV1 section : values()) {
+ if(!section.getRegex().isEmpty()) {
+ String seperator="|";
+ if(values().length-1==section.ordinal()) {
+ seperator="";
+ }
+ out=out.concat(section.getRegex()+seperator);
+ }
+ }
+ return out;
+ }
+ }
+
+ /**
+ * Saves all inputs of the input container
+ * @param file Where to save the container
+ * @param container The container to save
+ * @throws IOException When the input container is empty
+ */
+ public void saveToFileV1(File file, PlaybackControllerClient container) throws IOException {
+ saveToFileV1Until(file, container, -1);
+ }
+
+ /**
+ * Saves inputs up to a certain index of the input container
+ * @param file Where to save the container
+ * @param container The container to save
+ * @param index index until the inputs get saved
+ * @throws IOException When the input container is empty
+ */
+ public void saveToFileV1Until(File file, PlaybackControllerClient container, long index) throws IOException{
+ LOGGER.debug(LoggerMarkers.Playback, "Saving playback controller to file {}", file);
+ if (container.size() == 0) {
+ throw new IOException("There are no inputs to save to a file");
+ }
+ FileThread fileThread = new FileThread(file, false);
+// FileThread monitorThread= new FileThread(new File(file, "../"+file.getName().replace(".mctas", "")+".mon"), false);
+
+ fileThread.start();
+// monitorThread.start();
+
+// fileThread.addLine("################################################# TASFile ###################################################\n"
+// + "# Version:1 #\n"
+// + "# This file was generated using the Minecraft TASMod #\n"
+// + "# #\n"
+// + "# Any errors while reading this file will be printed out in the console and the chat #\n"
+// + "# #\n"
+// + "#------------------------------------------------ Header ---------------------------------------------------#\n"
+// + "#Author:" + container.getAuthors() + "\n"
+// + "# #\n"
+// + "#Title:" + container.getTitle() + "\n"
+// + "# #\n"
+// + "#Playing Time:" + container.getPlaytime() + "\n"
+// + "# #\n"
+// + "#Rerecords:"+container.getRerecords() + "\n"
+// + "# #\n"
+// + "#----------------------------------------------- Settings --------------------------------------------------#\n"
+// + "#StartPosition:"+container.getStartLocation()+"\n"
+// + "# #\n"
+// + "#StartSeed:" + container.getStartSeed() + "\n"
+// + "#############################################################################################################\n"
+// + "#Comments start with \"//\" at the start of the line, comments with # will not be saved\n");
+
+ BigArrayList ticks = container.getInputs();
+// Map>> cbytes= container.getControlBytes();
+// Map> comments = container.getComments();
+
+// for (int i = 0; i < ticks.size(); i++) {
+// if(i==index) {
+// break;
+// }
+//
+// // Add comments
+// if(comments.containsKey(i)) {
+// List multiLine=comments.get(i);
+// multiLine.forEach(comment -> {
+// fileThread.addLine("//"+comment+"\n");
+// });
+// }
+
+ // Add controlbytes
+// if(cbytes.containsKey(i)) {
+// List> cbytelist= cbytes.get(i);
+// String cbyteString= ControlByteHandler.toString(cbytelist);
+// if(!cbyteString.isEmpty()) {
+// fileThread.addLine(cbyteString);
+// }
+// }
+//
+// // Add a data line
+// TickContainer tickInput = ticks.get(i);
+// fileThread.addLine(tickInput.toString() + "~&\t\t\t\t//Monitoring:"+container.desyncMonitor.get(i)+"\n");
+// }
+// fileThread.close();
+ }
+
+ public int getFileVersion(File file) throws IOException {
+ LOGGER.trace(LoggerMarkers.Playback, "Retrieving file version from {}", file);
+ List lines = FileUtils.readLines(file, Charset.defaultCharset());
+ for (String line : lines) {
+ if (line.contains("Version")) {
+ String trimmed = line.replaceAll("#|\t", "");
+ int tick=0;
+ try {
+ tick=Integer.parseInt(trimmed.split(":")[1]);
+ } catch (NumberFormatException e) {
+ throw new IOException("Can't read the file version: "+trimmed);
+ }
+ return tick;
+ }
+ }
+ return 0;
+ }
+
+ public PlaybackControllerClient fromEntireFileV1(File file) throws IOException {
+ LOGGER.debug(LoggerMarkers.Playback, "Loading playback controller to file {}", file);
+ List lines = FileUtils.readLines(file, StandardCharsets.UTF_8);
+
+ File monitorFile=new File(file, "../"+file.getName().replace(".mctas", "")+".mon");
+
+ List monitorLines=new ArrayList<>();
+
+ // Read the legacy monitoring file system. Still reads the file but deletes it afterwards
+ if(monitorFile.exists()) {
+ monitorLines = FileUtils.readLines(monitorFile, StandardCharsets.UTF_8);
+ monitorFile.delete();
+ }
+ boolean oldmonfileLoaded=!monitorLines.isEmpty();
+
+ PlaybackControllerClient controller = new PlaybackControllerClient();
+
+ String author = "Insert author here";
+
+ String title = "Insert TAS category here";
+
+ String playtime = "00:00.0";
+
+ int rerecords = 0;
+
+ // No default start location
+ String startLocation="";
+
+ // Default the start seed to the current global ktrng seed. If KTRNG is not loaded, defaults to 0
+ long startSeed=TASmod.ktrngHandler.getGlobalSeedClient();
+
+ // Clear the current container before reading new data
+ controller.clear();
+
+ int linenumber = 0; //The current line number
+
+ for (String line : lines) {
+ linenumber++;
+ int tickcount=(int) controller.getInputs().size();
+ // Read out header
+ if (line.startsWith("#")) {
+ // Read author tag
+ if (line.startsWith("#Author:")) {
+ author = line.split(":")[1];
+ // Read title tag
+ } else if (line.startsWith("#Title:")) {
+ title = line.split(":")[1];
+ // Read playtime
+ } else if (line.startsWith("#Playing Time:")) {
+ playtime = line.split("Playing Time:")[1];
+ // Read rerecords
+ } else if (line.startsWith("#Rerecords:")) {
+ rerecords = Integer.parseInt(line.split(":")[1]);
+ // Read start position
+ } else if (line.startsWith("#StartPosition:")) {
+ startLocation = line.replace("#StartPosition:", "");
+ // Read start seed
+ } else if (line.startsWith("#StartSeed:")) {
+ startSeed = Long.parseLong(line.replace("#StartSeed:", ""));
+ }
+ // Read control bytes
+ } else if (line.startsWith("$") && line.replace('$', ' ').trim().contains(" ")) {
+ String[] sections = line.replace('$', ' ').trim().split(" ", 2);
+ if (sections.length == 0)
+ continue;
+ String control = sections[0];
+ String[] params = sections[1].split(" ");
+// List> cbytes = controller.getControlBytes().getOrDefault(tickcount, new ArrayList<>());
+// cbytes.add(Pair.of(control, params));
+// controller.getControlBytes().put(tickcount, cbytes);
+ //Read comments
+ } else if (line.startsWith("//")) {
+// List commentList = controller.getComments().getOrDefault(tickcount, new ArrayList<>());
+// commentList.add(line.replace("//", ""));
+// controller.getComments().put(tickcount, commentList);
+ //Read data
+ } else {
+
+ // Splitting the line into a data- and commentPart, the comment part will most likely contain the Monitoring
+ String dataPart=line;
+ String commentPart="";
+ if(line.contains("~&")) {
+ String[] splitComments=line.split("~&");
+ dataPart=splitComments[0];
+ commentPart=splitComments[1];
+ }
+ String[] sections = dataPart.split(SectionsV1.getRegexString());
+
+ if (sections.length != SectionsV1.values().length) {
+ throw new IOException("Error in line " + linenumber + ". Cannot read the line correctly");
+ }
+
+// controller.getInputs().add(new TickInputContainer(readTicks(sections[0], linenumber), readKeyboard(sections[1], linenumber), readMouse(sections[2], linenumber), readSubtick(sections[3], linenumber)));
+
+ if(!oldmonfileLoaded) {
+ String[] commentData = commentPart.split("Monitoring:");
+ if(commentData.length==2) {
+ monitorLines.add(commentData[1]);
+ }
+ }
+ }
+ }
+// controller.setAuthors(author);
+// controller.setTitle(title);
+// controller.setPlaytime(playtime);
+// controller.setRerecords(rerecords);
+// controller.setStartLocation(startLocation);
+// controller.setStartSeed(startSeed);
+ if(!monitorLines.isEmpty()) {
+// controller.desyncMonitor = new DesyncMonitoringFileCommand(controller, monitorLines);
+ }
+
+ //If an old monitoring file is loaded, save the file immediately to not loose any data.
+ if(oldmonfileLoaded) {
+ saveToFileV1(file, controller);
+ }
+
+ return controller;
+ }
+
+ private int readTicks(String section, int linenumber) throws IOException {
+ int ticks = 0;
+ try {
+ ticks = Integer.parseInt(section);
+ } catch (NumberFormatException e) {
+ throw new IOException(section + " is not a recognised number in line " + linenumber);
+ }
+ return ticks;
+ }
+
+ private VirtualKeyboard readKeyboard(String section, int linenumber) throws IOException {
+ VirtualKeyboard keyboard = new VirtualKeyboard();
+
+ // Remove the prefix
+ section = section.replace("Keyboard:", "");
+
+ // Split in keys and characters
+ String[] keys = section.split(";");
+
+ // If there is nothing, return the empty keyboard
+ if (keys.length == 0) {
+ return keyboard;
+ }
+
+ // Check if the keylist is empty
+ if (!keys[0].isEmpty()) {
+
+ // Split multiple keys
+ String[] splitKeys = keys[0].split(",");
+
+ for (String key : splitKeys) {
+
+// VirtualKey vkey = null;
+// // Check if the key is a keycode
+// if (isNumeric(key)) {
+// vkey = keyboard.get(Integer.parseInt(key));
+// } else {
+// vkey = keyboard.get(key);
+// }
+//
+// if (vkey == null) {
+// throw new IOException(key + " is not a recognised keyboard key in line " + linenumber);
+// }
+//
+// vkey.setPressed(true);
+ }
+ }
+
+ char[] chars = {};
+ //Check if the characterlist is empty
+ if (keys.length == 2) {
+ chars = keys[1].replace("\\n", "\n").toCharArray(); //Replacing the "\n" in lines to the character \n
+ }
+
+ for (char onechar : chars) {
+// keyboard.addChar(onechar);
+ }
+ return keyboard;
+ }
+
+ private VirtualMouse readMouse(String section, int linenumber) throws IOException {
+ VirtualMouse mouse = new VirtualMouse();
+
+ // Remove the prefix
+ section = section.replace("Mouse:", "");
+
+ //Split into buttons and paths...
+ String buttons = section.split(";")[0];
+ String path = section.split(";")[1];
+
+ //Check whether the button is empty
+ if(!buttons.isEmpty()) {
+
+ //Splitting multiple buttons
+ String[] splitButtons=buttons.split(",");
+ for (String button : splitButtons) {
+
+// VirtualKey vkey = null;
+// // Check if the key is a keycode
+// if (isNumeric(button)) {
+// vkey = mouse.get(Integer.parseInt(button));
+// } else {
+// vkey = mouse.get(button);
+// }
+// if (vkey == null) {
+// throw new IOException(button + " is not a recognised mouse key in line " + linenumber);
+// }
+// mouse.get(button).setPressed(true);
+ }
+ }
+// mouse.setPath(readPath(path, linenumber, mouse));
+
+ return mouse;
+ }
+
+// private List readPath(String section, int linenumber, VirtualMouse mouse) throws IOException {
+// List path = new ArrayList();
+//
+// section = section.replace("[", "").replace("]", "");
+// String[] pathNodes = section.split("->");
+//
+// for (String pathNode : pathNodes) {
+// String[] split = pathNode.split(",");
+//
+// int length=split.length;
+// int scrollWheel = 0;
+// int cursorX = 0;
+// int cursorY = 0;
+// try {
+// scrollWheel = Integer.parseInt(split[length-3]);
+// cursorX = Integer.parseInt(split[length-2]);
+// cursorY = Integer.parseInt(split[length-1]);
+// } catch (NumberFormatException e) {
+// throw new IOException("'" + pathNode + "' couldn't be read in line " + linenumber+": Something is not a number");
+// } catch (ArrayIndexOutOfBoundsException e) {
+// throw new IOException("'" + pathNode + "' couldn't be read in line " + linenumber+": Something is missing or is too much");
+// }
+// PathNode node = mouse.new PathNode();
+// for (int i=0; i serialiseHeader() {
+ List out = new ArrayList<>();
+ out.add(headerStart());
+ serialiseFlavorName(out);
+ serialiseFileCommandNames(out);
+ serialiseMetadata(out);
+ out.add(headerEnd());
+ return out;
+ }
+
+ protected void serialiseFlavorName(List out) {
+ out.add("Flavor: " + getExtensionName());
+ }
+
+ protected void serialiseFileCommandNames(List out) {
+ List stringlist = new ArrayList<>();
+ List extensionList = TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.getEnabled();
+ extensionList.forEach(extension -> stringlist.add(extension.getExtensionName()));
+ out.add("FileCommand-Extensions: " + String.join(", ", stringlist));
+ out.add("");
+ }
+
+ protected void serialiseMetadata(List out) {
+ List metadataList = TASmodAPIRegistry.PLAYBACK_METADATA.handleOnStore();
+
+ for (PlaybackMetadata metadata : metadataList) {
+ serialiseMetadataName(out, metadata.getExtensionName());
+ serialiseMetadataValue(out, metadata.getData());
+ out.add("");
+ }
+ }
+
+ protected void serialiseMetadataName(List out, String name) {
+ out.add(createCenteredHeading(name, '-', 50));
+ }
+
+ protected void serialiseMetadataValue(List out, LinkedHashMap data) {
+ data.forEach((key, value) -> {
+ out.add(String.format("%s:%s", key, value));
+ });
+ }
+
+ public BigArrayList serialise(BigArrayList inputs, long toTick) {
+ BigArrayList out = new BigArrayList<>();
+
+ for (int i = 0; i < inputs.size(); i++) {
+ if (toTick == i) {
+ break;
+ }
+ currentTick = i;
+ TickContainer container = inputs.get(i);
+ serialiseContainer(out, container);
+ previousTickContainer = container;
+ }
+ return out;
+ }
+
+ protected void serialiseContainer(BigArrayList out, TickContainer container) {
+ currentLine = out.size()-1;
+ List serialisedKeyboard = serialiseKeyboard(container.getKeyboard());
+ List serialisedMouse = serialiseMouse(container.getMouse());
+ List serialisedCameraAngle = serialiseCameraAngle(container.getCameraAngle());
+ pruneListEndEmpty(serialisedCameraAngle);
+
+ PlaybackFileCommandContainer fileCommandsInline = TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.handleOnSerialiseInline(currentTick, container);
+ PlaybackFileCommandContainer fileCommandsEndline = TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.handleOnSerialiseEndline(currentTick, container);
+
+ CommentContainer comments = container.getComments();
+ if (comments == null) {
+ comments = new CommentContainer(new ArrayList<>(), new ArrayList<>());
+ }
+ List serialisedInlineCommments = serialiseInlineComments(comments.getInlineComments(), fileCommandsInline.valuesBySubtick());
+ List serialisedEndlineComments = serialiseEndlineComments(comments.getEndlineComments(), fileCommandsEndline.valuesBySubtick());
+
+ addAll(out, serialisedInlineCommments);
+
+ mergeInputs(out, serialisedKeyboard, serialisedMouse, serialisedCameraAngle, serialisedEndlineComments);
+ }
+
+ protected String serialiseFileCommand(PlaybackFileCommand fileCommand) {
+ return String.format("$%s(%s);", fileCommand.getName(), String.join(", ", fileCommand.getArgs()));
+ }
+
+ protected String serialiseFileCommandsInLine(List fileCommands) {
+ if (fileCommands == null) {
+ return null;
+ }
+ List serialisedCommands = new ArrayList<>();
+ for (PlaybackFileCommand command : fileCommands) {
+ serialisedCommands.add(serialiseFileCommand(command));
+ }
+ return String.join(" ", serialisedCommands);
+ }
+
+ protected List serialiseKeyboard(VirtualKeyboard keyboard) {
+ List out = new ArrayList<>();
+
+ List subticks = new ArrayList<>(keyboard.getAll());
+ pruneListEndEmptySubtickable(subticks);
+
+ for (VirtualKeyboard subtick : subticks) {
+ out.add(subtick.toString2());
+ }
+ return out;
+ }
+
+ protected List serialiseMouse(VirtualMouse mouse) {
+ List out = new ArrayList<>();
+
+ List subticks = new ArrayList<>(mouse.getAll());
+ pruneListEndEmptySubtickable(subticks);
+
+ for (VirtualMouse subtick : subticks) {
+ out.add(subtick.toString2());
+ }
+ return out;
+ }
+
+ protected List serialiseCameraAngle(VirtualCameraAngle cameraAngle) {
+
+ VirtualCameraAngle previousCamera = null;
+ if(previousTickContainer != null) {
+ previousCamera = previousTickContainer.getCameraAngle();
+ }
+
+ List out = new ArrayList<>();
+ for (VirtualCameraAngle subtick : cameraAngle.getAll()) {
+
+ if(!subtick.equals(previousCamera))
+ out.add(subtick.toString2());
+
+ previousCamera = subtick;
+ }
+ return out;
+ }
+
+ protected List serialiseInlineComments(List inlineComments, List> fileCommandsInline) {
+ List out = new ArrayList<>();
+
+ Queue> fileCommandQueue = null;
+ if (fileCommandsInline != null) {
+ fileCommandQueue = new LinkedList<>(fileCommandsInline);
+ }
+
+ // Serialise comments and merge them with file commands
+ if (inlineComments != null) {
+
+ Queue commentQueue = new LinkedList<>(inlineComments);
+
+ // Iterate through comments
+ while (!commentQueue.isEmpty()) {
+ String comment = commentQueue.poll(); // Due to commentQueue being a LinkedList, comment can be null at this point!
+
+ String command = null;
+ if (fileCommandQueue != null) {
+ command = serialiseFileCommandsInLine(fileCommandQueue.poll()); // Trying to poll a fileCommand. Command can be null at this point
+ }
+
+ // Add an empty line if comment and command is null
+ if (comment == null && command == null) {
+ out.add("");
+ continue;
+ }
+
+ out.add(String.format("// %s", joinNotEmpty(" ", command, comment)));
+ }
+ }
+
+ if (fileCommandQueue != null) {
+
+ // If the fileCommandQueue is not empty or longer than the commentQueue,
+ // add the rest of the fileCommands to the end
+ while (!fileCommandQueue.isEmpty()) {
+
+ String command = serialiseFileCommandsInLine(fileCommandQueue.poll());
+ if (command != null) {
+ out.add(String.format("// %s", command));
+ } else {
+ out.add(""); // Add an empty line if command is null
+ }
+ }
+ }
+
+ return out;
+ }
+
+ protected List serialiseEndlineComments(List endlineComments, List> fileCommandsEndline) {
+ return serialiseInlineComments(endlineComments, fileCommandsEndline);
+ }
+
+ protected void mergeInputs(BigArrayList out, List serialisedKeyboard, List serialisedMouse, List serialisedCameraAngle, List serialisedEndlineComments) {
+ Queue keyboardQueue = new LinkedBlockingQueue<>(serialisedKeyboard);
+ Queue mouseQueue = new LinkedBlockingQueue<>(serialisedMouse);
+ Queue cameraAngleQueue = new LinkedBlockingQueue<>(serialisedCameraAngle);
+ Queue endlineQueue = new LinkedBlockingQueue<>(serialisedEndlineComments);
+
+ String kb = getOrEmpty(keyboardQueue.poll());
+ String ms = getOrEmpty(mouseQueue.poll());
+ String ca = getOrEmpty(cameraAngleQueue.poll());
+
+ String elc = getOrEmpty(endlineQueue.poll());
+ if (!elc.isEmpty()) {
+ elc = "\t\t" + elc;
+ }
+
+ out.add(String.format("%s|%s|%s|%s%s", currentTick, kb, ms, ca, elc));
+
+ currentSubtick = 0;
+ while (!keyboardQueue.isEmpty() || !mouseQueue.isEmpty() || !cameraAngleQueue.isEmpty()) {
+ currentSubtick++;
+ kb = getOrEmpty(keyboardQueue.poll());
+ ms = getOrEmpty(mouseQueue.poll());
+ ca = getOrEmpty(cameraAngleQueue.poll());
+
+ out.add(String.format("\t%s|%s|%s|%s", currentSubtick, kb, ms, ca));
+ }
+ currentSubtick = 0;
+ }
+
+ protected String getOrEmpty(String string) {
+ return string == null ? "" : string;
+ }
+
+ /**
+ * Joins strings together but ignores empty strings
+ *
+ * @param delimiter The delimiter of the joined string
+ * @param args The strings to join
+ * @return Joined string
+ */
+ protected String joinNotEmpty(String delimiter, Iterable args) {
+ String out = "";
+
+ List copy = new ArrayList<>();
+
+ args.forEach((arg) -> {
+ if (arg != null && !arg.isEmpty()) {
+ copy.add(arg);
+ }
+ });
+
+ out = String.join(delimiter, copy);
+
+ return out;
+ }
+
+ protected String joinNotEmpty(String delimiter, String... args) {
+ return joinNotEmpty(delimiter, Arrays.asList(args));
+ }
+
+ /*========================================================
+ _____ _ _ _
+ | __ \ (_) | (_)
+ | | | | ___ ___ ___ _ __ _ __ _| |_ ___ ___
+ | | | |/ _ \/ __|/ _ \ '__| |/ _` | | / __|/ _ \
+ | |__| | __/\__ \ __/ | | | (_| | | \__ \ __/
+ |_____/ \___||___/\___|_| |_|\__,_|_|_|___/\___|
+
+ ========================================================
+ *
+ */
+
+ public boolean deserialiseFlavorName(List headerLines) {
+ for (String line : headerLines) {
+ Matcher matcher = extract("^Flavor: " + getExtensionName(), line);
+
+ if (matcher.find()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void deserialiseHeader(List headerLines) {
+ deserialiseMetadata(headerLines);
+ deserialiseFileCommandNames(headerLines);
+ }
+
+ public List extractHeader(BigArrayList lines) {
+ List extracted = new ArrayList<>();
+
+ long maxExtract = 1000;
+
+ maxExtract = lines.size() < maxExtract ? lines.size() : maxExtract;
+
+ for (long i = 0; i < maxExtract; i++) {
+ String line = lines.get(i);
+ extracted.add(line);
+
+ if (line.equals(headerEnd()))
+ return extracted;
+ }
+ throw new PlaybackLoadException("Cannot find the end of the header");
+ }
+
+ protected void deserialiseFileCommandNames(List headerLines) {
+ for (String line : headerLines) {
+ Matcher matcher = extract("FileCommand-Extensions: ?(.*)", line);
+
+ if (matcher.find()) {
+ String extensionStrings = matcher.group(1);
+ String[] extensionNames = extensionStrings.split(", ?");
+
+ TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.setEnabled(Arrays.asList(extensionNames));
+ return;
+ }
+ }
+ throw new PlaybackLoadException("FileCommand-Extensions value was not found in the header");
+ }
+
+ protected void deserialiseMetadata(List headerLines) {
+ List out = new ArrayList<>();
+
+ String metadataName = null;
+ LinkedHashMap values = new LinkedHashMap<>();
+
+ for (String headerLine : headerLines) {
+
+ Matcher nameMatcher = extract("^-+ ([^-]+)", headerLine); // If the line starts with ###, an optional space char after and then capture the name
+ Matcher valueMatcher = extract("^([^#].*?):\\s*(.+)", headerLine); // If the line doesn't start with a #, then the key of the metadata, then a : then any or no number of whitespace chars, then the value of the metadata
+
+ if (nameMatcher.find()) {
+
+ if (metadataName != null && !metadataName.equals(nameMatcher.group(1))) { // If metadataName is null, then the first section begins
+ // If metadataName is different than the newMetadataName,
+ // then a new section begins and we first need to store the old.
+ out.add(PlaybackMetadata.fromHashMap(metadataName, values));
+ values.clear();
+ }
+ metadataName = nameMatcher.group(1).trim();
+ continue;
+
+ } else if (metadataName != null && valueMatcher.find()) {
+ values.put(valueMatcher.group(1).trim(), valueMatcher.group(2).trim());
+ }
+ }
+
+ if (metadataName != null)
+ out.add(PlaybackMetadata.fromHashMap(metadataName, values));
+
+ TASmodAPIRegistry.PLAYBACK_METADATA.handleOnLoad(out);
+ }
+
+ /**
+ * Deserialises the input part of the TASfile
+ *
+ * @param lines The serialised lines of the TASfile
+ * @param startPos The position when the header ends and the inputs start
+ * @return A list of {@link TickContainer}
+ */
+ public BigArrayList deserialise(BigArrayList lines, long startPos) {
+ BigArrayList out = new BigArrayList<>();
+ for (long i = startPos; i < lines.size(); i++) {
+ List container = new ArrayList<>();
+ // Extract the tick and set the index
+ i = extractContainer(container, lines, i);
+ currentLine = i;
+ currentTick++;
+ // Extract container
+ deserialiseContainer(out, container);
+ }
+ previousTickContainer = null;
+ return out;
+ }
+
+ protected enum ExtractPhases {
+ /**
+ * InlineComment phase.
+ *
+ *
+ * ---
+ * // This is a comment
+ * // $fileCommand();
+ *
+ * ---
+ *
+ *
+ * Empty lines also count as comments
+ */
+ COMMENTS,
+ /**
+ * Tick phase. Start with a number, then a | character
+ *
+ *
+ * ---
+ * 57|W,LCONTROL;w|;0,887,626|17.85;-202.74799
+ * ---
+ *
+ *
+ * Only one line should be in this phase
+ */
+ TICK,
+ /**
+ * Subtick phase. Start with a tabulator, then a number, then a | character
+ *
+ *
+ * --- 1||RC;0,1580,658|17.85;-202.74799\t\t// This is an endline comment
+ * 2||;0,1580,658|17.85;-202.74799 --- Can have multiple subticks
+ */
+ SUBTICK,
+ /**
+ * We are outside a tick
+ */
+ NONE
+ }
+
+ /**
+ *
+ * Extracts all the lines corresponding to one tick+subticks a.k.a one
+ * "container" from the incoming lines.
+ * The extracted ticks are easier to process than using a huge list.
+ *
+ * A container has multiple parts to it, that are split into
+ * {@link ExtractPhases}
+ * The container starts in {@link ExtractPhases#NONE}.
+ *
+ *
+ * --- {@link ExtractPhases#COMMENTS Comment phase} ---
+ * // This is a comment
+ * // $fileCommand();
+ * --- {@link ExtractPhases#TICK Tick phase} ---
+ * 57|W,LCONTROL;w|;0,887,626|17.85;-202.74799
+ * --- {@link ExtractPhases#SUBTICK Subtick phase} ---
+ * 1||RC;0,1580,658|17.85;-202.74799 // This is an endline comment
+ * 2||;0,1580,658|17.85;-202.74799
+ * ---------------------
+ *
+ *
+ * Logic
+ *
+ * - Phase: None
+ *
+ * - If a comment is found, set the phase to comment
+ * - If a tick is found, set the phase to tick
+ * - If a subtick is found, throw an error. Subticks always come after
+ * ticks
+ *
+ *
+ * - Phase: Comment
+ *
+ * - If a tick is found, set the phase to tick
+ * - If a subtick is found, throw an error. Subticks always come after
+ * ticks
+ *
+ *
+ * - Phase: Tick
+ *
+ * - If a subtick is found, set the phase to subticks
+ * - If a tick is found, end the extraction
+ * - If a comment is found, end the extraction
+ *
+ *
+ * - Phase: Subtick
+ *
+ * - If a tick is found, end the extraction
+ * - If a comment is found, end the extraction
+ *
+ *
+ *
+ *
+ * @param extracted The extracted lines, passed in by reference
+ * @param lines The line list
+ * @param startPos The start position of this tick
+ * @return The updated index for the next tick
+ */
+ protected long extractContainer(List extracted, BigArrayList lines, long startPos) {
+ ExtractPhases phase = ExtractPhases.NONE;
+
+ String commentRegex = "^//";
+ String tickRegex = "^\\d+\\|";
+ String subtickRegex = "^\t\\d+\\|";
+
+ long counter = 0L;
+ for (long i = startPos; i < lines.size(); i++) {
+ String line = lines.get(i);
+
+ switch (phase) {
+ case NONE:
+ if (contains(subtickRegex, line)) { // Subtick
+ throw new PlaybackLoadException(startPos + counter + 1, currentTick, currentSubtick, "Error while trying to parse the file. This should not be a subtick at this position");
+ }
+
+ if (contains(commentRegex, line) || line.isEmpty()) { // Comment
+ phase = ExtractPhases.COMMENTS;
+ } else if (contains(tickRegex, line)) { // Tick
+ phase = ExtractPhases.TICK;
+ }
+
+ break;
+ case COMMENTS:
+ if (contains(subtickRegex, line)) { // Subtick
+ throw new PlaybackLoadException(startPos + counter + 1, currentTick, currentSubtick, "Error while trying to parse the file. This should not be a subtick at this position");
+ }
+
+ if (contains(tickRegex, line)) { // Tick
+ phase = ExtractPhases.TICK;
+ }
+
+ break;
+ case TICK:
+ if (contains(subtickRegex, line)) { // Subtick
+ phase = ExtractPhases.SUBTICK;
+ }
+
+ if (contains(commentRegex, line) || contains(tickRegex, line) || line.isEmpty()) { // Comment
+ return startPos + counter - 1;
+ }
+
+ break;
+ case SUBTICK:
+ if (contains(commentRegex, line) || contains(tickRegex, line) || line.isEmpty()) { // Comment
+ return startPos + counter - 1;
+ }
+ break;
+ }
+ if (phase != ExtractPhases.NONE) {
+ extracted.add(line);
+ }
+ counter++;
+ }
+ return startPos + counter - 1;
+ }
+
+ protected void deserialiseContainer(BigArrayList out, List containerLines) {
+
+ List inlineComments = new ArrayList<>();
+ List tickLines = new ArrayList<>();
+ List> inlineFileCommands = new ArrayList<>();
+ splitContainer(containerLines, inlineComments, tickLines, inlineFileCommands);
+
+ List keyboardStrings = new ArrayList<>();
+ List mouseStrings = new ArrayList<>();
+ List cameraAngleStrings = new ArrayList<>();
+ List endlineComments = new ArrayList<>();
+ List> endlineFileCommands = new ArrayList<>();
+
+ splitInputs(containerLines, keyboardStrings, mouseStrings, cameraAngleStrings, endlineComments, endlineFileCommands);
+
+ pruneListEndNull(endlineComments);
+
+ VirtualKeyboard keyboard = deserialiseKeyboard(keyboardStrings);
+ VirtualMouse mouse = deserialiseMouse(mouseStrings);
+ VirtualCameraAngle cameraAngle = deserialiseCameraAngle(cameraAngleStrings);
+ CommentContainer comments = new CommentContainer(inlineComments, endlineComments);
+
+ TickContainer deserialisedContainer = new TickContainer(keyboard, mouse, cameraAngle, comments);
+
+ TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.handleOnDeserialiseInline(currentTick, deserialisedContainer, inlineFileCommands);
+ TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.handleOnDeserialiseEndline(currentTick, deserialisedContainer, endlineFileCommands);
+
+ previousTickContainer = deserialisedContainer;
+
+ out.add(deserialisedContainer);
+ }
+
+ /**
+ * Splits lines into comments and ticks.
+ *
+ * @param lines
+ */
+ protected void splitContainer(List lines, List