From c47b3bdbf6738d45582156b5748552eebe40519b Mon Sep 17 00:00:00 2001 From: Scribble Date: Sat, 18 Oct 2025 20:40:56 +0200 Subject: [PATCH 1/3] [Savestates] Split savestate/loadstate methods and add "inner" methods --- .../savestates/SavestateHandlerServer.java | 30 ++++++++++++++++--- .../tasmod/savestates/SavestateIndexer.java | 4 +++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index 3084befb..944d55b8 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.Logger; import com.minecrafttas.mctcommon.events.EventListenerRegistry; @@ -135,11 +136,20 @@ 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)); + + if (paths.getSavestate().index == 0) { + if (!ArrayUtils.contains(flags, SavestateFlags.BLOCK_CLIENT_SAVESTATE)) + flags = ArrayUtils.add(flags, SavestateFlags.BLOCK_CLIENT_SAVESTATE); + } + + savestateInner(paths, cb, flags); + } + + private void savestateInner(SavestatePaths paths, SavestateCallback cb, SavestateFlags... flags) { Path sourceFolder = paths.getSourceFolder(); Path targetFolder = paths.getTargetFolder(); int indexToSave = paths.getSavestate().index; logger.debug("Source: {}, Target: {}", sourceFolder, targetFolder); - EventListenerRegistry.fireEvent(EventSavestate.EventServerSavestate.class, server, paths); if (Files.exists(targetFolder)) { @@ -151,7 +161,7 @@ public void saveState(int index, String name, SavestateCallback cb, SavestateFla * Prevents creating an InputSavestate when saving at index 0 (Index 0 is the * savestate when starting a recording) */ - if (index != 0) { + if (!SavestateFlags.BLOCK_CLIENT_SAVESTATE.isBlocked(flags)) { /* * Send the name of the world to all players. This will make a savestate of the * recording on the client with that name @@ -235,10 +245,18 @@ public void loadState(int index, String name, SavestateCallback cb, SavestateFla SavestatePaths paths = indexer.loadSavestate(index, !SavestateFlags.BLOCK_CHANGE_INDEX.isBlocked(flags)); logger.debug(LoggerMarkers.Savestate, "Source: {}, Target: {}", paths.getSourceFolder(), paths.getTargetFolder()); + if (paths.getSavestate().index == 0) { + if (!ArrayUtils.contains(flags, SavestateFlags.BLOCK_CLIENT_SAVESTATE)) + flags = ArrayUtils.add(flags, SavestateFlags.BLOCK_CLIENT_SAVESTATE); + } + + loadStateInner(paths, cb, flags); + } + + private void loadStateInner(SavestatePaths paths, SavestateCallback cb, SavestateFlags... flags) { String worldname = server.getFolderName(); Path sourcefolder = paths.getSourceFolder(); Path targetfolder = paths.getTargetFolder(); - int indexToLoad = paths.getSavestate().index; EventListenerRegistry.fireEvent(EventSavestate.EventServerLoadstate.class, server, paths); @@ -247,7 +265,7 @@ public void loadState(int index, String name, SavestateCallback cb, SavestateFla * savestate when starting a recording. Not doing this will load an empty * InputSavestate) */ - if (indexToLoad != 0) { + if (!SavestateFlags.BLOCK_CLIENT_SAVESTATE.isBlocked(flags)) { try { // loadstate inputs client TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_LOAD).writeString(paths.getSavestate().folder.toString())); @@ -576,6 +594,10 @@ public static enum SavestateFlags { * Stops updating the current index when savestating/loadstating */ BLOCK_CHANGE_INDEX, + /** + * Stops the creation/loading of a client savestate + */ + BLOCK_CLIENT_SAVESTATE, /** * Stops setting the tickrate to 0 after a savestate/loadstate */ diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java index 0f4b4c08..b0faec5e 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java @@ -20,6 +20,7 @@ import java.util.stream.Stream; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; import com.minecrafttas.mctcommon.file.AbstractDataFile; import com.minecrafttas.tasmod.TASmod; @@ -473,8 +474,11 @@ public Savestate getCurrentSavestate() { */ public class Savestate extends AbstractDataFile { + @Nullable protected Integer index; + @Nullable protected String name; + @Nullable protected Date date; protected Path folder; protected Logger logger = TASmod.LOGGER; From b1b77c4588e9ca785cb89493e2d0c7aa1506b1d6 Mon Sep 17 00:00:00 2001 From: Scribble Date: Sat, 18 Oct 2025 22:33:31 +0200 Subject: [PATCH 2/3] [Savestates] Add temporary savestates for /record and /play --- .../tasmod/commands/CommandClearInputs.java | 8 +---- .../tasmod/commands/CommandPlay.java | 15 +++++--- .../tasmod/commands/CommandRecord.java | 22 ++++++++---- .../playback/PlaybackControllerServer.java | 36 +++++++++++++++++-- .../savestates/SavestateHandlerServer.java | 21 +++++++++-- .../tasmod/savestates/SavestateIndexer.java | 20 +++++++++-- 6 files changed, 98 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandClearInputs.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandClearInputs.java index ec5035e0..21c192e8 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandClearInputs.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandClearInputs.java @@ -1,8 +1,6 @@ package com.minecrafttas.tasmod.commands; import com.minecrafttas.tasmod.TASmod; -import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; -import com.minecrafttas.tasmod.registries.TASmodPackets; import net.minecraft.command.CommandBase; import net.minecraft.command.CommandException; @@ -25,11 +23,7 @@ public String getUsage(ICommandSender sender) { @Override public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException { if (sender instanceof EntityPlayer) { - try { - TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.PLAYBACK_CLEAR_INPUTS)); - } catch (Exception e) { - e.printStackTrace(); - } + TASmod.playbackControllerServer.clearInputs(); } } diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandPlay.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandPlay.java index 2f03c713..7a236c2f 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandPlay.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandPlay.java @@ -23,7 +23,7 @@ public String getName() { @Override public String getUsage(ICommandSender sender) { - return "/play"; + return "/play [nosave]"; } @Override @@ -41,9 +41,13 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args if (!(sender instanceof EntityPlayer)) { return; } - if (args.length < 1) { - TASmod.playbackControllerServer.togglePlayback(); - } else if (args.length > 1) { + if (args.length <= 1) { + boolean loadTempSavestate = true; + if (args.length == 1 && "nosave".equals(args[0])) { + loadTempSavestate = false; + } + TASmod.playbackControllerServer.togglePlayback(loadTempSavestate); + } else if (args.length > 2) { sender.sendMessage(new TextComponentString(TextFormatting.RED + "Too many arguments. " + getUsage(sender))); } @@ -51,6 +55,9 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args @Override public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, BlockPos targetPos) { + if (args.length == 1) { + return getListOfStringsMatchingLastWord(args, "nosave"); + } return super.getTabCompletions(server, sender, args, targetPos); } } diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandRecord.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandRecord.java index 2ba6abbe..09f2cf89 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandRecord.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandRecord.java @@ -10,6 +10,7 @@ import net.minecraft.command.ICommandSender; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.TextComponentString; import net.minecraft.util.text.TextFormatting; @@ -22,7 +23,7 @@ public String getName() { @Override public String getUsage(ICommandSender sender) { - return "/record"; + return "/record [nosave]"; } @Override @@ -40,14 +41,23 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args if (!(sender instanceof EntityPlayer)) { return; } - if (args.length < 1) { - TASmod.playbackControllerServer.toggleRecording(); -// TASmod.tickSchedulerServer.add(() ->{ -// TASmod.ktrngHandler.broadcastStartSeed(); -// }); + if (args.length <= 1) { + boolean saveTempSavestate = true; + if (args.length == 1 && "nosave".equals(args[0])) { + saveTempSavestate = false; + } + TASmod.playbackControllerServer.toggleRecording(saveTempSavestate); } else if (args.length > 1) { sender.sendMessage(new TextComponentString(TextFormatting.RED + "Too many arguments. " + getUsage(sender))); } } + + @Override + public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, BlockPos targetPos) { + if (args.length == 1) { + return getListOfStringsMatchingLastWord(args, "nosave"); + } + return super.getTabCompletions(server, sender, args, targetPos); + } } diff --git a/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerServer.java b/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerServer.java index c2d6ffa7..98cb0245 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerServer.java @@ -33,6 +33,8 @@ public class PlaybackControllerServer implements ServerPacketHandler { private TASstate state; + private boolean createState = true; + @Override public PacketID[] getAcceptedPacketIDs() { //@formatter:off @@ -62,7 +64,7 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws break; case PLAYBACK_CLEAR_INPUTS: - TASmod.server.sendToAll(new TASmodBufferBuilder(PLAYBACK_CLEAR_INPUTS)); + clearInputs(); break; case PLAYBACK_FULLPLAY: case PLAYBACK_FULLRECORD: @@ -98,14 +100,42 @@ public void setServerState(TASstate stateIn) { } } - public void toggleRecording() { + public void toggleRecording(boolean saveSavestate) { + if (state == TASstate.NONE && createState && saveSavestate) { + createState = false; + TASmod.savestateHandlerServer.saveStateTemp((paths) -> { + try { + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_SCREEN)); + } catch (Exception e) { + TASmod.LOGGER.catching(e); + } + }); + } setState(state == TASstate.RECORDING ? TASstate.NONE : TASstate.RECORDING); } - public void togglePlayback() { + public void togglePlayback(boolean loadSavestate) { + if (state == TASstate.NONE && loadSavestate) { + TASmod.savestateHandlerServer.loadStateTemp((paths) -> { + try { + TASmod.server.sendToAll(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_SCREEN)); + } catch (Exception e) { + TASmod.LOGGER.catching(e); + } + }); + } setState(state == TASstate.PLAYBACK ? TASstate.NONE : TASstate.PLAYBACK); } + public void clearInputs() { + createState = true; + try { + TASmod.server.sendToAll(new TASmodBufferBuilder(PLAYBACK_CLEAR_INPUTS)); + } catch (Exception e) { + e.printStackTrace(); + } + } + public TASstate getState() { return state; } diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index 944d55b8..ec90d991 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -145,15 +145,23 @@ public void saveState(int index, String name, SavestateCallback cb, SavestateFla savestateInner(paths, cb, flags); } + public void saveStateTemp(SavestateCallback cb) { + SavestatePaths paths = indexer.createTempSavestate(); + SavestateFlags[] flags = new SavestateFlags[] { SavestateFlags.BLOCK_CLIENT_SAVESTATE, SavestateFlags.BLOCK_PAUSE_TICKRATE }; + savestateInner(paths, cb, flags); + paths.getSavestate().save(); + } + private void savestateInner(SavestatePaths paths, SavestateCallback cb, SavestateFlags... flags) { Path sourceFolder = paths.getSourceFolder(); Path targetFolder = paths.getTargetFolder(); - int indexToSave = paths.getSavestate().index; + Integer indexToSave = paths.getSavestate().index; logger.debug("Source: {}, Target: {}", sourceFolder, targetFolder); EventListenerRegistry.fireEvent(EventSavestate.EventServerSavestate.class, server, paths); if (Files.exists(targetFolder)) { - logger.warn(LoggerMarkers.Savestate, "WARNING! Overwriting the savestate with the index {}", indexToSave); + if (indexToSave != null) + logger.warn(LoggerMarkers.Savestate, "WARNING! Overwriting the savestate with the index {}", indexToSave); deleteFolder(targetFolder); } @@ -253,6 +261,15 @@ public void loadState(int index, String name, SavestateCallback cb, SavestateFla loadStateInner(paths, cb, flags); } + public void loadStateTemp(SavestateCallback cb) { + SavestatePaths paths = indexer.loadTempSavestate(); + if (paths == null) + return; + SavestateFlags[] flags = new SavestateFlags[] { SavestateFlags.BLOCK_CLIENT_SAVESTATE, SavestateFlags.BLOCK_PAUSE_TICKRATE }; + loadStateInner(paths, cb, flags); + paths.getSavestate().save(); + } + private void loadStateInner(SavestatePaths paths, SavestateCallback cb, SavestateFlags... flags) { String worldname = server.getFolderName(); Path sourcefolder = paths.getSourceFolder(); diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java index b0faec5e..6aad9275 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java @@ -380,7 +380,8 @@ public void run() { Throwable error = new SavestateException("Savestate.json data file not found in " + savestateBaseDirectory.relativize(savestateDat)); savestate = new FailedSavestate(path, backupIndex, null, null, error); } - savestateList.put(savestate.getIndex(), savestate.clone()); + if (savestate.index != null) // Temporary savestates have no index and are not listed in the savestatelist + savestateList.put(savestate.getIndex(), savestate.clone()); }); sortSavestateList(); try { @@ -484,7 +485,7 @@ public class Savestate extends AbstractDataFile { protected Logger logger = TASmod.LOGGER; private Savestate(Path datFile, Path folder) { - this(datFile, -1, null, null, folder); + this(datFile, null, null, null, folder); } private Savestate(Path file, Integer index, String name, Date date, Path folder) { @@ -746,4 +747,19 @@ public int getNextIndex(int index) { } return index; } + + public SavestatePaths createTempSavestate() { + Path sourceDirectory = savesDir.resolve(worldname); + Path targetDirectory = currentSavestateDir.resolve(worldname + "-SavestateTemp"); + Savestate tempSavestate = new Savestate(targetDirectory.resolve(savestateFilePath), null, "Temporary Savestate!", new Date(), targetDirectory); + return SavestatePaths.of(tempSavestate, sourceDirectory, targetDirectory); + } + + public SavestatePaths loadTempSavestate() { + Path sourceDirectory = currentSavestateDir.resolve(worldname + "-SavestateTemp"); + if (!Files.exists(sourceDirectory)) + return null; + Path targetDirectory = savesDir.resolve(worldname); + return SavestatePaths.of(currentSavestate.clone(), sourceDirectory, targetDirectory); + } } From c0290f6ec14eec1d6ce957df4f0d6b6e3fce87d9 Mon Sep 17 00:00:00 2001 From: Scribble Date: Sat, 18 Oct 2025 23:01:54 +0200 Subject: [PATCH 3/3] [Savestates] Fix bug where the current savestate is not renamed --- .../tasmod/savestates/SavestateHandlerServer.java | 4 ++++ .../com/minecrafttas/tasmod/savestates/SavestateIndexer.java | 5 +++++ .../savestates/handlers/SavestateGuiHandlerServer.java | 1 + 3 files changed, 10 insertions(+) diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index ec90d991..a3a20e87 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -653,6 +653,10 @@ public void rename(int index, String name) throws SavestateException { rename(index, name, null); } + public void renameCurrent(String name) throws SavestateException { + indexer.renameCurrent(name); + } + public void rename(int index, String name, SavestateCallback cb) throws SavestateException { SavestatePaths paths = indexer.renameSavestate(index, name); if (cb != null) { diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java index 6aad9275..ea8e4e0c 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateIndexer.java @@ -240,6 +240,11 @@ public SavestatePaths renameSavestate(int index, String name) throws SavestateEx return SavestatePaths.of(savestateToRename, null, null); } + public void renameCurrent(String name) { + currentSavestate.name = name; + currentSavestate.save(); + } + /** * Deletes a savestate * @param index The index to delete diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateGuiHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateGuiHandlerServer.java index 3a86a23a..9a01ca14 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateGuiHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateGuiHandlerServer.java @@ -34,6 +34,7 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws String name = TASmodBufferBuilder.readString(buf); TASmod.gameLoopSchedulerServer.add(() -> { TASmod.savestateHandlerServer.rename(index, name); + TASmod.savestateHandlerServer.renameCurrent(name); }); TASmod.server.sendToAll(new TASmodBufferBuilder(SAVESTATE_CLEAR_SCREEN));