From b0516f62a824421b6eb46a6d7405d829aa52e0ca Mon Sep 17 00:00:00 2001 From: Scribble Date: Sat, 13 Sep 2025 19:40:34 +0200 Subject: [PATCH 01/19] [Savestates] Properly implement SavestateStorageExtensions Reimplements AbstractExtendStorage into SavestateStorageExtensions - Add JsonUtils for quickly storing Objects to json - Rename SavestateMotionStorage to ClientMotionStorage --- .../events/EventListenerRegistry.java | 2 +- .../java/com/minecrafttas/tasmod/TASmod.java | 16 +++- .../networking/TASmodBufferBuilder.java | 2 +- .../tasmod/registries/TASmodAPIRegistry.java | 9 +++ .../tasmod/registries/TASmodPackets.java | 2 +- .../storage/AbstractExtendStorage.java | 11 --- .../SavestateStorageExtensionBase.java | 29 +++++++ .../SavestateStorageExtensionRegistry.java | 78 +++++++++++++++++++ ...nStorage.java => ClientMotionStorage.java} | 60 ++++---------- .../minecrafttas/tasmod/util/JsonUtils.java | 33 ++++++++ 10 files changed, 178 insertions(+), 64 deletions(-) delete mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/AbstractExtendStorage.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionBase.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java rename src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/{SavestateMotionStorage.java => ClientMotionStorage.java} (78%) create mode 100644 src/main/java/com/minecrafttas/tasmod/util/JsonUtils.java 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/tasmod/TASmod.java b/src/main/java/com/minecrafttas/tasmod/TASmod.java index 6cb78d10..e45358c9 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmod.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmod.java @@ -27,10 +27,11 @@ 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.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; @@ -81,6 +82,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 +120,16 @@ public void onInitialize() { PacketHandlerRegistry.register(startPositionMetadataExtension); PacketHandlerRegistry.register(tabCompletionUtils); PacketHandlerRegistry.register(commandFileCommand); - SavestateMotionStorage motionStorage = new SavestateMotionStorage(); 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 +190,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/networking/TASmodBufferBuilder.java b/src/main/java/com/minecrafttas/tasmod/networking/TASmodBufferBuilder.java index 2f05ea58..673eeb5c 100644 --- a/src/main/java/com/minecrafttas/tasmod/networking/TASmodBufferBuilder.java +++ b/src/main/java/com/minecrafttas/tasmod/networking/TASmodBufferBuilder.java @@ -10,7 +10,7 @@ import com.minecrafttas.mctcommon.networking.ByteBufferBuilder; import com.minecrafttas.mctcommon.networking.interfaces.PacketID; import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; -import com.minecrafttas.tasmod.savestates.storage.builtin.SavestateMotionStorage.MotionData; +import com.minecrafttas.tasmod.savestates.storage.builtin.ClientMotionStorage.MotionData; import com.minecrafttas.tasmod.tickratechanger.TickrateChangerServer.TickratePauseState; import net.minecraft.nbt.CompressedStreamTools; diff --git a/src/main/java/com/minecrafttas/tasmod/registries/TASmodAPIRegistry.java b/src/main/java/com/minecrafttas/tasmod/registries/TASmodAPIRegistry.java index 67a84a3b..33816e55 100644 --- a/src/main/java/com/minecrafttas/tasmod/registries/TASmodAPIRegistry.java +++ b/src/main/java/com/minecrafttas/tasmod/registries/TASmodAPIRegistry.java @@ -7,6 +7,8 @@ import com.minecrafttas.tasmod.playback.tasfile.flavor.SerialiserFlavorBase; import com.minecrafttas.tasmod.playback.tasfile.flavor.SerialiserFlavorRegistry; import com.minecrafttas.tasmod.playback.tasfile.flavor.builtin.Beta1Flavor; +import com.minecrafttas.tasmod.savestates.storage.SavestateStorageExtensionBase; +import com.minecrafttas.tasmod.savestates.storage.SavestateStorageExtensionRegistry; import net.minecraft.command.CommandBase; @@ -47,4 +49,11 @@ public class TASmodAPIRegistry { * then create a command like normal, as it extends from the vanilla {@link CommandBase} */ public static final ClientCommandRegistry CLIENT_COMMANDS = new ClientCommandRegistry(); + + /** + *

Registry for registering additional data that should be stored or loaded during a Savestate or Loadstate respectively + * + *

Create a new SavestateStorageExtension by extending {@link SavestateStorageExtensionBase} + */ + public static final SavestateStorageExtensionRegistry SAVESTATE_STORAGE = new SavestateStorageExtensionRegistry(); } diff --git a/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java b/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java index 42f4e093..57671a2f 100644 --- a/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java +++ b/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java @@ -7,7 +7,7 @@ import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandExtension; import com.minecrafttas.tasmod.playback.tasfile.flavor.SerialiserFlavorBase; -import com.minecrafttas.tasmod.savestates.storage.builtin.SavestateMotionStorage.MotionData; +import com.minecrafttas.tasmod.savestates.storage.builtin.ClientMotionStorage.MotionData; import com.minecrafttas.tasmod.tickratechanger.TickrateChangerServer.TickratePauseState; import com.minecrafttas.tasmod.util.Ducks.ScoreboardDuck; diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/AbstractExtendStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/AbstractExtendStorage.java deleted file mode 100644 index d67ebc2b..00000000 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/AbstractExtendStorage.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.minecrafttas.tasmod.savestates.storage; - -import org.apache.logging.log4j.Logger; - -import com.minecrafttas.tasmod.TASmod; -import com.minecrafttas.tasmod.events.EventSavestate.EventServerLoadstate; -import com.minecrafttas.tasmod.events.EventSavestate.EventServerSavestate; - -public abstract class AbstractExtendStorage implements EventServerSavestate, EventServerLoadstate { - protected Logger logger = TASmod.LOGGER; -} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionBase.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionBase.java new file mode 100644 index 00000000..1f911302 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionBase.java @@ -0,0 +1,29 @@ +package com.minecrafttas.tasmod.savestates.storage; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.logging.log4j.Logger; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.minecrafttas.mctcommon.registry.Registerable; +import com.minecrafttas.tasmod.TASmod; +import com.minecrafttas.tasmod.util.JsonUtils; + +import net.minecraft.server.MinecraftServer; + +public abstract class SavestateStorageExtensionBase implements Registerable { + protected final Logger logger = TASmod.LOGGER; + protected final Gson json = JsonUtils.getJsonInstance(); + + public final Path fileName; + + public SavestateStorageExtensionBase(String fileName) { + this.fileName = Paths.get(fileName); + } + + public abstract JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave); + + public abstract void onLoadstate(MinecraftServer server, JsonObject loadedData); +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java new file mode 100644 index 00000000..e6f008c1 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java @@ -0,0 +1,78 @@ +package com.minecrafttas.tasmod.savestates.storage; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; + +import com.google.gson.JsonObject; +import com.minecrafttas.mctcommon.registry.AbstractRegistry; +import com.minecrafttas.tasmod.TASmod; +import com.minecrafttas.tasmod.events.EventSavestate.EventServerLoadstate; +import com.minecrafttas.tasmod.events.EventSavestate.EventServerSavestate; +import com.minecrafttas.tasmod.savestates.SavestateHandlerServer; +import com.minecrafttas.tasmod.savestates.exceptions.LoadstateException; +import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; +import com.minecrafttas.tasmod.util.JsonUtils; + +import net.minecraft.server.MinecraftServer; + +public class SavestateStorageExtensionRegistry extends AbstractRegistry implements EventServerSavestate, EventServerLoadstate { + + public SavestateStorageExtensionRegistry() { + super("SAVESTATESTORAGE_REGISTRY", new LinkedHashMap<>()); + } + + @Override + public void onServerSavestate(MinecraftServer server, int index, Path target, Path current) { + Path storageDir = current.resolve(SavestateHandlerServer.storageDir); + if (!Files.exists(storageDir)) { + try { + Files.createDirectory(storageDir); + } catch (IOException e) { + throw new SavestateException(e, "Can't create directory for savestate storage in savestate %s", current.getFileName()); + } + } + + for (SavestateStorageExtensionBase storage : REGISTRY.values()) { + Path dataPath = storageDir.resolve(storage.fileName); + JsonObject dataToSave = storage.onSavestate(server, new JsonObject()); + + try { + JsonUtils.saveJson(dataPath, dataToSave); + } catch (IOException e) { + throw new SavestateException(e, "Can't save %s from %s extension", storage.fileName, storage.getExtensionName()); + } + } + } + + @Override + public void onServerLoadstate(MinecraftServer server, int index, Path target, Path current) { + Path storageDir = target.resolve(SavestateHandlerServer.storageDir); + if (!Files.exists(storageDir)) { + try { + Files.createDirectory(storageDir); + } catch (IOException e) { + throw new LoadstateException(e, "Can't create directory for savestate storage in savestate %s", target.getFileName()); + } + } + + for (SavestateStorageExtensionBase storage : REGISTRY.values()) { + Path dataPath = storageDir.resolve(storage.fileName); + + if (!Files.exists(dataPath)) { + TASmod.LOGGER.warn("Could not load {} in {} extension", storage.fileName, storage.getExtensionName()); + return; + } + + JsonObject loadedData; + try { + loadedData = JsonUtils.loadJson(dataPath); + } catch (IOException e) { + throw new LoadstateException(e, "Can't load %s in %s extension", storage.fileName, storage.getExtensionName()); + } + + storage.onLoadstate(server, loadedData); + } + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/SavestateMotionStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/ClientMotionStorage.java similarity index 78% rename from src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/SavestateMotionStorage.java rename to src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/ClientMotionStorage.java index 95e4e935..42584e88 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/SavestateMotionStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/ClientMotionStorage.java @@ -4,11 +4,7 @@ import static com.minecrafttas.tasmod.registries.TASmodPackets.SAVESTATE_REQUEST_MOTION; import static com.minecrafttas.tasmod.registries.TASmodPackets.SAVESTATE_SET_MOTION; -import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -19,8 +15,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.minecrafttas.mctcommon.networking.Client.Side; @@ -33,11 +27,9 @@ import com.minecrafttas.tasmod.TASmodClient; import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.registries.TASmodPackets; -import com.minecrafttas.tasmod.savestates.SavestateHandlerServer; -import com.minecrafttas.tasmod.savestates.exceptions.LoadstateException; import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; import com.minecrafttas.tasmod.savestates.gui.GuiSavestateSavingScreen; -import com.minecrafttas.tasmod.savestates.storage.AbstractExtendStorage; +import com.minecrafttas.tasmod.savestates.storage.SavestateStorageExtensionBase; import com.minecrafttas.tasmod.util.LoggerMarkers; import net.fabricmc.api.EnvType; @@ -48,20 +40,17 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.management.PlayerList; -public class SavestateMotionStorage extends AbstractExtendStorage implements ClientPacketHandler, ServerPacketHandler { - - private static final Path fileName = Paths.get("clientMotion.json"); - private final Gson json; +public class ClientMotionStorage extends SavestateStorageExtensionBase implements ClientPacketHandler, ServerPacketHandler { private final Map> futures; - public SavestateMotionStorage() { - json = new GsonBuilder().setPrettyPrinting().create(); + public ClientMotionStorage() { + super("clientMotion.json"); futures = new HashMap<>(); } @Override - public void onServerSavestate(MinecraftServer server, int index, Path target, Path current) { + public JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave) { LOGGER.trace(LoggerMarkers.Savestate, "Request motion from client"); this.futures.clear(); @@ -78,8 +67,6 @@ public void onServerSavestate(MinecraftServer server, int index, Path target, Pa e.printStackTrace(); } - JsonObject playerJsonObject = new JsonObject(); - futures.forEach((player, future) -> { try { MotionData data = future.get(5L, TimeUnit.SECONDS); @@ -88,7 +75,7 @@ public void onServerSavestate(MinecraftServer server, int index, Path target, Pa if (player.getName().equals(server.getServerOwner())) { uuid = "singleplayer"; } - playerJsonObject.add(uuid, json.toJsonTree(data)); + dataToSave.add(uuid, json.toJsonTree(data)); } catch (TimeoutException e) { throw new SavestateException(e, "Writing client motion for %s timed out!", player.getName()); @@ -97,27 +84,14 @@ public void onServerSavestate(MinecraftServer server, int index, Path target, Pa } }); - saveJson(current, playerJsonObject); - } - - private void saveJson(Path current, JsonObject data) { - Path saveFile = current.resolve(SavestateHandlerServer.storageDir).resolve(fileName); - - String out = json.toJson(data); - - try { - Files.write(saveFile, out.getBytes()); - } catch (IOException e) { - throw new SavestateException(e, "Could not write to the file system"); - } + return dataToSave; } @Override - public void onServerLoadstate(MinecraftServer server, int index, Path target, Path current) { - JsonObject playerJsonObject = loadMotionData(target); + public void onLoadstate(MinecraftServer server, JsonObject loadedData) { PlayerList list = server.getPlayerList(); - for (Entry motionDataJsonElement : playerJsonObject.entrySet()) { + for (Entry motionDataJsonElement : loadedData.entrySet()) { String playerUUID = motionDataJsonElement.getKey(); MotionData motionData = json.fromJson(motionDataJsonElement.getValue(), MotionData.class); @@ -144,17 +118,6 @@ public void onServerLoadstate(MinecraftServer server, int index, Path target, Pa } } - private JsonObject loadMotionData(Path target) { - Path saveFile = target.resolve(SavestateHandlerServer.storageDir).resolve(fileName); - String in; - try { - in = new String(Files.readAllBytes(saveFile)); - } catch (IOException e) { - throw new LoadstateException(e, "Could not read from the file system"); - } - return json.fromJson(in, JsonObject.class); - } - @Override public PacketID[] getAcceptedPacketIDs() { return new PacketID[] { SAVESTATE_REQUEST_MOTION, SAVESTATE_SET_MOTION }; @@ -285,4 +248,9 @@ public float getJumpMovementVector() { return jumpMovementFactor; } } + + @Override + public String getExtensionName() { + return "ClientMotionStorage"; + } } diff --git a/src/main/java/com/minecrafttas/tasmod/util/JsonUtils.java b/src/main/java/com/minecrafttas/tasmod/util/JsonUtils.java new file mode 100644 index 00000000..33e5b22e --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/util/JsonUtils.java @@ -0,0 +1,33 @@ +package com.minecrafttas.tasmod.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; + +public class JsonUtils { + + public static Gson getJsonInstance() { + return new GsonBuilder().setPrettyPrinting().create(); + } + + public static void saveJson(Path savePath, JsonObject data) throws IOException { + saveJson(savePath, data, getJsonInstance()); + } + + public static void saveJson(Path savePath, JsonObject data, Gson jsonInstance) throws IOException { + String out = jsonInstance.toJson(data); + Files.write(savePath, out.getBytes()); + } + + public static JsonObject loadJson(Path loadPath) throws IOException { + return loadJson(loadPath, getJsonInstance()); + } + + public static JsonObject loadJson(Path loadPath, Gson jsonInstance) throws IOException { + return jsonInstance.fromJson(new String(Files.readAllBytes(loadPath)), JsonObject.class); + } +} From d00137cb780a60acf97f45ca32e8d837873d18f9 Mon Sep 17 00:00:00 2001 From: Scribble Date: Sun, 28 Sep 2025 22:12:39 +0200 Subject: [PATCH 02/19] [Savestates] Split SavestatePlayerHandler into client and server --- .../com/minecrafttas/tasmod/TASmodClient.java | 4 +- .../savestates/SavestateHandlerClient.java | 68 --------- .../savestates/SavestateHandlerServer.java | 8 +- .../SavestatePlayerHandlerClient.java | 133 ++++++++++++++++++ ...java => SavestatePlayerHandlerServer.java} | 38 +---- 5 files changed, 141 insertions(+), 110 deletions(-) create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandlerClient.java rename src/main/java/com/minecrafttas/tasmod/savestates/handlers/{SavestatePlayerHandler.java => SavestatePlayerHandlerServer.java} (83%) diff --git a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java index 6acd7502..38549ed6 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java @@ -40,7 +40,7 @@ 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.SavestatePlayerHandlerClient; import com.minecrafttas.tasmod.tickratechanger.TickrateChangerClient; import com.minecrafttas.tasmod.ticksync.TickSyncClient; import com.minecrafttas.tasmod.util.LoggerMarkers; @@ -172,7 +172,7 @@ 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()); } private void registerEventListeners() { diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java index 15abee1e..2d775d0c 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java @@ -16,7 +16,6 @@ import com.minecrafttas.mctcommon.networking.interfaces.PacketID; import com.minecrafttas.tasmod.TASmodClient; import com.minecrafttas.tasmod.events.EventSavestate; -import com.minecrafttas.tasmod.mixin.savestates.AccessorEntityLivingBase; import com.minecrafttas.tasmod.mixin.savestates.MixinChunkProviderClient; import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.playback.PlaybackControllerClient; @@ -28,7 +27,6 @@ import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; import com.minecrafttas.tasmod.savestates.gui.GuiSavestateSavingScreen; import com.minecrafttas.tasmod.util.Ducks.ChunkProviderDuck; -import com.minecrafttas.tasmod.util.Ducks.SubtickDuck; import com.minecrafttas.tasmod.util.Ducks.WorldClientDuck; import com.minecrafttas.tasmod.util.LoggerMarkers; import com.mojang.realmsclient.gui.ChatFormatting; @@ -38,10 +36,8 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.client.multiplayer.ChunkProviderClient; -import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.math.MathHelper; import net.minecraft.util.text.TextComponentString; -import net.minecraft.world.GameType; import net.minecraft.world.chunk.Chunk; /** @@ -272,70 +268,6 @@ private static void preload(BigArrayList containerList, long ind TASmodAPIRegistry.PLAYBACK_FILE_COMMAND.onPlaybackTick(index, containerToPreload); } - public static void loadPlayer(NBTTagCompound compound) { - LOGGER.trace(LoggerMarkers.Savestate, "Loading client player from NBT"); - Minecraft mc = Minecraft.getMinecraft(); - EntityPlayerSP player = mc.player; - - // Clear any accidental applied potion particles on the client - ((AccessorEntityLivingBase) player).clearPotionEffects(); - - /* - * TODO - * The following 20 lines are all one - * gross workaround for correctly applying the player motion - * to the client... - * - * The motion is applied - * to the player in a previous step and unfortunately - * player.readFromNBT(compound) overwrites the - * previously applied motion... - * - * So this workaround makes sure that the motion is not overwritten - * Fixing this, requires restructuring the steps for loadstating - * and since I plan to do this anyway at some point, I will - * leave this here and be done for today*/ - double x = player.motionX; - double y = player.motionY; - double z = player.motionZ; - - float rx = player.moveForward; - float ry = player.moveVertical; - float rz = player.moveStrafing; - - boolean sprinting = player.isSprinting(); - float jumpVector = player.jumpMovementFactor; - - player.readFromNBT(compound); - - player.motionX = x; - player.motionY = y; - player.motionZ = z; - - player.moveForward = rx; - player.moveVertical = ry; - player.moveStrafing = rz; - - player.setSprinting(sprinting); - player.jumpMovementFactor = jumpVector; - - LOGGER.trace(LoggerMarkers.Savestate, "Setting client gamemode"); - // #86 - int gamemode = compound.getInteger("playerGameType"); - GameType type = GameType.getByID(gamemode); - mc.playerController.setGameType(type); - - // Set the camera rotation to the player rotation - TASmodClient.virtual.CAMERA_ANGLE.setCamera(player.rotationPitch, player.rotationYaw); - SubtickDuck entityRenderer = (SubtickDuck) Minecraft.getMinecraft().entityRenderer; - entityRenderer.runUpdate(0); - - // Clear boss bars on savestate load - mc.ingameGUI.getBossOverlay().clearBossInfos(); - - EventListenerRegistry.fireEvent(EventSavestate.EventClientLoadPlayer.class, player); - } - /** * Unloads all chunks and reloads the renderer so no chunks will be visible * throughout the unloading progress
diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index 6ba4b3e7..fbdaf14c 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -39,7 +39,7 @@ import com.minecrafttas.tasmod.savestates.files.SavestateDataFile; import com.minecrafttas.tasmod.savestates.files.SavestateDataFile.DataValues; import com.minecrafttas.tasmod.savestates.files.SavestateTrackerFile; -import com.minecrafttas.tasmod.savestates.handlers.SavestatePlayerHandler; +import com.minecrafttas.tasmod.savestates.handlers.SavestatePlayerHandlerServer; import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler; import com.minecrafttas.tasmod.savestates.handlers.SavestateWorldHandler; import com.minecrafttas.tasmod.util.LoggerMarkers; @@ -84,7 +84,7 @@ public static enum SavestateState { private int latestIndex = 0; private int currentIndex; - private final SavestatePlayerHandler playerHandler; + private final SavestatePlayerHandlerServer playerHandler; private final SavestateWorldHandler worldHandler; public static final Path storageDir = Paths.get("tasmod/"); @@ -100,7 +100,7 @@ public static enum SavestateState { public SavestateHandlerServer(MinecraftServer server, Logger logger) { this.server = server; this.logger = logger; - this.playerHandler = new SavestatePlayerHandler(server); + this.playerHandler = new SavestatePlayerHandlerServer(server); this.worldHandler = new SavestateWorldHandler(server); createSavestateDirectory(); @@ -716,7 +716,7 @@ private void setCurrentIndex(int index) { logger.debug(LoggerMarkers.Savestate, "Setting the savestate index to {}", currentIndex); } - public SavestatePlayerHandler getPlayerHandler() { + public SavestatePlayerHandlerServer getPlayerHandler() { return playerHandler; } diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandlerClient.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandlerClient.java new file mode 100644 index 00000000..878ec01f --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandlerClient.java @@ -0,0 +1,133 @@ +package com.minecrafttas.tasmod.savestates.handlers; + +import static com.minecrafttas.tasmod.TASmod.LOGGER; +import static com.minecrafttas.tasmod.registries.TASmodPackets.SAVESTATE_PLAYER; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import com.minecrafttas.mctcommon.events.EventListenerRegistry; +import com.minecrafttas.mctcommon.networking.exception.PacketNotImplementedException; +import com.minecrafttas.mctcommon.networking.exception.WrongSideException; +import com.minecrafttas.mctcommon.networking.interfaces.ClientPacketHandler; +import com.minecrafttas.mctcommon.networking.interfaces.PacketID; +import com.minecrafttas.tasmod.TASmodClient; +import com.minecrafttas.tasmod.events.EventSavestate; +import com.minecrafttas.tasmod.mixin.savestates.AccessorEntityLivingBase; +import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; +import com.minecrafttas.tasmod.registries.TASmodPackets; +import com.minecrafttas.tasmod.util.Ducks.SubtickDuck; +import com.minecrafttas.tasmod.util.LoggerMarkers; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.GameType; + +public class SavestatePlayerHandlerClient implements ClientPacketHandler { + + public void loadPlayer(NBTTagCompound compound) { + LOGGER.trace(LoggerMarkers.Savestate, "Loading client player from NBT"); + Minecraft mc = Minecraft.getMinecraft(); + EntityPlayerSP player = mc.player; + + // Clear any accidental applied potion particles on the client + ((AccessorEntityLivingBase) player).clearPotionEffects(); + + /* + * TODO + * The following 20 lines are all one + * gross workaround for correctly applying the player motion + * to the client... + * + * The motion is applied + * to the player in a previous step and unfortunately + * player.readFromNBT(compound) overwrites the + * previously applied motion... + * + * So this workaround makes sure that the motion is not overwritten + * Fixing this, requires restructuring the steps for loadstating + * and since I plan to do this anyway at some point, I will + * leave this here and be done for today*/ + double x = player.motionX; + double y = player.motionY; + double z = player.motionZ; + + float rx = player.moveForward; + float ry = player.moveVertical; + float rz = player.moveStrafing; + + boolean sprinting = player.isSprinting(); + float jumpVector = player.jumpMovementFactor; + + player.readFromNBT(compound); + + player.motionX = x; + player.motionY = y; + player.motionZ = z; + + player.moveForward = rx; + player.moveVertical = ry; + player.moveStrafing = rz; + + player.setSprinting(sprinting); + player.jumpMovementFactor = jumpVector; + + LOGGER.trace(LoggerMarkers.Savestate, "Setting client gamemode"); + // #86 + int gamemode = compound.getInteger("playerGameType"); + GameType type = GameType.getByID(gamemode); + mc.playerController.setGameType(type); + + // Set the camera rotation to the player rotation + TASmodClient.virtual.CAMERA_ANGLE.setCamera(player.rotationPitch, player.rotationYaw); + SubtickDuck entityRenderer = (SubtickDuck) Minecraft.getMinecraft().entityRenderer; + entityRenderer.runUpdate(0); + + // Clear boss bars on savestate load + mc.ingameGUI.getBossOverlay().clearBossInfos(); + + EventListenerRegistry.fireEvent(EventSavestate.EventClientLoadPlayer.class, player); + } + + @Override + public PacketID[] getAcceptedPacketIDs() { + return new PacketID[] { + //@formatter:off + SAVESTATE_PLAYER + //@formatter:on + }; + } + + @Environment(EnvType.CLIENT) + @Override + public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception { + TASmodPackets packet = (TASmodPackets) id; + + switch (packet) { + case SAVESTATE_PLAYER: + NBTTagCompound compound; + try { + compound = TASmodBufferBuilder.readNBTTagCompound(buf); + } catch (IOException e) { + e.printStackTrace(); + break; + } + /* + * Fair warning: Do NOT read the buffer inside an addScheduledTask. Read it + * before that. The buffer will have the wrong limit, when the task is executed. + * This is probably due to the buffers being reused. + */ + Minecraft.getMinecraft().addScheduledTask(() -> { + loadPlayer(compound); + }); + break; + + default: + break; + } + } + +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandler.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandlerServer.java similarity index 83% rename from src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandler.java rename to src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandlerServer.java index 1a1706a2..8dad9eb0 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandler.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandlerServer.java @@ -3,7 +3,6 @@ import static com.minecrafttas.tasmod.TASmod.LOGGER; import static com.minecrafttas.tasmod.registries.TASmodPackets.SAVESTATE_PLAYER; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.UUID; @@ -11,7 +10,6 @@ import com.minecrafttas.mctcommon.networking.Client.Side; import com.minecrafttas.mctcommon.networking.exception.PacketNotImplementedException; import com.minecrafttas.mctcommon.networking.exception.WrongSideException; -import com.minecrafttas.mctcommon.networking.interfaces.ClientPacketHandler; import com.minecrafttas.mctcommon.networking.interfaces.PacketID; import com.minecrafttas.mctcommon.networking.interfaces.ServerPacketHandler; import com.minecrafttas.tasmod.TASmod; @@ -20,9 +18,6 @@ import com.minecrafttas.tasmod.savestates.SavestateHandlerClient; import com.minecrafttas.tasmod.util.LoggerMarkers; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.Minecraft; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; @@ -36,11 +31,11 @@ /** * Handles player related savestating methods */ -public class SavestatePlayerHandler implements ClientPacketHandler, ServerPacketHandler { +public class SavestatePlayerHandlerServer implements ServerPacketHandler { private final MinecraftServer server; - public SavestatePlayerHandler(MinecraftServer server) { + public SavestatePlayerHandlerServer(MinecraftServer server) { this.server = server; } @@ -196,33 +191,4 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws break; } } - - @Environment(EnvType.CLIENT) - @Override - public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception { - TASmodPackets packet = (TASmodPackets) id; - - switch (packet) { - case SAVESTATE_PLAYER: - NBTTagCompound compound; - try { - compound = TASmodBufferBuilder.readNBTTagCompound(buf); - } catch (IOException e) { - e.printStackTrace(); - break; - } - /* - * Fair warning: Do NOT read the buffer inside an addScheduledTask. Read it - * before that. The buffer will have the wrong limit, when the task is executed. - * This is probably due to the buffers being reused. - */ - Minecraft.getMinecraft().addScheduledTask(() -> { - SavestateHandlerClient.loadPlayer(compound); - }); - break; - - default: - break; - } - } } From 0ee9579c8969862e6e4fe7830a0e811542606a45 Mon Sep 17 00:00:00 2001 From: Scribble Date: Tue, 30 Sep 2025 11:11:32 +0200 Subject: [PATCH 03/19] [Common] Update datafile and config - Hide saveXML & saveJson and exclusively use save/load as public functions - Add comment to first line in json format - [Config] Switch to json format --- .../minecrafttas/mctcommon/Configuration.java | 32 +++- .../mctcommon/file/AbstractDataFile.java | 143 ++++++++++-------- .../com/minecrafttas/tasmod/TASmodClient.java | 5 +- .../PlaybackFileCommandsRegistry.java | 2 +- .../java/mctcommon/TestConfiguration.java | 12 +- 5 files changed, 121 insertions(+), 73 deletions(-) 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(" { + 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/tasmod/TASmodClient.java b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java index 38549ed6..68517cff 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java @@ -358,7 +358,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/playback/filecommands/PlaybackFileCommandsRegistry.java b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommandsRegistry.java index 5432f25d..db460bfe 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommandsRegistry.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/filecommands/PlaybackFileCommandsRegistry.java @@ -183,6 +183,6 @@ private void saveConfig() { nameList.add(element.getExtensionName()); }); config.set(TASmodConfig.EnabledFileCommands, String.join(", ", nameList)); - config.saveToXML(); + config.save(); } } diff --git a/src/test/java/mctcommon/TestConfiguration.java b/src/test/java/mctcommon/TestConfiguration.java index 27444d11..9ff5a5c6 100644 --- a/src/test/java/mctcommon/TestConfiguration.java +++ b/src/test/java/mctcommon/TestConfiguration.java @@ -16,7 +16,11 @@ import com.minecrafttas.mctcommon.ConfigurationRegistry; import com.minecrafttas.mctcommon.ConfigurationRegistry.ConfigOptions; -class TestConfiguration { +class TestConfiguration extends Configuration { + + public TestConfiguration() { + super("", Paths.get("src/test/resources/test.cfg"), new ConfigurationRegistry()); + } enum TestConfig implements ConfigOptions { FileToOpen("fileToOpen", ""), @@ -56,7 +60,7 @@ public String getExtensionName() { void beforeEach() { registry.register(TestConfig.values()); config = new Configuration("Test config", configPath, registry); - config.loadFromXML(); + config.load(); } @AfterEach @@ -79,7 +83,7 @@ void testIfInitialized() { void testDefault() throws Exception { Files.delete(configPath); config = new Configuration("Test config", configPath, registry); - config.loadFromXML(); + config.load(); assertEquals("", config.get(TestConfig.FileToOpen)); } @@ -89,7 +93,7 @@ void testDefault() throws Exception { @Test void testSavingAndLoading() { config.set(TestConfig.FileToOpen, "Test"); - config.loadFromXML(); + config.load(); assertEquals("Test", config.get(TestConfig.FileToOpen)); } From bf7b99ad339f9f9970514f509ab7a27aa70b4890 Mon Sep 17 00:00:00 2001 From: Scribble Date: Tue, 30 Sep 2025 11:11:32 +0200 Subject: [PATCH 04/19] [Savestates] Implement SavestateIndexer This indexer came originally from LoTAS-Light as a way to seperate the indexing portion from the technical side of savestating. This reduces the complexity of the, at this point, massive SavestateHandlerServer. This indexer also has the following advantages for the player: - Able to store names of savestates - New savestate folder structure, seperating the saves by worlds - Able to save the date of creation --- .../tasmod/commands/CommandFullPlay.java | 3 +- .../tasmod/commands/CommandFullRecord.java | 3 +- .../commands/CommandRestartAndPlay.java | 12 +- .../tasmod/commands/CommandSavestate.java | 29 +- .../tasmod/events/EventSavestate.java | 17 +- .../savestates/SavestateHandlerServer.java | 590 +++--------- .../savestates/SavestateHandlerServerOld.java | 881 ++++++++++++++++++ .../tasmod/savestates/SavestateIndexer.java | 572 ++++++++++++ .../SavestateResourcePackHandler.java | 4 +- .../handlers/SavestateWorldHandler.java | 4 +- .../SavestateStorageExtensionRegistry.java | 13 +- .../com/minecrafttas/tasmod/util/I18n.java | 12 + 12 files changed, 1639 insertions(+), 501 deletions(-) create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServerOld.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java create mode 100644 src/main/java/com/minecrafttas/tasmod/util/I18n.java diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandFullPlay.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandFullPlay.java index 8829aeeb..f38116e9 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandFullPlay.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandFullPlay.java @@ -4,6 +4,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.SavestateFlags; import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; import com.minecrafttas.tasmod.savestates.exceptions.LoadstateException; @@ -29,7 +30,7 @@ public String getUsage(ICommandSender sender) { @Override public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException { try { - TASmod.savestateHandlerServer.loadState(0, false, false); + TASmod.savestateHandlerServer.loadState(0, null, 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; diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandFullRecord.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandFullRecord.java index 9ab51422..28bbc7b4 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandFullRecord.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandFullRecord.java @@ -4,6 +4,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.SavestateFlags; import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; @@ -29,7 +30,7 @@ public String getUsage(ICommandSender sender) { @Override public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException { try { - TASmod.savestateHandlerServer.saveState(0, false); + TASmod.savestateHandlerServer.saveState(0, null, SavestateFlags.BLOCK_PAUSE_TICKRATE); } catch (SavestateException e) { sender.sendMessage(new TextComponentString(TextFormatting.RED + "Failed to create a savestate: " + e.getMessage())); return; diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandRestartAndPlay.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandRestartAndPlay.java index f0c8a290..7983e5f7 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,6 +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.SavestateFlags; import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; import com.minecrafttas.tasmod.savestates.exceptions.LoadstateException; @@ -49,7 +49,7 @@ 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) { @@ -58,14 +58,6 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args 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.tickratechanger.pauseGame(false); - return; } TASmod.playbackControllerServer.setServerState(TASstate.PLAYBACK); try { diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java index 63d66b46..4813dec4 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java @@ -1,10 +1,11 @@ package com.minecrafttas.tasmod.commands; -import java.io.IOException; +import java.util.ArrayList; import java.util.List; import com.minecrafttas.tasmod.TASmod; import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; +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; @@ -99,7 +100,7 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args } } 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()))); +// 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); @@ -165,7 +166,11 @@ public List getTabCompletions(MinecraftServer server, ICommandSender sen 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())); + List list = new ArrayList<>(); + for (Savestate savestate : TASmod.savestateHandlerServer.getSavestateInfo()) { + list.add(Integer.toString(savestate.getIndex())); + } + sender.sendMessage(new TextComponentString("Available indexes: " + TextFormatting.AQUA + String.join(",", list))); } return super.getTabCompletions(server, sender, args, targetPos); } @@ -174,10 +179,10 @@ public List getTabCompletions(MinecraftServer server, ICommandSender sen private void saveLatest() throws CommandException { try { - TASmod.savestateHandlerServer.saveState(); + TASmod.savestateHandlerServer.saveState(null); } catch (SavestateException e) { throw new CommandException(e.getMessage(), new Object[] {}); - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); throw new CommandException(e.getMessage(), new Object[] {}); } finally { @@ -191,10 +196,10 @@ private void saveWithIndex(String[] args) throws CommandException { if (indexToSave <= 0) { // Disallow to save on Savestate 0 indexToSave = -1; } - TASmod.savestateHandlerServer.saveState(indexToSave, true); + TASmod.savestateHandlerServer.saveState(indexToSave, null); } catch (SavestateException e) { throw new CommandException(e.getMessage(), new Object[] {}); - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); throw new CommandException(e.getMessage(), new Object[] {}); } finally { @@ -204,10 +209,10 @@ private void saveWithIndex(String[] args) throws CommandException { private void loadLatest() throws CommandException { try { - TASmod.savestateHandlerServer.loadState(); + TASmod.savestateHandlerServer.loadState(null); } catch (LoadstateException e) { throw new CommandException(e.getMessage(), new Object[] {}); - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); throw new CommandException(e.getMessage(), new Object[] {}); } finally { @@ -217,10 +222,10 @@ private void loadLatest() throws CommandException { private void loadLatest(String[] args) throws CommandException { try { - TASmod.savestateHandlerServer.loadState(processIndex(args[1]), true); + TASmod.savestateHandlerServer.loadState(processIndex(args[1]), null); } catch (LoadstateException e) { throw new CommandException(e.getMessage(), new Object[] {}); - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); throw new CommandException(e.getMessage(), new Object[] {}); } finally { @@ -239,7 +244,7 @@ private void delete(String[] args) throws CommandException { private void deleteMultiple(String[] args) throws CommandException { try { - TASmod.savestateHandlerServer.deleteSavestate(processIndex(args[1]), processIndex(args[2])); + TASmod.savestateHandlerServer.deleteSavestate(processIndex(args[1]), processIndex(args[2]), null, null); } catch (SavestateDeleteException e) { throw new CommandException(e.getMessage(), new Object[] {}); } diff --git a/src/main/java/com/minecrafttas/tasmod/events/EventSavestate.java b/src/main/java/com/minecrafttas/tasmod/events/EventSavestate.java index 70fde1f8..51ac126b 100644 --- a/src/main/java/com/minecrafttas/tasmod/events/EventSavestate.java +++ b/src/main/java/com/minecrafttas/tasmod/events/EventSavestate.java @@ -1,8 +1,7 @@ package com.minecrafttas.tasmod.events; -import java.nio.file.Path; - import com.minecrafttas.mctcommon.events.EventListenerRegistry.EventBase; +import com.minecrafttas.tasmod.savestates.SavestateIndexer.SavestatePaths; import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.server.MinecraftServer; @@ -18,11 +17,10 @@ interface EventServerSavestate extends EventBase { /** * Fired when saving a savestate, before the savestate folder is copied * - * @param index The savestate index for this savestate - * @param target Target folder, where the savestate is copied to - * @param current The current folder that will be copied from + * @param server The server instance + * @param paths The {@link SavestatePaths} object */ - public void onServerSavestate(MinecraftServer server, int index, Path target, Path current); + public void onServerSavestate(MinecraftServer server, SavestatePaths paths); } /** @@ -34,11 +32,10 @@ interface EventServerLoadstate extends EventBase { /** * Fired when loading a savestate, before the savestate folder is copied * - * @param index The savestate index for this loadstate - * @param target Target folder, where the savestate is copied to - * @param current The current folder that will be copied from + * @param server The server instance + * @param paths The {@link SavestatePaths} object */ - public void onServerLoadstate(MinecraftServer server, int index, Path target, Path current); + public void onServerLoadstate(MinecraftServer server, SavestatePaths paths); } /** diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index fbdaf14c..2653ca50 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -3,22 +3,16 @@ import static com.minecrafttas.tasmod.TASmod.LOGGER; import static com.minecrafttas.tasmod.registries.TASmodPackets.CLEAR_SCREEN; -import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Stream; +import java.util.stream.Collectors; -import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.Logger; import com.minecrafttas.mctcommon.events.EventListenerRegistry; @@ -33,12 +27,12 @@ import com.minecrafttas.tasmod.mixin.savestates.AccessorChunkLoader; import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.registries.TASmodPackets; +import com.minecrafttas.tasmod.savestates.SavestateIndexer.DeletionRunnable; +import com.minecrafttas.tasmod.savestates.SavestateIndexer.ErrorRunnable; +import com.minecrafttas.tasmod.savestates.SavestateIndexer.SavestatePaths; 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.savestates.files.SavestateDataFile; -import com.minecrafttas.tasmod.savestates.files.SavestateDataFile.DataValues; -import com.minecrafttas.tasmod.savestates.files.SavestateTrackerFile; import com.minecrafttas.tasmod.savestates.handlers.SavestatePlayerHandlerServer; import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler; import com.minecrafttas.tasmod.savestates.handlers.SavestateWorldHandler; @@ -69,20 +63,13 @@ public class SavestateHandlerServer implements ServerPacketHandler { private final MinecraftServer server; - private Path savestateDirectory; - public SavestateState state = SavestateState.NONE; + public SavestateState state = SavestateState.NONE; // TODO Make private - public static enum SavestateState { - SAVING, - LOADING, - NONE - } - - private final List indexList = new ArrayList<>(); - - private int latestIndex = 0; - private int currentIndex; + /** + * Manages enumeration and location of savestates on the file system + */ + private SavestateIndexer indexer; private final SavestatePlayerHandlerServer playerHandler; private final SavestateWorldHandler worldHandler; @@ -100,48 +87,28 @@ public static enum SavestateState { public SavestateHandlerServer(MinecraftServer server, Logger logger) { this.server = server; this.logger = logger; + this.playerHandler = new SavestatePlayerHandlerServer(server); this.worldHandler = new SavestateWorldHandler(server); - createSavestateDirectory(); - refresh(); - loadCurrentIndexFromFile(); + createIndexer(server); } - /** - * Creates a copy of the world that is currently being played and saves it in - * .minecraft/saves/savestates/worldname-Savestate[{@linkplain #currentIndex}+1] - *

- * Side: Server - * - * @throws SavestateException - * @throws IOException - */ - public void saveState() throws SavestateException, IOException { - saveState(-1, true); + public void saveState(SavestateCallback cb, SavestateFlags... options) throws Exception { + saveState(-1, null, cb, options); } - public void saveState(int savestateIndex, boolean tickrate0) throws SavestateException, IOException { - saveState(savestateIndex, tickrate0, true); + public void saveState(int index, SavestateCallback cb, SavestateFlags... flags) throws Exception { + saveState(index, null, cb, flags); } - /** - * Creates a copy of the world that is currently being played and saves it in - * .minecraft/saves/savestates/worldname-Savestate[savestateIndex] - *

- * Side: Server - * - * @param savestateIndex The index where the mod will save the savestate. - * index<0 if it should save it in the next index from - * the currentindex - * @param tickrate0 When true: Set's the game to tickrate 0 after creating a savestate - * @param changeIndex When true: Changes the index to the savestateIndex - * @throws SavestateException - * @throws IOException - */ - public void saveState(int savestateIndex, boolean tickrate0, boolean changeIndex) throws SavestateException, IOException { + public void saveState(String name, SavestateCallback cb, SavestateFlags... flags) throws Exception { + saveState(-1, name, cb, flags); + } + + public void saveState(int index, String name, SavestateCallback cb, SavestateFlags... flags) throws SavestateException, IOException { if (logger.isTraceEnabled()) { - logger.trace(LoggerMarkers.Savestate, "SAVING a savestate with index {}, tickrate0 is {} and changeIndex is {}", savestateIndex, tickrate0, changeIndex); + logger.trace(LoggerMarkers.Savestate, "SAVING a savestate with index {}. Flags: ", index, Arrays.stream(flags).map(Enum::toString).collect(Collectors.joining(","))); } else { logger.debug(LoggerMarkers.Savestate, "Creating new savestate"); } @@ -153,8 +120,8 @@ public void saveState(int savestateIndex, boolean tickrate0, boolean changeIndex throw new SavestateException("A loadstate operation is being carried out"); } + // Open GuiSavestateScreen try { - // Open GuiSavestateScreen TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_SCREEN)); } catch (Exception e) { e.printStackTrace(); @@ -163,9 +130,6 @@ public void saveState(int savestateIndex, boolean tickrate0, boolean changeIndex // Lock savestating and loadstating state = SavestateState.SAVING; - // Create a directory just in case - createSavestateDirectory(); - // Enable tickrate 0 TASmod.tickratechanger.pauseGame(true); @@ -173,46 +137,32 @@ public void saveState(int savestateIndex, boolean tickrate0, boolean changeIndex server.getPlayerList().saveAllPlayerData(); server.saveAllWorlds(false); - // Refreshing the index list - refresh(); - - // Setting the current index depending on the savestateIndex. - int indexToSave = savestateIndex; - if (savestateIndex < 0) { - indexToSave = currentIndex + 1; // If the savestateIndex <= 0, create a savestate at currentIndex+1 - } - - // Update current index - if (changeIndex) { - setCurrentIndex(indexToSave); - } else { - logger.warn(LoggerMarkers.Savestate, "Keeping the savestate index at {}", currentIndex); - } - - // Get the current and target directory for copying - String worldname = server.getFolderName(); - Path currentfolder = savestateDirectory.resolve(".." + File.separator + worldname); - Path targetfolder = getSavestateFile(indexToSave); + logger.trace("Create new savestate index via indexer"); + SavestatePaths paths = indexer.createSavestate(index, name, !SavestateFlags.BLOCK_CHANGE_INDEX.isBlocked(flags)); + Path sourceFolder = paths.getSourceFolder(); + Path targetFolder = paths.getSourceFolder(); + int indexToSave = paths.getSavestate().index; + logger.debug("Source: {}, Target: {}", paths.getSourceFolder(), paths.getTargetFolder()); - EventListenerRegistry.fireEvent(EventSavestate.EventServerSavestate.class, server, indexToSave, targetfolder, currentfolder); + EventListenerRegistry.fireEvent(EventSavestate.EventServerSavestate.class, server, paths); - if (Files.exists(targetfolder)) { + if (Files.exists(targetFolder)) { logger.warn(LoggerMarkers.Savestate, "WARNING! Overwriting the savestate with the index {}", indexToSave); - deleteFolder(targetfolder); + deleteFolder(targetFolder); } /* * Prevents creating an InputSavestate when saving at index 0 (Index 0 is the * savestate when starting a recording) */ - if (savestateIndex != 0) { + if (index != 0) { /* * Send the name of the world to all players. This will make a savestate of the * recording on the client with that name */ try { // savestate inputs client - TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_SAVE).writeString(getSavestateName(indexToSave))); + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_SAVE).writeString(paths.getSavestate().folder.toString())); } catch (Exception e) { e.printStackTrace(); } @@ -226,14 +176,8 @@ public void saveState(int savestateIndex, boolean tickrate0, boolean changeIndex } } - saveSavestateDataFile(false); - // Copy the directory - copyFolder(currentfolder, targetfolder); - - // Incrementing info file - SavestateTrackerFile tracker = new SavestateTrackerFile(savestateDirectory.resolve(worldname + "-info.txt")); - tracker.increaseSaveStateCount(); + copyFolder(sourceFolder, targetFolder); // Send a notification that the savestate has been loaded server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + indexToSave + " saved")); @@ -245,7 +189,7 @@ public void saveState(int savestateIndex, boolean tickrate0, boolean changeIndex e.printStackTrace(); } - if (!tickrate0) { + if (!SavestateFlags.BLOCK_PAUSE_TICKRATE.isBlocked(flags)) { TASmod.tickratechanger.pauseGame(false); } @@ -253,47 +197,21 @@ public void saveState(int savestateIndex, boolean tickrate0, boolean changeIndex state = SavestateState.NONE; } - /** - * Loads the latest savestate at {@linkplain #currentIndex} - * .minecraft/saves/savestates/worldname-Savestate[{@linkplain #currentIndex}] - *

- * Side: Server - * - * @throws LoadstateException - * @throws IOException - */ - public void loadState() throws LoadstateException, IOException { - loadState(-1, true); + public void loadState(SavestateCallback cb, SavestateFlags... flags) throws LoadstateException { + loadState(-1, null, cb, flags); } - /** - * - * @param savestateIndex - * @param tickrate0 - * - * @throws LoadstateException - * @throws IOException - */ - public void loadState(int savestateIndex, boolean tickrate0) throws LoadstateException, IOException { - loadState(savestateIndex, tickrate0, true); + public void loadState(int index, SavestateCallback cb, SavestateFlags... flags) throws LoadstateException { + loadState(index, null, cb, flags); } - /** - * Loads the latest savestate it can find in - * .minecraft/saves/savestates/worldname-Savestate - *

- * Side: Server - * - * @param savestateIndex The index where the mod will load the savestate. - * index<0 if it should load the currentindex - * @param tickrate0 When true: Set's the game to tickrate 0 after creating a savestate - * @param changeIndex When true: Changes the index to the savestateIndex - * @throws LoadstateException - * @throws IOException - */ - public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex) throws LoadstateException, IOException { + public void loadState(String name, SavestateCallback cb, SavestateFlags... flags) throws LoadstateException { + loadState(-1, name, cb, flags); + } + + public void loadState(int index, String name, SavestateCallback cb, SavestateFlags... flags) throws LoadstateException { if (logger.isTraceEnabled()) { - logger.trace(LoggerMarkers.Savestate, "LOADING a savestate with index {}, tickrate0 is {} and changeIndex is {}", savestateIndex, tickrate0, changeIndex); + logger.trace(LoggerMarkers.Savestate, "LOADING a savestate with index {}, ", index, Arrays.stream(flags).map(Enum::toString).collect(Collectors.joining(","))); } else { logger.debug(LoggerMarkers.Savestate, "Loading a savestate"); } @@ -307,43 +225,30 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex // Lock savestating and loadstating state = SavestateState.LOADING; - // Create a directory just in case - createSavestateDirectory(); - // Enable tickrate 0 TASmod.tickratechanger.pauseGame(true); - refresh(); - - int indexToLoad = savestateIndex < 0 ? currentIndex : savestateIndex; - - if (Files.exists(getSavestateFile(indexToLoad))) { - // Updating current index - if (changeIndex) { - setCurrentIndex(indexToLoad); - } else { - logger.warn(LoggerMarkers.Savestate, "Keeping the savestate index at {}", currentIndex); - } - } else { - throw new LoadstateException("Savestate " + indexToLoad + " doesn't exist"); - } - // Get the current and target directory for copying + logger.trace(LoggerMarkers.Savestate, "Load savestate index via indexer"); + SavestatePaths paths = indexer.loadSavestate(index, !SavestateFlags.BLOCK_CHANGE_INDEX.isBlocked(flags)); + logger.debug(LoggerMarkers.Savestate, "Source: {}, Target: {}", paths.getSourceFolder(), paths.getTargetFolder()); + String worldname = server.getFolderName(); - Path currentfolder = savestateDirectory.resolve(".." + File.separator + worldname); - Path targetfolder = getSavestateFile(indexToLoad); + Path sourcefolder = paths.getSourceFolder(); + Path targetfolder = paths.getTargetFolder(); + int indexToLoad = paths.getSavestate().index; - EventListenerRegistry.fireEvent(EventSavestate.EventServerLoadstate.class, server, indexToLoad, targetfolder, currentfolder); + EventListenerRegistry.fireEvent(EventSavestate.EventServerLoadstate.class, server, paths); /* * Prevents loading an InputSavestate when loading index 0 (Index 0 is the * savestate when starting a recording. Not doing this will load an empty * InputSavestate) */ - if (savestateIndex != 0) { + if (indexToLoad != 0) { try { // loadstate inputs client - TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_LOAD).writeString(getSavestateName(indexToLoad))); + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_LOAD).writeString(paths.getSavestate().folder.toString())); } catch (Exception e) { e.printStackTrace(); } @@ -353,11 +258,11 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex // world unload worldHandler.disableLevelSaving(); + // Unload chunks on client try { - // unload chunks on client TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_UNLOAD_CHUNKS)); } catch (Exception e) { - e.printStackTrace(); + logger.catching(e); } // Unload chunks on the server @@ -366,17 +271,13 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex worldHandler.flushSaveHandler(); // Delete and copy directories - deleteFolder(currentfolder); - copyFolder(targetfolder, currentfolder); - - // Loads savestate data from the file like name and ktrng seed if ktrng is loaded - loadSavestateDataFile(); + deleteFolder(sourcefolder); + copyFolder(targetfolder, sourcefolder); playerHandler.clearScoreboard(); // Load the world from disk -// server.loadAllWorlds(worldname, worldname, 0, WorldType.DEFAULT, ""); - worldHandler.loadAllWorlds(worldname, worldname); + worldHandler.loadAllWorlds(worldname); // Update the player and the client playerHandler.loadAndSendMotionToPlayer(); @@ -391,8 +292,8 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex SavestateResourcePackHandler.refreshServerResourcepack(server); // Incrementing info file - SavestateTrackerFile tracker = new SavestateTrackerFile(savestateDirectory.resolve(worldname + "-info.txt")); - tracker.increaseLoadstateCount(); +// SavestateTrackerFile tracker = new SavestateTrackerFile(savestateDirectory.resolve(worldname + "-info.txt")); // TODO Bring back the trackerfile! +// tracker.increaseLoadstateCount(); // Send a notification that the savestate has been loaded server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + indexToLoad + " loaded")); @@ -408,7 +309,7 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex LOGGER.catching(e); } - if (!tickrate0) { + if (!SavestateFlags.BLOCK_PAUSE_TICKRATE.isBlocked(flags)) { TASmod.tickratechanger.pauseGame(false); } @@ -427,144 +328,31 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex } /** - * Creates the savestate directory in case the user deletes it between - * savestates + * Create and set the {@link #indexer} based on the server + * @param server */ - private void createSavestateDirectory() { - logger.trace(LoggerMarkers.Savestate, "Creating savestate directory"); - - Path dataDirectory = server.getDataDirectory().toPath(); + private void createIndexer(MinecraftServer server) { + logger.trace(LoggerMarkers.Savestate, "Creating savestate indexer"); + Path dataDirectory = server.getDataDirectory().toPath(); // The basic minecraft data directory + Path savesDirectory = dataDirectory; // The location of minecraft saves. On the a dedicated server it's the same as the data directory if (!server.isDedicatedServer()) { - savestateDirectory = dataDirectory.resolve("saves/savestates"); - } else { - savestateDirectory = dataDirectory.resolve("savestates"); - } - if (!Files.exists(savestateDirectory)) { - try { - Files.createDirectory(savestateDirectory); - } catch (IOException e) { - logger.error("Could not create savestate directory"); - logger.catching(e); - } - } - } - - /** - * Refreshes the current savestate list and loads all indizes into {@link #indexList} - */ - private void refresh() { - logger.trace(LoggerMarkers.Savestate, "Refreshing savestate list"); - indexList.clear(); - if (!Files.isDirectory(savestateDirectory)) { - logger.error("Savestate directory is not a directory! {}", savestateDirectory.toAbsolutePath().toString()); - return; - } - - Stream files = null; - try { - files = Files.list(savestateDirectory); - } catch (IOException e) { - logger.error("Can't refresh savestatelist"); - logger.catching(e); - return; + savesDirectory = dataDirectory.resolve("saves"); // The location of minecraft saves. On the integrated server it's .minecraft/saves } - Stream filteredfiles = files.filter(file -> file.getFileName().toString().startsWith(server.getFolderName() + "-Savestate")); - filteredfiles.forEach(file -> { - int index = 0; - try { - Pattern patt = Pattern.compile("\\d+$"); - Matcher matcher = patt.matcher(file.getFileName().toString()); - if (matcher.find()) { - index = Integer.parseInt(matcher.group(0)); - } else { - logger.warn(String.format("Could not process the savestate %s", file.getFileName())); - return; - } - } catch (NumberFormatException e) { - logger.warn(String.format("Could not process the savestate %s", e.getMessage())); - return; - } - indexList.add(index); - }); + Path savestateBaseDirectory = savesDirectory.resolve("savestates"); // The base savestatedir: .minecraft/saves/savestates + String worldname = server.getWorldName(); - filteredfiles.close(); - files.close(); - - Collections.sort(indexList); - if (!indexList.isEmpty()) { - latestIndex = indexList.get(indexList.size() - 1); - } else { - latestIndex = 0; - } - } - - /** - * @param index The index of the savestate file that we want to get - * @return The file of the savestate from the specified index - */ - private Path getSavestateFile(int index) { - return savestateDirectory.resolve(getSavestateName(index)); - } - - /** - * @param index The index of the savestate file that we want to get - * @return The savestate name without any paths - */ - private String getSavestateName(int index) { - return server.getFolderName() + "-Savestate" + index; + logger.debug("Created savestate handler with saves: {}, savestates: {}, worldname: {}", savesDirectory, savestateBaseDirectory, worldname); + this.indexer = new SavestateIndexer(logger, savesDirectory, savestateBaseDirectory, worldname); } - /** - * Deletes the specified savestate - * - * @param index The index of the savestate that should be deleted - * @throws SavestateDeleteException - */ public void deleteSavestate(int index) throws SavestateDeleteException { logger.warn(LoggerMarkers.Savestate, "Deleting savestate {}", index); - if (state == SavestateState.SAVING) { - throw new SavestateDeleteException("A savestating operation is already being carried out"); - } - if (state == SavestateState.LOADING) { - throw new SavestateDeleteException("A loadstate operation is being carried out"); - } - if (index < 0) { - throw new SavestateDeleteException("Cannot delete the negative indexes"); - } - if (index == 0) { - throw new SavestateDeleteException("Cannot delete protected savestate 0"); - } - - Path toDelete = getSavestateFile(index); - if (Files.exists(getSavestateFile(index))) { -// try { - deleteFolder(toDelete); -// } catch (IOException e) { -// e.printStackTrace(); -// throw new SavestateDeleteException("Something went wrong while trying to delete the savestate " + index); -// } - } else { - throw new SavestateDeleteException(TextFormatting.YELLOW + "Savestate " + index + " doesn't exist, so it can't be deleted"); - } - - refresh(); - if (!indexList.contains(currentIndex)) { - setCurrentIndex(latestIndex); - } - // Send a notification that the savestate has been deleted - server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + index + " deleted")); + this.indexer.deleteSavestate(index); } - /** - * Deletes savestates in a range from "from" to "to" - * - * @param from - * @param to (inclusive) - * @throws SavestateDeleteException - */ - public void deleteSavestate(int from, int to) throws SavestateDeleteException { + public void deleteSavestate(int from, int to, SavestateCallback cb, ErrorRunnable err) throws SavestateDeleteException { logger.warn(LoggerMarkers.Savestate, "Deleting multiple savestates from {} to {}", from, to); if (state == SavestateState.SAVING) { throw new SavestateDeleteException("A savestating operation is already being carried out"); @@ -572,148 +360,13 @@ public void deleteSavestate(int from, int to) throws SavestateDeleteException { if (state == SavestateState.LOADING) { throw new SavestateDeleteException("A loadstate operation is being carried out"); } - if (from >= to) { - throw new SavestateDeleteException("Can't delete amounts that are negative or 0"); - } - for (int i = from; i <= to; i++) { - // System.out.println("Would've deleted savestate: "+i); - try { - deleteSavestate(i); - } catch (SavestateDeleteException e) { - server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.RED + e.getMessage())); - continue; - } - } - } - - /** - * @return A list of index numbers as string in the form of: "0, 1, 2, 3" - */ - public String getIndexesAsString() { - refresh(); - String out = ""; - for (int i : indexList) { - out = out.concat(" " + i + (i == indexList.size() - 1 ? "" : ",")); - } - return out; - } - - /** - * Saves the current index to the current world-folder (not the savestate - * folder) - * - * @param legacy If the data file should only store the index, since it comes from a legacy file format - */ - private void saveSavestateDataFile(boolean legacy) { - logger.trace(LoggerMarkers.Savestate, "Saving savestate data file"); - Path tasmodDir = savestateDirectory.resolve("../" + server.getFolderName() + "/tasmod/"); - if (!Files.exists(tasmodDir)) { - try { - Files.createDirectories(tasmodDir); - } catch (IOException e) { - e.printStackTrace(); - } - } - - Path savestateDat = tasmodDir.resolve("savestateData.txt"); - try { - Files.deleteIfExists(savestateDat); - } catch (IOException e) { - e.printStackTrace(); - } - - SavestateDataFile file = new SavestateDataFile(savestateDat); - - file.set(DataValues.INDEX, Integer.toString(currentIndex)); - - // if (!legacy) { - // if (TASmod.ktrngHandler.isLoaded()) { - // file.set(DataValues.SEED, Long.toString(TASmod.ktrngHandler.getGlobalSeedServer())); - // } - // } - - file.save(savestateDat); - } - - /** - * Loads information from savestateData.txt - *

- * This loads everything except the index, since that is loaded when the world is loaded - */ - private void loadSavestateDataFile() { - logger.trace(LoggerMarkers.Savestate, "Loading savestate data file"); - Path tasmodDir = savestateDirectory.resolve("../" + server.getFolderName()).resolve(storageDir); - Path savestateDat = tasmodDir.resolve("savestateData.txt"); - - if (!Files.exists(savestateDat)) { - return; - } - - SavestateDataFile datafile = new SavestateDataFile(savestateDirectory); - - datafile.load(savestateDat); - - // if (TASmod.ktrngHandler.isLoaded()) { - // String seedString = datafile.get(DataValues.SEED); - // if (seedString != null) { - // TASmod.ktrngHandler.sendGlobalSeedToServer(Long.parseLong(seedString)); - // } else { - // logger.warn("KTRNG seed not loaded because it was not found in savestateData.txt!"); - // } - // } - } - - /** - * Loads the current index to the current world-folder (not the savestate - * folder) - *

- * This ensures that the server knows the current index when loading the world - */ - public void loadCurrentIndexFromFile() { - logger.trace(LoggerMarkers.Savestate, "Loading current index from file"); - int index = -1; - Path tasmodDir = savestateDirectory.resolve("../" + server.getFolderName()).resolve(storageDir); - if (!Files.exists(tasmodDir)) { - try { - Files.createDirectory(tasmodDir); - } catch (IOException e) { - e.printStackTrace(); - } - } - - Path savestateDat = tasmodDir.resolve("savestate.data"); - if (Files.exists(savestateDat)) { - index = legacyIndexFile(savestateDat); - setCurrentIndex(index); - saveSavestateDataFile(true); - try { - Files.delete(savestateDat); - } catch (IOException e) { - e.printStackTrace(); - } - return; - } - - savestateDat = tasmodDir.resolve("savestateData.txt"); - if (Files.exists(savestateDat)) { - SavestateDataFile file = new SavestateDataFile(savestateDat); - file.load(savestateDat); - index = Integer.parseInt(file.get(DataValues.INDEX)); - if (index != 0) - setCurrentIndex(index); - else - setCurrentIndex(latestIndex); - } - } + DeletionRunnable onDelete = (paths) -> { + SavestateIndexer.deleteFolder(paths.getTargetFolder()); + cb.invoke(paths); + }; - private void setCurrentIndex(int index) { - if (index < 0) { - currentIndex = latestIndex; - } else { - currentIndex = index; - } - logger.debug(LoggerMarkers.Savestate, "Setting the savestate index to {}", currentIndex); + indexer.deleteMultipleSavestates(from, to, onDelete, err); } public SavestatePlayerHandlerServer getPlayerHandler() { @@ -721,7 +374,7 @@ public SavestatePlayerHandlerServer getPlayerHandler() { } public int getCurrentIndex() { - return currentIndex; + return indexer.getCurrentSavestate().index; } public void onLoadstateComplete() { @@ -733,28 +386,6 @@ public void onLoadstateComplete() { } } - private int legacyIndexFile(Path savestateDat) { - int index = -1; - List lines = new ArrayList(); - try { - lines = FileUtils.readLines(savestateDat.toFile(), StandardCharsets.UTF_8); - } catch (IOException e) { - logger.warn("No savestate.data file found in current world folder, ignoring it"); - } - if (!lines.isEmpty()) { - for (String line : lines) { - if (line.startsWith("currentIndex=")) { - try { - index = Integer.parseInt(line.split("=")[1]); - } catch (NumberFormatException e) { - e.printStackTrace(); - } - } - } - } - return index; - } - @Override public PacketID[] getAcceptedPacketIDs() { return new TASmodPackets[] { @@ -780,7 +411,7 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws Task savestateTask = () -> { try { - TASmod.savestateHandlerServer.saveState(index, true); + saveState(index, null); } catch (SavestateException e) { if (player != null) player.sendMessage(new TextComponentString(TextFormatting.RED + "Failed to create a savestate: " + e.getMessage())); @@ -793,7 +424,7 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws LOGGER.catching(e); } finally { - TASmod.savestateHandlerServer.state = SavestateState.NONE; + state = SavestateState.NONE; } }; @@ -807,13 +438,13 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws int indexing = TASmodBufferBuilder.readInt(buf); Task loadstateTask = () -> { try { - TASmod.savestateHandlerServer.loadState(indexing, true); + loadState(indexing, null); } catch (LoadstateException e) { if (player != null) player.sendMessage(new TextComponentString(TextFormatting.RED + "Failed to load a savestate: " + e.getMessage())); LOGGER.error(LoggerMarkers.Savestate, "Failed to create a savestate: " + e.getMessage()); - TASmod.savestateHandlerServer.state = SavestateState.NONE; + state = SavestateState.NONE; } catch (Exception e) { if (player != null) { Throwable cause = e.getCause(); @@ -824,7 +455,7 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws } LOGGER.throwing(e); - TASmod.savestateHandlerServer.state = SavestateState.NONE; + state = SavestateState.NONE; } }; TASmod.gameLoopSchedulerServer.add(loadstateTask); @@ -878,4 +509,49 @@ public static void deleteFolder(Path toDelete) { e.printStackTrace(); } } + + @FunctionalInterface + public interface SavestateCallback { + public void invoke(SavestatePaths path); + } + + public enum SavestateState { + SAVING, + LOADING, + NONE + } + + /** + * Acts as flags for savestates and loadstates + * + * Add these to the parameters to block certain savestate behaviour + * + * @author Scribble + */ + public static enum SavestateFlags { + /** + * Stops updating the current index when savestating/loadstating + */ + BLOCK_CHANGE_INDEX, + /** + * Stops setting the tickrate to 0 after a savestate/loadstate + */ + BLOCK_PAUSE_TICKRATE; + + public boolean isBlocked(SavestateFlags[] flagList) { + return Arrays.stream(flagList).anyMatch(this::equals); + } + } + + public List getSavestateInfo() { + return getSavestateInfo(-1, 10); + } + + public List getSavestateInfo(int index, int amount) { + return indexer.getSavestateList(index, amount); + } + + public int size() { + return indexer.size(); + } } diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServerOld.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServerOld.java new file mode 100644 index 00000000..48e4cd50 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServerOld.java @@ -0,0 +1,881 @@ +package com.minecrafttas.tasmod.savestates; + +import static com.minecrafttas.tasmod.TASmod.LOGGER; +import static com.minecrafttas.tasmod.registries.TASmodPackets.CLEAR_SCREEN; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.Logger; + +import com.minecrafttas.mctcommon.events.EventListenerRegistry; +import com.minecrafttas.mctcommon.networking.Client.Side; +import com.minecrafttas.mctcommon.networking.exception.PacketNotImplementedException; +import com.minecrafttas.mctcommon.networking.exception.WrongSideException; +import com.minecrafttas.mctcommon.networking.interfaces.PacketID; +import com.minecrafttas.mctcommon.networking.interfaces.ServerPacketHandler; +import com.minecrafttas.tasmod.TASmod; +import com.minecrafttas.tasmod.events.EventSavestate; +import com.minecrafttas.tasmod.mixin.savestates.AccessorAnvilChunkLoader; +import com.minecrafttas.tasmod.mixin.savestates.AccessorChunkLoader; +import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; +import com.minecrafttas.tasmod.registries.TASmodPackets; +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.savestates.files.SavestateDataFile; +import com.minecrafttas.tasmod.savestates.files.SavestateDataFile.DataValues; +import com.minecrafttas.tasmod.savestates.files.SavestateTrackerFile; +import com.minecrafttas.tasmod.savestates.handlers.SavestatePlayerHandlerServer; +import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler; +import com.minecrafttas.tasmod.savestates.handlers.SavestateWorldHandler; +import com.minecrafttas.tasmod.util.LoggerMarkers; +import com.minecrafttas.tasmod.util.Scheduler.Task; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.management.PlayerList; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.WorldServer; +import net.minecraft.world.chunk.storage.AnvilChunkLoader; + +/** + * Creates and loads savestates on both client and server without closing the + * world
+ * The old version that you may find in TASTools was heavily inspired by bspkrs' + * WorldStateCheckpoints, + * but this new version is completely self written. + * + * @author Scribble + * + */ +public class SavestateHandlerServerOld implements ServerPacketHandler { + + private final MinecraftServer server; + private Path savestateDirectory; + + public SavestateState state = SavestateState.NONE; + + public enum SavestateState { + SAVING, + LOADING, + NONE + } + + private final List indexList = new ArrayList<>(); + + private int latestIndex = 0; + private int currentIndex; + + private final SavestatePlayerHandlerServer playerHandler; + private final SavestateWorldHandler worldHandler; + + public static final Path storageDir = Paths.get("tasmod/"); + + private final Logger logger; + + /** + * Creates a savestate handler on the specified server + * @param logger + * + * @param The server that should store the savestates + */ + public SavestateHandlerServerOld(MinecraftServer server, Logger logger) { + this.server = server; + this.logger = logger; + this.playerHandler = new SavestatePlayerHandlerServer(server); + this.worldHandler = new SavestateWorldHandler(server); + + createSavestateDirectory(); + refresh(); + loadCurrentIndexFromFile(); + } + + /** + * Creates a copy of the world that is currently being played and saves it in + * .minecraft/saves/savestates/worldname-Savestate[{@linkplain #currentIndex}+1] + *

+ * Side: Server + * + * @throws SavestateException + * @throws IOException + */ + public void saveState() throws SavestateException, IOException { + saveState(-1, true); + } + + public void saveState(int savestateIndex, boolean tickrate0) throws SavestateException, IOException { + saveState(savestateIndex, tickrate0, true); + } + + /** + * Creates a copy of the world that is currently being played and saves it in + * .minecraft/saves/savestates/worldname-Savestate[savestateIndex] + *

+ * Side: Server + * + * @param savestateIndex The index where the mod will save the savestate. + * index<0 if it should save it in the next index from + * the currentindex + * @param tickrate0 When true: Set's the game to tickrate 0 after creating a savestate + * @param changeIndex When true: Changes the index to the savestateIndex + * @throws SavestateException + * @throws IOException + */ + public void saveState(int savestateIndex, boolean tickrate0, boolean changeIndex) throws SavestateException, IOException { + if (logger.isTraceEnabled()) { + logger.trace(LoggerMarkers.Savestate, "SAVING a savestate with index {}, tickrate0 is {} and changeIndex is {}", savestateIndex, tickrate0, changeIndex); + } else { + logger.debug(LoggerMarkers.Savestate, "Creating new savestate"); + } + + if (state == SavestateState.SAVING) { + throw new SavestateException("A savestating operation is already being carried out"); + } + if (state == SavestateState.LOADING) { + throw new SavestateException("A loadstate operation is being carried out"); + } + + try { + // Open GuiSavestateScreen + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_SCREEN)); + } catch (Exception e) { + e.printStackTrace(); + } + + // Lock savestating and loadstating + state = SavestateState.SAVING; + + // Create a directory just in case + createSavestateDirectory(); + + // Enable tickrate 0 + TASmod.tickratechanger.pauseGame(true); + + // Save the world! + server.getPlayerList().saveAllPlayerData(); + server.saveAllWorlds(false); + + // Refreshing the index list + refresh(); + + // Setting the current index depending on the savestateIndex. + int indexToSave = savestateIndex; + if (savestateIndex < 0) { + indexToSave = currentIndex + 1; // If the savestateIndex <= 0, create a savestate at currentIndex+1 + } + + // Update current index + if (changeIndex) { + setCurrentIndex(indexToSave); + } else { + logger.warn(LoggerMarkers.Savestate, "Keeping the savestate index at {}", currentIndex); + } + + // Get the current and target directory for copying + String worldname = server.getFolderName(); + Path currentfolder = savestateDirectory.resolve(".." + File.separator + worldname); + Path targetfolder = getSavestateFile(indexToSave); + + EventListenerRegistry.fireEvent(EventSavestate.EventServerSavestate.class, server, indexToSave, targetfolder, currentfolder); + + if (Files.exists(targetfolder)) { + logger.warn(LoggerMarkers.Savestate, "WARNING! Overwriting the savestate with the index {}", indexToSave); + deleteFolder(targetfolder); + } + + /* + * Prevents creating an InputSavestate when saving at index 0 (Index 0 is the + * savestate when starting a recording) + */ + if (savestateIndex != 0) { + /* + * Send the name of the world to all players. This will make a savestate of the + * recording on the client with that name + */ + try { + // savestate inputs client + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_SAVE).writeString(getSavestateName(indexToSave))); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // Wait for the chunkloader to save the game + for (WorldServer world : server.worlds) { + AnvilChunkLoader chunkloader = (AnvilChunkLoader) ((AccessorChunkLoader) world.getChunkProvider()).getChunkLoader(); + + while (((AccessorAnvilChunkLoader) chunkloader).getChunksToSave().size() > 0) { + } + } + + saveSavestateDataFile(false); + + // Copy the directory + copyFolder(currentfolder, targetfolder); + + // Incrementing info file + SavestateTrackerFile tracker = new SavestateTrackerFile(savestateDirectory.resolve(worldname + "-info.txt")); + tracker.increaseSaveStateCount(); + + // Send a notification that the savestate has been loaded + server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + indexToSave + " saved")); + + try { + // Close GuiSavestateScreen + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.CLEAR_SCREEN)); + } catch (Exception e) { + e.printStackTrace(); + } + + if (!tickrate0) { + TASmod.tickratechanger.pauseGame(false); + } + + // Unlock savestating + state = SavestateState.NONE; + } + + /** + * Loads the latest savestate at {@linkplain #currentIndex} + * .minecraft/saves/savestates/worldname-Savestate[{@linkplain #currentIndex}] + *

+ * Side: Server + * + * @throws LoadstateException + * @throws IOException + */ + public void loadState() throws LoadstateException, IOException { + loadState(-1, true); + } + + /** + * + * @param savestateIndex + * @param tickrate0 + * + * @throws LoadstateException + * @throws IOException + */ + public void loadState(int savestateIndex, boolean tickrate0) throws LoadstateException, IOException { + loadState(savestateIndex, tickrate0, true); + } + + /** + * Loads the latest savestate it can find in + * .minecraft/saves/savestates/worldname-Savestate + *

+ * Side: Server + * + * @param savestateIndex The index where the mod will load the savestate. + * index<0 if it should load the currentindex + * @param tickrate0 When true: Set's the game to tickrate 0 after creating a savestate + * @param changeIndex When true: Changes the index to the savestateIndex + * @throws LoadstateException + * @throws IOException + */ + public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex) throws LoadstateException, IOException { + if (logger.isTraceEnabled()) { + logger.trace(LoggerMarkers.Savestate, "LOADING a savestate with index {}, tickrate0 is {} and changeIndex is {}", savestateIndex, tickrate0, changeIndex); + } else { + logger.debug(LoggerMarkers.Savestate, "Loading a savestate"); + } + + if (state == SavestateState.SAVING) { + throw new LoadstateException("A savestating operation is already being carried out"); + } + if (state == SavestateState.LOADING) { + throw new LoadstateException("A loadstate operation is being carried out"); + } + // Lock savestating and loadstating + state = SavestateState.LOADING; + + // Create a directory just in case + createSavestateDirectory(); + + // Enable tickrate 0 + TASmod.tickratechanger.pauseGame(true); + + refresh(); + + int indexToLoad = savestateIndex < 0 ? currentIndex : savestateIndex; + + if (Files.exists(getSavestateFile(indexToLoad))) { + // Updating current index + if (changeIndex) { + setCurrentIndex(indexToLoad); + } else { + logger.warn(LoggerMarkers.Savestate, "Keeping the savestate index at {}", currentIndex); + } + } else { + throw new LoadstateException("Savestate " + indexToLoad + " doesn't exist"); + } + + // Get the current and target directory for copying + String worldname = server.getFolderName(); + Path currentfolder = savestateDirectory.resolve(".." + File.separator + worldname); + Path targetfolder = getSavestateFile(indexToLoad); + + EventListenerRegistry.fireEvent(EventSavestate.EventServerLoadstate.class, server, indexToLoad, targetfolder, currentfolder); + + /* + * Prevents loading an InputSavestate when loading index 0 (Index 0 is the + * savestate when starting a recording. Not doing this will load an empty + * InputSavestate) + */ + if (savestateIndex != 0) { + try { + // loadstate inputs client + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_LOAD).writeString(getSavestateName(indexToLoad))); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // Disabeling level saving for all worlds in case the auto save kicks in during + // world unload + worldHandler.disableLevelSaving(); + + try { + // unload chunks on client + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_UNLOAD_CHUNKS)); + } catch (Exception e) { + e.printStackTrace(); + } + + // Unload chunks on the server + worldHandler.disconnectPlayersFromChunkMap(); + worldHandler.unloadAllServerChunks(); + worldHandler.flushSaveHandler(); + + // Delete and copy directories + deleteFolder(currentfolder); + copyFolder(targetfolder, currentfolder); + + // Loads savestate data from the file like name and ktrng seed if ktrng is loaded + loadSavestateDataFile(); + + playerHandler.clearScoreboard(); + + // Load the world from disk +// server.loadAllWorlds(worldname, worldname, 0, WorldType.DEFAULT, ""); + worldHandler.loadAllWorlds(worldname); + + // Update the player and the client + playerHandler.loadAndSendMotionToPlayer(); + + // Load the chunks and send them to the client + worldHandler.addPlayersToChunkMap(); + + // Reenable level saving + worldHandler.enableLevelSaving(); + + // Refresh server resourcepacks on the client + SavestateResourcePackHandler.refreshServerResourcepack(server); + + // Incrementing info file + SavestateTrackerFile tracker = new SavestateTrackerFile(savestateDirectory.resolve(worldname + "-info.txt")); + tracker.increaseLoadstateCount(); + + // Send a notification that the savestate has been loaded + server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + indexToLoad + " loaded")); + + // Add players to the chunk + worldHandler.addPlayersToServerChunks(); + + worldHandler.sendChunksToClient(); + + try { + TASmod.server.sendToAll(new TASmodBufferBuilder(CLEAR_SCREEN)); + } catch (Exception e) { + LOGGER.catching(e); + } + + if (!tickrate0) { + TASmod.tickratechanger.pauseGame(false); + } + + // Unlock savestating + state = SavestateState.NONE; + + /* + * TODO Savestates can be reloaded without a tick passing... + * And since this scheduler is not cleared, it would execute the same task multiple times in the next tick + * Rn it's not a problem, but this should be looked at... + */ + TASmod.tickSchedulerServer.add(() -> { + EventListenerRegistry.fireEvent(EventSavestate.EventServerCompleteLoadstate.class); + onLoadstateComplete(); + }); + } + + /** + * Creates the savestate directory in case the user deletes it between + * savestates + */ + private void createSavestateDirectory() { + logger.trace(LoggerMarkers.Savestate, "Creating savestate directory"); + + Path dataDirectory = server.getDataDirectory().toPath(); + + if (!server.isDedicatedServer()) { + savestateDirectory = dataDirectory.resolve("saves/savestates"); + } else { + savestateDirectory = dataDirectory.resolve("savestates"); + } + if (!Files.exists(savestateDirectory)) { + try { + Files.createDirectory(savestateDirectory); + } catch (IOException e) { + logger.error("Could not create savestate directory"); + logger.catching(e); + } + } + } + + /** + * Refreshes the current savestate list and loads all indizes into {@link #indexList} + */ + private void refresh() { + logger.trace(LoggerMarkers.Savestate, "Refreshing savestate list"); + indexList.clear(); + if (!Files.isDirectory(savestateDirectory)) { + logger.error("Savestate directory is not a directory! {}", savestateDirectory.toAbsolutePath().toString()); + return; + } + + Stream files = null; + try { + files = Files.list(savestateDirectory); + } catch (IOException e) { + logger.error("Can't refresh savestatelist"); + logger.catching(e); + return; + } + Stream filteredfiles = files.filter(file -> file.getFileName().toString().startsWith(server.getFolderName() + "-Savestate")); + + filteredfiles.forEach(file -> { + int index = 0; + try { + Pattern patt = Pattern.compile("\\d+$"); + Matcher matcher = patt.matcher(file.getFileName().toString()); + if (matcher.find()) { + index = Integer.parseInt(matcher.group(0)); + } else { + logger.warn(String.format("Could not process the savestate %s", file.getFileName())); + return; + } + } catch (NumberFormatException e) { + logger.warn(String.format("Could not process the savestate %s", e.getMessage())); + return; + } + indexList.add(index); + }); + + filteredfiles.close(); + files.close(); + + Collections.sort(indexList); + if (!indexList.isEmpty()) { + latestIndex = indexList.get(indexList.size() - 1); + } else { + latestIndex = 0; + } + } + + /** + * @param index The index of the savestate file that we want to get + * @return The file of the savestate from the specified index + */ + private Path getSavestateFile(int index) { + return savestateDirectory.resolve(getSavestateName(index)); + } + + /** + * @param index The index of the savestate file that we want to get + * @return The savestate name without any paths + */ + private String getSavestateName(int index) { + return server.getFolderName() + "-Savestate" + index; + } + + /** + * Deletes the specified savestate + * + * @param index The index of the savestate that should be deleted + * @throws SavestateDeleteException + */ + public void deleteSavestate(int index) throws SavestateDeleteException { + logger.warn(LoggerMarkers.Savestate, "Deleting savestate {}", index); + if (state == SavestateState.SAVING) { + throw new SavestateDeleteException("A savestating operation is already being carried out"); + } + if (state == SavestateState.LOADING) { + throw new SavestateDeleteException("A loadstate operation is being carried out"); + } + if (index < 0) { + throw new SavestateDeleteException("Cannot delete the negative indexes"); + } + if (index == 0) { + throw new SavestateDeleteException("Cannot delete protected savestate 0"); + } + + Path toDelete = getSavestateFile(index); + if (Files.exists(getSavestateFile(index))) { +// try { + deleteFolder(toDelete); +// } catch (IOException e) { +// e.printStackTrace(); +// throw new SavestateDeleteException("Something went wrong while trying to delete the savestate " + index); +// } + } else { + throw new SavestateDeleteException(TextFormatting.YELLOW + "Savestate " + index + " doesn't exist, so it can't be deleted"); + } + + refresh(); + if (!indexList.contains(currentIndex)) { + setCurrentIndex(latestIndex); + } + // Send a notification that the savestate has been deleted + server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + index + " deleted")); + } + + /** + * Deletes savestates in a range from "from" to "to" + * + * @param from + * @param to (inclusive) + * @throws SavestateDeleteException + */ + public void deleteSavestate(int from, int to) throws SavestateDeleteException { + logger.warn(LoggerMarkers.Savestate, "Deleting multiple savestates from {} to {}", from, to); + if (state == SavestateState.SAVING) { + throw new SavestateDeleteException("A savestating operation is already being carried out"); + } + if (state == SavestateState.LOADING) { + throw new SavestateDeleteException("A loadstate operation is being carried out"); + } + if (from >= to) { + throw new SavestateDeleteException("Can't delete amounts that are negative or 0"); + } + for (int i = from; i <= to; i++) { + // System.out.println("Would've deleted savestate: "+i); + try { + deleteSavestate(i); + } catch (SavestateDeleteException e) { + server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.RED + e.getMessage())); + continue; + } + } + } + + /** + * @return A list of index numbers as string in the form of: "0, 1, 2, 3" + */ + public String getIndexesAsString() { + refresh(); + String out = ""; + for (int i : indexList) { + out = out.concat(" " + i + (i == indexList.size() - 1 ? "" : ",")); + } + return out; + } + + /** + * Saves the current index to the current world-folder (not the savestate + * folder) + * + * @param legacy If the data file should only store the index, since it comes from a legacy file format + */ + private void saveSavestateDataFile(boolean legacy) { + logger.trace(LoggerMarkers.Savestate, "Saving savestate data file"); + Path tasmodDir = savestateDirectory.resolve("../" + server.getFolderName() + "/tasmod/"); + if (!Files.exists(tasmodDir)) { + try { + Files.createDirectories(tasmodDir); + } catch (IOException e) { + e.printStackTrace(); + } + } + + Path savestateDat = tasmodDir.resolve("savestateData.txt"); + try { + Files.deleteIfExists(savestateDat); + } catch (IOException e) { + e.printStackTrace(); + } + + SavestateDataFile file = new SavestateDataFile(savestateDat); + + file.set(DataValues.INDEX, Integer.toString(currentIndex)); + + // if (!legacy) { + // if (TASmod.ktrngHandler.isLoaded()) { + // file.set(DataValues.SEED, Long.toString(TASmod.ktrngHandler.getGlobalSeedServer())); + // } + // } + + file.save(savestateDat); + } + + /** + * Loads information from savestateData.txt + *

+ * This loads everything except the index, since that is loaded when the world is loaded + */ + private void loadSavestateDataFile() { + logger.trace(LoggerMarkers.Savestate, "Loading savestate data file"); + Path tasmodDir = savestateDirectory.resolve("../" + server.getFolderName()).resolve(storageDir); + Path savestateDat = tasmodDir.resolve("savestateData.txt"); + + if (!Files.exists(savestateDat)) { + return; + } + + SavestateDataFile datafile = new SavestateDataFile(savestateDirectory); + + datafile.load(savestateDat); + + // if (TASmod.ktrngHandler.isLoaded()) { + // String seedString = datafile.get(DataValues.SEED); + // if (seedString != null) { + // TASmod.ktrngHandler.sendGlobalSeedToServer(Long.parseLong(seedString)); + // } else { + // logger.warn("KTRNG seed not loaded because it was not found in savestateData.txt!"); + // } + // } + } + + /** + * Loads the current index to the current world-folder (not the savestate + * folder) + *

+ * This ensures that the server knows the current index when loading the world + */ + public void loadCurrentIndexFromFile() { + logger.trace(LoggerMarkers.Savestate, "Loading current index from file"); + int index = -1; + Path tasmodDir = savestateDirectory.resolve("../" + server.getFolderName()).resolve(storageDir); + if (!Files.exists(tasmodDir)) { + try { + Files.createDirectory(tasmodDir); + } catch (IOException e) { + e.printStackTrace(); + } + } + + Path savestateDat = tasmodDir.resolve("savestate.data"); + if (Files.exists(savestateDat)) { + index = legacyIndexFile(savestateDat); + setCurrentIndex(index); + saveSavestateDataFile(true); + try { + Files.delete(savestateDat); + } catch (IOException e) { + e.printStackTrace(); + } + return; + } + + savestateDat = tasmodDir.resolve("savestateData.txt"); + if (Files.exists(savestateDat)) { + SavestateDataFile file = new SavestateDataFile(savestateDat); + file.load(savestateDat); + + index = Integer.parseInt(file.get(DataValues.INDEX)); + if (index != 0) + setCurrentIndex(index); + else + setCurrentIndex(latestIndex); + } + } + + private void setCurrentIndex(int index) { + if (index < 0) { + currentIndex = latestIndex; + } else { + currentIndex = index; + } + logger.debug(LoggerMarkers.Savestate, "Setting the savestate index to {}", currentIndex); + } + + public SavestatePlayerHandlerServer getPlayerHandler() { + return playerHandler; + } + + public int getCurrentIndex() { + return currentIndex; + } + + public void onLoadstateComplete() { + logger.trace(LoggerMarkers.Savestate, "Running loadstate complete event"); + PlayerList playerList = server.getPlayerList(); + for (EntityPlayerMP player : playerList.getPlayers()) { + NBTTagCompound nbttagcompound = playerList.readPlayerDataFromFile(player); + playerHandler.reattachEntityToPlayer(nbttagcompound, player.getServerWorld(), player); + } + } + + private int legacyIndexFile(Path savestateDat) { + int index = -1; + List lines = new ArrayList(); + try { + lines = FileUtils.readLines(savestateDat.toFile(), StandardCharsets.UTF_8); + } catch (IOException e) { + logger.warn("No savestate.data file found in current world folder, ignoring it"); + } + if (!lines.isEmpty()) { + for (String line : lines) { + if (line.startsWith("currentIndex=")) { + try { + index = Integer.parseInt(line.split("=")[1]); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + } + } + } + return index; + } + + @Override + public PacketID[] getAcceptedPacketIDs() { + return new TASmodPackets[] { + //@formatter:off + TASmodPackets.SAVESTATE_SAVE, + TASmodPackets.SAVESTATE_LOAD, + TASmodPackets.SAVESTATE_SCREEN, + TASmodPackets.SAVESTATE_UNLOAD_CHUNKS + //@formatter:on + }; + } + + @Override + public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception { + // TODO Permissions + TASmodPackets packet = (TASmodPackets) id; + + EntityPlayerMP player = TASmod.getServerInstance().getPlayerList().getPlayerByUsername(username); + + switch (packet) { + case SAVESTATE_SAVE: + int index = TASmodBufferBuilder.readInt(buf); + + Task savestateTask = () -> { + try { + saveState(index, true); + } catch (SavestateException e) { + if (player != null) + player.sendMessage(new TextComponentString(TextFormatting.RED + "Failed to create a savestate: " + e.getMessage())); + + LOGGER.error(LoggerMarkers.Savestate, "Failed to create a savestate"); + LOGGER.catching(e); + } catch (Exception e) { + if (player != null) + player.sendMessage(new TextComponentString(TextFormatting.RED + "Failed to create a savestate: " + e.getClass().getName().toString() + ": " + e.getMessage())); + + LOGGER.catching(e); + } finally { + state = SavestateState.NONE; + } + }; + + if (TASmod.tickratechanger.ticksPerSecond == 0) + TASmod.gameLoopSchedulerServer.add(savestateTask); + else + TASmod.tickSchedulerServer.add(savestateTask); + break; + + case SAVESTATE_LOAD: + int indexing = TASmodBufferBuilder.readInt(buf); + Task loadstateTask = () -> { + try { + loadState(indexing, true); + } catch (LoadstateException e) { + if (player != null) + player.sendMessage(new TextComponentString(TextFormatting.RED + "Failed to load a savestate: " + e.getMessage())); + + LOGGER.error(LoggerMarkers.Savestate, "Failed to create a savestate: " + e.getMessage()); + state = SavestateState.NONE; + } catch (Exception e) { + if (player != null) { + Throwable cause = e.getCause(); + if (cause == null) { + cause = e; + } + player.sendMessage(new TextComponentString(String.format("Failed to load a savestate: %s", cause.getMessage())).setStyle(new Style().setColor(TextFormatting.RED))); + } + + LOGGER.throwing(e); + state = SavestateState.NONE; + } + }; + TASmod.gameLoopSchedulerServer.add(loadstateTask); + break; + + case SAVESTATE_SCREEN: + case SAVESTATE_UNLOAD_CHUNKS: + throw new WrongSideException(id, Side.SERVER); + default: + throw new PacketNotImplementedException(packet, this.getClass(), Side.SERVER); + } + } + + public static void copyFolder(Path src, Path dest) { + try { + Files.walk(src).forEach(s -> { + try { + Path d = dest.resolve(src.relativize(s)); + if (Files.isDirectory(s)) { + if (!Files.exists(d)) + Files.createDirectory(d); + return; + } + Files.copy(s, d, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void deleteFolder(Path toDelete) { + try { + Files.walk(toDelete).forEach(s -> { + if (toDelete.equals(s)) + return; + if (Files.isDirectory(s)) { + deleteFolder(s); + } else { + try { + Files.delete(s); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + Files.delete(toDelete); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java new file mode 100644 index 00000000..9b558572 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java @@ -0,0 +1,572 @@ +package com.minecrafttas.tasmod.savestates; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.logging.log4j.Logger; + +import com.minecrafttas.mctcommon.file.AbstractDataFile; +import com.minecrafttas.tasmod.TASmod; +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.I18n; + +/** + * Manages the savestates on the filesystem and assignes new indices + * + * @author Scribble + */ +public class SavestateIndexer { + + private final Logger logger; + private final Path savestateBaseDirectory; + private Path savesDir; + private final String worldname; + private final Path currentSavestateDir; + private final LinkedHashMap savestateList; + private Savestate currentSavestate; + + private static final Path savestateDatPath = Paths.get("tas/savestate.json"); + + public SavestateIndexer(Logger logger, Path savesDir, Path savestateBaseDirectory, String worldname) { + this.logger = logger; + this.savestateBaseDirectory = savestateBaseDirectory; + this.savesDir = savesDir; + this.worldname = worldname; + this.currentSavestateDir = savestateBaseDirectory.resolve(String.format("%s-Savestates", worldname)); + savestateList = new LinkedHashMap<>(); + createSavestateDir(); + + Path savestateDat = savesDir.resolve(worldname).resolve(savestateDatPath); + if (Files.exists(savestateDat)) { + currentSavestate = new Savestate(savestateDat, null); + currentSavestate.load(); + } else { + currentSavestate = new Savestate(savestateDat, 0, null, null, null); + } + reload(); + } + + private void createSavestateDir() { + try { + Files.createDirectories(currentSavestateDir); + } catch (IOException e) { + logger.catching(e); + } + } + + public SavestatePaths createSavestate(int index) { + return createSavestate(index, null, true); + } + + public SavestatePaths createSavestate(int index, String name, boolean changeIndex) { + logger.trace("Creating a savestate in indexer"); + if (index < 0) { + index = currentSavestate.getIndex() + 1; + } + + if (name == null) { + name = "Savestate #" + index; + } + + int savedIndex = index; + + currentSavestate.index = index; + currentSavestate.name = name; + currentSavestate.date = new Date(); + + currentSavestate.save(); + + if (!changeIndex) + currentSavestate.index = savedIndex; + + Path sourceDir = savesDir.resolve(worldname); + Path targetDir = currentSavestateDir.resolve(worldname + "-Savestate" + index); + + Savestate newSavestate = currentSavestate.clone(targetDir.resolve(savestateDatPath), targetDir); + + savestateList.put(index, newSavestate); + sortSavestateList(); + + return SavestatePaths.of(newSavestate.clone(), sourceDir, targetDir); + } + + public SavestatePaths loadSavestate(int index, boolean changeIndex) throws LoadstateException { + logger.trace("Loading a savestate in indexer"); + if (index < 0) { + index = currentSavestate.getIndex(); + + if (savestateList.containsKey(index)) { + index = findLatestIndex(index); + } + } + + Savestate savestateToLoad = savestateList.get(index); + + if (savestateToLoad == null) { + throw new LoadstateException(I18n.format("msg.tasmod.savestate.error.noexist", index)); + } + + int savedIndex = currentSavestate.index; + this.currentSavestate = savestateToLoad.clone(savesDir.resolve(worldname).resolve(savestateDatPath), savesDir.resolve(worldname)); + + Path sourceDir = savestateToLoad.getFolder(); + Path targetDir = savesDir.resolve(worldname); + + if (sourceDir != null && !Files.exists(sourceDir)) { + Path missingFile = savesDir.relativize(sourceDir); + throw new LoadstateException(I18n.format("msg.tasmod.savestate.error.filenoexist", missingFile)); + } + + SavestatePaths out = SavestatePaths.of(currentSavestate.clone(), sourceDir, targetDir); + + if (!changeIndex) + currentSavestate.index = savedIndex; + + return out; + } + + public SavestatePaths renameSavestate(int index, String name) throws SavestateException { + Savestate savestateToRename = savestateList.get(index); + + if (savestateToRename == null) { + throw new SavestateException(I18n.format("msg.tasmod.savestate.error.noexist", index)); + } else if (savestateToRename instanceof FailedSavestate) { + throw new SavestateException(I18n.format("msg.tasmod.savestate.rename.error")); + } + + if (name.isEmpty()) { + name = "Savestate #" + index; + } + + savestateToRename.name = name; + savestateToRename.save(); + + return SavestatePaths.of(savestateToRename, null, null); + } + + public SavestatePaths deleteSavestate(int index) throws SavestateDeleteException { + logger.trace("Deleting savestate {}", index); + + if (!savestateList.containsKey(index)) { + throw new SavestateDeleteException(I18n.format("msg.tasmod.savestate.error.noexist", index)); + } + + Savestate toDelete = savestateList.get(index); + Path targetDir = toDelete.getFolder(); + SavestatePaths out = SavestatePaths.of(toDelete, null, targetDir); + + savestateList.remove(index); + + if (!savestateList.containsKey(currentSavestate.index)) { + currentSavestate.index = findLatestIndex(currentSavestate.index); + } + + return out; + } + + public void deleteMultipleSavestates(int from, int to, DeletionRunnable onDelete, ErrorRunnable onError) { + if (from >= to) { + onError.run(new SavestateDeleteException("Can't delete amounts that are negative or 0")); + return; + } + for (int i = from; i <= to; i++) { + try { + onDelete.run(deleteSavestate(i)); + } catch (Exception e) { + onError.run(e); + } + } + } + + private void sortSavestateList() { + LinkedHashMap copy = new LinkedHashMap<>(); + //@formatter:off + savestateList.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(entry -> copy.put(entry.getKey(), entry.getValue())); + //@formatter:on + savestateList.clear(); + savestateList.putAll(copy); + } + + public void reload() { + logger.trace("Reloading savestate indexes"); + savestateList.clear(); + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + Stream stream = null; + try { + stream = Files.list(currentSavestateDir); // Get a list of paths in the specified directory + } catch (IOException e) { + logger.catching(e); + return; + } + + //@formatter:off + Set pathSet = stream + .filter(file -> Files.isDirectory(file)) + .filter(file -> file.getFileName().toString().startsWith(worldname)) + .collect(Collectors.toSet()); + //@formatter:on + + stream.close(); + + Pattern backupIndexPattern = Pattern.compile("-Savestate(\\d+)$"); + + pathSet.forEach(path -> { + Path savestateDat = path.resolve(savestateDatPath); + + Savestate savestate = null; + + /* + * Read the index from the folder if the savestate file + * doesn't exist + */ + if (!Files.exists(savestateDat)) { + String filename = path.getFileName().toString(); + Matcher matcher = backupIndexPattern.matcher(filename); + int backupIndex = -1; + if (matcher.find()) { + backupIndex = Integer.parseInt(matcher.group(1)); + } + + logger.warn("Savestate {} does not contain a valid savestate.json, skipping", backupIndex); + Throwable error = new SavestateException("Savestate.json data file not found in " + savestateBaseDirectory.relativize(savestateDat)); + savestate = new FailedSavestate(path, backupIndex, null, null, error); + } else { + savestate = new Savestate(savestateDat, path); + savestate.load(); + } + savestateList.put(savestate.getIndex(), savestate.clone()); + }); + sortSavestateList(); + try { + currentSavestate.index = findLatestIndex(currentSavestate.index); + } catch (Exception e) { + logger.catching(e); + } + } + }, "Savestate Reload"); + t.run(); + } + + public Set getIndexList() { + return savestateList.keySet(); + } + + public List getSavestateList() { + return getSavestateList(currentSavestate.index); + } + + public List getSavestateList(int center) { + return getSavestateList(center, 10); + } + + public List getSavestateList(int center, int amount) { + List out = new LinkedList<>(); + if (center < 0) { + savestateList.forEach((key, value) -> out.add(value)); + return out; + } + + LinkedHashMap copy = new LinkedHashMap<>(savestateList); + int delta = ((int) amount / 2); + + for (int i = center - delta; i <= center + delta; i++) { + Savestate entry = copy.get(i); + if (entry != null) + out.add(entry); + } + return out; + } + + public int size() { + return savestateList.size(); + } + + public int findLatestIndex(int start) { + if (savestateList.containsKey(start)) + return start; + + for (int i = start; i >= 0; i--) { + if (savestateList.containsKey(i) && !(savestateList.get(i) instanceof FailedSavestate)) { + return i; + } + } + return 0; + } + + public Savestate getCurrentSavestate() { + return currentSavestate; + } + + /** + * Data class, containing information about the Savestate + * + *

+ * + * @author Scribble + */ + public class Savestate extends AbstractDataFile { + + protected Integer index; + protected String name; + protected Date date; + protected Path folder; + protected Logger logger = TASmod.LOGGER; + + private Savestate(Path datFile, Path folder) { + this(datFile, -1, null, null, folder); + } + + private Savestate(Path file, Integer index, String name, Date date, Path folder) { + super(file, "Savestate", "Stores savestate related data"); + this.index = index; + this.name = name; + this.date = date; + this.folder = folder; + } + + private Savestate(Path file, Properties properties, Integer index, String name, Date date, Path folder) { + this(file, index, name, date, folder); + this.properties = properties; + } + + public Integer getIndex() { + return index; + } + + public String getName() { + return name; + } + + public Date getDate() { + return date; + } + + public Path getFolder() { + return folder; + } + + @Override + public void save() { + if (index != null) + properties.setProperty(Options.INDEX.toString(), Integer.toString(index)); + if (name != null) + properties.setProperty(Options.NAME.toString(), name); + if (date != null) + properties.setProperty(Options.DATE.toString(), Long.toString(ChronoUnit.SECONDS.between(Instant.EPOCH, date.toInstant()))); + super.saveToJson(); + } + + @Override + public void load() { + super.loadFromJson(); + try { + String loadedIndex = properties.getProperty(Options.INDEX.toString()); + if (loadedIndex != null) + this.index = Integer.parseInt(loadedIndex); + } catch (Exception e) { + logger.error("Can't parse '{}' in {}", Options.INDEX.toString(), currentSavestateDir.resolve(savestateDatPath)); + logger.catching(e); + } + this.name = properties.getProperty(Options.NAME.toString()); + try { + String loadedDate = properties.getProperty(Options.DATE.toString()); + if (loadedDate != null) + this.date = parseDate(loadedDate); + } catch (Exception e) { + logger.error("Can't parse '{}' in {}", Options.DATE.toString(), currentSavestateDir.resolve(savestateDatPath)); + logger.catching(e); + } + } + + @Override + protected Savestate clone() { + return new Savestate(file, properties, index, name, date, folder); + } + + /** + * Clone with a new file + * @param newFile The new file to point to + * @return The new savestate + */ + protected Savestate clone(Path newFile, Path newFolder) { + return new Savestate(newFile, properties, index, name, date, newFolder); + } + + private Date parseDate(String dateString) throws Exception { + long unixTimestamp = Long.parseLong(dateString); + return Date.from(Instant.ofEpochSecond(unixTimestamp)); + } + } + + public class FailedSavestate extends Savestate { + + private final Throwable t; + + public FailedSavestate(Path file, Throwable t) { + this(file, null, null, null, t); + } + + public FailedSavestate(Path file, Integer index, String name, Date date, Throwable t) { + super(file, index, name, date, null); + this.t = t; + } + + public FailedSavestate(Path file, Properties properties, Integer index, String name, Date date, Throwable t) { + super(file, index, name, date, null); + this.t = t; + } + + public Throwable getError() { + return t; + } + + @Override + public void saveToJson() { + } + + @Override + public void save() { + } + + @Override + public void loadFromJson() { + } + + @Override + public void load() { + } + + @Override + protected FailedSavestate clone() { + return new FailedSavestate(file, properties, index, name, date, t); + } + } + + /** + * Data class containing: + * + * + * @author Scribble + */ + public static class SavestatePaths { + private final Savestate savestate; + private final Path sourceFolder; + private final Path targetFolder; + + private SavestatePaths(Savestate savestate, Path sourceFolder, Path targetFolder) { + this.savestate = savestate; + this.sourceFolder = sourceFolder; + this.targetFolder = targetFolder; + } + + public Savestate getSavestate() { + return savestate; + } + + public Path getSourceFolder() { + return sourceFolder; + } + + public Path getTargetFolder() { + return targetFolder; + } + + public static SavestatePaths of(Savestate savestate, Path sourceFolder, Path targetFolder) { + return new SavestatePaths(savestate, sourceFolder, targetFolder); + } + } + + public static void copyFolder(Path src, Path dest) { + try { + Files.walk(src).forEach(s -> { + try { + Path d = dest.resolve(src.relativize(s)); + if (Files.isDirectory(s)) { + if (!Files.exists(d)) + Files.createDirectory(d); + return; + } + Files.copy(s, d, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void deleteFolder(Path toDelete) { + try { + Files.walk(toDelete).forEach(s -> { + if (toDelete.equals(s)) + return; + if (Files.isDirectory(s)) { + deleteFolder(s); + } else { + try { + Files.delete(s); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + Files.delete(toDelete); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @FunctionalInterface + public interface DeletionRunnable { + public void run(SavestatePaths paths); + } + + @FunctionalInterface + public interface ErrorRunnable { + public void run(Exception e); + } + + private enum Options { + INDEX, + NAME, + DATE; + + @Override + public String toString() { + return super.toString().toLowerCase(); + } + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java index 520280b5..dcd76051 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java @@ -1,7 +1,6 @@ package com.minecrafttas.tasmod.savestates.handlers; import java.nio.ByteBuffer; -import java.nio.file.Path; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; @@ -20,6 +19,7 @@ import com.minecrafttas.tasmod.events.EventSavestate; import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.registries.TASmodPackets; +import com.minecrafttas.tasmod.savestates.SavestateIndexer.SavestatePaths; import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; import com.minecrafttas.tasmod.savestates.gui.GuiResourcepackWarn; import com.minecrafttas.tasmod.util.LoggerMarkers; @@ -48,7 +48,7 @@ public class SavestateResourcePackHandler implements EventSavestate.EventServerL public static CountDownLatch clientRPLatch; @Override - public void onServerLoadstate(MinecraftServer server, int index, Path target, Path current) { + public void onServerLoadstate(MinecraftServer server, SavestatePaths paths) { if (server.getResourcePackUrl().isEmpty() || server.isDedicatedServer()) return; diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateWorldHandler.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateWorldHandler.java index 6a08c9b5..fc5fd636 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateWorldHandler.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateWorldHandler.java @@ -217,7 +217,7 @@ public void sendChunksToClient() { } } - public void loadAllWorlds(String string, String string2) { + public void loadAllWorlds(String string) { server.convertMapIfNeeded(string); server.worlds = new WorldServer[3]; server.timeOfLastDimensionTick = new long[server.worlds.length][100]; @@ -227,7 +227,7 @@ public void loadAllWorlds(String string, String string2) { if (worldInfo == null) { // worldInfo = new WorldInfo(server.worldSettings, string2); } else { - worldInfo.setWorldName(string2); + worldInfo.setWorldName(string); } for (int i = 0; i < server.worlds.length; i++) { diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java index e6f008c1..ef8d4442 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java @@ -11,6 +11,7 @@ import com.minecrafttas.tasmod.events.EventSavestate.EventServerLoadstate; import com.minecrafttas.tasmod.events.EventSavestate.EventServerSavestate; import com.minecrafttas.tasmod.savestates.SavestateHandlerServer; +import com.minecrafttas.tasmod.savestates.SavestateIndexer.SavestatePaths; import com.minecrafttas.tasmod.savestates.exceptions.LoadstateException; import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; import com.minecrafttas.tasmod.util.JsonUtils; @@ -24,13 +25,13 @@ public SavestateStorageExtensionRegistry() { } @Override - public void onServerSavestate(MinecraftServer server, int index, Path target, Path current) { - Path storageDir = current.resolve(SavestateHandlerServer.storageDir); + public void onServerSavestate(MinecraftServer server, SavestatePaths paths) { + Path storageDir = paths.getSourceFolder().resolve(SavestateHandlerServer.storageDir); if (!Files.exists(storageDir)) { try { Files.createDirectory(storageDir); } catch (IOException e) { - throw new SavestateException(e, "Can't create directory for savestate storage in savestate %s", current.getFileName()); + throw new SavestateException(e, "Can't create directory for savestate storage in savestate %s", paths.getSourceFolder().getFileName()); } } @@ -47,13 +48,13 @@ public void onServerSavestate(MinecraftServer server, int index, Path target, Pa } @Override - public void onServerLoadstate(MinecraftServer server, int index, Path target, Path current) { - Path storageDir = target.resolve(SavestateHandlerServer.storageDir); + public void onServerLoadstate(MinecraftServer server, SavestatePaths paths) { + Path storageDir = paths.getTargetFolder().resolve(SavestateHandlerServer.storageDir); if (!Files.exists(storageDir)) { try { Files.createDirectory(storageDir); } catch (IOException e) { - throw new LoadstateException(e, "Can't create directory for savestate storage in savestate %s", target.getFileName()); + throw new LoadstateException(e, "Can't create directory for savestate storage in savestate %s", paths.getTargetFolder().getFileName()); } } diff --git a/src/main/java/com/minecrafttas/tasmod/util/I18n.java b/src/main/java/com/minecrafttas/tasmod/util/I18n.java new file mode 100644 index 00000000..19eb6f28 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/util/I18n.java @@ -0,0 +1,12 @@ +package com.minecrafttas.tasmod.util; + +public class I18n { + + public static String format(String string, Object... args) { + return net.minecraft.client.resources.I18n.format(string, args); + } + + public static boolean hasKey(String key) { + return net.minecraft.client.resources.I18n.hasKey(key); + } +} From 3a8a8c445b832b67c302432517edecfba48ea23e Mon Sep 17 00:00:00 2001 From: Scribble Date: Tue, 30 Sep 2025 17:56:17 +0200 Subject: [PATCH 05/19] [Savestates] Change savestate data directory from "tasmod" to "tas" --- .../tasmod/savestates/SavestateHandlerServer.java | 3 --- .../minecrafttas/tasmod/savestates/SavestateIndexer.java | 3 ++- .../storage/SavestateStorageExtensionRegistry.java | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index 2653ca50..ea748638 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -7,7 +7,6 @@ import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.List; @@ -74,8 +73,6 @@ public class SavestateHandlerServer implements ServerPacketHandler { private final SavestatePlayerHandlerServer playerHandler; private final SavestateWorldHandler worldHandler; - public static final Path storageDir = Paths.get("tasmod/"); - private final Logger logger; /** diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java index 9b558572..4995cb3e 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java @@ -43,7 +43,8 @@ public class SavestateIndexer { private final LinkedHashMap savestateList; private Savestate currentSavestate; - private static final Path savestateDatPath = Paths.get("tas/savestate.json"); + public static final Path savestateDataDir = Paths.get("tas"); + private static final Path savestateDatPath = savestateDataDir.resolve("savestate.json"); public SavestateIndexer(Logger logger, Path savesDir, Path savestateBaseDirectory, String worldname) { this.logger = logger; diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java index ef8d4442..8ab26bec 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java @@ -10,7 +10,7 @@ import com.minecrafttas.tasmod.TASmod; import com.minecrafttas.tasmod.events.EventSavestate.EventServerLoadstate; import com.minecrafttas.tasmod.events.EventSavestate.EventServerSavestate; -import com.minecrafttas.tasmod.savestates.SavestateHandlerServer; +import com.minecrafttas.tasmod.savestates.SavestateIndexer; import com.minecrafttas.tasmod.savestates.SavestateIndexer.SavestatePaths; import com.minecrafttas.tasmod.savestates.exceptions.LoadstateException; import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; @@ -26,7 +26,7 @@ public SavestateStorageExtensionRegistry() { @Override public void onServerSavestate(MinecraftServer server, SavestatePaths paths) { - Path storageDir = paths.getSourceFolder().resolve(SavestateHandlerServer.storageDir); + Path storageDir = paths.getSourceFolder().resolve(SavestateIndexer.savestateDataDir); if (!Files.exists(storageDir)) { try { Files.createDirectory(storageDir); @@ -49,7 +49,7 @@ public void onServerSavestate(MinecraftServer server, SavestatePaths paths) { @Override public void onServerLoadstate(MinecraftServer server, SavestatePaths paths) { - Path storageDir = paths.getTargetFolder().resolve(SavestateHandlerServer.storageDir); + Path storageDir = paths.getTargetFolder().resolve(SavestateIndexer.savestateDataDir); if (!Files.exists(storageDir)) { try { Files.createDirectory(storageDir); From 2d568d5dfe59d800af02ec18317fb722020e336c Mon Sep 17 00:00:00 2001 From: Scribble Date: Tue, 30 Sep 2025 17:58:13 +0200 Subject: [PATCH 06/19] Bump version to Beta2 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b4d8ae87b4b56466c1289d5e56b9a265263902d7 Mon Sep 17 00:00:00 2001 From: Scribble Date: Wed, 1 Oct 2025 21:51:51 +0200 Subject: [PATCH 07/19] [Savestates] Readd tracker file --- .../tasmod/savestates/SavestateHandlerServer.java | 4 ++-- .../tasmod/savestates/SavestateHandlerServerOld.java | 2 +- .../minecrafttas/tasmod/savestates/SavestateIndexer.java | 7 +++++++ .../tasmod/savestates/files/SavestateTrackerFile.java | 5 ++--- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index ea748638..d0f3dbae 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -137,9 +137,9 @@ public void saveState(int index, String name, SavestateCallback cb, SavestateFla logger.trace("Create new savestate index via indexer"); SavestatePaths paths = indexer.createSavestate(index, name, !SavestateFlags.BLOCK_CHANGE_INDEX.isBlocked(flags)); Path sourceFolder = paths.getSourceFolder(); - Path targetFolder = paths.getSourceFolder(); + Path targetFolder = paths.getTargetFolder(); int indexToSave = paths.getSavestate().index; - logger.debug("Source: {}, Target: {}", paths.getSourceFolder(), paths.getTargetFolder()); + logger.debug("Source: {}, Target: {}", sourceFolder, targetFolder); EventListenerRegistry.fireEvent(EventSavestate.EventServerSavestate.class, server, paths); diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServerOld.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServerOld.java index 48e4cd50..a2b9e850 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServerOld.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServerOld.java @@ -233,7 +233,7 @@ public void saveState(int savestateIndex, boolean tickrate0, boolean changeIndex // Incrementing info file SavestateTrackerFile tracker = new SavestateTrackerFile(savestateDirectory.resolve(worldname + "-info.txt")); - tracker.increaseSaveStateCount(); + tracker.increaseSavestateCount(); // Send a notification that the savestate has been loaded server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + indexToSave + " saved")); diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java index 4995cb3e..381e9afa 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java @@ -26,6 +26,7 @@ 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.savestates.files.SavestateTrackerFile; import com.minecrafttas.tasmod.util.I18n; /** @@ -42,6 +43,7 @@ public class SavestateIndexer { private final Path currentSavestateDir; private final LinkedHashMap savestateList; private Savestate currentSavestate; + private SavestateTrackerFile trackerfile; public static final Path savestateDataDir = Paths.get("tas"); private static final Path savestateDatPath = savestateDataDir.resolve("savestate.json"); @@ -52,6 +54,7 @@ public SavestateIndexer(Logger logger, Path savesDir, Path savestateBaseDirector this.savesDir = savesDir; this.worldname = worldname; this.currentSavestateDir = savestateBaseDirectory.resolve(String.format("%s-Savestates", worldname)); + this.trackerfile = new SavestateTrackerFile(currentSavestateDir.resolve(String.format(worldname + "-info.txt"))); savestateList = new LinkedHashMap<>(); createSavestateDir(); @@ -103,6 +106,8 @@ public SavestatePaths createSavestate(int index, String name, boolean changeInde Savestate newSavestate = currentSavestate.clone(targetDir.resolve(savestateDatPath), targetDir); + trackerfile.increaseSavestateCount(); + savestateList.put(index, newSavestate); sortSavestateList(); @@ -138,6 +143,8 @@ public SavestatePaths loadSavestate(int index, boolean changeIndex) throws Loads SavestatePaths out = SavestatePaths.of(currentSavestate.clone(), sourceDir, targetDir); + trackerfile.increaseLoadstateCount(); + if (!changeIndex) currentSavestate.index = savedIndex; diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/files/SavestateTrackerFile.java b/src/main/java/com/minecrafttas/tasmod/savestates/files/SavestateTrackerFile.java index bac408d6..53cf1b4f 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/files/SavestateTrackerFile.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/files/SavestateTrackerFile.java @@ -1,6 +1,5 @@ package com.minecrafttas.tasmod.savestates.files; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -16,7 +15,7 @@ public class SavestateTrackerFile extends AbstractDataFile { // This shouldn't matter... static is fine! - public SavestateTrackerFile(Path saveLocation) throws IOException { + public SavestateTrackerFile(Path saveLocation) { super(saveLocation, "savestate tracker", "This file was generated by TASmod/LoTAS and diplays info about the usage of savestates"); if (!Files.exists(saveLocation)) { @@ -37,7 +36,7 @@ private void setSavestateCount(int savestatecount) { setCount("Total Savestates", savestatecount); } - public void increaseSaveStateCount() { + public void increaseSavestateCount() { setSavestateCount(getSavestateCount() + 1); save(); } From bea92a9976c10dec2297f9e6e1977a20776f50bd Mon Sep 17 00:00:00 2001 From: Scribble Date: Thu, 9 Oct 2025 21:53:28 +0200 Subject: [PATCH 08/19] [Common] Fix TASmod logger being used instead of the MCTCommon one --- .../minecrafttas/mctcommon/registry/AbstractRegistry.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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()); } } From b905d23e2c1dd6a193fb717d72151e66634b8925 Mon Sep 17 00:00:00 2001 From: Scribble Date: Thu, 9 Oct 2025 21:53:28 +0200 Subject: [PATCH 09/19] [Commands] Updating /folder savestates to open the correct savestate folder --- .../tasmod/commands/client/CommandFolder.java | 17 ++++++++++++++++- .../savestates/SavestateHandlerServer.java | 4 ++++ .../tasmod/savestates/SavestateIndexer.java | 6 +++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/minecrafttas/tasmod/commands/client/CommandFolder.java b/src/main/java/com/minecrafttas/tasmod/commands/client/CommandFolder.java index ba844ef3..1ccdcf82 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/client/CommandFolder.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/client/CommandFolder.java @@ -4,16 +4,23 @@ import java.awt.Desktop; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import com.minecrafttas.tasmod.TASmod; import com.minecrafttas.tasmod.TASmodClient; +import net.minecraft.client.Minecraft; import net.minecraft.command.CommandException; import net.minecraft.command.ICommandSender; import net.minecraft.server.MinecraftServer; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.ChatType; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextFormatting; public class CommandFolder extends ClientCommandBase { @@ -67,8 +74,16 @@ private void openTASFolder() { private void openSavestates() { Path file = TASmodClient.savestatedirectory; + if (TASmod.getServerInstance() != null) { + file = TASmod.savestateHandlerServer.getCurrentSavestateDir(); + } + + if (!Files.exists(file)) { + Minecraft.getMinecraft().ingameGUI.addChatMessage(ChatType.CHAT, new TextComponentString("Can't open savestates, as the directory doesn't exist").setStyle(new Style().setColor(TextFormatting.RED))); + return; + } + try { - TASmodClient.createSavestatesDir(); Desktop.getDesktop().open(file.toFile()); } catch (IOException e) { LOGGER.error("Something went wrong while opening ", file); diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index d0f3dbae..69d9ab9a 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -551,4 +551,8 @@ public List getSavestateInfo(int index, int amount) public int size() { return indexer.size(); } + + public Path getCurrentSavestateDir() { + return indexer.getCurrentSavestateDir(); + } } diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java index 381e9afa..2e473b34 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java @@ -43,7 +43,7 @@ public class SavestateIndexer { private final Path currentSavestateDir; private final LinkedHashMap savestateList; private Savestate currentSavestate; - private SavestateTrackerFile trackerfile; + private final SavestateTrackerFile trackerfile; public static final Path savestateDataDir = Paths.get("tas"); private static final Path savestateDatPath = savestateDataDir.resolve("savestate.json"); @@ -577,4 +577,8 @@ public String toString() { return super.toString().toLowerCase(); } } + + public Path getCurrentSavestateDir() { + return currentSavestateDir; + } } From a4ee2a4353ed4347c219147042c8200d87cd3259 Mon Sep 17 00:00:00 2001 From: Scribble Date: Thu, 9 Oct 2025 22:44:14 +0200 Subject: [PATCH 10/19] [Savestates] Add documentation to indexer --- .../tasmod/savestates/SavestateIndexer.java | 137 ++++++++++++++++-- 1 file changed, 128 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java index 2e473b34..0f966e63 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java @@ -36,18 +36,58 @@ */ public class SavestateIndexer { + /** + * The logger + */ private final Logger logger; - private final Path savestateBaseDirectory; + + /** + * The Minecraft saves dir, usually .minecraft/saves + */ private Path savesDir; + /** + * The base directory of savestates, usually .minecraft/saves/savestates + */ + private final Path savestateBaseDirectory; + /** + * The name of the world that is currently open + */ private final String worldname; + /** + * The name of the current savestate dir, usually .minecraft/saves/savestates/worldname-Savestates + */ private final Path currentSavestateDir; + + /** + * The list of savestates + */ private final LinkedHashMap savestateList; + /** + * The current savestate that is loaded + */ private Savestate currentSavestate; - private final SavestateTrackerFile trackerfile; + /** + * The data directory within a savestate, usually .minecraft/saves/savestates/worldname-Savestates/worldname-Savestate1/tas + */ public static final Path savestateDataDir = Paths.get("tas"); - private static final Path savestateDatPath = savestateDataDir.resolve("savestate.json"); + /** + * The savestate file within a savestate, usually .minecraft/saves/savestates/worldname-Savestates/worldname-Savestate1/tas/savestate.json + */ + private static final Path savestateFilePath = savestateDataDir.resolve("savestate.json"); + + /** + * The file keeping a tally of total savestates and loadstates + */ + private final SavestateTrackerFile trackerfile; + /** + * Creates a new savestate indexer + * @param logger The logger used for logging + * @param savesDir The Minecraft saves directory + * @param savestateBaseDirectory The base directory of savestates + * @param worldname The name of the world that is currently open + */ public SavestateIndexer(Logger logger, Path savesDir, Path savestateBaseDirectory, String worldname) { this.logger = logger; this.savestateBaseDirectory = savestateBaseDirectory; @@ -58,7 +98,7 @@ public SavestateIndexer(Logger logger, Path savesDir, Path savestateBaseDirector savestateList = new LinkedHashMap<>(); createSavestateDir(); - Path savestateDat = savesDir.resolve(worldname).resolve(savestateDatPath); + Path savestateDat = savesDir.resolve(worldname).resolve(savestateFilePath); if (Files.exists(savestateDat)) { currentSavestate = new Savestate(savestateDat, null); currentSavestate.load(); @@ -68,6 +108,9 @@ public SavestateIndexer(Logger logger, Path savesDir, Path savestateBaseDirector reload(); } + /** + * Creates the directories leading to {@link #currentSavestateDir} + */ private void createSavestateDir() { try { Files.createDirectories(currentSavestateDir); @@ -76,10 +119,22 @@ private void createSavestateDir() { } } + /** + * Create a new savestate + * @param index The index to save + * @return The {@link SavestatePaths} + */ public SavestatePaths createSavestate(int index) { return createSavestate(index, null, true); } + /** + * Creates a new savestate with parameters + * @param index The index to save + * @param name The name of the savestate + * @param changeIndex True if the index should be changed. + * @return The {@link SavestatePaths} + */ public SavestatePaths createSavestate(int index, String name, boolean changeIndex) { logger.trace("Creating a savestate in indexer"); if (index < 0) { @@ -104,7 +159,7 @@ public SavestatePaths createSavestate(int index, String name, boolean changeInde Path sourceDir = savesDir.resolve(worldname); Path targetDir = currentSavestateDir.resolve(worldname + "-Savestate" + index); - Savestate newSavestate = currentSavestate.clone(targetDir.resolve(savestateDatPath), targetDir); + Savestate newSavestate = currentSavestate.clone(targetDir.resolve(savestateFilePath), targetDir); trackerfile.increaseSavestateCount(); @@ -114,6 +169,13 @@ public SavestatePaths createSavestate(int index, String name, boolean changeInde return SavestatePaths.of(newSavestate.clone(), sourceDir, targetDir); } + /** + * Loads a savestate + * @param index The index to load + * @param changeIndex True if the index should be changed. + * @return The {@link SavestatePaths} + * @throws LoadstateException If it can't find the savestate + */ public SavestatePaths loadSavestate(int index, boolean changeIndex) throws LoadstateException { logger.trace("Loading a savestate in indexer"); if (index < 0) { @@ -131,7 +193,7 @@ public SavestatePaths loadSavestate(int index, boolean changeIndex) throws Loads } int savedIndex = currentSavestate.index; - this.currentSavestate = savestateToLoad.clone(savesDir.resolve(worldname).resolve(savestateDatPath), savesDir.resolve(worldname)); + this.currentSavestate = savestateToLoad.clone(savesDir.resolve(worldname).resolve(savestateFilePath), savesDir.resolve(worldname)); Path sourceDir = savestateToLoad.getFolder(); Path targetDir = savesDir.resolve(worldname); @@ -151,6 +213,13 @@ public SavestatePaths loadSavestate(int index, boolean changeIndex) throws Loads return out; } + /** + * Renames a savestate + * @param index The index to rename + * @param name The new name + * @return The {@link SavestatePaths} + * @throws SavestateException If the savestate doesn't exist or it can't be renamed + */ public SavestatePaths renameSavestate(int index, String name) throws SavestateException { Savestate savestateToRename = savestateList.get(index); @@ -170,6 +239,12 @@ public SavestatePaths renameSavestate(int index, String name) throws SavestateEx return SavestatePaths.of(savestateToRename, null, null); } + /** + * Deletes a savestate + * @param index The index to delete + * @return The {@link SavestatePaths} + * @throws SavestateDeleteException If the savestate doesn't exist + */ public SavestatePaths deleteSavestate(int index) throws SavestateDeleteException { logger.trace("Deleting savestate {}", index); @@ -190,6 +265,13 @@ public SavestatePaths deleteSavestate(int index) throws SavestateDeleteException return out; } + /** + * Deletes multiple savestates + * @param from The starting savestates + * @param to The end of the savestates (inclusive) + * @param onDelete Runnable that is run every time a savestate is deleted + * @param onError Runnable that is run ever time a savestate fails to be deleted + */ public void deleteMultipleSavestates(int from, int to, DeletionRunnable onDelete, ErrorRunnable onError) { if (from >= to) { onError.run(new SavestateDeleteException("Can't delete amounts that are negative or 0")); @@ -204,6 +286,9 @@ public void deleteMultipleSavestates(int from, int to, DeletionRunnable onDelete } } + /** + * Sorts the savestate list by key to keep an order + */ private void sortSavestateList() { LinkedHashMap copy = new LinkedHashMap<>(); //@formatter:off @@ -215,6 +300,9 @@ private void sortSavestateList() { savestateList.putAll(copy); } + /** + * Reloads the {@link #savestateList} from the disk + */ public void reload() { logger.trace("Reloading savestate indexes"); savestateList.clear(); @@ -242,7 +330,7 @@ public void run() { Pattern backupIndexPattern = Pattern.compile("-Savestate(\\d+)$"); pathSet.forEach(path -> { - Path savestateDat = path.resolve(savestateDatPath); + Path savestateDat = path.resolve(savestateFilePath); Savestate savestate = null; @@ -278,10 +366,16 @@ public void run() { t.run(); } + /** + * @return The indexes from the {@link #savestateList} + */ public Set getIndexList() { return savestateList.keySet(); } + /** + * @return The savestate list + */ public List getSavestateList() { return getSavestateList(currentSavestate.index); } @@ -308,10 +402,18 @@ public List getSavestateList(int center, int amount) { return out; } + /** + * @return The size of the {@link #savestateList}t + */ public int size() { return savestateList.size(); } + /** + * Finds the latest index of a savestate + * @param start The starting index + * @return The latest index + */ public int findLatestIndex(int start) { if (savestateList.containsKey(start)) return start; @@ -324,6 +426,9 @@ public int findLatestIndex(int start) { return 0; } + /** + * @return The {@link #currentSavestate} + */ public Savestate getCurrentSavestate() { return currentSavestate; } @@ -400,7 +505,7 @@ public void load() { if (loadedIndex != null) this.index = Integer.parseInt(loadedIndex); } catch (Exception e) { - logger.error("Can't parse '{}' in {}", Options.INDEX.toString(), currentSavestateDir.resolve(savestateDatPath)); + logger.error("Can't parse '{}' in {}", Options.INDEX.toString(), currentSavestateDir.resolve(savestateFilePath)); logger.catching(e); } this.name = properties.getProperty(Options.NAME.toString()); @@ -409,7 +514,7 @@ public void load() { if (loadedDate != null) this.date = parseDate(loadedDate); } catch (Exception e) { - logger.error("Can't parse '{}' in {}", Options.DATE.toString(), currentSavestateDir.resolve(savestateDatPath)); + logger.error("Can't parse '{}' in {}", Options.DATE.toString(), currentSavestateDir.resolve(savestateFilePath)); logger.catching(e); } } @@ -434,6 +539,11 @@ private Date parseDate(String dateString) throws Exception { } } + /** + * A savestate failing to save + * + * @author Scribble + */ public class FailedSavestate extends Savestate { private final Throwable t; @@ -516,6 +626,11 @@ public static SavestatePaths of(Savestate savestate, Path sourceFolder, Path tar } } + /** + * Copies a folder recursively. Replaces existing files + * @param src The source folder to copy + * @param dest The destination + */ public static void copyFolder(Path src, Path dest) { try { Files.walk(src).forEach(s -> { @@ -536,6 +651,10 @@ public static void copyFolder(Path src, Path dest) { } } + /** + * Deletes a folder recursively + * @param toDelete The folder to delete + */ public static void deleteFolder(Path toDelete) { try { Files.walk(toDelete).forEach(s -> { From 3350160a4317c0db9978aeeaa1dda926b13cbdb6 Mon Sep 17 00:00:00 2001 From: Scribble Date: Tue, 14 Oct 2025 21:34:00 +0200 Subject: [PATCH 11/19] [Savestates] Update savestate command pt.1 - Update log4j config to make trace messages purple - Add savestate saving guis --- .../exception/WrongSideException.java | 2 - .../interfaces/ClientPacketHandler.java | 9 + .../java/com/minecrafttas/tasmod/TASmod.java | 2 + .../com/minecrafttas/tasmod/TASmodClient.java | 2 + .../tasmod/commands/CommandFullPlay.java | 3 +- .../tasmod/commands/CommandFullRecord.java | 3 +- .../commands/CommandRestartAndPlay.java | 3 +- .../tasmod/commands/CommandSavestate.java | 500 ++++++++++++------ .../tasmod/mixin/MixinMinecraftServer.java | 2 +- .../savestates/MixinNetHandlerPlayServer.java | 2 +- .../tasmod/registries/TASmodKeybinds.java | 3 + .../tasmod/registries/TASmodPackets.java | 25 +- .../savestates/SavestateHandlerClient.java | 14 +- .../savestates/SavestateHandlerServer.java | 82 ++- .../savestates/SavestateHandlerServerOld.java | 12 +- .../tasmod/savestates/SavestateIndexer.java | 3 +- .../tasmod/savestates/gui/GuiSavestate.java | 29 + .../savestates/gui/GuiSavestateDone.java | 34 ++ .../gui/GuiSavestateLoadingScreen.java | 31 -- .../savestates/gui/GuiSavestateRename.java | 90 ++++ .../gui/GuiSavestateSavingScreen.java | 30 -- .../handlers/SavestateGuiHandlerClient.java | 110 ++++ .../handlers/SavestateGuiHandlerServer.java | 40 ++ .../storage/builtin/ClientMotionStorage.java | 4 - .../minecrafttas/tasmod/util/Component.java | 76 +++ .../resources/assets/tasmod/lang/en_us.json | 37 +- src/main/resources/log4j.xml | 2 +- 27 files changed, 856 insertions(+), 294 deletions(-) create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestate.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateDone.java delete mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateLoadingScreen.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateRename.java delete mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateSavingScreen.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateGuiHandlerClient.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateGuiHandlerServer.java create mode 100644 src/main/java/com/minecrafttas/tasmod/util/Component.java 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/tasmod/TASmod.java b/src/main/java/com/minecrafttas/tasmod/TASmod.java index e45358c9..d53bd2d5 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmod.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmod.java @@ -30,6 +30,7 @@ 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.ClientMotionStorage; import com.minecrafttas.tasmod.tickratechanger.TickrateChangerServer; @@ -120,6 +121,7 @@ public void onInitialize() { PacketHandlerRegistry.register(startPositionMetadataExtension); PacketHandlerRegistry.register(tabCompletionUtils); PacketHandlerRegistry.register(commandFileCommand); + PacketHandlerRegistry.register(new SavestateGuiHandlerServer()); PacketHandlerRegistry.register(motionStorage); SavestateResourcePackHandler resourcepackHandler = new SavestateResourcePackHandler(); PacketHandlerRegistry.register(resourcepackHandler); diff --git a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java index 68517cff..94328e48 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java @@ -40,6 +40,7 @@ import com.minecrafttas.tasmod.registries.TASmodKeybinds; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.SavestateHandlerClient; +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; @@ -173,6 +174,7 @@ private void registerNetworkPacketHandlers() { PacketHandlerRegistry.register(tickratechanger); PacketHandlerRegistry.register(savestateHandlerClient); PacketHandlerRegistry.register(new SavestatePlayerHandlerClient()); + PacketHandlerRegistry.register(new SavestateGuiHandlerClient()); } private void registerEventListeners() { diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandFullPlay.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandFullPlay.java index f38116e9..7168e7c2 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandFullPlay.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandFullPlay.java @@ -5,7 +5,6 @@ import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateFlags; -import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; import com.minecrafttas.tasmod.savestates.exceptions.LoadstateException; import net.minecraft.command.CommandBase; @@ -39,7 +38,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 28bbc7b4..fd93f7d5 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandFullRecord.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandFullRecord.java @@ -5,7 +5,6 @@ import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateFlags; -import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; import net.minecraft.command.CommandBase; @@ -39,7 +38,7 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args 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 7983e5f7..97bc4a28 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandRestartAndPlay.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandRestartAndPlay.java @@ -10,7 +10,6 @@ import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateFlags; -import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; import com.minecrafttas.tasmod.savestates.exceptions.LoadstateException; import net.minecraft.client.Minecraft; @@ -55,7 +54,7 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args 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 4813dec4..6487cc28 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java @@ -1,23 +1,28 @@ package com.minecrafttas.tasmod.commands; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +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.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.LoggerMarkers; 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.TextComponentString; -import net.minecraft.util.text.TextFormatting; -import net.minecraft.util.text.event.ClickEvent; public class CommandSavestate extends CommandBase { @@ -28,7 +33,7 @@ public String getName() { @Override public String getUsage(ICommandSender sender) { - return "/savestate [index]"; + return "/savestate save|load|delete|reload|rename|info|import"; } @Override @@ -36,237 +41,382 @@ 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[] {}); - } - } else if ("deletDis".equals(args[0])) { - if (args.length == 3) { - deleteMultiple(args); - } - } 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); - } 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; } + String third = getRestArgsAsString(2, args); + saveIndexName(sender, index, third); + return; + + } else { + second = getRestArgsAsString(1, args); + saveName(sender, second); + return; } } - } - private void sendHelp(ICommandSender sender) throws CommandException { - sendHelp(sender, 1); - } + 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; + } - private void sendHelp(ICommandSender sender, int i) throws CommandException { - int currentIndex = TASmod.savestateHandlerServer.getCurrentIndex(); - if (i > 3) { - throw new CommandException("This help page doesn't exist (yet?)", new Object[] {}); + String fourth = args[3]; + if ("force".equals(fourth)) { + deleteDis(sender, indexFrom, indexTo); + return; + } } - 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)))); + + else if ("reload".equals(first)) { + reload(sender); return; } - 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); + + else if ("rename".equals(first)) { + int index = processIndex(args[2]); + String name = getRestArgsAsString(3, 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; + } + + 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 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])) { - List list = new ArrayList<>(); - for (Savestate savestate : TASmod.savestateHandlerServer.getSavestateInfo()) { - list.add(Integer.toString(savestate.getIndex())); + 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<>(); } - sender.sendMessage(new TextComponentString("Available indexes: " + TextFormatting.AQUA + String.join(",", list))); + } - return super.getTabCompletions(server, sender, args, targetPos); + + 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 { + return getListOfStringsMatchingLastWord(args, "all"); + } + } + } + return new ArrayList<>(); } - // ====================================================================== + private void info(ICommandSender sender) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command Info"); + + } + + private void infoIndex(ICommandSender sender, int index) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command InfoIndex {}", index); + } + + private void infoIndexAmount(ICommandSender sender, int index, int amount) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command InfoIndexAmount {}|{}", index, amount); + } + + private void infoAll(ICommandSender sender) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command InfoAll"); + } + + private void saveNew(ICommandSender sender) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command SaveNew"); + + SavestateCallback doneSavingCallback = (paths -> { + if (sender instanceof EntityPlayerMP) { + try { + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_RENAME_SCREEN).writeInt(paths.getSavestate().getIndex()).writeString(sender.getName())); + } catch (Exception e) { + TASmod.LOGGER.catching(e); + } + } + }); - private void saveLatest() throws CommandException { try { - TASmod.savestateHandlerServer.saveState(null); + TASmod.savestateHandlerServer.saveState(doneSavingCallback); } catch (SavestateException e) { - throw new CommandException(e.getMessage(), new Object[] {}); - } catch (Exception e) { - e.printStackTrace(); - throw new CommandException(e.getMessage(), new Object[] {}); - } finally { - TASmod.savestateHandlerServer.state = SavestateState.NONE; + TASmod.LOGGER.catching(e); } } - private void saveWithIndex(String[] args) throws CommandException { + private void saveIndex(ICommandSender sender, int index) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command SaveIndex {}", index); try { - int indexToSave = processIndex(args[1]); - if (indexToSave <= 0) { // Disallow to save on Savestate 0 - indexToSave = -1; - } - TASmod.savestateHandlerServer.saveState(indexToSave, null); + TASmod.savestateHandlerServer.saveState(index, null); } catch (SavestateException e) { - throw new CommandException(e.getMessage(), new Object[] {}); - } catch (Exception e) { - e.printStackTrace(); - throw new CommandException(e.getMessage(), new Object[] {}); - } finally { - TASmod.savestateHandlerServer.state = SavestateState.NONE; + TASmod.LOGGER.catching(e); } } - private void loadLatest() throws CommandException { + private void saveIndexName(ICommandSender sender, int index, String name) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command SaveNameIndex {}|{}", index, name); + try { + TASmod.savestateHandlerServer.saveState(index, name, null); + } catch (SavestateException e) { + TASmod.LOGGER.catching(e); + } + } + + private void saveName(ICommandSender sender, String name) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command SaveName {}", name); + try { + TASmod.savestateHandlerServer.saveState(name, null); + } catch (SavestateException e) { + TASmod.LOGGER.catching(e); + } + } + + private void loadRecent(ICommandSender sender) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command LoadRecent"); try { TASmod.savestateHandlerServer.loadState(null); } catch (LoadstateException e) { - throw new CommandException(e.getMessage(), new Object[] {}); - } catch (Exception e) { - e.printStackTrace(); - throw new CommandException(e.getMessage(), new Object[] {}); - } finally { - TASmod.savestateHandlerServer.state = SavestateState.NONE; + TASmod.LOGGER.catching(e); } } - private void loadLatest(String[] args) throws CommandException { + private void loadIndex(ICommandSender sender, int index) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command LoadIndex {}", index); try { - TASmod.savestateHandlerServer.loadState(processIndex(args[1]), null); + TASmod.savestateHandlerServer.loadState(index, null); } catch (LoadstateException e) { - throw new CommandException(e.getMessage(), new Object[] {}); - } catch (Exception e) { - e.printStackTrace(); - throw new CommandException(e.getMessage(), new Object[] {}); - } finally { - TASmod.savestateHandlerServer.state = SavestateState.NONE; + TASmod.LOGGER.catching(e); } } - private void delete(String[] args) throws CommandException { - int arg1 = processIndex(args[1]); + private void delete(ICommandSender sender, int index) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command Delete {}", index); try { - TASmod.savestateHandlerServer.deleteSavestate(arg1); + TASmod.savestateHandlerServer.deleteSavestate(index); } catch (SavestateDeleteException e) { - throw new CommandException(e.getMessage(), new Object[] {}); + e.printStackTrace(); } } - private void deleteMultiple(String[] args) throws CommandException { + private void deleteMore(ICommandSender sender, int indexFrom, int indexTo) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command DeleteMore {}|{}", indexFrom, indexTo); + deleteDis(sender, indexFrom, indexTo); + } + + private void deleteDis(ICommandSender sender, int indexFrom, int indexTo) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command DeleteDis {}|{}", indexFrom, indexTo); try { - TASmod.savestateHandlerServer.deleteSavestate(processIndex(args[1]), processIndex(args[2]), null, null); + TASmod.savestateHandlerServer.deleteSavestate(indexFrom, indexTo, null, null); } catch (SavestateDeleteException e) { - throw new CommandException(e.getMessage(), new Object[] {}); + e.printStackTrace(); } } + private void reload(ICommandSender sender) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command Reload"); + TASmod.savestateHandlerServer.reload(); + } + + private void rename(ICommandSender sender, int index, String name) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command Rename {}|{}", index, name); + TASmod.savestateHandlerServer.rename(index, name); + } + + private void importing(ICommandSender sender) { + TASmod.LOGGER.trace(LoggerMarkers.Savestate, "Command Import"); + + } // ====================================================================== private int processIndex(String arg) throws CommandException { if ("~".equals(arg)) { return TASmod.savestateHandlerServer.getCurrentIndex(); - } else if (arg.matches("~-?\\d")) { + } else if (arg.matches("~-?\\d+")) { arg = arg.replace("~", ""); - int i = Integer.parseInt(arg); + int i = parseInt(arg); return TASmod.savestateHandlerServer.getCurrentIndex() + i; } else { int i = 0; - try { - i = Integer.parseInt(arg); - } catch (NumberFormatException e) { - throw new CommandException("The specified index is not a number: %s", arg); - } + i = parseInt(arg); return i; } } + + /** + * Utility method to check if the string is numeric + * + * @param string The string to search + * @return True if the string is numeric + */ + private boolean isNumeric(String string) { + return Pattern.matches("~|((~-?)?\\d+)", string); + } + + private String getRestArgsAsString(int start, String[] args) { + return String.join(" ", Arrays.copyOfRange(args, start, args.length)); + } + + private List getIndexes(String[] args) { + List info = TASmod.savestateHandlerServer.getSavestateInfo(); + List out = new ArrayList<>(); + info.forEach(save -> { + out.add(Integer.toString(save.getIndex())); + }); + return getListOfStringsMatchingLastWord(args, out); + } } diff --git a/src/main/java/com/minecrafttas/tasmod/mixin/MixinMinecraftServer.java b/src/main/java/com/minecrafttas/tasmod/mixin/MixinMinecraftServer.java index 3ff76f6e..5a57d75d 100644 --- a/src/main/java/com/minecrafttas/tasmod/mixin/MixinMinecraftServer.java +++ b/src/main/java/com/minecrafttas/tasmod/mixin/MixinMinecraftServer.java @@ -110,7 +110,7 @@ public void redirectThreadSleep(long msToTick) { TASmod.gameLoopSchedulerServer.runAllTasks(); - boolean stopTaskQueue = TASmod.savestateHandlerServer != null && TASmod.savestateHandlerServer.state == SavestateState.LOADING; + boolean stopTaskQueue = TASmod.savestateHandlerServer != null && TASmod.savestateHandlerServer.getState() == SavestateState.LOADING; if (!stopTaskQueue) { synchronized (this.futureTaskQueue) { while (!this.futureTaskQueue.isEmpty()) { diff --git a/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinNetHandlerPlayServer.java b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinNetHandlerPlayServer.java index d6a4a802..b11527a5 100644 --- a/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinNetHandlerPlayServer.java +++ b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinNetHandlerPlayServer.java @@ -18,6 +18,6 @@ public class MixinNetHandlerPlayServer { */ @Redirect(method = "processPlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/EntityPlayerMP;isInvulnerableDimensionChange()Z")) public boolean redirect_processPlayer(EntityPlayerMP parentIn) { - return !parentIn.isInvulnerableDimensionChange() && TASmod.savestateHandlerServer.state != SavestateState.LOADING; + return !parentIn.isInvulnerableDimensionChange() && TASmod.savestateHandlerServer.getState() != SavestateState.LOADING; } } diff --git a/src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java b/src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java index 35c5458e..f78821c2 100644 --- a/src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java +++ b/src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java @@ -7,10 +7,12 @@ import com.minecrafttas.tasmod.TASmodClient; import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; +import com.minecrafttas.tasmod.savestates.gui.GuiSavestateRename; import com.minecrafttas.tasmod.virtual.VirtualKeybindings; import net.minecraft.client.Minecraft; import net.minecraft.client.settings.KeyBinding; +import net.minecraft.util.text.TextComponentString; public enum TASmodKeybinds { TICKRATE_0("Tickrate 0 Key", "TASmod", Keyboard.KEY_F8, () -> TASmodClient.tickratechanger.togglePause(), VirtualKeybindings::isKeyDown), @@ -45,6 +47,7 @@ public enum TASmodKeybinds { TASmodClient.virtual.CAMERA_ANGLE.updateNextCameraAngle(0, 45); }), TEST1("Various Testing", "TASmod", Keyboard.KEY_F12, () -> { + Minecraft.getMinecraft().displayGuiScreen(new GuiSavestateRename(new TextComponentString("Test"), 1)); }, VirtualKeybindings::isKeyDown), TEST2("Various Testing2", "TASmod", Keyboard.KEY_F7, () -> { }, VirtualKeybindings::isKeyDown); diff --git a/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java b/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java index 57671a2f..88963a9e 100644 --- a/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java +++ b/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java @@ -7,6 +7,9 @@ import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; import com.minecrafttas.tasmod.playback.filecommands.PlaybackFileCommand.PlaybackFileCommandExtension; import com.minecrafttas.tasmod.playback.tasfile.flavor.SerialiserFlavorBase; +import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; +import com.minecrafttas.tasmod.savestates.gui.GuiSavestate; +import com.minecrafttas.tasmod.savestates.gui.GuiSavestateDone; import com.minecrafttas.tasmod.savestates.storage.builtin.ClientMotionStorage.MotionData; import com.minecrafttas.tasmod.tickratechanger.TickrateChangerServer.TickratePauseState; import com.minecrafttas.tasmod.util.Ducks.ScoreboardDuck; @@ -63,11 +66,27 @@ public enum TASmodPackets implements PacketID { */ SAVESTATE_LOAD, /** - *

Opens or closes the savestate screen on the client + *

Opens the {@link GuiSavestate} screen on the client *

SIDE: Client
- * ARGS: none + * ARGS: int The {@link SavestateState} for displaying the correct message + */ + SAVESTATE_LOADING_SCREEN, + /** + *

Opens the {@link GuiSavestateDone} screen on the client + *

SIDE: Client
+ * ARGS:
+ * int The index of the savestate to display in the gui
+ * String The name of the savestate to display in the gui
+ */ + SAVESTATE_DONE_SCREEN, + /** + *

Opens the {@link GuiSavestateDone} screen on the client + *

SIDE: BOTH
+ * Server->Client int The index of the savestate to display in the gui
+ * Client->Server int The index of the savestate to rename
+ * Client->Server String The name of the savestate to be renamed
*/ - SAVESTATE_SCREEN, + SAVESTATE_RENAME_SCREEN, /** *

Sends the playerdata of the player to the client, inluding the motion *

SIDE: Client
diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java index 2d775d0c..57ef950c 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java @@ -25,7 +25,6 @@ import com.minecrafttas.tasmod.registries.TASmodAPIRegistry; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; -import com.minecrafttas.tasmod.savestates.gui.GuiSavestateSavingScreen; import com.minecrafttas.tasmod.util.Ducks.ChunkProviderDuck; import com.minecrafttas.tasmod.util.Ducks.WorldClientDuck; import com.minecrafttas.tasmod.util.LoggerMarkers; @@ -294,7 +293,6 @@ public PacketID[] getAcceptedPacketIDs() { //@formatter:off TASmodPackets.SAVESTATE_SAVE, TASmodPackets.SAVESTATE_LOAD, - TASmodPackets.SAVESTATE_SCREEN, TASmodPackets.SAVESTATE_UNLOAD_CHUNKS }; //@formatter:on } @@ -307,7 +305,7 @@ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws switch (packet) { case SAVESTATE_SAVE: String savestateName = TASmodBufferBuilder.readString(buf); - Minecraft.getMinecraft().addScheduledTask(() -> { + mc.addScheduledTask(() -> { // Create client savestate try { @@ -322,7 +320,7 @@ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws case SAVESTATE_LOAD: // Load client savestate String loadstateName = TASmodBufferBuilder.readString(buf); - Minecraft.getMinecraft().addScheduledTask(() -> { + mc.addScheduledTask(() -> { try { SavestateHandlerClient.loadstate(loadstateName); } catch (IOException e) { @@ -332,15 +330,9 @@ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws } }); break; - case SAVESTATE_SCREEN: - // Open Savestate screen - Minecraft.getMinecraft().addScheduledTask(() -> { - mc.displayGuiScreen(new GuiSavestateSavingScreen()); - }); - break; case SAVESTATE_UNLOAD_CHUNKS: - Minecraft.getMinecraft().addScheduledTask(() -> { + mc.addScheduledTask(() -> { SavestateHandlerClient.unloadAllClientChunks(); }); break; diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index 69d9ab9a..b86c9734 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -63,7 +63,7 @@ public class SavestateHandlerServer implements ServerPacketHandler { private final MinecraftServer server; - public SavestateState state = SavestateState.NONE; // TODO Make private + private SavestateState state = SavestateState.NONE; // TODO Make private /** * Manages enumeration and location of savestates on the file system @@ -91,19 +91,19 @@ public SavestateHandlerServer(MinecraftServer server, Logger logger) { createIndexer(server); } - public void saveState(SavestateCallback cb, SavestateFlags... options) throws Exception { + public void saveState(SavestateCallback cb, SavestateFlags... options) throws SavestateException { saveState(-1, null, cb, options); } - public void saveState(int index, SavestateCallback cb, SavestateFlags... flags) throws Exception { + public void saveState(int index, SavestateCallback cb, SavestateFlags... flags) throws SavestateException { saveState(index, null, cb, flags); } - public void saveState(String name, SavestateCallback cb, SavestateFlags... flags) throws Exception { + public void saveState(String name, SavestateCallback cb, SavestateFlags... flags) throws SavestateException { saveState(-1, name, cb, flags); } - public void saveState(int index, String name, SavestateCallback cb, SavestateFlags... flags) throws SavestateException, IOException { + public void saveState(int index, String name, SavestateCallback cb, SavestateFlags... flags) throws SavestateException { if (logger.isTraceEnabled()) { logger.trace(LoggerMarkers.Savestate, "SAVING a savestate with index {}. Flags: ", index, Arrays.stream(flags).map(Enum::toString).collect(Collectors.joining(","))); } else { @@ -119,9 +119,9 @@ public void saveState(int index, String name, SavestateCallback cb, SavestateFla // Open GuiSavestateScreen try { - TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_SCREEN)); + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_LOADING_SCREEN).writeInt(SavestateState.SAVING.ordinal())); } catch (Exception e) { - e.printStackTrace(); + logger.catching(e); } // Lock savestating and loadstating @@ -177,7 +177,7 @@ public void saveState(int index, String name, SavestateCallback cb, SavestateFla copyFolder(sourceFolder, targetFolder); // Send a notification that the savestate has been loaded - server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + indexToSave + " saved")); +// server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + indexToSave + " saved")); try { // Close GuiSavestateScreen @@ -190,6 +190,9 @@ public void saveState(int index, String name, SavestateCallback cb, SavestateFla TASmod.tickratechanger.pauseGame(false); } + if (cb != null) + cb.invoke(paths); + // Unlock savestating state = SavestateState.NONE; } @@ -222,6 +225,13 @@ public void loadState(int index, String name, SavestateCallback cb, SavestateFla // Lock savestating and loadstating state = SavestateState.LOADING; + // Open GuiSavestateScreen + try { + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_LOADING_SCREEN).writeInt(SavestateState.LOADING.ordinal())); + } catch (Exception e) { + logger.catching(e); + } + // Enable tickrate 0 TASmod.tickratechanger.pauseGame(true); @@ -288,13 +298,6 @@ public void loadState(int index, String name, SavestateCallback cb, SavestateFla // Refresh server resourcepacks on the client SavestateResourcePackHandler.refreshServerResourcepack(server); - // Incrementing info file -// SavestateTrackerFile tracker = new SavestateTrackerFile(savestateDirectory.resolve(worldname + "-info.txt")); // TODO Bring back the trackerfile! -// tracker.increaseLoadstateCount(); - - // Send a notification that the savestate has been loaded - server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + indexToLoad + " loaded")); - // Add players to the chunk worldHandler.addPlayersToServerChunks(); @@ -310,6 +313,9 @@ public void loadState(int index, String name, SavestateCallback cb, SavestateFla TASmod.tickratechanger.pauseGame(false); } + if (cb != null) + cb.invoke(paths); + // Unlock savestating state = SavestateState.NONE; @@ -360,7 +366,8 @@ public void deleteSavestate(int from, int to, SavestateCallback cb, ErrorRunnabl DeletionRunnable onDelete = (paths) -> { SavestateIndexer.deleteFolder(paths.getTargetFolder()); - cb.invoke(paths); + if (cb != null) + cb.invoke(paths); }; indexer.deleteMultipleSavestates(from, to, onDelete, err); @@ -389,7 +396,6 @@ public PacketID[] getAcceptedPacketIDs() { //@formatter:off TASmodPackets.SAVESTATE_SAVE, TASmodPackets.SAVESTATE_LOAD, - TASmodPackets.SAVESTATE_SCREEN, TASmodPackets.SAVESTATE_UNLOAD_CHUNKS //@formatter:on }; @@ -400,20 +406,32 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws // TODO Permissions TASmodPackets packet = (TASmodPackets) id; - EntityPlayerMP player = TASmod.getServerInstance().getPlayerList().getPlayerByUsername(username); + EntityPlayerMP player = server.getPlayerList().getPlayerByUsername(username); switch (packet) { case SAVESTATE_SAVE: int index = TASmodBufferBuilder.readInt(buf); + SavestateCallback cb = (paths) -> { + /* + * Opens the savestate rename screen only for the player who initiated the savestate. + * Once the player is done renaming the savestate, the screens are cleared for all players. + */ + try { + TASmod.server.sendTo(player, new TASmodBufferBuilder(TASmodPackets.SAVESTATE_RENAME_SCREEN).writeInt(paths.getSavestate().index)); + } catch (Exception e) { + LOGGER.catching(e); + } + }; + Task savestateTask = () -> { try { - saveState(index, null); + saveState(index, cb); } catch (SavestateException e) { if (player != null) player.sendMessage(new TextComponentString(TextFormatting.RED + "Failed to create a savestate: " + e.getMessage())); - LOGGER.error(LoggerMarkers.Savestate, "Failed to create a savestate"); + LOGGER.error("Failed to create a savestate"); LOGGER.catching(e); } catch (Exception e) { if (player != null) @@ -458,7 +476,6 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws TASmod.gameLoopSchedulerServer.add(loadstateTask); break; - case SAVESTATE_SCREEN: case SAVESTATE_UNLOAD_CHUNKS: throw new WrongSideException(id, Side.SERVER); default: @@ -555,4 +572,27 @@ public int size() { public Path getCurrentSavestateDir() { return indexer.getCurrentSavestateDir(); } + + public void resetState() { + state = SavestateState.NONE; + } + + public SavestateState getState() { + return state; + } + + public void rename(int index, String name) throws SavestateException { + rename(index, name, null); + } + + public void rename(int index, String name, SavestateCallback cb) throws SavestateException { + SavestatePaths paths = indexer.renameSavestate(index, name); + if (cb != null) { + cb.invoke(paths); + } + } + + public void reload() { + indexer.reload(); + } } diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServerOld.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServerOld.java index a2b9e850..f462b288 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServerOld.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServerOld.java @@ -153,11 +153,11 @@ public void saveState(int savestateIndex, boolean tickrate0, boolean changeIndex throw new SavestateException("A loadstate operation is being carried out"); } + // Open GuiSavestateScreen try { - // Open GuiSavestateScreen - TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_SCREEN)); + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_LOADING_SCREEN).writeInt(SavestateState.SAVING.ordinal())); } catch (Exception e) { - e.printStackTrace(); + logger.catching(e); } // Lock savestating and loadstating @@ -214,7 +214,7 @@ public void saveState(int savestateIndex, boolean tickrate0, boolean changeIndex // savestate inputs client TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_SAVE).writeString(getSavestateName(indexToSave))); } catch (Exception e) { - e.printStackTrace(); + logger.catching(e); } } @@ -761,7 +761,7 @@ public PacketID[] getAcceptedPacketIDs() { //@formatter:off TASmodPackets.SAVESTATE_SAVE, TASmodPackets.SAVESTATE_LOAD, - TASmodPackets.SAVESTATE_SCREEN, + TASmodPackets.SAVESTATE_LOADING_SCREEN, TASmodPackets.SAVESTATE_UNLOAD_CHUNKS //@formatter:on }; @@ -830,7 +830,7 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws TASmod.gameLoopSchedulerServer.add(loadstateTask); break; - case SAVESTATE_SCREEN: + case SAVESTATE_LOADING_SCREEN: case SAVESTATE_UNLOAD_CHUNKS: throw new WrongSideException(id, Side.SERVER); default: diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java index 0f966e63..9b511dbd 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java @@ -281,7 +281,8 @@ public void deleteMultipleSavestates(int from, int to, DeletionRunnable onDelete try { onDelete.run(deleteSavestate(i)); } catch (Exception e) { - onError.run(e); + if (onError != null) + onError.run(e); } } } diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestate.java b/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestate.java new file mode 100644 index 00000000..aefe1453 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestate.java @@ -0,0 +1,29 @@ +package com.minecrafttas.tasmod.savestates.gui; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.util.text.ITextComponent; + +public class GuiSavestate extends GuiScreen { + + private final ITextComponent msg; + + public GuiSavestate(ITextComponent msg) { + this.mc = Minecraft.getMinecraft(); + this.msg = msg; + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + this.drawDefaultBackground(); + + drawCenteredString(fontRenderer, msg.getFormattedText(), width / 2, 90, 0xFFFFFF); + + super.drawScreen(mouseX, mouseY, partialTicks); + } + + @Override + public boolean doesGuiPauseGame() { + return true; + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateDone.java b/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateDone.java new file mode 100644 index 00000000..98b245cc --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateDone.java @@ -0,0 +1,34 @@ +package com.minecrafttas.tasmod.savestates.gui; + +import net.minecraft.client.gui.GuiButton; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentTranslation; + +public class GuiSavestateDone extends GuiSavestate { + + public GuiSavestateDone(ITextComponent msg) { + super(msg); + } + + @Override + public void initGui() { + int boxWidth = 200; + buttonList.add(new GuiButton(1, width / 2 - (boxWidth / 2), height / 2 + 62, boxWidth, 20, new TextComponentTranslation("gui.tasmod.savestate.button.closegui").getFormattedText())); + } + + @Override + protected void actionPerformed(GuiButton guiButton) { + switch (guiButton.id) { + case 1: + onGuiClosed(); + break; + } + } + + @Override + protected void keyTyped(char c, int i) { + if (i == 1) { + mc.displayGuiScreen(null); + } + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateLoadingScreen.java b/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateLoadingScreen.java deleted file mode 100644 index 74f89922..00000000 --- a/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateLoadingScreen.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.minecrafttas.tasmod.savestates.gui; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.gui.ScaledResolution; -import net.minecraft.client.resources.I18n; - -public class GuiSavestateLoadingScreen extends GuiScreen { - - public GuiSavestateLoadingScreen() { - this.mc = Minecraft.getMinecraft(); - } - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - this.drawDefaultBackground(); - - ScaledResolution scaled = new ScaledResolution(Minecraft.getMinecraft()); - int width = scaled.getScaledWidth(); - int height = scaled.getScaledHeight(); - - drawCenteredString(fontRenderer, I18n.format("Loading a savestate!"), width / 2, height / 4 + 50 + -16, 0xFFFFFF); // Loading a savestate! - - super.drawScreen(mouseX, mouseY, partialTicks); - } - - @Override - public boolean doesGuiPauseGame() { - return true; - } -} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateRename.java b/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateRename.java new file mode 100644 index 00000000..d13e5168 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateRename.java @@ -0,0 +1,90 @@ +package com.minecrafttas.tasmod.savestates.gui; + +import org.lwjgl.input.Keyboard; + +import com.minecrafttas.tasmod.TASmod; +import com.minecrafttas.tasmod.TASmodClient; +import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; +import com.minecrafttas.tasmod.registries.TASmodPackets; + +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiTextField; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.util.text.TextFormatting; + +public class GuiSavestateRename extends GuiSavestate { + + private int index; + private GuiTextField renameField; + + public GuiSavestateRename(ITextComponent msg, int index) { + super(msg); + this.index = index; + } + + @Override + public void initGui() { + this.buttonList.clear(); + int boxWidth = 200; + buttonList.add(new GuiButton(1, width / 2 - (boxWidth / 2) - 1, height / 2 + 62, boxWidth + 3, 20, new TextComponentTranslation("gui.tasmod.savestate.save.rename.button").getFormattedText())); + renameField = new GuiTextField(2, fontRenderer, width / 2 - (boxWidth / 2), height / 2 + 40, boxWidth, 20); + renameField.setFocused(true); + renameField.setMaxStringLength(1000 * 19); + Keyboard.enableRepeatEvents(true); + } + + @Override + protected void actionPerformed(GuiButton guiButton) { + switch (guiButton.id) { + case 1: + renameAndExit(); + break; + } + } + + @Override + protected void keyTyped(char c, int i) { + this.renameField.textboxKeyTyped(c, i); + if (i == 28) { + renameAndExit(); + return; + } + super.keyTyped(c, i); + } + + @Override + protected void mouseClicked(int i, int j, int k) { + super.mouseClicked(i, j, k); + this.renameField.mouseClicked(i, j, k); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + super.drawScreen(mouseX, mouseY, partialTicks); + this.renameField.drawTextBox(); + if (renameField.getText().isEmpty()) + this.drawString(fontRenderer, new TextComponentString("Savestate #" + index).setStyle(new Style().setColor(TextFormatting.DARK_GRAY)).getFormattedText(), renameField.x + 3, renameField.y + 6, 0xFFFFFF); + } + + private void renameAndExit() { + try { + TASmodClient.client.send(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_RENAME_SCREEN).writeInt(index).writeString(renameField.getText())); + } catch (Exception e) { + TASmod.LOGGER.catching(e); + } + mc.displayGuiScreen(null); + } + + @Override + public void updateScreen() { + this.renameField.updateCursorCounter(); + } + + @Override + public void onGuiClosed() { + Keyboard.enableRepeatEvents(false); + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateSavingScreen.java b/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateSavingScreen.java deleted file mode 100644 index c9cfc9e4..00000000 --- a/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiSavestateSavingScreen.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.minecrafttas.tasmod.savestates.gui; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.gui.ScaledResolution; -import net.minecraft.client.resources.I18n; - -public class GuiSavestateSavingScreen extends GuiScreen { - - public GuiSavestateSavingScreen() { - this.mc = Minecraft.getMinecraft(); - } - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - this.drawDefaultBackground(); - - ScaledResolution scaled = new ScaledResolution(Minecraft.getMinecraft()); - int width = scaled.getScaledWidth(); - int height = scaled.getScaledHeight(); - - drawCenteredString(fontRenderer, I18n.format("Making a savestate, please wait!"), width / 2, height / 4 + 34 + -16, 0xFFFFFF); // Making a savestate, please wait! - super.drawScreen(mouseX, mouseY, partialTicks); - } - - @Override - public boolean doesGuiPauseGame() { - return true; - } -} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateGuiHandlerClient.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateGuiHandlerClient.java new file mode 100644 index 00000000..0470379c --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateGuiHandlerClient.java @@ -0,0 +1,110 @@ +package com.minecrafttas.tasmod.savestates.handlers; + +import static com.minecrafttas.tasmod.registries.TASmodPackets.SAVESTATE_DONE_SCREEN; +import static com.minecrafttas.tasmod.registries.TASmodPackets.SAVESTATE_LOADING_SCREEN; +import static com.minecrafttas.tasmod.registries.TASmodPackets.SAVESTATE_RENAME_SCREEN; + +import java.nio.ByteBuffer; + +import com.minecrafttas.mctcommon.networking.Client.Side; +import com.minecrafttas.mctcommon.networking.exception.PacketNotImplementedException; +import com.minecrafttas.mctcommon.networking.exception.WrongSideException; +import com.minecrafttas.mctcommon.networking.interfaces.ClientPacketHandler; +import com.minecrafttas.mctcommon.networking.interfaces.PacketID; +import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; +import com.minecrafttas.tasmod.registries.TASmodPackets; +import com.minecrafttas.tasmod.savestates.SavestateHandlerServer.SavestateState; +import com.minecrafttas.tasmod.savestates.gui.GuiSavestate; +import com.minecrafttas.tasmod.savestates.gui.GuiSavestateDone; +import com.minecrafttas.tasmod.savestates.gui.GuiSavestateRename; +import com.minecrafttas.tasmod.util.Component; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.text.TextFormatting; + +public class SavestateGuiHandlerClient implements ClientPacketHandler { + + public SavestateGuiHandlerClient() { + } + + @Override + public PacketID[] getAcceptedPacketIDs() { + //@formatter:off + return new PacketID[] { + SAVESTATE_LOADING_SCREEN, + SAVESTATE_DONE_SCREEN, + SAVESTATE_RENAME_SCREEN + }; + //@formatter:on + } + + @Override + public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception { + TASmodPackets packet = (TASmodPackets) id; + Minecraft mc = Minecraft.getMinecraft(); + + switch (packet) { + case SAVESTATE_LOADING_SCREEN: + // Open Savestate screen + SavestateState state = SavestateState.values()[TASmodBufferBuilder.readInt(buf)]; + mc.addScheduledTask(() -> { + + String msg = ""; + if (state == SavestateState.SAVING) + msg = "gui.tasmod.savestate.save.start"; + else if (state == SavestateState.LOADING) + msg = "gui.tasmod.savestate.load.start"; + else { + mc.displayGuiScreen(null); + return; + } + + mc.displayGuiScreen(new GuiSavestate(Component.translatable(msg).withStyle(TextFormatting.YELLOW).build())); + }); + break; + case SAVESTATE_RENAME_SCREEN: + int index = TASmodBufferBuilder.readInt(buf); + mc.addScheduledTask(() -> { + displayGuiRename(index); + }); + break; + case SAVESTATE_DONE_SCREEN: + index = TASmodBufferBuilder.readInt(buf); + String name = TASmodBufferBuilder.readString(buf); + mc.addScheduledTask(() -> { + displayGuiDone(index, name); + }); + break; + default: + throw new PacketNotImplementedException(packet, Side.CLIENT); + } + } + + private void displayGuiRename(int index) { + Minecraft mc = Minecraft.getMinecraft(); + //@formatter:off + mc.displayGuiScreen( + new GuiSavestateRename( + Component.translatable("gui.tasmod.savestate.save.rename", + Component.literal(Integer.toString(index)).withStyle(t->t.setColor(TextFormatting.AQUA)) + ).withStyle(t->t.setColor(TextFormatting.GREEN)).build(), + index + ) + ); + //@formatter:on + } + + private void displayGuiDone(int index, String name) { + Minecraft mc = Minecraft.getMinecraft(); + //@formatter:off + mc.displayGuiScreen( + new GuiSavestateDone( + Component.translatable("gui.tasmod.savestate.save.end", + Component.literal(name).withStyle(TextFormatting.YELLOW), + Component.literal(Integer.toString(index)).withStyle(TextFormatting.AQUA) + ).withStyle(TextFormatting.GREEN).build() + ) + ); + //@formatter:on + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateGuiHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateGuiHandlerServer.java new file mode 100644 index 00000000..d595f098 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateGuiHandlerServer.java @@ -0,0 +1,40 @@ +package com.minecrafttas.tasmod.savestates.handlers; + +import static com.minecrafttas.tasmod.registries.TASmodPackets.SAVESTATE_RENAME_SCREEN; + +import java.nio.ByteBuffer; + +import com.minecrafttas.mctcommon.networking.Client.Side; +import com.minecrafttas.mctcommon.networking.exception.PacketNotImplementedException; +import com.minecrafttas.mctcommon.networking.exception.WrongSideException; +import com.minecrafttas.mctcommon.networking.interfaces.PacketID; +import com.minecrafttas.mctcommon.networking.interfaces.ServerPacketHandler; +import com.minecrafttas.tasmod.TASmod; +import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; +import com.minecrafttas.tasmod.registries.TASmodPackets; + +public class SavestateGuiHandlerServer implements ServerPacketHandler { + + @Override + public PacketID[] getAcceptedPacketIDs() { + return new PacketID[] { SAVESTATE_RENAME_SCREEN }; + } + + @Override + public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception { + TASmodPackets packet = (TASmodPackets) id; + + switch (packet) { + case SAVESTATE_RENAME_SCREEN: + int index = TASmodBufferBuilder.readInt(buf); + String name = TASmodBufferBuilder.readString(buf); + TASmod.gameLoopSchedulerServer.add(() -> { + TASmod.savestateHandlerServer.rename(index, name); + }); + break; + default: + throw new PacketNotImplementedException(packet, Side.SERVER); + } + } + +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/ClientMotionStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/ClientMotionStorage.java index 42584e88..26b7151b 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/ClientMotionStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/ClientMotionStorage.java @@ -28,7 +28,6 @@ import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; -import com.minecrafttas.tasmod.savestates.gui.GuiSavestateSavingScreen; import com.minecrafttas.tasmod.savestates.storage.SavestateStorageExtensionBase; import com.minecrafttas.tasmod.util.LoggerMarkers; @@ -134,9 +133,6 @@ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws case SAVESTATE_REQUEST_MOTION: if (player != null) { - if (!(mc.currentScreen instanceof GuiSavestateSavingScreen)) { - mc.displayGuiScreen(new GuiSavestateSavingScreen()); - } //@formatter:off MotionData motionData = new MotionData( player.motionX, diff --git a/src/main/java/com/minecrafttas/tasmod/util/Component.java b/src/main/java/com/minecrafttas/tasmod/util/Component.java new file mode 100644 index 00000000..26e2b56f --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/util/Component.java @@ -0,0 +1,76 @@ +package com.minecrafttas.tasmod.util; + +import java.util.function.UnaryOperator; + +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.util.text.TextFormatting; + +/** + * 1.20 Style Component library backported to 1.12 + * + * @author Scribble + */ +public class Component { + + private final ITextComponent component; + private final Style style = new Style(); + + private Component(ITextComponent component) { + this.component = component; + } + + public Component withStyle(TextFormatting color) { + style.setColor(color); + return this; + } + + public Component withStyle(UnaryOperator