diff --git a/gradle.properties b/gradle.properties index 9c8d687f..205e8e64 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,5 +16,5 @@ mod_email=scribble@minecrafttas.com # TASmod properties group=com.minecrafttas artifact=TASmod-1.12.2 -version=Beta1.2 +version=Beta2 release=false diff --git a/src/main/java/com/minecrafttas/mctcommon/Configuration.java b/src/main/java/com/minecrafttas/mctcommon/Configuration.java index 686f3e63..c740f691 100644 --- a/src/main/java/com/minecrafttas/mctcommon/Configuration.java +++ b/src/main/java/com/minecrafttas/mctcommon/Configuration.java @@ -1,5 +1,6 @@ package com.minecrafttas.mctcommon; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Properties; @@ -23,16 +24,37 @@ public Configuration(String comment, Path configFile, ConfigurationRegistry regi } @Override - public void loadFromXML() { + public void load() { + if (Files.exists(file)) { - loadFromXML(file); + String in = null; + try { + in = readFile(file); + } catch (IOException e) { + MCTCommon.LOGGER.catching(e); + return; + } + + if (in.startsWith(" keybindings; + private Map keybindings; /** * Initialize keybind manage @@ -71,7 +31,7 @@ public String toString() { */ public KeybindManager(IsKeyDownFunc defaultFunction) { this.defaultFunction = defaultFunction; - this.keybindings = new ArrayList<>(); + this.keybindings = new HashMap<>(); } /** @@ -79,7 +39,7 @@ public KeybindManager(IsKeyDownFunc defaultFunction) { */ @Override public void onRunClientGameLoop(Minecraft mc) { - for (Keybind keybind : this.keybindings) { + for (Keybind keybind : this.keybindings.values()) { IsKeyDownFunc keyDown = keybind.isKeyDownFunc != null ? keybind.isKeyDownFunc : defaultFunction; if (keyDown.isKeyDown(keybind.vanillaKeyBinding)) { keybind.onKeyDown.run(); @@ -88,26 +48,87 @@ public void onRunClientGameLoop(Minecraft mc) { } + public void registerKeybinds(GameSettings options, Class keybindIDclass) { + if (keybindIDclass.isEnum()) + registerKeybinds(options, keybindIDclass.getEnumConstants()); + } + + public void registerKeybinds(GameSettings options, KeybindID... keybind) { + for (KeybindID keybindEnum : keybind) { + registerKeybind(options, keybindEnum, keybindEnum.getKeybind()); + } + } + /** - * Register new keybind + * Register a new keybind * - * @param keybind Keybind to register - * @param options + * @param keybindID The {@link KeybindID} to register this underI + * @param keybind The {@link Keybind} to register */ - public void registerKeybind(Keybind keybind, GameSettings options) { - this.keybindings.add(keybind); + public void registerKeybind(GameSettings options, KeybindID keybindID, Keybind keybind) { + this.keybindings.put(keybindID, keybind); KeyBinding keyBinding = keybind.vanillaKeyBinding; - if (!AccessorKeyBinding.getCategoryOrder().containsKey(keybind.category)) - AccessorKeyBinding.getCategoryOrder().put(keybind.category, AccessorKeyBinding.getCategoryOrder().size() + 1); + Map categoryOrder = AccessorKeyBinding.getCategoryOrder(); + + if (!categoryOrder.containsKey(keybind.category)) + categoryOrder.put(keybind.category, categoryOrder.size() + 1); // add keybinding options.keyBindings = ArrayUtils.add(options.keyBindings, keyBinding); } + public Keybind getKeybind(KeybindID id) { + return keybindings.get(id); + } + @FunctionalInterface public static interface IsKeyDownFunc { public boolean isKeyDown(KeyBinding keybind); } + + public static interface KeybindID { + public Keybind getKeybind(); + } + + public static class Keybind { + + public final KeyBinding vanillaKeyBinding; + private final String category; + private final Runnable onKeyDown; + private final IsKeyDownFunc isKeyDownFunc; + + /** + * Initialize keybind + * + * @param name Name of keybind + * @param category Category of keybind + * @param defaultKey Default key of keybind + * @param onKeyDown Will be run when the keybind is pressed + */ + public Keybind(String name, String category, int defaultKey, Runnable onKeyDown) { + this(name, category, defaultKey, onKeyDown, null); + } + + /** + * Initialize keybind with a different "isKeyDown" method + * + * @param name Name of keybind + * @param category Category of keybind + * @param defaultKey Default key of keybind + * @param onKeyDown Will be run when the keybind is pressed + */ + public Keybind(String name, String category, int defaultKey, Runnable onKeyDown, IsKeyDownFunc func) { + this.vanillaKeyBinding = new KeyBinding(name, defaultKey, category); + this.category = category; + this.onKeyDown = onKeyDown; + this.isKeyDownFunc = func; + } + + @Override + public String toString() { + return this.vanillaKeyBinding.getKeyDescription(); + } + } } diff --git a/src/main/java/com/minecrafttas/mctcommon/events/EventListenerRegistry.java b/src/main/java/com/minecrafttas/mctcommon/events/EventListenerRegistry.java index 65aa9aa0..1170342e 100644 --- a/src/main/java/com/minecrafttas/mctcommon/events/EventListenerRegistry.java +++ b/src/main/java/com/minecrafttas/mctcommon/events/EventListenerRegistry.java @@ -255,7 +255,7 @@ public static Object fireEvent(Class if (newReturnValue != null) returnValue = newReturnValue; } catch (IllegalAccessException | InvocationTargetException e) { - throw new EventException(eventClass, e); + throw new EventException(eventClass, e.getCause()); } catch (IllegalArgumentException e) { throw new EventException(String.format("Event fired with the wrong number of parameters. Expected: %s, Actual: %s", method.getParameterCount(), eventParams.length), eventClass, e); } diff --git a/src/main/java/com/minecrafttas/mctcommon/file/AbstractDataFile.java b/src/main/java/com/minecrafttas/mctcommon/file/AbstractDataFile.java index 132beba3..dcd1cb2e 100644 --- a/src/main/java/com/minecrafttas/mctcommon/file/AbstractDataFile.java +++ b/src/main/java/com/minecrafttas/mctcommon/file/AbstractDataFile.java @@ -84,13 +84,84 @@ public static void createDirectory(Path directory) throws IOException { Files.createDirectories(directory); } + public void save() { + this.saveToProperties(); + } + + public void save(Path file) { + this.saveToProperties(file); + } + + protected void saveToProperties() { + this.saveToProperties(file); + } + + protected void saveToProperties(Path file) { + try { + OutputStream fos = Files.newOutputStream(file); + properties.store(fos, comment); + fos.close(); + } catch (IOException e) { + MCTCommon.LOGGER.catching(e); + } + } + + /** + * Saves the {@link #properties} to the {@link #file} location + */ + protected void saveToXML() { + this.saveToXML(file); + } + + /** + * Saves the {@link #properties} to a specified file + * @param file The file to save the {@link #properties} to + */ + protected void saveToXML(Path file) { + try { + OutputStream fos = Files.newOutputStream(file); + properties.storeToXML(fos, comment, "UTF-8"); + fos.close(); + } catch (IOException e) { + MCTCommon.LOGGER.catching(e); + } + } + + protected void saveToJson() { + saveToJson(file); + } + + protected void saveToJson(Path file) { + //@formatter:off + Gson json = new GsonBuilder() + .registerTypeAdapter(Properties.class, new PropertiesSerializer()) + .setPrettyPrinting() + .create(); + //@formatter:on + try { + String element = json.toJson(properties); + element = String.format("// %s\n", comment) + element; + Files.write(file, element.getBytes()); + } catch (IOException e) { + MCTCommon.LOGGER.catching(e); + } + } + public void load() { + loadFromProperties(); + } + + public void load(Path file) { + loadFromProperties(file); + } + + protected void loadFromProperties() { if (Files.exists(file)) { - load(file); + loadFromProperties(file); } } - public void load(Path file) { + protected void loadFromProperties(Path file) { InputStream fis; Properties newProp = new Properties(); try { @@ -113,7 +184,7 @@ public void load(Path file) { /** * Loads the xml {@link #file} into {@link #properties} if it exists */ - public void loadFromXML() { + protected void loadFromXML() { if (Files.exists(file)) { loadFromXML(file); } @@ -122,7 +193,7 @@ public void loadFromXML() { /** * @param file The xml file to load into {@link #properties} */ - public void loadFromXML(Path file) { + protected void loadFromXML(Path file) { InputStream fis; Properties newProp = new Properties(); try { @@ -142,11 +213,11 @@ public void loadFromXML(Path file) { this.properties = newProp; } - public void loadFromJson() { + protected void loadFromJson() { loadFromJson(file); } - public void loadFromJson(Path file) { + protected void loadFromJson(Path file) { //@formatter:off Gson json = new GsonBuilder() .registerTypeAdapter(Properties.class, new PropertiesDeserializer()) @@ -155,7 +226,7 @@ public void loadFromJson(Path file) { String in; try { - in = new String(Files.readAllBytes(file)); + in = readFile(file); } catch (IOException e) { MCTCommon.LOGGER.catching(e); return; @@ -164,61 +235,11 @@ public void loadFromJson(Path file) { properties = json.fromJson(in, Properties.class); } - public void save() { - this.save(file); - } - - public void save(Path file) { - try { - OutputStream fos = Files.newOutputStream(file); - properties.store(fos, comment); - fos.close(); - } catch (IOException e) { - MCTCommon.LOGGER.catching(e); - } - } - - /** - * Saves the {@link #properties} to the {@link #file} location - */ - public void saveToXML() { - this.saveToXML(file); - } - - /** - * Saves the {@link #properties} to a specified file - * @param file The file to save the {@link #properties} to - */ - public void saveToXML(Path file) { - try { - OutputStream fos = Files.newOutputStream(file); - properties.storeToXML(fos, comment, "UTF-8"); - fos.close(); - } catch (IOException e) { - MCTCommon.LOGGER.catching(e); - } - } - - public void saveToJson() { - saveToJson(file); - } - - public void saveToJson(Path file) { - //@formatter:off - Gson json = new GsonBuilder() - .registerTypeAdapter(Properties.class, new PropertiesSerializer()) - .setPrettyPrinting() - .create(); - //@formatter:on - try { - String element = json.toJson(properties); - Files.write(file, element.getBytes()); - } catch (IOException e) { - MCTCommon.LOGGER.catching(e); - } + protected String readFile(Path file) throws IOException { + return new String(Files.readAllBytes(file)); } - public class PropertiesSerializer implements JsonSerializer { + private class PropertiesSerializer implements JsonSerializer { @Override public JsonElement serialize(Properties src, Type typeOfSrc, JsonSerializationContext context) { @@ -230,7 +251,7 @@ public JsonElement serialize(Properties src, Type typeOfSrc, JsonSerializationCo } } - public class PropertiesDeserializer implements JsonDeserializer { + private class PropertiesDeserializer implements JsonDeserializer { @Override public Properties deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { diff --git a/src/main/java/com/minecrafttas/mctcommon/networking/ByteBufferBuilder.java b/src/main/java/com/minecrafttas/mctcommon/networking/ByteBufferBuilder.java index 8558439b..2b74add5 100644 --- a/src/main/java/com/minecrafttas/mctcommon/networking/ByteBufferBuilder.java +++ b/src/main/java/com/minecrafttas/mctcommon/networking/ByteBufferBuilder.java @@ -135,6 +135,11 @@ public ByteBufferBuilder writeByteArray(byte[] value) { return this; } + public ByteBufferBuilder writeEnum(Enum state) { + this.writeShort((short) state.ordinal()); + return this; + } + /** * Unlocks the buffer from the pool making it available for other uses */ @@ -201,6 +206,12 @@ public static byte[] readByteArray(ByteBuffer buf) { return array; } + public static > T readEnum(Class clazz, ByteBuffer buf) { + if (!clazz.isEnum()) + throw new IllegalArgumentException("Class is not an enum"); + return clazz.getEnumConstants()[buf.getShort()]; + } + /** * Debug packetname * diff --git a/src/main/java/com/minecrafttas/mctcommon/networking/exception/WrongSideException.java b/src/main/java/com/minecrafttas/mctcommon/networking/exception/WrongSideException.java index 3812ec94..c6c0291b 100644 --- a/src/main/java/com/minecrafttas/mctcommon/networking/exception/WrongSideException.java +++ b/src/main/java/com/minecrafttas/mctcommon/networking/exception/WrongSideException.java @@ -5,8 +5,6 @@ public class WrongSideException extends Exception { - private static final long serialVersionUID = 1439028694540465537L; - public WrongSideException(PacketID packet, Client.Side side) { super(String.format("The packet %s is sent to the wrong side: %s", packet.getName(), side.name())); } diff --git a/src/main/java/com/minecrafttas/mctcommon/networking/interfaces/ClientPacketHandler.java b/src/main/java/com/minecrafttas/mctcommon/networking/interfaces/ClientPacketHandler.java index c4ba3d69..6e7e76c0 100644 --- a/src/main/java/com/minecrafttas/mctcommon/networking/interfaces/ClientPacketHandler.java +++ b/src/main/java/com/minecrafttas/mctcommon/networking/interfaces/ClientPacketHandler.java @@ -7,5 +7,14 @@ public interface ClientPacketHandler extends PacketHandlerBase { + /** + * Called when a packet reaches the client + * @param id The packet id. + * @param buf The buffer with data from the server side + * @param username The username of the current player + * @throws PacketNotImplementedException If a packet is not implemented + * @throws WrongSideException If the packet is sent to the wrong client + * @throws Exception Anything else + */ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception; } diff --git a/src/main/java/com/minecrafttas/mctcommon/registry/AbstractRegistry.java b/src/main/java/com/minecrafttas/mctcommon/registry/AbstractRegistry.java index b2d6813a..d82441c7 100644 --- a/src/main/java/com/minecrafttas/mctcommon/registry/AbstractRegistry.java +++ b/src/main/java/com/minecrafttas/mctcommon/registry/AbstractRegistry.java @@ -3,7 +3,7 @@ import java.util.Arrays; import java.util.Map; -import com.minecrafttas.tasmod.TASmod; +import com.minecrafttas.mctcommon.MCTCommon; public abstract class AbstractRegistry { protected final Map REGISTRY; @@ -20,12 +20,12 @@ public void register(V registryObject) { } if (containsClass(registryObject)) { - TASmod.LOGGER.warn("Trying to register an object in {}, but another instance of this class is already registered: {}", name, registryObject.getClass().getName()); + MCTCommon.LOGGER.warn("Trying to register an object in {}, but another instance of this class is already registered: {}", name, registryObject.getClass().getName()); return; } if (REGISTRY.containsKey(registryObject.getExtensionName())) { - TASmod.LOGGER.warn("Trying to register the an object in {}, but an extension with the same name is already registered: {}", registryObject.getExtensionName()); + MCTCommon.LOGGER.warn("Trying to register the an object in {}, but an extension with the same name is already registered: {}", registryObject.getExtensionName()); return; } @@ -50,7 +50,7 @@ public void unregister(V registryObject) { if (REGISTRY.containsKey(registryObject.getExtensionName())) { REGISTRY.remove(registryObject.getExtensionName()); } else { - TASmod.LOGGER.warn("Trying to unregister an object from {}, but it was not registered: {}", name, registryObject.getClass().getName()); + MCTCommon.LOGGER.warn("Trying to unregister an object from {}, but it was not registered: {}", name, registryObject.getClass().getName()); } } diff --git a/src/main/java/com/minecrafttas/tasmod/TASmod.java b/src/main/java/com/minecrafttas/tasmod/TASmod.java index 6cb78d10..06cb1ed1 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmod.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmod.java @@ -23,18 +23,20 @@ import com.minecrafttas.tasmod.commands.CommandSaveTAS; import com.minecrafttas.tasmod.commands.CommandSavestate; import com.minecrafttas.tasmod.commands.CommandTickrate; -import com.minecrafttas.tasmod.commands.TabCompletionUtils; import com.minecrafttas.tasmod.handlers.PlayUntilHandler; import com.minecrafttas.tasmod.playback.PlaybackControllerServer; import com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension; +import com.minecrafttas.tasmod.registries.TASmodAPIRegistry; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.SavestateHandlerServer; +import com.minecrafttas.tasmod.savestates.handlers.SavestateGuiHandlerServer; import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler; -import com.minecrafttas.tasmod.savestates.storage.builtin.SavestateMotionStorage; +import com.minecrafttas.tasmod.savestates.storage.builtin.ClientMotionStorage; import com.minecrafttas.tasmod.tickratechanger.TickrateChangerServer; import com.minecrafttas.tasmod.ticksync.TickSyncServer; import com.minecrafttas.tasmod.util.LoggerMarkers; import com.minecrafttas.tasmod.util.Scheduler; +import com.minecrafttas.tasmod.util.TabCompletionUtils; import net.fabricmc.api.ModInitializer; import net.fabricmc.loader.api.FabricLoader; @@ -81,6 +83,8 @@ public class TASmod implements ModInitializer, EventServerInit, EventServerStop public static final PlayUntilHandler playUntil = new PlayUntilHandler(); + public static ClientMotionStorage motionStorage = new ClientMotionStorage(); + @Override public void onInitialize() { @@ -117,14 +121,17 @@ public void onInitialize() { PacketHandlerRegistry.register(startPositionMetadataExtension); PacketHandlerRegistry.register(tabCompletionUtils); PacketHandlerRegistry.register(commandFileCommand); - SavestateMotionStorage motionStorage = new SavestateMotionStorage(); + PacketHandlerRegistry.register(new SavestateGuiHandlerServer()); PacketHandlerRegistry.register(motionStorage); - EventListenerRegistry.register(motionStorage); SavestateResourcePackHandler resourcepackHandler = new SavestateResourcePackHandler(); PacketHandlerRegistry.register(resourcepackHandler); EventListenerRegistry.register(resourcepackHandler); PacketHandlerRegistry.register(playUntil); EventListenerRegistry.register(playUntil); + + EventListenerRegistry.register(TASmodAPIRegistry.SAVESTATE_STORAGE); + + registerSavestateStorage(); } @Override @@ -185,8 +192,11 @@ public void onServerStop(MinecraftServer mcserver) { } } + private void registerSavestateStorage() { + TASmodAPIRegistry.SAVESTATE_STORAGE.register(motionStorage); + } + public static MinecraftServer getServerInstance() { return serverInstance; } - } diff --git a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java index 6acd7502..1ce39bdc 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java @@ -40,7 +40,8 @@ import com.minecrafttas.tasmod.registries.TASmodKeybinds; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.SavestateHandlerClient; -import com.minecrafttas.tasmod.savestates.handlers.SavestatePlayerHandler; +import com.minecrafttas.tasmod.savestates.handlers.SavestateGuiHandlerClient; +import com.minecrafttas.tasmod.savestates.handlers.SavestatePlayerHandlerClient; import com.minecrafttas.tasmod.tickratechanger.TickrateChangerClient; import com.minecrafttas.tasmod.ticksync.TickSyncClient; import com.minecrafttas.tasmod.util.LoggerMarkers; @@ -125,7 +126,9 @@ public void onInitializeClient() { registerConfigValues(); - loadConfig(Minecraft.getMinecraft()); + Minecraft mc = Minecraft.getMinecraft(); + + loadConfig(mc); virtual = new VirtualInput(LOGGER); @@ -172,7 +175,8 @@ private void registerNetworkPacketHandlers() { PacketHandlerRegistry.register(ticksyncClient); PacketHandlerRegistry.register(tickratechanger); PacketHandlerRegistry.register(savestateHandlerClient); - PacketHandlerRegistry.register(new SavestatePlayerHandler(null)); //TODO Split player handler into client and server + PacketHandlerRegistry.register(new SavestatePlayerHandlerClient()); + PacketHandlerRegistry.register(new SavestateGuiHandlerClient()); } private void registerEventListeners() { @@ -313,7 +317,8 @@ private void initializeCustomPacketHandler() { @Override public void onOptionsInit(GameSettings options) { - Arrays.stream(TASmodKeybinds.valuesKeybind()).forEach((keybind) -> keybindManager.registerKeybind(keybind, options)); + // Initialize keybind manager + keybindManager.registerKeybinds(options, TASmodKeybinds.class); Arrays.stream(TASmodKeybinds.valuesVanillaKeybind()).forEach(VirtualKeybindings::registerBlockedKeyBinding); } @@ -358,7 +363,8 @@ private void loadConfig(Minecraft mc) { } } config = new Configuration("TASmod configuration", configDir.resolve("tasmod.cfg"), CONFIG_REGISTRY); - config.loadFromXML(); - config.saveToXML(); + + config.load(); + config.save(); } } diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandFullPlay.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandFullPlay.java index 8829aeeb..b6068335 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandFullPlay.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandFullPlay.java @@ -4,7 +4,8 @@ import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; import com.minecrafttas.tasmod.registries.TASmodPackets; -import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; +import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateCallback; +import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateFlags; import com.minecrafttas.tasmod.savestates.exceptions.LoadstateException; import net.minecraft.command.CommandBase; @@ -28,8 +29,17 @@ public String getUsage(ICommandSender sender) { @Override public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException { + + SavestateCallback cb = (paths) -> { + try { + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_SCREEN)); + } catch (Exception e) { + TASmod.LOGGER.catching(e); + } + }; + try { - TASmod.savestateHandlerServer.loadState(0, false, false); + TASmod.savestateHandlerServer.loadState(0, cb, SavestateFlags.BLOCK_CHANGE_INDEX, SavestateFlags.BLOCK_PAUSE_TICKRATE); } catch (LoadstateException e) { sender.sendMessage(new TextComponentString(TextFormatting.RED + "Failed to load a savestate: " + e.getMessage())); return; @@ -38,7 +48,7 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args e.printStackTrace(); return; } finally { - TASmod.savestateHandlerServer.state = SavestateState.NONE; + TASmod.savestateHandlerServer.resetState(); } TASmod.playbackControllerServer.setServerState(TASstate.PLAYBACK); try { diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandFullRecord.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandFullRecord.java index 9ab51422..797a2a18 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandFullRecord.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandFullRecord.java @@ -4,7 +4,8 @@ import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; import com.minecrafttas.tasmod.registries.TASmodPackets; -import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; +import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateCallback; +import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateFlags; import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; import net.minecraft.command.CommandBase; @@ -28,17 +29,22 @@ public String getUsage(ICommandSender sender) { @Override public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException { + + SavestateCallback cb = (paths) -> { + try { + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_SCREEN)); + } catch (Exception e) { + TASmod.LOGGER.catching(e); + } + }; + try { - TASmod.savestateHandlerServer.saveState(0, false); + TASmod.savestateHandlerServer.saveState(0, cb, SavestateFlags.BLOCK_PAUSE_TICKRATE); } catch (SavestateException e) { sender.sendMessage(new TextComponentString(TextFormatting.RED + "Failed to create a savestate: " + e.getMessage())); return; - } catch (Exception e) { - e.printStackTrace(); - sender.sendMessage(new TextComponentString(TextFormatting.RED + "Failed to create a savestate: " + e.getCause().toString())); - return; } finally { - TASmod.savestateHandlerServer.state = SavestateState.NONE; + TASmod.savestateHandlerServer.resetState(); } TASmod.playbackControllerServer.setServerState(TASstate.RECORDING); try { diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandRestartAndPlay.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandRestartAndPlay.java index f0c8a290..97bc4a28 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandRestartAndPlay.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandRestartAndPlay.java @@ -2,7 +2,6 @@ import java.io.File; import java.io.FileFilter; -import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -10,7 +9,7 @@ import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; import com.minecrafttas.tasmod.registries.TASmodPackets; -import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; +import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateFlags; import com.minecrafttas.tasmod.savestates.exceptions.LoadstateException; import net.minecraft.client.Minecraft; @@ -49,21 +48,13 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args name = name.concat(args[i] + spacer); } try { - TASmod.savestateHandlerServer.loadState(0, false); + TASmod.savestateHandlerServer.loadState(0, null, SavestateFlags.BLOCK_PAUSE_TICKRATE); } catch (LoadstateException e) { TASmod.LOGGER.catching(e); if (e.getMessage() != null) { sender.sendMessage(new TextComponentString(TextFormatting.RED + "Could not load the initial savestate: " + e.getMessage())); } - TASmod.savestateHandlerServer.state = SavestateState.NONE; - TASmod.tickratechanger.pauseGame(false); - return; - } catch (IOException e) { - TASmod.LOGGER.catching(e); - if (e.getMessage() != null) { - sender.sendMessage(new TextComponentString(TextFormatting.RED + "Could not load the initial savestate: " + e.getMessage())); - } - TASmod.savestateHandlerServer.state = SavestateState.NONE; + TASmod.savestateHandlerServer.resetState(); TASmod.tickratechanger.pauseGame(false); return; } diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java index 63d66b46..0373883c 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java @@ -1,25 +1,46 @@ package com.minecrafttas.tasmod.commands; -import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.function.UnaryOperator; +import java.util.regex.Pattern; import com.minecrafttas.tasmod.TASmod; -import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; +import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; +import com.minecrafttas.tasmod.registries.TASmodPackets; +import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateCallback; +import com.minecrafttas.tasmod.savestates.SavestateIndexer.ErrorRunnable; +import com.minecrafttas.tasmod.savestates.SavestateIndexer.FailedSavestate; +import com.minecrafttas.tasmod.savestates.SavestateIndexer.Savestate; import com.minecrafttas.tasmod.savestates.exceptions.LoadstateException; import com.minecrafttas.tasmod.savestates.exceptions.SavestateDeleteException; import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; +import com.minecrafttas.tasmod.util.Component; +import com.minecrafttas.tasmod.util.Component.CClickEvent; +import com.minecrafttas.tasmod.util.Component.CHoverEvent; +import com.minecrafttas.tasmod.util.I18n; +import com.minecrafttas.tasmod.util.LoggerMarkers; +import net.minecraft.client.Minecraft; import net.minecraft.command.CommandBase; import net.minecraft.command.CommandException; import net.minecraft.command.ICommandSender; +import net.minecraft.command.WrongUsageException; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.server.MinecraftServer; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.Style; import net.minecraft.util.text.TextComponentString; import net.minecraft.util.text.TextFormatting; import net.minecraft.util.text.event.ClickEvent; +import net.minecraft.util.text.event.HoverEvent; public class CommandSavestate extends CommandBase { + public static boolean once = true; + @Override public String getName() { return "savestate"; @@ -27,7 +48,7 @@ public String getName() { @Override public String getUsage(ICommandSender sender) { - return "/savestate [index]"; + return "/savestate save|load|delete|reload|rename|info"; } @Override @@ -35,233 +56,693 @@ public int getRequiredPermissionLevel() { return 2; } + /** + *
+	 * savestate -> info
+	 * ├── index -> infoIndex
+	 * │   └── amount -> infoIndexAmount
+	 * ├── save -> saveNew
+	 * │   ├── index -> saveIndex
+	 * │   │   └── name -> saveNameIndex
+	 * │   └── name -> saveName
+	 * ├── load -> loadRecent
+	 * │   └── index -> loadIndex
+	 * ├── delete
+	 * │   └── index -> delete
+	 * │       └── indexTo -> deleteMore
+	 * │           └── force -> deleteDis
+	 * ├── reload -> reload
+	 * ├── rename
+	 * │   └── index
+	 * │       └── name -> rename
+	 * ├── info -> info
+	 * │   ├── index -> infoIndex
+	 * │   │   └── amount -> infoIndexAmount
+	 * │   └── all -> infoAll
+	 * └── import -> importing
+	 * 
+ * @param server The MinecraftServer + * @param sender The command sender + * @param args The command arguments + */ @Override public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException { - if (args.length == 0) { - sendHelp(sender); - } else if (args.length >= 1) { - if ("save".equals(args[0])) { - if (args.length == 1) { - TASmod.gameLoopSchedulerServer.add(() -> { - try { - saveLatest(); - } catch (CommandException e) { - e.printStackTrace(); - } - }); - } else if (args.length == 2) { - TASmod.gameLoopSchedulerServer.add(() -> { - try { - saveWithIndex(args); - } catch (CommandException e) { - e.printStackTrace(); - } - }); - } else { - throw new CommandException("Too many arguments!", new Object[] {}); - } - } else if ("load".equals(args[0])) { - if (args.length == 1) { - TASmod.gameLoopSchedulerServer.add(() -> { - try { - loadLatest(); - } catch (CommandException e) { - e.printStackTrace(); - } - }); - } else if (args.length == 2) { - TASmod.gameLoopSchedulerServer.add(() -> { - try { - loadLatest(args); - } catch (CommandException e) { - e.printStackTrace(); - } - }); - } else { - throw new CommandException("Too many arguments!", new Object[] {}); - } - } else if ("delete".equals(args[0])) { - if (args.length == 2) { - delete(args); - } else if (args.length == 3) { - int args1 = processIndex(args[1]); - int args2 = processIndex(args[2]); - int count = (args2 + 1) - args1; - TextComponentString confirm = new TextComponentString(TextFormatting.YELLOW + "Are you sure you want to delete " + count + (count == 1 ? " savestate? " : " savestates? ") + TextFormatting.GREEN + "[YES]"); - confirm.getStyle().setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("/savestate deletDis %s %s", args[1], args[2]))); - sender.sendMessage(confirm); - } else { - throw new CommandException("Too many arguments!", new Object[] {}); + int length = args.length; + + if (length == 0) { + info(sender); + return; + } + + String first = args[0]; + + if (isNumeric(first)) { + int index = processIndex(first); + + if (length == 1) { + infoIndex(sender, index); + return; + } + + String second = args[1]; + int amount = parseInt(second); + infoIndexAmount(sender, index, amount); + return; + } + + else if ("save".equals(first)) { + + if (length == 1) { + saveNew(sender); + return; + } + + String second = args[1]; + if (isNumeric(second)) { + int index = processIndex(second); + + if (length == 2) { + saveIndex(sender, index); + return; } - } else if ("deletDis".equals(args[0])) { - if (args.length == 3) { - deleteMultiple(args); + + String third = getRestArgsAsString(2, args); + saveIndexName(sender, index, third); + return; + + } else { + second = getRestArgsAsString(1, args); + saveName(sender, second); + return; + } + } + + else if ("load".equals(first)) { + + if (length == 1) { + loadRecent(sender); + return; + } + + String second = args[1]; + int index = processIndex(second); + loadIndex(sender, index); + return; + } + + else if ("delete".equals(first)) { + + if (length == 1) { + throw new WrongUsageException("/savestate delete [indexTo]"); + } + + String second = args[1]; + int indexFrom = processIndex(second); + if (length == 2) { + delete(sender, indexFrom); + return; + } + + String third = args[2]; + int indexTo = processIndex(third); + if (length == 3) { + deleteMore(sender, indexFrom, indexTo); + return; + } + + String fourth = args[3]; + if ("force".equals(fourth)) { + deleteDis(sender, indexFrom, indexTo); + return; + } + } + + else if ("reload".equals(first)) { + reload(sender); + return; + } + + else if ("rename".equals(first)) { + int index = processIndex(args[1]); + String name = getRestArgsAsString(2, args); + + rename(sender, index, name); + return; + } + + else if ("info".equals(first)) { + + if (length == 1) { + info(sender); + return; + } + + String second = args[1]; + if (isNumeric(second)) { + int index = processIndex(second); + + if (length == 2) { + infoIndex(sender, index); + return; } - } else if ("list".equals(args[0])) { - sender.sendMessage(new TextComponentString(String.format("The current savestate index is %s%s", TextFormatting.AQUA, TASmod.savestateHandlerServer.getCurrentIndex()))); - sender.sendMessage(new TextComponentString(String.format("Available indexes are %s%s", TextFormatting.AQUA, TASmod.savestateHandlerServer.getIndexesAsString().isEmpty() ? "None" : TASmod.savestateHandlerServer.getIndexesAsString()))); - } else if ("help".equals(args[0])) { - if (args.length == 1) { - sendHelp(sender); - } else if (args.length == 2) { - int i = 1; - try { - i = Integer.parseInt(args[1]); - } catch (NumberFormatException e) { - throw new CommandException("Page number was not a number %s", new Object[] { args[1] }); - } - sendHelp(sender, i); + + String third = args[2]; + int amount = parseInt(third); + infoIndexAmount(sender, index, amount); + return; + } else if ("all".equals(second)) { + infoAll(sender); + return; + } + } + +// else if ("import".equals(first)) { +// importing(sender); +// } + + throw new WrongUsageException(getUsage(sender)); + } + + @Override + public List getTabCompletions(MinecraftServer minecraftServer, ICommandSender iCommandSender, String[] args, BlockPos blockPos) { + int length = args.length; + if (length == 1) { + return getListOfStringsMatchingLastWord(args, "save", "load", "delete", "reload", "rename", "info", "import"); + } + + String first = args[0]; + if ("save".equals(first)) { + if (length == 2) { + return getIndexes(args); + } + String second = args[1]; + if (isNumeric(second)) { + iCommandSender.sendMessage(new TextComponentString("Type the name of the savestate")); + return new ArrayList<>(); + } + + } + + else if ("load".equals(first)) { + if (length == 2) { + return getIndexes(args); + } + } + + else if ("delete".equals(first)) { + if (length <= 3) { + return getIndexes(args); + } + } + + else if ("rename".equals(first)) { + if (length == 2) { + return getIndexes(args); + } else if (length == 3) { + iCommandSender.sendMessage(new TextComponentString("Type the new name of the savestate")); + return new ArrayList<>(); + } + } + + else if ("info".equals(first)) { + if (length == 2) { + String second = args[1]; + if (isNumeric(second)) { + return getIndexes(args); } else { - throw new CommandException("Too many arguments", new Object[] {}); + return getListOfStringsMatchingLastWord(args, "all"); } - } } + return new ArrayList<>(); } - private void sendHelp(ICommandSender sender) throws CommandException { - sendHelp(sender, 1); + private void info(ICommandSender sender) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command Info"); + infoIndexAmount(sender, null, null); } - private void sendHelp(ICommandSender sender, int i) throws CommandException { + private void infoIndex(ICommandSender sender, Integer index) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command InfoIndex {}", index); + infoIndexAmount(sender, index, null); + } + + @SuppressWarnings("unused") + private void infoIndexAmount(ICommandSender sender, Integer indexToDisplay, Integer amount) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command InfoIndexAmount {}|{}", indexToDisplay, amount); + int currentIndex = TASmod.savestateHandlerServer.getCurrentIndex(); - if (i > 3) { - throw new CommandException("This help page doesn't exist (yet?)", new Object[] {}); + int size = TASmod.savestateHandlerServer.size(); + + if (size == 0) + sendHelp(sender); + + if (indexToDisplay == null) { + indexToDisplay = currentIndex; } - if (i == 1) { - sender.sendMessage(new TextComponentString(TextFormatting.GOLD + "-------------------Savestate Help 1--------------------\n" + TextFormatting.RESET - + "Makes a backup of the minecraft world you are currently playing.\n\n" - + "The mod will keep track of the number of savestates you made in the 'current index' number which is currently " + TextFormatting.AQUA + currentIndex + TextFormatting.RESET - + String.format(". If you make a new savestate via %s/savestate save%s or by pressing %sJ%s by default, ", TextFormatting.AQUA, TextFormatting.RESET, TextFormatting.AQUA, TextFormatting.RESET) - + "the current index will increase by one. " - + String.format("If you load a savestate with %s/savestate load%s or %sK%s by default, it will load the savestate at the current index.\n", TextFormatting.AQUA, TextFormatting.RESET, TextFormatting.AQUA, TextFormatting.RESET))); - } else if (i == 2) { - sender.sendMessage(new TextComponentString(String.format("%1$s-------------------Savestate Help 2--------------------\n" - + "You can load or save savestates in different indexes by specifying the index: %3$s/savestate %4$s %5$s%2$s\n" - + "This will change the %5$scurrent index%2$s to the index you specified.\n\n" - + "So, if you have the savestates %3$s1, 2, 3%2$s and your %5$scurrent index%2$s is %3$s3%2$s, %3$s/savestate %4$sload %5$s2%2$s will load the second savestate and will set the %5$scurrent index%2$s to %3$s2%2$s.\n" - + "But if you savestate again you will OVERWRITE the third savestate, so keep that in mind!!\n\n" - + "The savestate at index 0 will be the savestate when you started the TAS recording and can't be deleted or overwritten with this command", /*1*/TextFormatting.GOLD, /*2*/TextFormatting.RESET, /*3*/TextFormatting.AQUA, /*4*/TextFormatting.GREEN, /*5*/TextFormatting.YELLOW))); - } else if (i == 3) { - sender.sendMessage(new TextComponentString(String.format("%1$s-------------------Savestate Help 3--------------------\n%2$s" - + "%3$s/savestate %4$ssave%2$s - Make a savestate at the next index\n" - + "%3$s/savestate %4$ssave%5$s %2$s - Make a savestate at the specified index\n" - + "%3$s/savestate %4$sload%2$s - Load the savestate at the current index\n" - + "%3$s/savestate %4$sload%5$s %2$s - Load the savestate at the specified index\n" - + "%3$s/savestate %4$sdelete%5$s %2$s - Delete the savestate at the specified index\n" - + "%3$s/savestate %4$sdelete%5$s %2$s - Delete the savestates from the fromIndex to the toIndex\n" - + "%3$s/savestate %4$slist%2$s - Shows the current index as well as the available indexes\n" - + "\nInstead of %4$s %2$syou can use ~ to specify an index relative to the current index e.g. %3$s~-1%2$s will currently load %6$s\n", - /*1*/TextFormatting.GOLD, /*2*/TextFormatting.RESET, /*3*/TextFormatting.AQUA, /*4*/TextFormatting.GREEN, /*5*/TextFormatting.YELLOW, /*6*/(currentIndex - 1)))); - return; + if (amount == null) { + amount = 10; } - TextComponentString nextPage = new TextComponentString(TextFormatting.GOLD + "Click here to go to the next help page (" + (i + 1) + ")\n"); - nextPage.getStyle().setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/savestate help " + (i + 1) + "")); - sender.sendMessage(nextPage); - } - @Override - public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, BlockPos targetPos) { - if (args.length == 1) { - return getListOfStringsMatchingLastWord(args, new String[] { "save", "load", "delete", "list", "help" }); - } else if (args.length == 2 && !"list".equals(args[0])) { - sender.sendMessage(new TextComponentString("Available indexes: " + TextFormatting.AQUA + TASmod.savestateHandlerServer.getIndexesAsString())); + sender.sendMessage(Component.literal("").build()); // Print an empty line + + String format = "MM/dd/yyyy hh:mm:ss a"; // TODO Add server config + if (!sender.getServer().isDedicatedServer()) { + format = I18n.format("msg.tasmod.savestate.dateformat"); } - return super.getTabCompletions(server, sender, args, targetPos); - } + SimpleDateFormat dateFormat = new SimpleDateFormat(format); - // ====================================================================== + List savestateList = TASmod.savestateHandlerServer.getSavestateInfo(indexToDisplay, amount); - private void saveLatest() throws CommandException { - try { - TASmod.savestateHandlerServer.saveState(); - } catch (SavestateException e) { - throw new CommandException(e.getMessage(), new Object[] {}); - } catch (IOException e) { - e.printStackTrace(); - throw new CommandException(e.getMessage(), new Object[] {}); - } finally { - TASmod.savestateHandlerServer.state = SavestateState.NONE; + if (savestateList.size() < size && once) { + sender.sendMessage(Component.translatable("msg.tasmod.savestate.omitted", "/savestate info all").withStyle(TextFormatting.RED, TextFormatting.ITALIC).build()); + once = false; + } + + for (Savestate savestate : savestateList) { + + String index = savestate.getIndex() == null ? "" : Integer.toString(savestate.getIndex()); + boolean isCurrentIndex = savestate.getIndex() == currentIndex; + String name = savestate.getName() == null ? "" : savestate.getName(); + String date = savestate.getDate() == null ? "" : dateFormat.format(savestate.getDate()); + + TextFormatting indexColor = isCurrentIndex ? TextFormatting.AQUA : TextFormatting.BLUE; + TextFormatting nameColor = isCurrentIndex ? TextFormatting.WHITE : TextFormatting.GRAY; + TextFormatting dateColor = isCurrentIndex ? TextFormatting.AQUA : TextFormatting.DARK_AQUA; + TextFormatting saveColor = isCurrentIndex ? TextFormatting.LIGHT_PURPLE : TextFormatting.DARK_PURPLE; + TextFormatting deleteColor = isCurrentIndex ? TextFormatting.RED : TextFormatting.DARK_RED; + TextFormatting renameColor = isCurrentIndex ? TextFormatting.YELLOW : TextFormatting.GOLD; + TextFormatting loadColor = isCurrentIndex ? TextFormatting.GREEN : TextFormatting.DARK_GREEN; + + //@formatter:off + UnaryOperator