diff --git a/src/main/java/com/minecrafttas/mctcommon/registry/Registerable.java b/src/main/java/com/minecrafttas/mctcommon/registry/Registerable.java index 226179a6..a7b49278 100644 --- a/src/main/java/com/minecrafttas/mctcommon/registry/Registerable.java +++ b/src/main/java/com/minecrafttas/mctcommon/registry/Registerable.java @@ -1,5 +1,14 @@ package com.minecrafttas.mctcommon.registry; +/** + * Interface for declaring that a class can be registered by a class of type {@link AbstractRegistry} + * + * @author Scribble + */ public interface Registerable { + + /** + * @return The name of the extension that is registered + */ public String getExtensionName(); } \ No newline at end of file diff --git a/src/main/java/com/minecrafttas/tasmod/TASmod.java b/src/main/java/com/minecrafttas/tasmod/TASmod.java index a495dfad..c0ead5bd 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmod.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmod.java @@ -26,7 +26,7 @@ import com.minecrafttas.tasmod.commands.CommandTickrate; import com.minecrafttas.tasmod.commands.TabCompletionUtils; import com.minecrafttas.tasmod.playback.PlaybackControllerServer; -import com.minecrafttas.tasmod.playback.metadata.integrated.StartpositionMetadataExtension; +import com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.SavestateHandlerServer; import com.minecrafttas.tasmod.savestates.storage.SavestateMotionStorage; diff --git a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java index 1d6a854a..ffd81dae 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java @@ -25,12 +25,13 @@ import com.minecrafttas.tasmod.handlers.LoadingScreenHandler; import com.minecrafttas.tasmod.playback.PlaybackControllerClient; import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; -import com.minecrafttas.tasmod.playback.filecommands.integrated.DesyncMonitorFileCommandExtension; -import com.minecrafttas.tasmod.playback.filecommands.integrated.LabelFileCommandExtension; -import com.minecrafttas.tasmod.playback.filecommands.integrated.OptionsFileCommandExtension; -import com.minecrafttas.tasmod.playback.metadata.integrated.CreditsMetadataExtension; -import com.minecrafttas.tasmod.playback.metadata.integrated.StartpositionMetadataExtension; -import com.minecrafttas.tasmod.playback.tasfile.flavor.integrated.Beta1Flavor; +import com.minecrafttas.tasmod.playback.filecommands.builtin.DesyncMonitorFileCommandExtension; +import com.minecrafttas.tasmod.playback.filecommands.builtin.LabelFileCommandExtension; +import com.minecrafttas.tasmod.playback.filecommands.builtin.OptionsFileCommandExtension; +import com.minecrafttas.tasmod.playback.metadata.builtin.CreditsMetadataExtension; +import com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension; +import com.minecrafttas.tasmod.playback.tasfile.flavor.builtin.AlphaFlavor; +import com.minecrafttas.tasmod.playback.tasfile.flavor.builtin.Beta1Flavor; import com.minecrafttas.tasmod.registries.TASmodAPIRegistry; import com.minecrafttas.tasmod.registries.TASmodConfig; import com.minecrafttas.tasmod.registries.TASmodKeybinds; @@ -312,9 +313,11 @@ private void registerPlaybackMetadata(Minecraft mc) { } public static Beta1Flavor betaFlavor = new Beta1Flavor(); + public static AlphaFlavor alphaFlavor = new AlphaFlavor(); private void registerSerialiserFlavors(Minecraft mc) { TASmodAPIRegistry.SERIALISER_FLAVOR.register(betaFlavor); + TASmodAPIRegistry.SERIALISER_FLAVOR.register(alphaFlavor); } public static DesyncMonitorFileCommandExtension desyncMonitorFileCommandExtension; diff --git a/src/main/java/com/minecrafttas/tasmod/gui/InfoHud.java b/src/main/java/com/minecrafttas/tasmod/gui/InfoHud.java index f2008f90..da37107f 100644 --- a/src/main/java/com/minecrafttas/tasmod/gui/InfoHud.java +++ b/src/main/java/com/minecrafttas/tasmod/gui/InfoHud.java @@ -19,7 +19,7 @@ import com.minecrafttas.tasmod.TASmodClient; import com.minecrafttas.tasmod.events.EventClient.EventDrawHotbar; import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; -import com.minecrafttas.tasmod.playback.filecommands.integrated.DesyncMonitorFileCommandExtension; +import com.minecrafttas.tasmod.playback.filecommands.builtin.DesyncMonitorFileCommandExtension; import com.mojang.realmsclient.gui.ChatFormatting; import net.minecraft.client.Minecraft; diff --git a/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerClient.java b/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerClient.java index 08cca459..683f5473 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerClient.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerClient.java @@ -580,7 +580,7 @@ public void setPlayUntil(int until) { // ============================================================== /** - * Storage class which stores the keyboard, mouse and subticks of a given tick. + * Storage class which stores the keyboard, mouse, subticks and comments of a given tick. * * @author Scribble * @@ -628,7 +628,13 @@ public VirtualCameraAngle getCameraAngle() { return cameraAngle; } + /** + * @return The comment container of this controller. If {@link #comments} is null, returns an empty container. + */ public CommentContainer getComments() { + if (comments == null) { + return new CommentContainer(); + } return comments; } @@ -647,6 +653,11 @@ public boolean equals(Object other) { } } + /** + * Storage class for storing {@link CommentContainer#inlineComments inline} and {@link CommentContainer#endlineComments endline} comments + * + * @author Scribble + */ public static class CommentContainer implements Serializable { /** @@ -835,6 +846,7 @@ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws if (mc.world != null) mc.ingameGUI.getChatGUI().printChatMessage(new TextComponentString(TextFormatting.RED + "Loading failed, something went very wrong")); LOGGER.catching(e); + return; } if (mc.world != null) diff --git a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommand.java b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommand.java index 21a66872..a34beee4 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommand.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommand.java @@ -59,7 +59,7 @@ public static abstract class PlaybackFileCommandExtension implements Registerabl protected final Path tempDir; public PlaybackFileCommandExtension() { - this(null); + this((Path) null); } /** @@ -69,13 +69,18 @@ public PlaybackFileCommandExtension() { * @param tempFolderName The name of the temp folder */ public PlaybackFileCommandExtension(String tempFolderName) { - if (tempFolderName == null) { + this(TASmodClient.tasfiledirectory.resolve("temp").resolve(tempFolderName)); + } + + public PlaybackFileCommandExtension(Path tempDirectory) { + if (tempDirectory == null) { tempDir = null; return; } - this.tempDir = TASmodClient.tasfiledirectory.resolve("temp").resolve(tempFolderName); + + tempDir = tempDirectory; try { - AbstractDataFile.createDirectory(tempDir); + AbstractDataFile.createDirectory(tempDirectory); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommandsRegistry.java b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommandsRegistry.java index f5fd3c3f..29d472df 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommandsRegistry.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommandsRegistry.java @@ -16,9 +16,9 @@ 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<>()); } @@ -38,16 +38,16 @@ public void unregister(PlaybackFileCommandExtension extension) { 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) { + if (extension == null) { return false; } extension.setEnabled(enabled); enabledExtensions = getEnabled(); - - if(saveToConfig) { + + if (saveToConfig) { saveConfig(); } return true; @@ -59,16 +59,20 @@ private void disableAll() { }); } + public void setEnabled(String... extensionNames) { + setEnabled(Arrays.asList(extensionNames)); + } + 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) + if (saveToConfig) saveConfig(); } @@ -83,24 +87,24 @@ public List getEnabled() { return out; } - - public List getAll(){ + + public List getAll() { return new ArrayList<>(REGISTRY.values()); } - + @Override public void onRecordTick(long index, TickContainer container) { enabledExtensions.forEach(extension -> { - if(extension.isEnabled()) { + if (extension.isEnabled()) { extension.onRecord(index, container); } }); } - + @Override public void onPlaybackTick(long index, TickContainer container) { enabledExtensions.forEach(extension -> { - if(extension.isEnabled()) { + if (extension.isEnabled()) { extension.onPlayback(index, container); } }); @@ -109,8 +113,8 @@ public void onPlaybackTick(long index, TickContainer 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) { + PlaybackFileCommandContainer extensionContainer = extension.onSerialiseInlineComment(currentTick, container); + if (extensionContainer != null) { out.putAll(extensionContainer); } } @@ -120,8 +124,8 @@ public PlaybackFileCommandContainer handleOnSerialiseInline(long currentTick, Ti public PlaybackFileCommandContainer handleOnSerialiseEndline(long currentTick, TickContainer container) { PlaybackFileCommandContainer out = new PlaybackFileCommandContainer(); for (PlaybackFileCommandExtension extension : enabledExtensions) { - PlaybackFileCommandContainer extensionContainer=extension.onSerialiseEndlineComment(currentTick, container); - if(extensionContainer!=null) { + PlaybackFileCommandContainer extensionContainer = extension.onSerialiseEndlineComment(currentTick, container); + if (extensionContainer != null) { out.putAll(extensionContainer); } } @@ -155,7 +159,7 @@ public void setConfig(Configuration config) { this.config = config; loadConfig(); } - + private void loadConfig() { if (config == null) { return; @@ -163,14 +167,14 @@ private void loadConfig() { 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 ->{ + List nameList = new ArrayList<>(); + + enabledExtensions.forEach(element -> { nameList.add(element.getExtensionName()); }); config.set(TASmodConfig.EnabledFileCommands, String.join(", ", nameList)); diff --git a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/DesyncMonitorFileCommandExtension.java b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/builtin/DesyncMonitorFileCommandExtension.java similarity index 94% rename from src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/DesyncMonitorFileCommandExtension.java rename to src/main/java/com/minecrafttas/tasmod/playback/filecommands/builtin/DesyncMonitorFileCommandExtension.java index 045e377a..ae6c25e7 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/DesyncMonitorFileCommandExtension.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/builtin/DesyncMonitorFileCommandExtension.java @@ -1,7 +1,8 @@ -package com.minecrafttas.tasmod.playback.filecommands.integrated; +package com.minecrafttas.tasmod.playback.filecommands.builtin; import java.io.IOException; import java.io.Serializable; +import java.nio.file.Path; import java.text.NumberFormat; import java.text.ParseException; import java.util.List; @@ -40,12 +41,22 @@ public class DesyncMonitorFileCommandExtension extends PlaybackFileCommandExtens private MonitorContainer currentValues; public DesyncMonitorFileCommandExtension() { - super("monitoring"); + this("monitoring"); + } + + public DesyncMonitorFileCommandExtension(String tempDirName) { + super(tempDirName); this.monitorContainer = new BigArrayList(tempDir.toString()); // Is enabled by default enabled = true; } + public DesyncMonitorFileCommandExtension(Path tempDir) { + super(tempDir); + this.monitorContainer = new BigArrayList<>(tempDir.toString()); + enabled = true; + } + @Override public String getExtensionName() { return "tasmod_desyncMonitor@v1"; @@ -124,7 +135,7 @@ public void onPlayback(long tick, TickContainer tickContainer) { private MonitorContainer loadFromFile(long tick, String[] args) throws PlaybackLoadException { if (args.length != 6) - throw new PlaybackLoadException("Tick %s: desyncMonitorArgsLength "); + throw new PlaybackLoadException("Could not load desyncMonitor file command in tick %s. The amount of arguments doesn't match: %s", tick, args.length); double x = 0; double y = 0; @@ -349,9 +360,9 @@ public void onClear() { } catch (IOException e) { e.printStackTrace(); } - monitorContainer = new BigArrayList(tempDir.toString()); + monitorContainer = new BigArrayList(); lastStatus = TextFormatting.GRAY + "Empty"; lastPos = ""; lastMotion = ""; } -} +} \ No newline at end of file diff --git a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/LabelFileCommandExtension.java b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/builtin/LabelFileCommandExtension.java similarity index 73% rename from src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/LabelFileCommandExtension.java rename to src/main/java/com/minecrafttas/tasmod/playback/filecommands/builtin/LabelFileCommandExtension.java index 97a523f3..d2f880b5 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/LabelFileCommandExtension.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/builtin/LabelFileCommandExtension.java @@ -1,6 +1,7 @@ -package com.minecrafttas.tasmod.playback.filecommands.integrated; +package com.minecrafttas.tasmod.playback.filecommands.builtin; import java.io.IOException; +import java.nio.file.Path; import com.dselent.bigarraylist.BigArrayList; import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TickContainer; @@ -16,7 +17,17 @@ public class LabelFileCommandExtension extends PlaybackFileCommandExtension { BigArrayList label = new BigArrayList<>(); public LabelFileCommandExtension() { - super("label"); + this("label"); + } + + public LabelFileCommandExtension(String tempDirName) { + super(tempDirName); + this.label = new BigArrayList<>(tempDir.toString()); + enabled = true; + } + + public LabelFileCommandExtension(Path tempDir) { + super(tempDir); this.label = new BigArrayList<>(tempDir.toString()); enabled = true; } @@ -31,6 +42,15 @@ public String[] getFileCommandNames() { return new String[] { "label" }; } + @Override + public PlaybackFileCommandContainer onSerialiseInlineComment(long tick, TickContainer tickContainer) { + PlaybackFileCommandContainer fileCommandContainer = new PlaybackFileCommandContainer(); + if (label.size() != 0 && label.get(tick).get("label") != null) { + fileCommandContainer = label.get(tick); + } + return fileCommandContainer; + } + @Override public void onDeserialiseInlineComment(long tick, TickContainer container, PlaybackFileCommandContainer fileCommandContainer) { if (fileCommandContainer.containsKey("label")) { diff --git a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/OptionsFileCommandExtension.java b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/builtin/OptionsFileCommandExtension.java similarity index 73% rename from src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/OptionsFileCommandExtension.java rename to src/main/java/com/minecrafttas/tasmod/playback/filecommands/builtin/OptionsFileCommandExtension.java index 2a1a5ef1..8923ab2b 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/integrated/OptionsFileCommandExtension.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/builtin/OptionsFileCommandExtension.java @@ -1,6 +1,7 @@ -package com.minecrafttas.tasmod.playback.filecommands.integrated; +package com.minecrafttas.tasmod.playback.filecommands.builtin; import java.io.IOException; +import java.nio.file.Path; import com.dselent.bigarraylist.BigArrayList; import com.minecrafttas.tasmod.TASmod; @@ -18,11 +19,21 @@ public class OptionsFileCommandExtension extends PlaybackFileCommandExtension { BigArrayList hud; public OptionsFileCommandExtension() { - super("hud"); + this("hud"); + } + + public OptionsFileCommandExtension(String tempDirName) { + super(tempDirName); hud = new BigArrayList<>(tempDir.toString()); enabled = true; } + public OptionsFileCommandExtension(Path tempDir) { + super(tempDir); + this.hud = new BigArrayList<>(tempDir.toString()); + enabled = true; + } + @Override public String getExtensionName() { return "tasmod_options@v1"; @@ -33,6 +44,15 @@ public String[] getFileCommandNames() { return new String[] { "hud" }; } + @Override + public PlaybackFileCommandContainer onSerialiseInlineComment(long tick, TickContainer tickContainer) { + PlaybackFileCommandContainer fileCommandContainer = new PlaybackFileCommandContainer(); + if (hud.size() != 0 && hud.get(tick).get("hud") != null) { + fileCommandContainer = hud.get(tick); + } + return fileCommandContainer; + } + @Override public void onDeserialiseInlineComment(long tick, TickContainer container, PlaybackFileCommandContainer fileCommandContainer) { if (fileCommandContainer.containsKey("hud")) { @@ -58,6 +78,11 @@ public void onPlayback(long tick, TickContainer tickContainer) { for (PlaybackFileCommand command : line) { String[] args = command.getArgs(); if (args.length == 1) { + /* + * Ok this may seem dumb, but Boolean.parseBoolean returns false, + * even if something other then true or false was passed... + * If someone finds something less idiotic please tell me... + */ switch (args[0]) { case "true": shouldRenderHud = true; 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 fe4da30c..aada9d18 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/metadata/PlaybackMetadata.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/metadata/PlaybackMetadata.java @@ -23,12 +23,12 @@ public PlaybackMetadata(PlaybackMetadataExtension extension) { this(extension.getExtensionName()); } - private PlaybackMetadata(String extensionName) { + public PlaybackMetadata(String extensionName) { this.extensionName = extensionName; this.data = new LinkedHashMap(); } - private PlaybackMetadata(String extensionName, LinkedHashMap data) { + public PlaybackMetadata(String extensionName, LinkedHashMap data) { this.extensionName = extensionName; this.data = data; } @@ -37,10 +37,18 @@ public void setValue(String key, String value) { data.put(key, value); } + public void setValue(Object key, String value) { + setValue(key.toString(), value); + } + public String getValue(String key) { return data.get(key); } + public String getValue(Object key) { + return getValue(key.toString()); + } + @Override public String toString() { String out = ""; diff --git a/src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/CreditsMetadataExtension.java b/src/main/java/com/minecrafttas/tasmod/playback/metadata/builtin/CreditsMetadataExtension.java similarity index 69% rename from src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/CreditsMetadataExtension.java rename to src/main/java/com/minecrafttas/tasmod/playback/metadata/builtin/CreditsMetadataExtension.java index a7eec12d..c1e7275b 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/CreditsMetadataExtension.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/metadata/builtin/CreditsMetadataExtension.java @@ -1,4 +1,4 @@ -package com.minecrafttas.tasmod.playback.metadata.integrated; +package com.minecrafttas.tasmod.playback.metadata.builtin; import static com.minecrafttas.tasmod.TASmod.LOGGER; @@ -25,24 +25,24 @@ public class CreditsMetadataExtension extends PlaybackMetadataExtension implemen /** * The title/category of the TAS (e.g. KillSquid - Any% Glitched) */ - private String title = "Insert TAS category here"; + protected String title = "Insert TAS category here"; /** * The author(s) of the TAS (e.g. Scribble, Pancake) */ - private String authors = "Insert author here"; + protected String authors = "Insert author here"; /** * How long the TAS is going to take (e.g. 00:01.0 or 20ticks) */ - private String playtime = "00:00.0"; + protected String playtime = "00:00.0"; /** * How often a savestate was loaded as a measurement of effort (e.g. 200) */ - private int rerecords = 0; + protected int rerecords = 0; /** * If the credits where already printed in this instance */ - private boolean creditsPrinted = false; + protected boolean creditsPrinted = false; @Override public String getExtensionName() { @@ -54,29 +54,51 @@ public void onCreate() { // Unused atm } + public enum CreditFields { + Title("Title"), + Author("Author"), + PlayTime("Playing Time"), + Rerecords("Rerecords"); + + private final String name; + + private CreditFields(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } + @Override public PlaybackMetadata onStore() { PlaybackMetadata metadata = new PlaybackMetadata(this); - metadata.setValue("Title", title); - metadata.setValue("Author", authors); - metadata.setValue("Playing Time", playtime); - metadata.setValue("Rerecords", Integer.toString(rerecords)); + metadata.setValue(CreditFields.Title, title); + metadata.setValue(CreditFields.Author, authors); + metadata.setValue(CreditFields.PlayTime, playtime); + metadata.setValue(CreditFields.Rerecords, Integer.toString(rerecords)); return metadata; } @Override public void onLoad(PlaybackMetadata metadata) { - title = metadata.getValue("Title"); - authors = metadata.getValue("Author"); - playtime = metadata.getValue("Playing Time"); + title = getOrDefault(metadata.getValue(CreditFields.Title), title); + authors = getOrDefault(metadata.getValue(CreditFields.Author), authors); + playtime = getOrDefault(metadata.getValue(CreditFields.PlayTime), playtime); try { - rerecords = Integer.parseInt(metadata.getValue("Rerecords")); + rerecords = Integer.parseInt(getOrDefault(metadata.getValue(CreditFields.Rerecords), Integer.toString(rerecords))); } catch (NumberFormatException e) { rerecords = 0; throw new PlaybackLoadException(e); } } + protected String getOrDefault(String value, String defaultVal) { + return value != null ? value : defaultVal; + } + @Override public void onClear() { title = "Insert TAS category here"; @@ -101,7 +123,7 @@ public void onPlaybackJoinedWorld(TASstate state) { } } - private void printMessage(String msg, TextFormatting format) { + protected void printMessage(String msg, TextFormatting format) { String formatString = ""; if (format != null) formatString = format.toString(); diff --git a/src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/StartpositionMetadataExtension.java b/src/main/java/com/minecrafttas/tasmod/playback/metadata/builtin/StartpositionMetadataExtension.java similarity index 78% rename from src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/StartpositionMetadataExtension.java rename to src/main/java/com/minecrafttas/tasmod/playback/metadata/builtin/StartpositionMetadataExtension.java index 1534db65..1e07763e 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/metadata/integrated/StartpositionMetadataExtension.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/metadata/builtin/StartpositionMetadataExtension.java @@ -1,4 +1,4 @@ -package com.minecrafttas.tasmod.playback.metadata.integrated; +package com.minecrafttas.tasmod.playback.metadata.builtin; import static com.minecrafttas.tasmod.TASmod.LOGGER; @@ -36,15 +36,15 @@ public class StartpositionMetadataExtension extends PlaybackMetadataExtension im /** * The startposition of the playback */ - StartPosition startPosition = null; + protected StartPosition startPosition = null; public static class StartPosition { - final double x; - final double y; - final double z; - final float pitch; - final float yaw; + public final double x; + public final double y; + public final double z; + public final float pitch; + public final float yaw; public StartPosition(double x, double y, double z, float pitch, float yaw) { this.x = x; @@ -70,6 +70,25 @@ public void onCreate() { // Unused atm } + public enum StartPositionFields { + X("x"), + Y("y"), + Z("z"), + Pitch("pitch"), + Yaw("yaw"); + + private final String name; + + private StartPositionFields(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } + @Override public PlaybackMetadata onStore() { PlaybackMetadata metadata = new PlaybackMetadata(this); @@ -78,25 +97,29 @@ public PlaybackMetadata onStore() { if (this.startPosition != null) { startPositionToStore = startPosition; } - metadata.setValue("x", Double.toString(startPositionToStore.x)); - metadata.setValue("y", Double.toString(startPositionToStore.y)); - metadata.setValue("z", Double.toString(startPositionToStore.z)); - metadata.setValue("pitch", Float.toString(startPositionToStore.pitch)); - metadata.setValue("yaw", Float.toString(startPositionToStore.yaw)); + metadata.setValue(StartPositionFields.X, Double.toString(startPositionToStore.x)); + metadata.setValue(StartPositionFields.Y, Double.toString(startPositionToStore.y)); + metadata.setValue(StartPositionFields.Z, Double.toString(startPositionToStore.z)); + metadata.setValue(StartPositionFields.Pitch, Float.toString(startPositionToStore.pitch)); + metadata.setValue(StartPositionFields.Yaw, Float.toString(startPositionToStore.yaw)); return metadata; } @Override public void onLoad(PlaybackMetadata metadata) { - 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); + double x = getDouble(StartPositionFields.X, metadata); + double y = getDouble(StartPositionFields.Y, metadata); + double z = getDouble(StartPositionFields.Z, metadata); + float pitch = getFloat(StartPositionFields.Pitch, metadata); + float yaw = getFloat(StartPositionFields.Yaw, metadata); this.startPosition = new StartPosition(x, y, z, pitch, yaw); } + private double getDouble(Object key, PlaybackMetadata metadata) { + return getDouble(key.toString(), metadata); + } + private double getDouble(String key, PlaybackMetadata metadata) { String out = metadata.getValue(key); if (out != null) { @@ -110,6 +133,10 @@ private double getDouble(String key, PlaybackMetadata metadata) { } } + private float getFloat(Object key, PlaybackMetadata metadata) { + return getFloat(key.toString(), metadata); + } + private float getFloat(String key, PlaybackMetadata metadata) { String out = metadata.getValue(key); if (out != null) { @@ -158,7 +185,7 @@ public void updateStartPosition() { if (player != null) startPosition = new StartPosition(player.posX, player.posY, player.posZ, player.rotationPitch, player.rotationYaw); else - LOGGER.warn("Start position not set, the player was null! This will make problems when storing inputs!"); + LOGGER.warn("Start position not set, the player was null! This will create problems when storing inputs!"); } 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 8a03969a..a110cbad 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiser.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiser.java @@ -177,7 +177,7 @@ public static BigArrayList loadFromFile(Path file, String flavorN // 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"); + throw new PlaybackLoadException("Detected flavor %s in the TASfile, which does not match the specified flavor: %s", flavorInFile.getExtensionName(), flavor.getExtensionName()); } flavor.setProcessExtensions(processExtensions); @@ -234,7 +234,7 @@ public static BigArrayList loadFromFile(Path file, SerialiserFlav */ public static SerialiserFlavorBase searchForFlavor(List lines, List flavorList) { for (SerialiserFlavorBase flavor : flavorList) { - if (flavor.deserialiseFlavorName(lines)) { + if (flavor.checkFlavorName(lines)) { return flavor.clone(); } } diff --git a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiserOld.java b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiserOld.java index 79e2805c..7aac7671 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiserOld.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/PlaybackSerialiserOld.java @@ -14,7 +14,7 @@ import com.dselent.bigarraylist.BigArrayList; 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.playback.filecommands.builtin.DesyncMonitorFileCommandExtension; import com.minecrafttas.tasmod.util.FileThread; import com.minecrafttas.tasmod.util.LoggerMarkers; import com.minecrafttas.tasmod.virtual.VirtualCameraAngle; diff --git a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/SerialiserFlavorBase.java b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/SerialiserFlavorBase.java index 592bcf69..cd1d48a7 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/SerialiserFlavorBase.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/SerialiserFlavorBase.java @@ -10,6 +10,9 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; import com.dselent.bigarraylist.BigArrayList; import com.minecrafttas.mctcommon.registry.Registerable; @@ -19,6 +22,7 @@ import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandContainer; import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandExtension; import com.minecrafttas.tasmod.playback.metadata.PlaybackMetadata; +import com.minecrafttas.tasmod.playback.tasfile.PlaybackSerialiser; import com.minecrafttas.tasmod.playback.tasfile.exception.PlaybackLoadException; import com.minecrafttas.tasmod.registries.TASmodAPIRegistry; import com.minecrafttas.tasmod.virtual.Subtickable; @@ -27,12 +31,60 @@ import com.minecrafttas.tasmod.virtual.VirtualKeyboard; import com.minecrafttas.tasmod.virtual.VirtualMouse; +/** + *

The base class of a flavor + * + *

All serialisation and deserialisation is broken apart into functions whenever possible,
+ * with the intention of allowing small changes to the existing syntax. + * + *

Adding functionality to playback should be made via {@link PlaybackFileCommand PlaybackFileCommands}
+ * instead of creating a new syntax and adding new information to the header should be made via {@link PlaybackMetadata} + * + *

Sections

+ *

The TASfile has 2 main sections, which are called seperately by the {@link PlaybackSerialiser}: + * + *

    + *
  1. + * Header
    + * Contains metadata about this TAS, like credits and start position,
    + * but also a list of enabled extensions and the name of the flavor that was used to encode the file. + *
  2. + *
  3. + * Content
    + * Contains the actual inputs per tick, inputs in a subtick (a.k.a in a frame), comments and other extensions. + *
  4. + *
+ * + * Both sections have serialise and deserialise methods: + * + *
    + *
  • Serialisation + *
      + *
    • {@link #serialiseHeader()}
    • + *
    • {@link #serialise(BigArrayList, long)}
    • + *
    + *
  • + *
  • Deserialisation + *
      + *
    • {@link #deserialiseHeader(List)}
    • + *
    • {@link #deserialise(BigArrayList, long)}
    • + *
    + *
  • + *
+ * + * Clicking on either of these will lead you to a breakdown in their respective javadocs + * + * @author Scribble + */ public abstract class SerialiserFlavorBase implements Registerable { + /** + * The current line that is being serialised or deserialised. Used for debugging + */ protected long currentLine = 1; /** - * The current tick that is being serialised or deserialised + * The current tick that is being serialised or deserialised. Used for debugging */ protected long currentTick = 0; @@ -41,6 +93,9 @@ public abstract class SerialiserFlavorBase implements Registerable { */ protected int currentSubtick = 0; + /** + * Previous serialised or deserialised container, used for allowing relative values in {@link #deserialiseRelativeFloat(String, String, Float) deserialiseRelativeFloat} + */ protected TickContainer previousTickContainer = null; /** @@ -48,25 +103,6 @@ public abstract class SerialiserFlavorBase implements Registerable { */ protected boolean processExtensions = true; - protected String headerStart() { - return createCenteredHeading("TASfile", '#', 50); - } - - /** - * @return The regex used for detecting comment lines - */ - protected String singleComment() { - return "^//"; - } - - protected String endlineComment() { - return "(//.+)"; - } - - protected String headerEnd() { - return createPaddedString('#', 50); - } - /*============================================== _____ _ _ _ / ____| (_) | (_) @@ -77,6 +113,73 @@ protected String headerEnd() { ==============================================*/ + /* + _ _ ____ __ ____ ____ ____ + ( )_( )( ___) /__\ ( _ \( ___)( _ \ + ) _ ( )__) /(__)\ )(_) ))__) ) / + (_) (_)(____)(__)(__)(____/(____)(_)\_) + + */ + + /** + *
Example
+ *
+	 * ##################### TASfile ####################
+	 * 
+ * @return The very top of the header + */ + protected String headerStart() { + return createCenteredHeading("TASfile", '#', 50); + } + + /** + *

The end of the header, used for detecting when the header stops + *

Example
+ *
+	 * ##################################################
+	 * 
+ * @return The end of the header + */ + protected String headerEnd() { + return createPaddedString('#', 50); + } + + /** + *

Serialises the flavor of this file, the enabled file commands and other metadata + *

Tree
+ *
+	 * serialiseHeader
+	 *	├── {@link #headerStart()}
+	 *	├── {@link #serialiseFlavorName(List)}
+	 *	├── {@link #serialiseFileCommandNames(List)}
+	 *	├── {@link #serialiseMetadata(List)}
+	 *	│   ├── {@link #serialiseMetadataName(List, String)}
+	 *	│   └── {@link #serialiseMetadataValues(List, LinkedHashMap)}
+	 *	└── {@link #headerEnd()}
+	 * 
+ *
Example
+ *
+	 * ##################### TASfile ####################					// {@link #headerStart()}
+	 * Flavor: beta1 										// {@link #serialiseFlavorName(List)}
+	 * FileCommand-Extensions: tasmod_desyncMonitor@v1, tasmod_options@v1, tasmod_label@v1	// {@link #serialiseFileCommandNames(List)}
+	 * 
+	 * --------------------- Credits -------------------- 					// {@link #serialiseMetadataName(List, String)}
+	 * Title:Insert TAS category here 							// {@link #serialiseMetadataValues(List, LinkedHashMap)}
+	 * Author:Insert author here
+	 * Playing Time:00:00.0
+	 * Rerecords:0
+	 * 
+	 * ----------------- Start Position -----------------
+	 * x:-32.577311363268976
+	 * y:56.0
+	 * z:-4.457057187505265
+	 * pitch:29.25007
+	 * yaw:-88.80094
+	 * 
+	 * ##################################################					// {@link #headerEnd()}
+	 * 
+ * @return List of lines containing the header + */ public List serialiseHeader() { List out = new ArrayList<>(); out.add(headerStart()); @@ -87,10 +190,29 @@ public List serialiseHeader() { return out; } + /** + *

How the flavor name is serialised + *

You normally don't have to edit this,
+ * as the flavor name is taken from the extension name. + *

Example
+ *
+	 * Flavor: beta1
+	 * 
+ * + * @param out The serialised lines, passed by reference + */ protected void serialiseFlavorName(List out) { out.add("Flavor: " + getExtensionName()); } + /** + *

Adds the file commands that are enabled to the lines + *

Example
+ *
+	 * FileCommand-Extensions: tasmod_label@v1, tasmod_desyncMonitor@v1
+	 * 
+ * @param out The serialised lines, passed by reference + */ protected void serialiseFileCommandNames(List out) { List stringlist = new ArrayList<>(); List extensionList = TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.getEnabled(); @@ -101,6 +223,25 @@ protected void serialiseFileCommandNames(List out) { out.add(""); } + /** + *

Serialises the metadata to the header of the TASfile + *

Example
+ *
+	 * --------------------- Credits --------------------
+	 * Title:Insert TAS category here
+	 * Author:Insert author here
+	 * Playing Time:00:00.0
+	 * Rerecords:0
+	 * 
+	 * ----------------- Start Position -----------------
+	 * x:-32.577311363268976
+	 * y:56.0
+	 * z:-4.457057187505265
+	 * pitch:29.25007
+	 * yaw:-88.80094
+	 * 
+ * @param out + */ protected void serialiseMetadata(List out) { if (!processExtensions) return; @@ -109,21 +250,76 @@ protected void serialiseMetadata(List out) { for (PlaybackMetadata metadata : metadataList) { serialiseMetadataName(out, metadata.getExtensionName()); - serialiseMetadataValue(out, metadata.getData()); + serialiseMetadataValues(out, metadata.getData()); out.add(""); } } + /** + *

Serialises only the name of the metadata section + *

Example
+ *
+	 * --------------------- Credits --------------------
+	 * 
+ * @param out The lines passed in by reference + * @param name The name to process + */ protected void serialiseMetadataName(List out, String name) { out.add(createCenteredHeading(name, '-', 50)); } - protected void serialiseMetadataValue(List out, LinkedHashMap data) { + /** + *

Serialises only the values of the metadata section + *

Example
+ *
+	 * Title:Insert TAS category here
+	 * Author:Insert author here
+	 * Playing Time:00:00.0
+	 * Rerecords:0
+	 * 
+ * @param out + * @param data + */ + protected void serialiseMetadataValues(List out, LinkedHashMap data) { data.forEach((key, value) -> { out.add(String.format("%s:%s", key, value)); }); } + /* + ___ _____ _ _ ____ ____ _ _ ____ + / __)( _ )( \( )(_ _)( ___)( \( )(_ _) + ( (__ )(_)( ) ( )( )__) ) ( )( + \___)(_____)(_)\_) (__) (____)(_)\_) (__) + + */ + + /** + *

Serialises a list of inputs into a list of strings + *

Tree
+ *
+	 * serialise
+	 * └── {@link #serialiseContainer(BigArrayList, TickContainer)}
+	 *     ├── {@link #serialiseKeyboard(VirtualKeyboard)}
+	 *     │   └── {@link #serialiseKeyboardSubtick(VirtualKeyboard)}
+	 *     ├── {@link #serialiseMouse(VirtualMouse)}
+	 *     │   └── {@link #serialiseMouseSubtick(VirtualMouse)}
+	 *     ├── {@link #serialiseCameraAngle(VirtualCameraAngle)}
+	 *     │   └── {@link #serialiseCameraAngleSubtick(VirtualCameraAngle)}
+	 *     ├── {@link #serialiseInlineComments(List, List)}
+	 *     │   └── {@link #serialiseFileCommandsInline(List)}
+	 *     │       └── {@link #serialiseFileCommand(PlaybackFileCommand)}
+	 *     ├── {@link #serialiseEndlineComments(List, List)}	// Same as serialiseInlineComments
+	 *     │   └── {@link #serialiseFileCommandsEndline(List)}	// Unused
+	 *     │       └── {@link #serialiseFileCommand(PlaybackFileCommand)}
+	 *     └── {@link #mergeInputs(BigArrayList, List, List, List, List)}
+	 *         ├── {@link #mergeInput(long, String, String, String, String)}
+	 *         └── {@link #mergeSubtickInput(long, String, String, String, String)}
+	 * 
+ * @param inputs The inputs to serialise + * @param toTick The tick where to stop, used for partial serialisation by savestates. -1 to serialise all + * @return The list of lines + */ public BigArrayList serialise(BigArrayList inputs, long toTick) { BigArrayList out = new BigArrayList<>(); @@ -139,6 +335,12 @@ public BigArrayList serialise(BigArrayList inputs, long t return out; } + /** + * Main serialising method of a single {@link TickContainer} + * + * @param out The list of serialised lines, passed in by reference + * @param container The {@link TickContainer} to serialise + */ protected void serialiseContainer(BigArrayList out, TickContainer container) { currentLine = out.size() - 1; List serialisedKeyboard = serialiseKeyboard(container.getKeyboard()); @@ -150,58 +352,110 @@ protected void serialiseContainer(BigArrayList out, TickContainer contai 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 serialisedInlineComments = 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) { - if (!processExtensions) - return ""; - 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); + mergeInputs(out, serialisedKeyboard, serialisedMouse, serialisedCameraAngle, serialisedInlineComments, serialisedEndlineComments); } + /** + *

Serialises a {@link VirtualKeyboard} + * + *

A {@link VirtualKeyboard} is most often comprised of multiple subticks,
+ * which are each serialised in {@link #serialiseKeyboardSubtick(VirtualKeyboard)} + *

Example
+ *
+	 * 	W;w
+	 * 	A;a
+	 * 	S,D;sd
+	 * 
+ * @param keyboard The keyboard to serialise + * @return A list of serialised keyboardSubticks + */ protected List serialiseKeyboard(VirtualKeyboard keyboard) { List out = new ArrayList<>(); List subticks = new ArrayList<>(keyboard.getAll()); - pruneListEndEmptySubtickable(subticks); +// pruneListEndEmptySubtickable(subticks); for (VirtualKeyboard subtick : subticks) { - out.add(subtick.toString2()); + out.add(serialiseKeyboardSubtick(subtick)); } return out; } + /** + *

Serialises a single keyboard subtick + *

Used for setting the format of a keyboard input in general + *

The input is split between keycodes and key characters by a semicolon.
+ * While the key code can be written with a number (e.g. the key with the label W is keycode 17),
+ * Only the "name of the keycode" is used for serialisation. After the semicolon a key character is used.
+ * This is what is used for the chat or books, as, when holding e.g. shift, a capitalized character is used instead (SHIFT,W;W).
+ * Keycodes on the other hand have no concept of capitalisation and are used for the actual movement keys (Forward, Backward) + *

Example
+ *
+	 * 	W,S;ws
+	 * 
+ * @param keyboardSubtick The subtick to serialise + * @return The serialised subtick + */ + protected String serialiseKeyboardSubtick(VirtualKeyboard keyboardSubtick) { + return String.format("%s;%s", String.join(",", keyboardSubtick.getCurrentPresses()), charListToString(keyboardSubtick.getCharList())); + } + + /** + *

Serialises a {@link VirtualMouse} + *

A {@link VirtualMouse} is most often comprised of multiple subticks,
+ * which are each serialised in {@link #serialiseMouseSubtick(VirtualMouse)} + *

Example
+ *
+	 * 	LC;0,15,21
+	 * 	RC;-15,15,21
+	 * 	RC,MC;30,14,20
+	 * 
+ * @param mouse The mouse to serialise + * @return A list of serialised mouse subticks + */ protected List serialiseMouse(VirtualMouse mouse) { List out = new ArrayList<>(); List subticks = new ArrayList<>(mouse.getAll()); - pruneListEndEmptySubtickable(subticks); +// pruneListEndEmptySubtickable(subticks); for (VirtualMouse subtick : subticks) { - out.add(subtick.toString2()); + out.add(serialiseMouseSubtick(subtick)); } return out; } + /** + *

Serialises a single mouse subtick + *

The mouse subtick is comprised of the following:
+ * mouseKeycodes;scrollWheel,cursorX,cursorY + *

Example
+ *
+	 * 	LC;0,15,21
+	 * 
+ * @param mouseSubtick The mouse subtick to serialise + * @return The serialised mouse subtick + */ + protected String serialiseMouseSubtick(VirtualMouse mouseSubtick) { + return String.format("%s;%s,%s,%s", String.join(",", mouseSubtick.getCurrentPresses()), mouseSubtick.getScrollWheel(), mouseSubtick.getCursorX(), mouseSubtick.getCursorY()); + } + + /** + *

Serialises a {@link VirtualCameraAngle} + *

A {@link VirtualCameraAngle} is most often comprised of multiple subticks,
+ * which are each serialised in {@link #serialiseCameraAngleSubtick(VirtualCameraAngle)} + *

Example
+ *
+	 * 	35;26
+	 * 	34;25
+	 * 	140;-130
+	 * 
+ * @param cameraAngle Camera angle to serialise + * @return The serialised list of camera angles + */ protected List serialiseCameraAngle(VirtualCameraAngle cameraAngle) { VirtualCameraAngle previousCamera = null; @@ -210,13 +464,41 @@ protected List serialiseCameraAngle(VirtualCameraAngle cameraAngle) { for (VirtualCameraAngle subtick : cameraAngle.getAll()) { if (!subtick.equals(previousCamera)) - out.add(String.format("%s;%s", subtick.getYaw(), subtick.getPitch())); + out.add(serialiseCameraAngleSubtick(subtick)); previousCamera = subtick; } return out; } + /** + *

Serialises a single camera angle subtick + *

The subtick is comprised of:
+ * The camera angle yaw and the camera angle pitch + *

Example
+ *
+	 * 	140;-130
+	 * 
+ * + * @param cameraAngleSubtick The camera angle subtick to serialise + * @return The serialised camera angle subtick + */ + protected String serialiseCameraAngleSubtick(VirtualCameraAngle cameraAngleSubtick) { + return String.format("%s;%s", cameraAngleSubtick.getYaw(), cameraAngleSubtick.getPitch()); + } + + /** + *

Serialise comments that take up an entire line + *

In addition, comments can contain {@link PlaybackFileCommand FileCommands} that are serialised in {@link #serialiseFileCommandsInline(List)} + *

Example
+ *
+	 * // Inline comment
+	 * 12|W;w||0;0
+	 * 
+ * @param inlineComments The list of inline comments to serialise + * @param fileCommandsInline The list of file commands to serialise + * @return List of comments including file commands + */ protected List serialiseInlineComments(List inlineComments, List> fileCommandsInline) { List out = new ArrayList<>(); @@ -236,7 +518,7 @@ protected List serialiseInlineComments(List inlineComments, List String command = null; if (fileCommandQueue != null) { - command = serialiseFileCommandsInLine(fileCommandQueue.poll()); // Trying to poll a fileCommand. Command can be null at this point + 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 @@ -245,7 +527,7 @@ protected List serialiseInlineComments(List inlineComments, List continue; } - out.add(String.format("// %s", joinNotEmpty(" ", command, comment))); + out.add(serialiseInlineComment(joinNotEmpty(" ", command, comment))); } } @@ -255,9 +537,9 @@ protected List serialiseInlineComments(List inlineComments, List // add the rest of the fileCommands to the end while (!fileCommandQueue.isEmpty()) { - String command = serialiseFileCommandsInLine(fileCommandQueue.poll()); + String command = serialiseFileCommandsInline(fileCommandQueue.poll()); if (command != null) { - out.add(String.format("// %s", command)); + out.add(serialiseInlineComment(command)); } else { out.add(""); // Add an empty line if command is null } @@ -267,39 +549,249 @@ protected List serialiseInlineComments(List inlineComments, List return out; } + protected String serialiseInlineComment(String comment) { + return String.format("// %s", comment); + } + + /** + *

Serialise comments that are written at the end of the line + *

Example
+ *
+	 *	12|W;w||0;0	// Endline comment
+	 * 
+ * @param endlineComments The list of endline comments to serialise + * @param fileCommandsEndline The list of file commands to serialise + * @return The serialised comments + */ protected List serialiseEndlineComments(List endlineComments, List> fileCommandsEndline) { - return serialiseInlineComments(endlineComments, fileCommandsEndline); + List out = new ArrayList<>(); + + Queue> fileCommandQueue = null; + if (fileCommandsEndline != null) { + fileCommandQueue = new LinkedList<>(fileCommandsEndline); + } + + // Serialise comments and merge them with file commands + if (endlineComments != null) { + + Queue commentQueue = new LinkedList<>(endlineComments); + + // 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 = serialiseFileCommandsEndline(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(serialiseEndlineComment(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 = serialiseFileCommandsEndline(fileCommandQueue.poll()); + if (command != null) { + out.add(serialiseEndlineComment(command)); + } else { + out.add(""); // Add an empty line if command is null + } + } + } + + return out; } - protected void mergeInputs(BigArrayList out, List serialisedKeyboard, List serialisedMouse, List serialisedCameraAngle, List serialisedEndlineComments) { + protected String serialiseEndlineComment(String comment) { + return String.format("// %s", comment); + } + + /** + *

Serialises a list of file commands in an inline comment + *

Uses {@link #serialiseFileCommand(PlaybackFileCommand) serialiseFileCommand} for the actual file command format,
+ * while this method dictates how they are joined together + *

By default, multiple file commands may be serialised like this + *

Example
+ *
+	 * 	// $fileCommandName1(argument1); $fileCommandName2(argument1, argument2);
+	 * 
+ * @param fileCommands The file commands to serialise + * @return A string of serialised file commands or null if fileCommands is null + */ + protected String serialiseFileCommandsInline(List fileCommands) { + // File commands is null if there are no file commands in the comment. + // Return null if that is the case + if (fileCommands == null) { + return null; + } + List serialisedCommands = new ArrayList<>(); + for (PlaybackFileCommand command : fileCommands) { + serialisedCommands.add(serialiseFileCommand(command)); + } + return String.join(" ", serialisedCommands); + } + + /** + *

Serialises a list of file commands in an endline comment + *

This is added in case a flavor needs a different format for endline and inline commands,
+ * but by default this is the same as {@link #serialiseFileCommandsInline(List) serialiseFileCommandsInLine} + *

Example
+ *
+	 * 	12|W;w||0;0	// $fileCommandName1(argument1); $fileCommandName2(argument1, argument2);
+	 * 
+ * @param fileCommands The file commands to serialise + * @return A string of serialised file commands or null if fileCommands is null + */ + protected String serialiseFileCommandsEndline(List fileCommands) { + return serialiseFileCommandsInline(fileCommands); + } + + /** + *

Serialises a single file command. + *

Used for setting the format of file commands + *

Example
+ *
+	 * 	$fileCommandName(argument1, argument2, argument3);
+	 * 
+ * + *

Has to check if {@link #processExtensions} is false + * @param fileCommand The {@link PlaybackFileCommand} to serialise + * @return The serialised file command, empty if {@link #processExtensions} is false + */ + protected String serialiseFileCommand(PlaybackFileCommand fileCommand) { + if (!processExtensions) + return ""; + return String.format("$%s(%s);", fileCommand.getName(), String.join(", ", fileCommand.getArgs())); + } + + /** + *

Merges lists of keyboard, mouse, camera angle, inline and endline comments together into one string + *

Example
+ *
+	 * // Inline comment
+	 * // $inlineFileCommand(arg);
+	 * 256|W;w|;0,0,0|31.778223;85.11482		// Endline comment
+	 *	1|W,S;s|;0,0,0|34.47822;82.56482	// $endlineFileCommand(arg)
+	 *	2|;||37.02822;79.86482
+	 * 
+ * @param out The list of lines that will be written to file, passed in by reference + * @param serialisedKeyboard The serialised keyboard from {@link #serialiseKeyboard(VirtualKeyboard)} + * @param serialisedMouse The serialised mouse from {@link #serialiseMouse(VirtualMouse)} + * @param serialisedCameraAngle The serialised camera angle from {@link #serialiseCameraAngle(VirtualCameraAngle)} + * @param serialisedInlineComments The inline comments from {@link #serialiseInlineComments(List, List)} + * @param serialisedEndlineComments The endline comments from {@link #serialiseEndlineComments(List, List)} + */ + protected void mergeInputs(BigArrayList out, List serialisedKeyboard, List serialisedMouse, List serialisedCameraAngle, List serialisedInlineComments, List serialisedEndlineComments) { + + /* + * Firstly add inline comments, as they appear before the inputs in the container: + * Example: + * // This is an inline comment + * 1|||0;0 + */ + addAll(out, serialisedInlineComments); + + /* + * Copy inputs with ticks and subticks into a queue, + * so they can be serialised even if the length is different + */ Queue keyboardQueue = new LinkedBlockingQueue<>(serialisedKeyboard); Queue mouseQueue = new LinkedBlockingQueue<>(serialisedMouse); Queue cameraAngleQueue = new LinkedBlockingQueue<>(serialisedCameraAngle); - Queue endlineQueue = new LinkedBlockingQueue<>(serialisedEndlineComments); + Queue endlineCommentQueue = new LinkedBlockingQueue<>(serialisedEndlineComments); String kb = getOrEmpty(keyboardQueue.poll()); String ms = getOrEmpty(mouseQueue.poll()); String ca = getOrEmpty(cameraAngleQueue.poll()); - String elc = getOrEmpty(endlineQueue.poll()); + String elc = getOrEmpty(endlineCommentQueue.poll()); if (!elc.isEmpty()) { elc = "\t\t" + elc; } - out.add(String.format("%s|%s|%s|%s%s", currentTick, kb, ms, ca, elc)); + // Add tick line, not indented + out.add(mergeInput(currentTick, kb, ms, ca, elc)); + // Add subtick lines, not indented currentSubtick = 0; while (!keyboardQueue.isEmpty() || !mouseQueue.isEmpty() || !cameraAngleQueue.isEmpty()) { currentSubtick++; kb = getOrEmpty(keyboardQueue.poll()); ms = getOrEmpty(mouseQueue.poll()); ca = getOrEmpty(cameraAngleQueue.poll()); + elc = getOrEmpty(endlineCommentQueue.poll()); + if (!elc.isEmpty()) { + elc = "\t\t" + elc; + } - out.add(String.format("\t%s|%s|%s|%s", currentSubtick, kb, ms, ca)); + out.add(mergeSubtickInput(currentSubtick, kb, ms, ca, elc)); + } + + /* + * Add the rest of the endline comments. + * Normally there shouldn't be more comments + * than subticks, but maybe some file command extension + * demands it + */ + while (!endlineCommentQueue.isEmpty()) { + elc = getOrEmpty(endlineCommentQueue.poll()); + out.add(String.format("\t|||;\t\t%s", elc)); } currentSubtick = 0; } + /** + *

How parent inputs are merged + *

Example
+ *
+	 * 256|W;w|;0,0,0|31.778223;85.11482		// Endline comment
+	 * 
+ * + * @param currentTick The current tick + * @param keyboard The serialised keyboard + * @param mouse The serialised mouse + * @param cameraAngle The serialises camera angle + * @param endLineComment The end line comment + * @return The merged strings + */ + protected String mergeInput(long currentTick, String keyboard, String mouse, String cameraAngle, String endLineComment) { + return String.format("%s|%s|%s|%s%s", currentTick, keyboard, mouse, cameraAngle, endLineComment); + } + + /** + *

How subtick inputs are merged + *

Example
+ *
+	 * 256|W;w|;0,0,0|31.778223;85.11482		// Parent
+	 *	1|W,S;s|;0,0,0|34.47822;82.56482		// Subtick
+	 * 
+ * @param currentSubtick The current subtick in this sequence + * @param keyboard The serialised keyboard + * @param mouse The serialised mouse + * @param cameraAngle The serialised camera angle + * @param endLineComment The serialised end line comment + * @return The merged subticks + */ + protected String mergeSubtickInput(int currentSubtick, String keyboard, String mouse, String cameraAngle, String endLineComment) { + return String.format("\t%s|%s|%s|%s%s", currentSubtick, keyboard, mouse, cameraAngle, endLineComment); + } + + /** + * If a string is null, return an empty string + * @param string String to check + * @return The string or empty if null + */ protected String getOrEmpty(String string) { return string == null ? "" : string; } @@ -327,6 +819,13 @@ protected String joinNotEmpty(String delimiter, Iterable args) { return out; } + /** + * 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, String... args) { return joinNotEmpty(delimiter, Arrays.asList(args)); } @@ -343,7 +842,21 @@ protected String joinNotEmpty(String delimiter, String... args) { * */ - public boolean deserialiseFlavorName(List headerLines) { + /* + _ _ ____ __ ____ ____ ____ + ( )_( )( ___) /__\ ( _ \( ___)( _ \ + ) _ ( )__) /(__)\ )(_) ))__) ) / + (_) (_)(____)(__)(__)(____/(____)(_)\_) + + */ + + /** + *

Checks if the name of this flavor is present in the header of the TASfile. + *

Used to determine the flavor of the file if the flavor is not given + * @param headerLines The lines from the header to check + * @return True, if the flavor name is present in the header + */ + public boolean checkFlavorName(List headerLines) { for (String line : headerLines) { Matcher matcher = extract("^Flavor: " + getExtensionName(), line); @@ -354,11 +867,14 @@ public boolean deserialiseFlavorName(List headerLines) { return false; } - public void deserialiseHeader(List headerLines) { - deserialiseMetadata(headerLines); - deserialiseFileCommandNames(headerLines); - } - + /** + *

Extracts the header from the TASfile + *

Optimization to seperate the header from the actual inputs.
+ * Only reads a maximum of 1000 and until it finds {@link #headerEnd()} + * @param lines The total lines to check + * @return The list of lines containing the header + * @throws PlaybackLoadException If the end of the header is not found after 1000 lines + */ public List extractHeader(BigArrayList lines) { List extracted = new ArrayList<>(); @@ -376,28 +892,26 @@ public List extractHeader(BigArrayList lines) { throw new PlaybackLoadException("Cannot find the end of the header"); } - protected void deserialiseFileCommandNames(List headerLines) { - if (!processExtensions) // Stops FileCommandProcessing - return; - - for (String line : headerLines) { - Matcher matcher = extract("FileCommand-Extensions: ?(.*)", line); - - if (matcher.find()) { - - if (!processExtensions) - return; - - 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"); + /** + *

Deserialise header lines + *

+	 *	deserialiseHeader
+	 *  ├── {@link #deserialiseMetadata(List)}
+	 *  └── {@link #deserialiseFileCommandNames(List)}
+	 * 
+ * @param headerLines The header lines to deserialise + * @see #serialiseHeader() + */ + public void deserialiseHeader(List headerLines) { + deserialiseMetadata(headerLines); + deserialiseFileCommandNames(headerLines); } + /** + *

Deserialises the TASfile metadata + * @param headerLines + * @see #serialiseMetadata(List) + */ protected void deserialiseMetadata(List headerLines) { if (!processExtensions) return; @@ -434,12 +948,40 @@ protected void deserialiseMetadata(List headerLines) { TASmodAPIRegistry.PLAYBACK_METADATA.handleOnLoad(out); } + /** + *

Deserialises file command extension names and enables them + * @param headerLines The header lines to search + * @see #serialiseFileCommandNames(List) + * @throws PlaybackLoadException If the "FileCommand-Extensions" keyword is not found in the header + */ + protected void deserialiseFileCommandNames(List headerLines) { + if (!processExtensions) // Stops FileCommandProcessing + return; + + for (String line : headerLines) { + Matcher matcher = extract("FileCommand-Extensions: ?(.*)", line); + + if (matcher.find()) { + + if (!processExtensions) + return; + + String extensionStrings = matcher.group(1); + String[] extensionNames = extensionStrings.split(", ?"); + + TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.setEnabled(extensionNames); + return; + } + } + throw new PlaybackLoadException("FileCommand-Extensions value was not found in the header"); + } + /** * 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} + * @return A list of {@link TickContainer TickContainers} */ public BigArrayList deserialise(BigArrayList lines, long startPos) { BigArrayList out = new BigArrayList<>(); @@ -448,7 +990,7 @@ public BigArrayList deserialise(BigArrayList lines, long // Extract the tick and set the index i = extractContainer(container, lines, i); currentLine = i; - // Extract container + // Deserialise container deserialiseContainer(out, container); currentTick++; } @@ -618,8 +1160,10 @@ protected void deserialiseContainer(BigArrayList out, List inlineComments = new ArrayList<>(); List tickLines = new ArrayList<>(); + splitContainer(containerLines, inlineComments, tickLines); + List> inlineFileCommands = new ArrayList<>(); - splitContainer(containerLines, inlineComments, tickLines, inlineFileCommands); + deserialiseMultipleInlineComments(inlineComments, inlineFileCommands); List keyboardStrings = new ArrayList<>(); List mouseStrings = new ArrayList<>(); @@ -627,7 +1171,7 @@ protected void deserialiseContainer(BigArrayList out, List endlineComments = new ArrayList<>(); List> endlineFileCommands = new ArrayList<>(); - splitInputs(containerLines, keyboardStrings, mouseStrings, cameraAngleStrings, endlineComments, endlineFileCommands); + splitInputs(tickLines, keyboardStrings, mouseStrings, cameraAngleStrings, endlineComments, endlineFileCommands); pruneListEndNull(endlineComments); @@ -653,23 +1197,32 @@ protected void deserialiseContainer(BigArrayList out, List lines, List comments, List tick, List> inlineFileCommands) { + protected void splitContainer(List lines, List inlineComments, List ticks) { for (String line : lines) { if (contains(singleComment(), line)) { - List deserialisedFileCommand = new ArrayList<>(); - comments.add(deserialiseInlineComment(line, deserialisedFileCommand)); - if (deserialisedFileCommand.isEmpty()) { - deserialisedFileCommand = null; - } - inlineFileCommands.add(deserialisedFileCommand); + inlineComments.add(line); } else { - tick.add(line); + ticks.add(line); + } + } + } + + protected void deserialiseMultipleInlineComments(List inlineComments, List> inlineFileCommands) { + for (int i = 0; i < inlineComments.size(); i++) { + List deserialisedFileCommand = new ArrayList<>(); + String comment = inlineComments.get(i); + + inlineComments.set(i, deserialiseInlineComment(comment, deserialisedFileCommand)); + + if (deserialisedFileCommand.isEmpty()) { + deserialisedFileCommand = null; } + inlineFileCommands.add(deserialisedFileCommand); } } protected String deserialiseInlineComment(String comment, List deserialisedFileCommands) { - comment = deserialiseFileCommands(comment, deserialisedFileCommands); + comment = deserialiseFileCommandsInline(comment, deserialisedFileCommands); comment = extract("^// ?(.+)", comment, 1); if (comment != null) { comment = comment.trim(); @@ -681,12 +1234,39 @@ protected String deserialiseInlineComment(String comment, List deserialisedFileCommands) { - return deserialiseInlineComment(comment, deserialisedFileCommands); + comment = deserialiseFileCommandsEndline(comment, deserialisedFileCommands); + comment = extract("^// ?(.+)", comment, 1); + if (comment != null) { + comment = comment.trim(); + if (comment.isEmpty()) { + comment = null; + } + } + return comment; } - protected String deserialiseFileCommands(String comment, List deserialisedFileCommands) { + protected String deserialiseFileCommandsInline(String comment, List deserialisedFileCommands) { + Matcher matcher = extract("\\$(.+?)\\((.*?)\\);", comment); + // Iterate through all file commands and add each to the list + while (matcher.find()) { + String name = matcher.group(1); + String[] args = matcher.group(2).split(", ?"); + + if (processExtensions) + deserialisedFileCommands.add(new PlaybackFileCommand(name, args)); + + comment = matcher.replaceFirst(""); + matcher.reset(comment); + } + + return comment; + } + + protected String deserialiseFileCommandsEndline(String comment, List deserialisedFileCommands) { Matcher matcher = extract("\\$(.+?)\\((.*?)\\);", comment); + + // Iterate through all file commands and add each to the list while (matcher.find()) { String name = matcher.group(1); String[] args = matcher.group(2).split(", ?"); @@ -912,7 +1492,7 @@ protected void splitInputs(List lines, List serialisedKeyboard, } for (String line : lines) { - Matcher tickMatcher = extract("^\\t?\\d+\\|(.*?)\\|(.*?)\\|(\\S*)\\s?", line); + Matcher tickMatcher = extract(splitInputRegex(), line); if (tickMatcher.find()) { if (!tickMatcher.group(1).isEmpty()) { @@ -943,6 +1523,10 @@ protected void splitInputs(List lines, List serialisedKeyboard, } } + protected String splitInputRegex() { + return "^\\t?\\d+\\|(.*?)\\|(.*?)\\|(\\S*)\\s?"; + } + protected Matcher extract(String regex, String haystack) { Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE); Matcher matcher = pattern.matcher(haystack); @@ -1070,6 +1654,17 @@ protected > void pruneListEndEmptySubtickable(List l } } + /** + * @return The regex used for detecting comment lines + */ + protected String singleComment() { + return "^//"; + } + + protected String endlineComment() { + return "(//.+)"; + } + @Override public abstract SerialiserFlavorBase clone(); @@ -1082,7 +1677,24 @@ public boolean equals(Object obj) { return super.equals(obj); } + /** + * Set if extensions should be loaded. + * + * Setting this to false will stop {@link TASmodAPIRegistry#PLAYBACK_FILE_COMMAND} and {@link TASmodAPIRegistry#PLAYBACK_METADATA} from being processed + * + * @param processExtensions + */ public void setProcessExtensions(boolean processExtensions) { this.processExtensions = processExtensions; } + + protected String charListToString(List charList) { + String charString = ""; + if (!charList.isEmpty()) { + charString = charList.stream().map(Object::toString).collect(Collectors.joining()); + charString = StringUtils.replace(charString, "\r", "\\n"); + charString = StringUtils.replace(charString, "\n", "\\n"); + } + return charString; + } } diff --git a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/builtin/AlphaFlavor.java b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/builtin/AlphaFlavor.java new file mode 100644 index 00000000..81ced51b --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/builtin/AlphaFlavor.java @@ -0,0 +1,539 @@ +package com.minecrafttas.tasmod.playback.tasfile.flavor.builtin; + +import static com.minecrafttas.tasmod.playback.metadata.builtin.CreditsMetadataExtension.CreditFields.Author; +import static com.minecrafttas.tasmod.playback.metadata.builtin.CreditsMetadataExtension.CreditFields.PlayTime; +import static com.minecrafttas.tasmod.playback.metadata.builtin.CreditsMetadataExtension.CreditFields.Rerecords; +import static com.minecrafttas.tasmod.playback.metadata.builtin.CreditsMetadataExtension.CreditFields.Title; +import static com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension.StartPositionFields.Pitch; +import static com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension.StartPositionFields.X; +import static com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension.StartPositionFields.Y; +import static com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension.StartPositionFields.Yaw; +import static com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension.StartPositionFields.Z; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.regex.Matcher; + +import com.dselent.bigarraylist.BigArrayList; +import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TickContainer; +import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand; +import com.minecrafttas.tasmod.playback.metadata.PlaybackMetadata; +import com.minecrafttas.tasmod.playback.tasfile.exception.PlaybackLoadException; +import com.minecrafttas.tasmod.playback.tasfile.flavor.SerialiserFlavorBase; +import com.minecrafttas.tasmod.registries.TASmodAPIRegistry; +import com.minecrafttas.tasmod.virtual.VirtualCameraAngle; +import com.minecrafttas.tasmod.virtual.VirtualKey; +import com.minecrafttas.tasmod.virtual.VirtualKeyboard; +import com.minecrafttas.tasmod.virtual.VirtualMouse; + +public class AlphaFlavor extends SerialiserFlavorBase { + + @Override + public String getExtensionName() { + return "alpha"; + } + + @Override + public SerialiserFlavorBase clone() { + return new AlphaFlavor(); + } + + @Override + protected String headerStart() { + return "################################################# TASFile ###################################################\n"; + } + + @Override + public List serialiseHeader() { + List out = new ArrayList<>(); + + out.add(headerStart() + + "# 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" + + "# #"); + serialiseMetadata(out); + out.add(headerEnd()); + out.add("#Comments start with \"//\" at the start of the line, comments with # will not be saved"); + return out; + } + + @Override + protected String headerEnd() { + return "#############################################################################################################"; + } + + @Override + protected void serialiseMetadata(List out) { + if (!processExtensions) + return; + + List metadataList = TASmodAPIRegistry.PLAYBACK_METADATA.handleOnStore(); + + PlaybackMetadata credits = null; + PlaybackMetadata startPosition = null; + + for (PlaybackMetadata metadata : metadataList) { + String name = metadata.getExtensionName(); + if (name.equals("Credits")) + credits = metadata; + else if (name.equals("Start Position")) + startPosition = metadata; + } + out.add("#------------------------------------------------ Header ---------------------------------------------------#\n" + + "#Author:" + credits.getValue(Author) + "\n" + + "# #\n" + + "#Title:" + credits.getValue(Title) + "\n" + + "# #\n" + + "#Playing Time:" + credits.getValue(PlayTime) + "\n" + + "# #\n" + + "#Rerecords:" + credits.getValue(Rerecords) + "\n" + + "# #\n" + + "#----------------------------------------------- Settings --------------------------------------------------#\n" + + "#StartPosition:" + processStartPosition(startPosition) + "\n" + + "# #\n" + + "#StartSeed:" + 0); // TODO Add ktrng seed? + } + + protected String processStartPosition(PlaybackMetadata startPosition) { + LinkedHashMap data = startPosition.getData(); + return String.join(",", data.values()); + } + + @Override + public boolean checkFlavorName(List headerLines) { + for (String line : headerLines) { + Matcher matcher = extract("^#.*Version:1", line); + + if (matcher.find()) { + return true; + } + } + return false; + } + + @Override + protected List serialiseKeyboard(VirtualKeyboard keyboard) { + /* + * Old code from when I did not know String.join exists, + * kept relatively unaltered because I want to. + */ + List out = new ArrayList<>(); + + List stringy = keyboard.getCurrentPresses(); + String keyString = ""; + if (!stringy.isEmpty()) { + String seperator = ","; + for (int i = 0; i < stringy.size(); i++) { + if (i == stringy.size() - 1) { + seperator = ""; + } + if (stringy.get(i).equals("ZERO")) + continue; + keyString = keyString.concat(stringy.get(i) + seperator); + } + } + List charList = keyboard.getCharList(); + String charString = ""; + if (!charList.isEmpty()) { + for (int i = 0; i < charList.size(); i++) { + charString = charString.concat(Character.toString(charList.get(i))); + } + charString = charString.replace("\r", "\\n"); + charString = charString.replace("\n", "\\n"); + } + + out.add("Keyboard:" + keyString + ";" + charString); // Keyboard didn't support subticks, only the current key is processed + + return out; + } + + @Override + protected List serialiseMouse(VirtualMouse mouse) { + /* + * Old code from when I did not know String.join exists, + * kept relatively unaltered because I want to. + */ + List out = new ArrayList<>(); + List stringy = mouse.getCurrentPresses(); + String keyString = ""; + if (!stringy.isEmpty()) { + String seperator = ","; + for (int i = 0; i < stringy.size(); i++) { + if (i == stringy.size() - 1) { + seperator = ""; + } + if (stringy.get(i).equals("MOUSEMOVED")) + continue; + keyString = keyString.concat(stringy.get(i) + seperator); + } + } + + List path = new ArrayList<>(mouse.getAll()); // I previously called subticks "paths" as it was mainly used for the mouse... +// pruneListEndEmptySubtickable(path); + + /* + * The mouse supported subticks, + * but it was handled differently in alpha... + * The subticks where added in square brackets, seperated by a "->" + * Not the best solution in hindsight, + * but that was apparently the first thing that came to my mind back then... + */ + String pathString = ""; + if (!path.isEmpty()) { + String seperator = "->"; + for (int i = 0; i < path.size(); i++) { + if (i == path.size() - 1) { + seperator = ""; + } + + VirtualMouse singlePath = path.get(i); + + pathString = pathString.concat("[" + serialisePath(singlePath) + "]" + seperator); + } + } + out.add("Mouse:" + keyString + ";" + pathString); + return out; + } + + protected String serialisePath(VirtualMouse path) { + String keyString = ""; + List strings = new ArrayList(); + + path.getPressedKeys().forEach((virtualkeys) -> { + strings.add(VirtualKey.getName(virtualkeys)); + }); + if (!strings.isEmpty()) { + String seperator = ","; + for (int i = 0; i < strings.size(); i++) { + if (i == strings.size() - 1) { + seperator = ""; + } + keyString = keyString.concat(strings.get(i) + seperator); + } + } + if (keyString.isEmpty()) { + return "MOUSEMOVED," + path.getScrollWheel() + "," + path.getCursorX() + "," + path.getCursorY(); + } else { + return keyString + "," + path.getScrollWheel() + "," + path.getCursorX() + "," + path.getCursorY(); + } + } + + @Override + protected List serialiseCameraAngle(VirtualCameraAngle subticks) { + List out = new ArrayList<>(); + + /* + * The camera was called "subticks" in previous iterations of this code. + * To honor this fact, it is also called subticks here, even though + * actual subticks were not supported + */ + float pitch = subticks.getPitch() == null ? 0f : subticks.getPitch(); + float yaw = subticks.getYaw() == null ? 0f : subticks.getYaw(); + out.add("Camera:" + pitch + ";" + yaw); + return out; + } + + @Override + protected String serialiseFileCommandsInline(List fileCommands) { + if (fileCommands == null) { + return null; + } + List serialisedCommands = new ArrayList<>(); + for (PlaybackFileCommand command : fileCommands) { + if ("hud".equals(command.getName())) { + serialisedCommands.add(String.format("$hud %s", command.getArgs()[0].equals("true") ? "on" : "off")); + } + if ("label".equals(command.getName())) { + serialisedCommands.add(String.format("$info %s", command.getArgs().length == 0 ? "off" : String.join(" ", command.getArgs()))); + } + } + return String.join(" ", serialisedCommands); + } + + @Override + protected String serialiseFileCommandsEndline(List fileCommands) { + if (fileCommands == null) { + return null; + } + List serialisedCommands = new ArrayList<>(); + for (PlaybackFileCommand command : fileCommands) { + if ("desyncMonitor".equals(command.getName())) { + serialisedCommands.add(String.format("Monitoring:%s 0", String.join(" ", command.getArgs()))); + } + } + return String.join(" ", serialisedCommands); + } + + @Override + protected String serialiseInlineComment(String comment) { + return String.format("//%s", comment); + } + + @Override + protected String serialiseEndlineComment(String comment) { + return String.format("//%s", comment); + } + + @Override + protected String mergeInput(long currentTick, String keyboard, String mouse, String cameraAngle, String endLineComment) { + return String.format("%s|%s|%s|%s~&\t\t%s", currentTick, keyboard, mouse, cameraAngle, endLineComment); + } + + @Override + protected void deserialiseMetadata(List headerLines) { + String author = "Insert author here"; + + String title = "Insert TAS category here"; + + String playtime = "00:00.0"; + + String rerecords = "0"; + // No default start location + String startLocation = ""; + + for (String line : headerLines) { + 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 = 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:", "")); +// } + } + + PlaybackMetadata creditsMetada = new PlaybackMetadata("Credits"); + creditsMetada.setValue(Author, author); + creditsMetada.setValue(Title, title); + creditsMetada.setValue(PlayTime, playtime); + creditsMetada.setValue(Rerecords, rerecords); + + PlaybackMetadata startPositionMetadata = new PlaybackMetadata("Start Position"); + String[] split = startLocation.split(","); + startPositionMetadata.setValue(X, split[0]); + startPositionMetadata.setValue(Y, split[1]); + startPositionMetadata.setValue(Z, split[2]); + startPositionMetadata.setValue(Pitch, split[3]); + startPositionMetadata.setValue(Yaw, split[4]); + + List metadataList = new ArrayList<>(); + metadataList.add(creditsMetada); + metadataList.add(startPositionMetadata); + + TASmodAPIRegistry.PLAYBACK_METADATA.handleOnLoad(metadataList); + } + + @Override + protected String splitInputRegex() { + return "^\\d+\\|(.*?)\\|(.*?)\\|(\\S*)~&"; + } + + @Override + protected String deserialiseFileCommandsInline(String comment, List deserialisedFileCommands) { + Matcher matcher = extract("\\$(.+?) (.+?)", comment); + + // Iterate through all file commands and add each to the list + while (matcher.find()) { + String name = matcher.group(1); + String[] args = matcher.group(2).split(" "); + + if ("hud".equals(name)) { + args[0] = "on".equals(args[0]) ? "true" : "false"; + } else if ("info".equals(name)) { + name = "label"; + args[0] = "off".equals(args[0]) ? "" : args[0]; + } + + if (processExtensions) + deserialisedFileCommands.add(new PlaybackFileCommand(name, args)); + + comment = matcher.replaceFirst(""); + matcher.reset(comment); + } + + return comment; + } + + @Override + protected String deserialiseFileCommandsEndline(String comment, List deserialisedFileCommands) { + Matcher matcher = extract("Monitoring:(.+)", comment); + + // Iterate through all file commands and add each to the list + while (matcher.find()) { + String name = "desyncMonitor"; + String[] args = matcher.group(1).split(" "); + + String[] shortenedArgs = new String[6]; + for (int i = 0; i < 6; i++) { + shortenedArgs[i] = args[i]; + } + + if (processExtensions) + deserialisedFileCommands.add(new PlaybackFileCommand(name, shortenedArgs)); + + comment = matcher.replaceFirst(""); + matcher.reset(comment); + } + + return comment; + } + + @Override + protected VirtualKeyboard deserialiseKeyboard(List keyboardStrings) { + VirtualKeyboard out = new VirtualKeyboard(); + + currentSubtick = 0; + for (String line : keyboardStrings) { + Matcher matcher = extract("Keyboard:(.*?);(.*)", line); + if (matcher.find()) { + String[] keys = matcher.group(1).split(","); + char[] chars = matcher.group(2).toCharArray(); + + int[] keycodes = deserialiseVirtualKeyboardKey(keys); + out.updateFromState(keycodes, chars); + } else { + throw new PlaybackLoadException(currentLine, currentTick, currentSubtick, "Keyboard could not be read. Probably a missing semicolon: %s", line); + } + currentSubtick++; + } + return out; + } + + @Override + public BigArrayList deserialise(BigArrayList lines, long startPos) { + BigArrayList out = new BigArrayList<>(); + for (long i = startPos; i < lines.size(); i++) { + + if (lines.get(i).startsWith("#")) { + continue; + } + List container = new ArrayList<>(); + // Extract the tick and set the index + i = extractContainer(container, lines, i); + currentLine = i; + // Deserialise container + deserialiseContainer(out, container); + currentTick++; + } + previousTickContainer = null; + return out; + } + + @Override + protected VirtualMouse deserialiseMouse(List mouseStrings) { + String section = mouseStrings.get(0); + 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(","); + int[] keys = deserialiseVirtualMouseKey(splitButtons); + mouse.updateFromState(keys, 0, 0, 0); + } + readPath(path, mouse); + + return mouse; + } + + protected void readPath(String section, VirtualMouse mouse) { + + 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 PlaybackLoadException("'" + pathNode + "' couldn't be read in line " + currentLine + ": Something is not a number"); + } catch (ArrayIndexOutOfBoundsException e) { + throw new PlaybackLoadException("'" + pathNode + "' couldn't be read in line " + currentLine + ": Something is missing or is too much"); + } + List keyList = new ArrayList<>(); + for (int i = 0; i < length - 3; i++) { + String key = split[i]; + Integer keyCode = VirtualKey.getKeycode(key); + if (keyCode == null) { + throw new PlaybackLoadException(currentLine, currentTick, currentSubtick, "Could not find keycode"); + } + keyList.add(keyCode); + } + int[] keyListArray = new int[keyList.size()]; + for (int i = 0; i < keyList.size(); i++) { + keyListArray[i] = keyList.get(i); + } + mouse.updateFromState(keyListArray, scrollWheel, cursorX, cursorY); + } + } + + @Override + protected VirtualCameraAngle deserialiseCameraAngle(List cameraAngleStrings) { + VirtualCameraAngle out = new VirtualCameraAngle(); + + currentSubtick = 0; + Float previousPitch = previousTickContainer == null ? null : previousTickContainer.getCameraAngle().getPitch(); + Float previousYaw = previousTickContainer == null ? null : previousTickContainer.getCameraAngle().getYaw(); + + for (String line : cameraAngleStrings) { + Matcher matcher = extract("Camera:(.+?);(.+)", line); + + if (matcher.find()) { + String cameraYawString = matcher.group(2); + String cameraPitchString = matcher.group(1); + + Float cameraYaw = null; + Float cameraPitch = null; + + if (!"null".equals(cameraYawString)) + cameraYaw = deserialiseRelativeFloat("camera yaw", cameraYawString, previousYaw); + + if (!"null".equals(cameraPitchString)) + cameraPitch = deserialiseRelativeFloat("camera pitch", cameraPitchString, previousPitch); + + out.updateFromState(cameraPitch, cameraYaw); + } else { + throw new PlaybackLoadException(currentLine, currentTick, currentSubtick, "Camera is missing a semicolon"); + } + currentSubtick++; + } + return out; + } + + @Override + protected void deserialiseFileCommandNames(List headerLines) { + /* + * Alpha has these file commands hardcoded + */ + TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.setEnabled("tasmod_label@v1", "tasmod_desyncMonitor@v1", "tasmod_options@v1"); + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/integrated/Beta1Flavor.java b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/builtin/Beta1Flavor.java similarity index 80% rename from src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/integrated/Beta1Flavor.java rename to src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/builtin/Beta1Flavor.java index 39f5164c..8801f687 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/integrated/Beta1Flavor.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/tasfile/flavor/builtin/Beta1Flavor.java @@ -1,4 +1,4 @@ -package com.minecrafttas.tasmod.playback.tasfile.flavor.integrated; +package com.minecrafttas.tasmod.playback.tasfile.flavor.builtin; import com.minecrafttas.tasmod.playback.tasfile.flavor.SerialiserFlavorBase; diff --git a/src/main/java/com/minecrafttas/tasmod/registries/TASmodAPIRegistry.java b/src/main/java/com/minecrafttas/tasmod/registries/TASmodAPIRegistry.java index 62804042..308cedf9 100644 --- a/src/main/java/com/minecrafttas/tasmod/registries/TASmodAPIRegistry.java +++ b/src/main/java/com/minecrafttas/tasmod/registries/TASmodAPIRegistry.java @@ -4,7 +4,7 @@ import com.minecrafttas.tasmod.playback.metadata.PlaybackMetadataRegistry; import com.minecrafttas.tasmod.playback.tasfile.flavor.SerialiserFlavorBase; import com.minecrafttas.tasmod.playback.tasfile.flavor.SerialiserFlavorRegistry; -import com.minecrafttas.tasmod.playback.tasfile.flavor.integrated.Beta1Flavor; +import com.minecrafttas.tasmod.playback.tasfile.flavor.builtin.Beta1Flavor; public class TASmodAPIRegistry { /** diff --git a/src/test/java/tasmod/playback/tasfile/SerialiserFlavorBaseTest.java b/src/test/java/tasmod/playback/tasfile/SerialiserFlavorBaseTest.java index c5e2c610..6e060930 100644 --- a/src/test/java/tasmod/playback/tasfile/SerialiserFlavorBaseTest.java +++ b/src/test/java/tasmod/playback/tasfile/SerialiserFlavorBaseTest.java @@ -773,9 +773,10 @@ void testSplitContainer() { List actualComments = new ArrayList<>(); List actualTick = new ArrayList<>(); - List> actualInlineFileCommands = new ArrayList<>(); + splitContainer(lines, actualComments, actualTick); - splitContainer(lines, actualComments, actualTick, actualInlineFileCommands); + List> actualInlineFileCommands = new ArrayList<>(); + deserialiseMultipleInlineComments(actualComments, actualInlineFileCommands); List expectedComments = new ArrayList<>(); List expectedTicks = new ArrayList<>(); diff --git a/src/test/java/tasmod/playback/tasfile/builtin/AlphaFlavorTest.java b/src/test/java/tasmod/playback/tasfile/builtin/AlphaFlavorTest.java new file mode 100644 index 00000000..e253a214 --- /dev/null +++ b/src/test/java/tasmod/playback/tasfile/builtin/AlphaFlavorTest.java @@ -0,0 +1,220 @@ +package tasmod.playback.tasfile.builtin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandExtension; +import com.minecrafttas.tasmod.playback.filecommands.builtin.DesyncMonitorFileCommandExtension; +import com.minecrafttas.tasmod.playback.filecommands.builtin.LabelFileCommandExtension; +import com.minecrafttas.tasmod.playback.filecommands.builtin.OptionsFileCommandExtension; +import com.minecrafttas.tasmod.playback.metadata.PlaybackMetadata; +import com.minecrafttas.tasmod.playback.metadata.builtin.CreditsMetadataExtension; +import com.minecrafttas.tasmod.playback.metadata.builtin.CreditsMetadataExtension.CreditFields; +import com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension; +import com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension.StartPosition; +import com.minecrafttas.tasmod.playback.tasfile.flavor.builtin.AlphaFlavor; +import com.minecrafttas.tasmod.registries.TASmodAPIRegistry; + +public class AlphaFlavorTest extends AlphaFlavor { + + CreditsMetadataExtensionTest creditsMetadataExtension; + StartpositionMetadataExtensionTest startpositionMetadataExtension; + + private class CreditsMetadataExtensionTest extends CreditsMetadataExtension { + public String getTitle() { + return title; + } + + public String getAuthors() { + return authors; + } + + public String getPlayTime() { + return playtime; + } + + public int getRerecords() { + return rerecords; + } + } + + private class StartpositionMetadataExtensionTest extends StartpositionMetadataExtension { + public StartPosition getStartPosition() { + return startPosition; + } + } + + @AfterEach + void afterEach() { + TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.clear(); + TASmodAPIRegistry.PLAYBACK_METADATA.clear(); + TASmodAPIRegistry.SERIALISER_FLAVOR.clear(); + + this.currentTick = 0; + this.currentSubtick = 0; + this.previousTickContainer = null; + } + + @BeforeEach + void beforeEach() { + creditsMetadataExtension = new CreditsMetadataExtensionTest(); + startpositionMetadataExtension = new StartpositionMetadataExtensionTest(); + + PlaybackMetadata creditsMetadata = new PlaybackMetadata(creditsMetadataExtension); + creditsMetadata.setValue(CreditFields.Author, "Scribal"); + creditsMetadataExtension.onLoad(creditsMetadata); + + Path temp = Paths.get("src/test/resources/temp"); + + TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.register(new LabelFileCommandExtension(temp), new DesyncMonitorFileCommandExtension(temp), new OptionsFileCommandExtension(temp)); + + TASmodAPIRegistry.PLAYBACK_METADATA.register(creditsMetadataExtension, startpositionMetadataExtension); + } + + @Test + void testSerialiseHeader() { + List actual = serialiseHeader(); + List expected = new ArrayList<>(); + + expected.add("################################################# 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" + + "# #"); + expected.add("#------------------------------------------------ Header ---------------------------------------------------#\n" + + "#Author:" + "Scribal" + "\n" + + "# #\n" + + "#Title:" + "Insert TAS category here" + "\n" + + "# #\n" + + "#Playing Time:" + "00:00.0" + "\n" + + "# #\n" + + "#Rerecords:" + 0 + "\n" + + "# #\n" + + "#----------------------------------------------- Settings --------------------------------------------------#\n" + + "#StartPosition:" + "0.0,0.0,0.0,0.0,0.0" + "\n" + + "# #\n" + + "#StartSeed:" + 0); + expected.add("#############################################################################################################"); + expected.add("#Comments start with \"//\" at the start of the line, comments with # will not be saved"); + + assertIterableEquals(expected, actual); + } + + @Test + void testCheckFlavorName() { + List data = new ArrayList<>(); + data.add("################################################# 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" + + "# #"); + data.add("#------------------------------------------------ Header ---------------------------------------------------#\n" + + "#Author:" + "Scribal" + "\n" + + "# #\n" + + "#Title:" + "Insert TAS category here" + "\n" + + "# #\n" + + "#Playing Time:" + "00:00.0" + "\n" + + "# #\n" + + "#Rerecords:" + 0 + "\n" + + "# #\n" + + "#----------------------------------------------- Settings --------------------------------------------------#\n" + + "#StartPosition:" + "0.0,0.0,0.0,0.0,0.0" + "\n" + + "# #\n" + + "#StartSeed:" + 0); + data.add("#############################################################################################################\n" + + "#Comments start with \"//\" at the start of the line, comments with # will not be saved"); + + assertTrue(checkFlavorName(data)); + } + + @Test + void testCheckFlavorNameFalse() { + List data = new ArrayList<>(); + data.add("################################################# TASFile ###################################################\n" + + "# Version:2 #\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" + + "# #"); + data.add("#------------------------------------------------ Header ---------------------------------------------------#\n" + + "#Author:" + "Scribal" + "\n" + + "# #\n" + + "#Title:" + "Insert TAS category here" + "\n" + + "# #\n" + + "#Playing Time:" + "00:00.0" + "\n" + + "# #\n" + + "#Rerecords:" + 0 + "\n" + + "# #\n" + + "#----------------------------------------------- Settings --------------------------------------------------#\n" + + "#StartPosition:" + "0.0,0.0,0.0,0.0,0.0" + "\n" + + "# #\n" + + "#StartSeed:" + 0); + data.add("#############################################################################################################\n" + + "#Comments start with \"//\" at the start of the line, comments with # will not be saved"); + + assertFalse(checkFlavorName(data)); + } + + @Test + void testDeserialiseMetadata() { + List data = new ArrayList<>(); + data.add("################################################# TASFile ###################################################"); + data.add("# Version:1 #"); + data.add("# This file was generated using the Minecraft TASMod #"); + data.add("# #"); + data.add("# Any errors while reading this file will be printed out in the console and the chat #"); + data.add("# #"); + data.add("#------------------------------------------------ Header ---------------------------------------------------#"); + data.add("#Author:" + "Scribble"); + data.add("# #"); + data.add("#Title:" + "Beef"); + data.add("# #"); + data.add("#Playing Time:" + "00:01.0"); + data.add("# #"); + data.add("#Rerecords:" + 20); + data.add("# #"); + data.add("#----------------------------------------------- Settings --------------------------------------------------#"); + data.add("#StartPosition:" + "1.0,2.0,3.0,4.0,5.0"); + data.add("# #"); + data.add("#StartSeed:" + 0); + data.add("#############################################################################################################"); + data.add("#Comments start with \"//\" at the start of the line, comments with # will not be saved"); + + deserialiseMetadata(data); + + assertEquals("Scribble", creditsMetadataExtension.getAuthors()); + assertEquals("Beef", creditsMetadataExtension.getTitle()); + assertEquals("00:01.0", creditsMetadataExtension.getPlayTime()); + assertEquals(20, creditsMetadataExtension.getRerecords()); + + StartPosition pos = startpositionMetadataExtension.getStartPosition(); + assertEquals(1.0D, pos.x); + assertEquals(2.0D, pos.y); + assertEquals(3.0D, pos.z); + assertEquals(4.0F, pos.pitch); + assertEquals(5.0F, pos.yaw); + } + + @Test + void testDeserialiseFileCommandNames() { + deserialiseFileCommandNames(new ArrayList<>()); + + List fcList = TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.getEnabled(); + assertTrue(fcList.get(0) instanceof LabelFileCommandExtension); + assertTrue(fcList.get(1) instanceof DesyncMonitorFileCommandExtension); + assertTrue(fcList.get(2) instanceof OptionsFileCommandExtension); + } +}