diff --git a/build.gradle b/build.gradle index 5ce7d23b..0aff5c5e 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ apply plugin: 'org.spongepowered.mixin' //Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. -version = "Alpha7-WIP" +version = "Alpha7" group = "de.scribble.lp.tastools" // http://maven.apache.org/guides/mini/guide-naming-conventions.html archivesBaseName = "TASmod-1.12.2" diff --git a/src/main/java/de/scribble/lp/tasmod/TASmod.java b/src/main/java/de/scribble/lp/tasmod/TASmod.java index ac6b2a22..68528e83 100644 --- a/src/main/java/de/scribble/lp/tasmod/TASmod.java +++ b/src/main/java/de/scribble/lp/tasmod/TASmod.java @@ -15,6 +15,8 @@ import de.scribble.lp.tasmod.commands.recording.CommandRecord; import de.scribble.lp.tasmod.commands.savetas.CommandSaveTAS; import de.scribble.lp.tasmod.commands.tutorial.CommandPlaybacktutorial; +import de.scribble.lp.tasmod.savestates.server.SavestateCommand; +import de.scribble.lp.tasmod.savestates.server.SavestateHandler; import de.scribble.lp.tasmod.savestates.server.SavestateTrackerFile; import de.scribble.lp.tasmod.tickratechanger.CommandTickrate; import de.scribble.lp.tasmod.util.ModIncompatibleException; @@ -53,6 +55,8 @@ public class TASmod { public static final Logger logger = LogManager.getLogger("TASMod"); public static ContainerStateServer containerStateServer; + + public static SavestateHandler savestateHandler; @EventHandler public void preInit(FMLPreInitializationEvent ev) throws Exception { @@ -99,6 +103,7 @@ public void serverStart(FMLServerStartingEvent ev) { ev.registerServerCommand(new CommandPlaybacktutorial()); ev.registerServerCommand(new CommandFolder()); ev.registerServerCommand(new CommandClearInputs()); + ev.registerServerCommand(new SavestateCommand()); // Save Loadstate Count File savestateDirectory = new File(serverInstance.getDataDirectory() + File.separator + "saves" + File.separator + "savestates" + File.separator); @@ -107,6 +112,8 @@ public void serverStart(FMLServerStartingEvent ev) { } catch (IOException e) { e.printStackTrace(); } + + savestateHandler=new SavestateHandler(ev.getServer()); } public static TASmod getInstance() { diff --git a/src/main/java/de/scribble/lp/tasmod/events/KeybindingEvents.java b/src/main/java/de/scribble/lp/tasmod/events/KeybindingEvents.java index e2575c9a..1180ed08 100644 --- a/src/main/java/de/scribble/lp/tasmod/events/KeybindingEvents.java +++ b/src/main/java/de/scribble/lp/tasmod/events/KeybindingEvents.java @@ -19,7 +19,6 @@ public class KeybindingEvents { public static void fireKeybindingsEvent() { - VirtualKeybindings.increaseCooldowntimer(); if (VirtualKeybindings.isKeyDownExceptTextfield(ClientProxy.savestateSaveKey)) { diff --git a/src/main/java/de/scribble/lp/tasmod/inputcontainer/InputContainer.java b/src/main/java/de/scribble/lp/tasmod/inputcontainer/InputContainer.java index 0c6a5d45..af989cbd 100644 --- a/src/main/java/de/scribble/lp/tasmod/inputcontainer/InputContainer.java +++ b/src/main/java/de/scribble/lp/tasmod/inputcontainer/InputContainer.java @@ -68,7 +68,7 @@ public class InputContainer { private BigArrayList inputs = new BigArrayList(directory + File.separator + "temp"); public DesyncMonitoring dMonitor = new DesyncMonitoring(); - + // ===================================================================================================== private String authors = "Insert author here"; @@ -123,6 +123,7 @@ public String setTASState(TASstate stateIn, boolean verbose) { return verbose ? TextFormatting.RED + "An error occured while reading the start location of the TAS. The file might be broken" : ""; } } + Minecraft.getMinecraft().gameSettings.chatLinks = false; // #119 index = 0; state = TASstate.PLAYBACK; return verbose ? TextFormatting.GREEN + "Starting playback" : ""; @@ -177,76 +178,6 @@ public TASstate getState() { return state; } - @Deprecated - public String setRecording(boolean enabled) { - return setRecording(enabled, true); - } - - /** - * Starts/Stops a recording - * - * @param enabled If true: starts a recording, else stops a running recording - * @return Chat message depending on the state - */ - @Deprecated - public String setRecording(boolean enabled, boolean verbose) { - if (state == TASstate.PLAYBACK) { - return verbose ? TextFormatting.RED + "A playback is already running" : ""; - } - if (enabled) { - state = TASstate.RECORDING; - } else { - state = TASstate.NONE; - } - - if (state == TASstate.RECORDING) { - if (Minecraft.getMinecraft().player != null) { - startLocation = getStartLocation(Minecraft.getMinecraft().player); // TODO #99 Make this a secondary command - } - return verbose ? TextFormatting.GREEN + "Starting the recording" : ""; - } else if (state == TASstate.NONE) { - return verbose ? TextFormatting.GREEN + "Stopping the recording" : ""; - } - return ""; - } - - @Deprecated - public String setPlayback(boolean enabled) { - return setPlayback(enabled, true); - } - - /** - * Starts/Stops a playback - * - * @param enabled If true: start a playback, else aborts a running playback - * @return Chat message depending on the state - */ - @Deprecated - public String setPlayback(boolean enabled, boolean verbose) { - if (state == TASstate.RECORDING) - return verbose ? TextFormatting.RED + "A recording is already running" : ""; - if (enabled) { - state = TASstate.PLAYBACK; - } else { - state = TASstate.NONE; - } - if (state == TASstate.PLAYBACK) { - if (Minecraft.getMinecraft().player != null && !startLocation.isEmpty()) { - try { - tpPlayer(startLocation); - } catch (NumberFormatException e) { - state = TASstate.NONE; - e.printStackTrace(); - return verbose ? TextFormatting.RED + "An error occured while reading the start location of the TAS. The file might be broken" : ""; - } - } - index = 0; - return verbose ? TextFormatting.GREEN + "Starting playback" : ""; - } else { - return verbose ? TextFormatting.GREEN + "Aborting playback" : ""; - } - } - // ===================================================================================================== // Methods to update the temporary variables of the container. // These act as an input and output, depending if a recording or a playback is diff --git a/src/main/java/de/scribble/lp/tasmod/mixin/MixinMinecraftServer.java b/src/main/java/de/scribble/lp/tasmod/mixin/MixinMinecraftServer.java index cf3a34a7..db9bad61 100644 --- a/src/main/java/de/scribble/lp/tasmod/mixin/MixinMinecraftServer.java +++ b/src/main/java/de/scribble/lp/tasmod/mixin/MixinMinecraftServer.java @@ -37,8 +37,8 @@ public long modifyMSPT(long fiftyLong) { @Redirect(method = "run", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;tick()V", ordinal = 1)) public void redirectTick(MinecraftServer server) { this.tick(); - if (SavestateHandler.state == SavestateState.WASLOADING) { - SavestateHandler.state = SavestateState.NONE; + if (TASmod.savestateHandler.state == SavestateState.WASLOADING) { + TASmod.savestateHandler.state = SavestateState.NONE; SavestateHandler.playerLoadSavestateEventServer(); } diff --git a/src/main/java/de/scribble/lp/tasmod/mixin/MixinDragonFightManager.java b/src/main/java/de/scribble/lp/tasmod/mixin/fixes/MixinDragonFightManager.java similarity index 94% rename from src/main/java/de/scribble/lp/tasmod/mixin/MixinDragonFightManager.java rename to src/main/java/de/scribble/lp/tasmod/mixin/fixes/MixinDragonFightManager.java index cb9f51b3..cd15a9a3 100644 --- a/src/main/java/de/scribble/lp/tasmod/mixin/MixinDragonFightManager.java +++ b/src/main/java/de/scribble/lp/tasmod/mixin/fixes/MixinDragonFightManager.java @@ -1,4 +1,4 @@ -package de.scribble.lp.tasmod.mixin; +package de.scribble.lp.tasmod.mixin.fixes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; diff --git a/src/main/java/de/scribble/lp/tasmod/mixin/fixes/MixinMinecraftFullscreen.java b/src/main/java/de/scribble/lp/tasmod/mixin/fixes/MixinMinecraftFullscreen.java new file mode 100644 index 00000000..95ccb514 --- /dev/null +++ b/src/main/java/de/scribble/lp/tasmod/mixin/fixes/MixinMinecraftFullscreen.java @@ -0,0 +1,24 @@ +package de.scribble.lp.tasmod.mixin.fixes; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import de.scribble.lp.tasmod.ClientProxy; +import net.minecraft.client.Minecraft; +import net.minecraft.client.settings.GameSettings; + +@Mixin(Minecraft.class) +public class MixinMinecraftFullscreen { + + @Shadow + private GameSettings gameSettings; + + @Inject(method = "toggleFullscreen", at = @At("RETURN")) + public void inject_toggleFullscreen(CallbackInfo ci) { + int keyF11=this.gameSettings.keyBindFullscreen.getKeyCode(); + ClientProxy.virtual.getNextKeyboard().get(keyF11).setPressed(false); + } +} diff --git a/src/main/java/de/scribble/lp/tasmod/savestates/server/GuiSavestateSavingScreen.java b/src/main/java/de/scribble/lp/tasmod/savestates/server/GuiSavestateSavingScreen.java index 035b637e..fd21c174 100644 --- a/src/main/java/de/scribble/lp/tasmod/savestates/server/GuiSavestateSavingScreen.java +++ b/src/main/java/de/scribble/lp/tasmod/savestates/server/GuiSavestateSavingScreen.java @@ -7,7 +7,12 @@ public class GuiSavestateSavingScreen extends GuiScreen{ - + @Override + public void initGui() { + this.mc=Minecraft.getMinecraft(); + super.initGui(); + } + @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.drawDefaultBackground(); diff --git a/src/main/java/de/scribble/lp/tasmod/savestates/server/LoadstatePacket.java b/src/main/java/de/scribble/lp/tasmod/savestates/server/LoadstatePacket.java index ccf818c4..f27c2a23 100644 --- a/src/main/java/de/scribble/lp/tasmod/savestates/server/LoadstatePacket.java +++ b/src/main/java/de/scribble/lp/tasmod/savestates/server/LoadstatePacket.java @@ -5,13 +5,30 @@ public class LoadstatePacket implements IMessage{ + public int index; + + /** + * Load a savestate at the current index + */ public LoadstatePacket() { + index=-1; + } + + /** + * Load the savestate at the specified index + * @param index The index to load the savestate + */ + public LoadstatePacket(int index) { + this.index = index; } + @Override public void fromBytes(ByteBuf buf) { + index=buf.readInt(); } @Override public void toBytes(ByteBuf buf) { + buf.writeInt(index); } } diff --git a/src/main/java/de/scribble/lp/tasmod/savestates/server/LoadstatePacketHandler.java b/src/main/java/de/scribble/lp/tasmod/savestates/server/LoadstatePacketHandler.java index daed6f0c..8a6bb309 100644 --- a/src/main/java/de/scribble/lp/tasmod/savestates/server/LoadstatePacketHandler.java +++ b/src/main/java/de/scribble/lp/tasmod/savestates/server/LoadstatePacketHandler.java @@ -1,5 +1,6 @@ package de.scribble.lp.tasmod.savestates.server; +import de.scribble.lp.tasmod.TASmod; import de.scribble.lp.tasmod.savestates.server.chunkloading.SavestatesChunkControl; import de.scribble.lp.tasmod.savestates.server.exceptions.LoadstateException; import net.minecraft.client.Minecraft; @@ -22,15 +23,14 @@ public IMessage onMessage(LoadstatePacket message, MessageContext ctx) { return; } try { - SavestateHandler.loadState(); + TASmod.savestateHandler.loadState(message.index); } catch (LoadstateException e) { player.sendMessage(new TextComponentString(TextFormatting.RED+"Failed to load a savestate: "+e.getMessage())); - } catch (Exception e) { player.sendMessage(new TextComponentString(TextFormatting.RED+"Failed to load a savestate: "+e.getCause().toString())); e.printStackTrace(); } finally { - SavestateHandler.state=SavestateState.NONE; + TASmod.savestateHandler.state=SavestateState.NONE; } }); }else { diff --git a/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestateCommand.java b/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestateCommand.java new file mode 100644 index 00000000..715a7ca8 --- /dev/null +++ b/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestateCommand.java @@ -0,0 +1,250 @@ +package de.scribble.lp.tasmod.savestates.server; + +import java.io.IOException; +import java.util.List; + +import com.mojang.realmsclient.gui.ChatFormatting; + +import de.scribble.lp.tasmod.ClientProxy; +import de.scribble.lp.tasmod.TASmod; +import de.scribble.lp.tasmod.savestates.server.exceptions.LoadstateException; +import de.scribble.lp.tasmod.savestates.server.exceptions.SavestateDeleteException; +import de.scribble.lp.tasmod.savestates.server.exceptions.SavestateException; +import de.scribble.lp.tasmod.virtual.VirtualInput; +import de.scribble.lp.tasmod.virtual.VirtualKeyboard; +import net.minecraft.client.Minecraft; +import net.minecraft.command.CommandBase; +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.TextComponentString; +import net.minecraft.util.text.event.ClickEvent; + +public class SavestateCommand extends CommandBase { + + @Override + public String getName() { + return "savestate"; + } + + @Override + public String getUsage(ICommandSender sender) { + return "/savestate [index]"; + } + + @Override + public int getRequiredPermissionLevel() { + return 2; + } + + @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) { + saveLatest(); + } else if (args.length == 2) { + saveWithIndex(args); + } else { + throw new CommandException("Too many arguments!", new Object[] {}); + } + } else if ("load".equals(args[0])) { + if (args.length == 1) { + loadLatest(); + } else if (args.length == 2) { + loadLatest(args); + } 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 - args1; + TextComponentString confirm = new TextComponentString(ChatFormatting.YELLOW + "Are you sure you want to delete " + count + (count == 1 ? " savestate? " : " savestates? ") + ChatFormatting.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 ("info".equals(args[0])) { + sender.sendMessage(new TextComponentString(String.format("The current savestate index is %s%s", ChatFormatting.AQUA, TASmod.savestateHandler.getCurrentIndex()))); + sender.sendMessage(new TextComponentString(String.format("Available indexes are %s%s", ChatFormatting.AQUA, TASmod.savestateHandler.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[] {}); + } + + } + } + } + + private void sendHelp(ICommandSender sender) throws CommandException { + sendHelp(sender, 1); + } + + private void sendHelp(ICommandSender sender, int i) throws CommandException { + int currentIndex = TASmod.savestateHandler.getCurrentIndex(); + if (i > 3) { + throw new CommandException("This help page doesn't exist (yet?)", new Object[] {}); + } + if(i==1) { + sender.sendMessage(new TextComponentString(ChatFormatting.GOLD+"-------------------Savestate Help 1--------------------\n"+ChatFormatting.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 "+ChatFormatting.AQUA+currentIndex+ChatFormatting.RESET + + String.format(". If you make a new savestate via %s/savestate save%s or by pressing %sJ%s by default, ", ChatFormatting.AQUA, ChatFormatting.RESET, ChatFormatting.AQUA, ChatFormatting.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", ChatFormatting.AQUA, ChatFormatting.RESET, ChatFormatting.AQUA, ChatFormatting.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*/ChatFormatting.GOLD, /*2*/ChatFormatting.RESET, /*3*/ChatFormatting.AQUA, /*4*/ChatFormatting.GREEN, /*5*/ChatFormatting.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$sinfo%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*/ChatFormatting.GOLD, /*2*/ChatFormatting.RESET, /*3*/ChatFormatting.AQUA, /*4*/ChatFormatting.GREEN, /*5*/ChatFormatting.YELLOW, /*6*/(currentIndex - 1)))); + return; + } + TextComponentString nextPage=new TextComponentString(ChatFormatting.GOLD+"Click here to go to the next help page ("+(i+1)+")\n"); + nextPage.getStyle().setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/savestate help "+(i+1)+"")); + sender.sendMessage(nextPage); + } + + @Override + public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, BlockPos targetPos) { + if (args.length == 1) { + return getListOfStringsMatchingLastWord(args, new String[] { "save", "load", "delete", "info", "help"}); + } else if (args.length == 2 && !"info".equals(args[0])) { + sender.sendMessage(new TextComponentString("Available indexes: " + ChatFormatting.AQUA + TASmod.savestateHandler.getIndexesAsString())); + } + return super.getTabCompletions(server, sender, args, targetPos); + } + + // ====================================================================== + + private void saveLatest() throws CommandException { + try { + TASmod.savestateHandler.saveState(); + } catch (SavestateException e) { + throw new CommandException(e.getMessage(), new Object[] {}); + } catch (IOException e) { + e.printStackTrace(); + throw new CommandException(e.getMessage(), new Object[] {}); + } finally { + TASmod.savestateHandler.state = SavestateState.NONE; + } + } + + private void saveWithIndex(String[] args) throws CommandException { + try { + int indexToSave = processIndex(args[1]); + if (indexToSave <= 0) { // Disallow to save on Savestate 0 + indexToSave = -1; + } + TASmod.savestateHandler.saveState(indexToSave); + } catch (SavestateException e) { + throw new CommandException(e.getMessage(), new Object[] {}); + } catch (IOException e) { + e.printStackTrace(); + throw new CommandException(e.getMessage(), new Object[] {}); + } finally { + TASmod.savestateHandler.state = SavestateState.NONE; + } + } + + private void loadLatest() throws CommandException { + try { + TASmod.savestateHandler.loadState(); + } catch (LoadstateException e) { + throw new CommandException(e.getMessage(), new Object[] {}); + } catch (IOException e) { + e.printStackTrace(); + throw new CommandException(e.getMessage(), new Object[] {}); + } finally { + TASmod.savestateHandler.state = SavestateState.NONE; + } + } + + private void loadLatest(String[] args) throws CommandException { + try { + TASmod.savestateHandler.loadState(processIndex(args[1])); + } catch (LoadstateException e) { + throw new CommandException(e.getMessage(), new Object[] {}); + } catch (IOException e) { + e.printStackTrace(); + throw new CommandException(e.getMessage(), new Object[] {}); + } finally { + TASmod.savestateHandler.state = SavestateState.NONE; + } + } + + private void delete(String[] args) throws CommandException { + int arg1 = processIndex(args[1]); + if (arg1 == 0) { + throw new CommandException("Cannot delete savestate 0", new Object[] {}); + } + try { + TASmod.savestateHandler.deleteSavestate(arg1); + } catch (SavestateDeleteException e) { + throw new CommandException(e.getMessage(), new Object[] {}); + } + } + + private void deleteMultiple(String[] args) throws CommandException { + try { + TASmod.savestateHandler.deleteSavestate(processIndex(args[1]), processIndex(args[2])); + } catch (SavestateDeleteException e) { + throw new CommandException(e.getMessage(), new Object[] {}); + } + } + + // ====================================================================== + + private int processIndex(String arg) throws CommandException { + if ("~".equals(arg)) { + return TASmod.savestateHandler.getCurrentIndex(); + } else if (arg.matches("~-?\\d")) { + arg = arg.replace("~", ""); + int i = Integer.parseInt(arg); + return TASmod.savestateHandler.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); + } + return i; + } + } +} diff --git a/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestateHandler.java b/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestateHandler.java index 306392fe..a0106834 100644 --- a/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestateHandler.java +++ b/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestateHandler.java @@ -3,15 +3,21 @@ import java.io.File; import java.io.FileFilter; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import de.scribble.lp.tasmod.CommonProxy; import de.scribble.lp.tasmod.TASmod; -import de.scribble.lp.tasmod.savestates.client.InputSavestatesHandler; import de.scribble.lp.tasmod.savestates.client.InputSavestatesPacket; import de.scribble.lp.tasmod.savestates.server.chunkloading.SavestatesChunkControl; import de.scribble.lp.tasmod.savestates.server.exceptions.LoadstateException; +import de.scribble.lp.tasmod.savestates.server.exceptions.SavestateDeleteException; import de.scribble.lp.tasmod.savestates.server.exceptions.SavestateException; import de.scribble.lp.tasmod.savestates.server.motion.ClientMotionServer; import de.scribble.lp.tasmod.savestates.server.playerloading.SavestatePlayerLoading; @@ -20,6 +26,7 @@ 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.TextComponentString; import net.minecraft.util.text.TextFormatting; import net.minecraft.world.WorldServer; @@ -29,320 +36,469 @@ import net.minecraftforge.fml.relauncher.SideOnly; /** - * 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, + * 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 ScribbleLP * */ public class SavestateHandler { - private static MinecraftServer server=TASmod.getServerInstance(); - private static File savestateDirectory; - - public static SavestateState state=SavestateState.NONE; - + + private MinecraftServer server; + private File savestateDirectory; + + public SavestateState state = SavestateState.NONE; + + private final List indexList = new ArrayList<>(); + + private int latestIndex = 0; + private int currentIndex; + /** - * Creates a copy of the currently played world and saves it in .minecraft/saves/savestates/worldname
- * Called in {@link SavestatePacketHandler}
+ * Creates a savestate handler on the specified server + * + * @param The server that should store the savestates + */ + public SavestateHandler(MinecraftServer server) { + this.server = 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 - * @param savestateIndex The index where the mod will save the savestate -1 if it should load the latest + * * @throws SavestateException * @throws IOException */ - public static void saveState(int savestateIndex) throws SavestateException, IOException { - if(state==SavestateState.SAVING) { + public void saveState() throws SavestateException, IOException { + saveState(-1); + } + + /** + * 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 + * @throws SavestateException + * @throws IOException + */ + public void saveState(int savestateIndex) throws SavestateException, IOException { + if (state == SavestateState.SAVING) { throw new SavestateException("A savestating operation is already being carried out"); } - if(state==SavestateState.LOADING) { + if (state == SavestateState.LOADING) { throw new SavestateException("A loadstate operation is being carried out"); } - //Lock savestating and loadstating - state=SavestateState.SAVING; - - //Create a directory just in case + // Lock savestating and loadstating + state = SavestateState.SAVING; + + // Create a directory just in case createSavestateDirectory(); - - //Enable tickrate 0 + + // Enable tickrate 0 TickrateChangerServer.changeServerTickrate(0); TickrateChangerServer.changeClientTickrate(0); - - //Update the server variable - server=TASmod.getServerInstance(); - - //Get the motion from the client + + // Update the server variable + server = TASmod.getServerInstance(); + + // Get the motion from the client ClientMotionServer.requestMotionFromClient(); - - //Save the world! + + // Save the world! server.getPlayerList().saveAllPlayerData(); server.saveAllWorlds(true); - - - //Get the current and target directory for copying - String worldname=server.getFolderName(); - File currentfolder=new File(savestateDirectory,".."+File.separator+worldname); - File targetfolder=getNextSaveFolderLocation(worldname); - - //Send the name of the world to all players. This will make a savestate of the recording on the client with that name - CommonProxy.NETWORK.sendToAll(new InputSavestatesPacket(true, nameWhenSaving(worldname))); - - //Wait for the chunkloader to save the game - for(WorldServer world:server.worlds) { - AnvilChunkLoader chunkloader=(AnvilChunkLoader)world.getChunkProvider().chunkLoader; - while(chunkloader.getPendingSaveCount()>0) { + + // Refreshing the index list + refresh(); + + // Setting the current index depending on the savestateIndex. + if (savestateIndex < 0) { + setCurrentIndex(currentIndex + 1); // If the savestateIndex <= 0, create a savestate at currentIndex+1 + } else { + setCurrentIndex(savestateIndex); + } + + // Get the current and target directory for copying + String worldname = server.getFolderName(); + File currentfolder = new File(savestateDirectory, ".." + File.separator + worldname); + File targetfolder = getSavestateFile(currentIndex); + + if (targetfolder.exists()) { + TASmod.logger.warn("WARNING! Overwriting the savestate with the index {}", currentIndex); + FileUtils.deleteDirectory(targetfolder); + } + + // Send the name of the world to all players. This will make a savestate of the + // recording on the client with that name + CommonProxy.NETWORK.sendToAll(new InputSavestatesPacket(true, getSavestateName(currentIndex))); + + // Wait for the chunkloader to save the game + for (WorldServer world : server.worlds) { + AnvilChunkLoader chunkloader = (AnvilChunkLoader) world.getChunkProvider().chunkLoader; + while (chunkloader.getPendingSaveCount() > 0) { } } - - //Copy the directory + + // Copy the directory FileUtils.copyDirectory(currentfolder, targetfolder); - - //Incrementing info file - SavestateTrackerFile tracker = new SavestateTrackerFile(new File(savestateDirectory, worldname+"-info.txt")); + + // Incrementing info file + SavestateTrackerFile tracker = new SavestateTrackerFile(new File(savestateDirectory, worldname + "-info.txt")); tracker.increaseSavestates(); tracker.saveFile(); - - //Close the GuiSavestateScreen on the client + + saveCurrentIndexToFile(); + + // Send a notification that the savestate has been loaded + server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + currentIndex + " saved")); + + // Close the GuiSavestateScreen on the client CommonProxy.NETWORK.sendToAll(new SavestatePacket()); - - //Unlock savestating - state=SavestateState.NONE; - } - - private static String nextSaveName(String worldname, int index) { - File[] listofFiles=savestateDirectory.listFiles(new FileFilter() { - @Override - public boolean accept(File pathname) { - return pathname.getName().startsWith(worldname); - } - - }); - if(index<0) { - } - return ""; + // Unlock savestating + state = SavestateState.NONE; } - - /** - * Searches through the savestate folder to look for the next possible savestate foldername
- * Savestate equivalent to {@link SavestateHandler#getLatestSavestateLocation(String)} - * @param worldname The worldname of the current world - * @return targetsavefolder The file where the savestate should be copied to - * @throws SavestateException if the found savestates count is greater or equal than 300 - */ - @Deprecated - private static File getNextSaveFolderLocation(String worldname) throws SavestateException { - int i = 1; - int limit=300; - File targetsavefolder=null; - while (i <= limit) { - if (i >= limit) { - throw new SavestateException("Savestatecount is greater or equal than "+limit); - } - targetsavefolder = new File(savestateDirectory,worldname + "-Savestate" + Integer.toString(i)+File.separator); - - if (!targetsavefolder.exists()) { - break; - } - i++; - } - return targetsavefolder; - } - + /** - * Get's the correct string of the savestate, used in {@linkplain InputSavestatesHandler#savestate(String)} + * Loads the latest savestate at {@linkplain #currentIndex} + * .minecraft/saves/savestates/worldname-Savestate[{@linkplain #currentIndex}] + * + * Side: Server * - * @param worldname the name of the world currently on the server - * @return The correct name of the next savestate + * @throws LoadstateException + * @throws IOException */ - @Deprecated - private static String nameWhenSaving(String worldname) { - int i = 1; - int limit=300; - File targetsavefolder=null; - String name=""; - while (i <= limit) { - if (i >= limit) { - break; - } - name=worldname + "-Savestate" + Integer.toString(i); - targetsavefolder = new File(savestateDirectory,name+File.separator); - - if (!targetsavefolder.exists()) { - break; - } - i++; - } - return name; + public void loadState() throws LoadstateException, IOException { + loadState(-1); } - + /** - * Loads the latest savestate it can find in .minecraft/saves/savestates/worldname-Savestate + * Loads the latest savestate it can find in + * .minecraft/saves/savestates/worldname-Savestate * * Side: Server + * * @throws LoadstateException * @throws IOException */ - public static void loadState() throws LoadstateException, IOException { - if(state==SavestateState.SAVING) { + public void loadState(int savestateIndex) throws LoadstateException, IOException { + if (state == SavestateState.SAVING) { throw new LoadstateException("A savestating operation is already being carried out"); } - if(state==SavestateState.LOADING) { + 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 + // Lock savestating and loadstating + state = SavestateState.LOADING; + + // Create a directory just in case createSavestateDirectory(); - - //Enable tickrate 0 + + // Enable tickrate 0 TickrateChangerServer.changeServerTickrate(0); TickrateChangerServer.changeClientTickrate(0); - - - //Update the server instance - server=TASmod.getServerInstance(); - - //Get the current and target directory for copying - String worldname=server.getFolderName(); - File currentfolder=new File(savestateDirectory,".."+File.separator+worldname); - File targetfolder=getLatestSavestateLocation(worldname); - - //Load savestate on the client - CommonProxy.NETWORK.sendToAll(new InputSavestatesPacket(false, nameWhenLoading(worldname))); - - //Disabeling level saving for all worlds in case the auto save kicks in during world unload - for(WorldServer world: server.worlds) { - world.disableLevelSaving=true; - } - - //Unload chunks on the client + + // Update the server instance + server = TASmod.getServerInstance(); + + refresh(); + + int indexToLoad = savestateIndex < 0 ? currentIndex : savestateIndex; + + if (!getSavestateFile(indexToLoad).exists()) { + throw new LoadstateException("Savestate " + indexToLoad + " doesn't exist"); + } else { + setCurrentIndex(indexToLoad); + } + + // Get the current and target directory for copying + String worldname = server.getFolderName(); + File currentfolder = new File(savestateDirectory, ".." + File.separator + worldname); + File targetfolder = getSavestateFile(currentIndex); + + // Load savestate on the client + CommonProxy.NETWORK.sendToAll(new InputSavestatesPacket(false, getSavestateName(currentIndex))); + + // Disabeling level saving for all worlds in case the auto save kicks in during + // world unload + for (WorldServer world : server.worlds) { + world.disableLevelSaving = true; + } + + // Unload chunks on the client CommonProxy.NETWORK.sendToAll(new LoadstatePacket()); - - //Unload chunks on the server + + // Unload chunks on the server SavestatesChunkControl.disconnectPlayersFromChunkMap(); SavestatesChunkControl.unloadAllServerChunks(); SavestatesChunkControl.flushSaveHandler(); - - - //Delete and copy directories + + // Delete and copy directories FileUtils.deleteDirectory(currentfolder); FileUtils.copyDirectory(targetfolder, currentfolder); - - - //Update the player and the client + + // Update the player and the client SavestatePlayerLoading.loadAndSendMotionToPlayer(); - //Update the session.lock file so minecraft behaves and saves the world + // Update the session.lock file so minecraft behaves and saves the world SavestatesChunkControl.updateSessionLock(); - //Load the chunks and send them to the client + // Load the chunks and send them to the client SavestatesChunkControl.addPlayersToChunkMap(); - - - //Enable level saving again - for(WorldServer world: server.worlds) { - world.disableLevelSaving=false; - } - - //Incrementing info file - SavestateTrackerFile tracker = new SavestateTrackerFile(new File(savestateDirectory, worldname+"-info.txt")); + + // Enable level saving again + for (WorldServer world : server.worlds) { + world.disableLevelSaving = false; + } + + // Incrementing info file + SavestateTrackerFile tracker = new SavestateTrackerFile(new File(savestateDirectory, worldname + "-info.txt")); tracker.increaseRerecords(); tracker.saveFile(); - - //Send a notification that the savestate has been loaded - server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN+"Savestate loaded")); - + + saveCurrentIndexToFile(); + + // Send a notification that the savestate has been loaded + server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + currentIndex + " loaded")); + WorldServer[] worlds = DimensionManager.getWorlds(); - - for(WorldServer world : worlds) { - world.tick(); - } - - //Unlock loadstating - state=SavestateState.WASLOADING; + + for (WorldServer world : worlds) { + world.tick(); + } + + // Unlock loadstating + state = SavestateState.WASLOADING; } - + /** - * Searches through the savestate folder to look for the latest savestate
- * Loadstate equivalent to {@link SavestateHandler#getNextSaveFolderLocation(String)} - * @param worldname - * @return targetsavefolder - * @throws LoadstateException if there is no savestate or more than 300 savestates + * Creates the savestate directory in case the user deletes it between + * savestates */ - private static File getLatestSavestateLocation(String worldname) throws LoadstateException { - int i=1; - int limit=300; - - File targetsavefolder=null; - while(i<=300) { - targetsavefolder = new File(savestateDirectory,worldname+"-Savestate"+Integer.toString(i)); - if (!targetsavefolder.exists()) { - if(i-1==0) { - throw new LoadstateException("Couldn't find any savestates"); - } - if(i>300) { - throw new LoadstateException("Savestatecount is greater or equal than "+limit); + private void createSavestateDirectory() { + if (!server.isDedicatedServer()) { + savestateDirectory = new File(server.getDataDirectory() + File.separator + "saves" + File.separator + "savestates" + File.separator); + } else { + savestateDirectory = new File(server.getDataDirectory() + File.separator + "savestates" + File.separator); + } + if (!savestateDirectory.exists()) { + savestateDirectory.mkdir(); + } + } + + private void refresh() { + indexList.clear(); + File[] files = savestateDirectory.listFiles(new FileFilter() { + + @Override + public boolean accept(File pathname) { + return pathname.getName().startsWith(server.getFolderName() + "-Savestate"); + } + + }); + int index = 0; + for (File file : files) { + try { + Pattern patt = Pattern.compile("\\d+$"); + Matcher matcher = patt.matcher(file.getName()); + if (matcher.find()) { + index = Integer.parseInt(matcher.group(0)); + } else { + TASmod.logger.warn(String.format("Could not process the savestate %s", file.getName())); + continue; } - targetsavefolder = new File(savestateDirectory,worldname+"-Savestate"+Integer.toString(i-1)); - break; + } catch (NumberFormatException e) { + TASmod.logger.warn(String.format("Could not process the savestate %s", e.getMessage())); + continue; } - i++; + indexList.add(index); + } + Collections.sort(indexList); + if (!indexList.isEmpty()) { + latestIndex = indexList.get(indexList.size() - 1); + } else { + latestIndex = 0; } - return targetsavefolder; } - + + /** + * @param index The index of the savestate file that we want to get + * @return The file of the savestate from the specified index + */ + private File getSavestateFile(int index) { + return new File(savestateDirectory, getSavestateName(index)); + } + /** - * Get's the correct string of the loadstate, used in {@linkplain InputSavestatesHandler#loadstate(String)} + * @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 worldname the name of the world currently on the server - * @return The correct name of the next loadstate + * @param index The index of the savestate that should be deleted + * @throws SavestateDeleteException */ - private static String nameWhenLoading(String worldname) throws LoadstateException { - int i=1; - int limit=300; - String name=""; - File targetsavefolder=null; - while(i<=300) { - targetsavefolder = new File(savestateDirectory,worldname+"-Savestate"+Integer.toString(i)); - if (!targetsavefolder.exists()) { - if(i-1==0) { - throw new LoadstateException("Couldn't find any savestates"); - } - if(i>300) { - throw new LoadstateException("Savestatecount is greater or equal than "+limit); - } - - name=worldname+"-Savestate"+Integer.toString(i-1); - break; + public void deleteSavestate(int index) throws SavestateDeleteException { + 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"); + } + File toDelete = getSavestateFile(index); + if (toDelete.exists()) { + try { + FileUtils.deleteDirectory(toDelete); + } catch (IOException e) { + e.printStackTrace(); + throw new SavestateDeleteException("Something went wrong while trying to delete the savestate " + index); } - i++; + } else { + throw new SavestateDeleteException(TextFormatting.YELLOW + "Savestate " + index + " doesn't exist, so it can't be deleted"); } - return name; + 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")); } /** - * Creates the savestate directory in case the user deletes it between savestates + * Deletes savestates in a range from "from" to "to" + * + * @param from + * @param to (exclusive) + * @throws SavestateDeleteException */ - private static void createSavestateDirectory() { - if(!server.isDedicatedServer()) { - savestateDirectory=new File(server.getDataDirectory()+File.separator+"saves"+File.separator+"savestates"+File.separator); - }else { - savestateDirectory=new File(server.getDataDirectory()+File.separator+"savestates"+File.separator); + public void deleteSavestate(int from, int to) throws SavestateDeleteException { + if (state == SavestateState.SAVING) { + throw new SavestateDeleteException("A savestating operation is already being carried out"); } - if(!savestateDirectory.exists()) { - savestateDirectory.mkdir(); + 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++) { + if (i == 0) { + continue; + } + try { + deleteSavestate(i); + } catch (SavestateDeleteException e) { + server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.RED + e.getMessage())); + continue; + } + } + } + + 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) + */ + private void saveCurrentIndexToFile() { + File tasmodDir = new File(savestateDirectory, "../" + server.getFolderName() + "/tasmod/"); + if (!tasmodDir.exists()) { + tasmodDir.mkdir(); } + File savestateDat = new File(tasmodDir, "savestate.data"); + List lines = new ArrayList(); + lines.add("currentIndex=" + currentIndex); + try { + FileUtils.writeLines(savestateDat, lines); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Loads the current index to the current world-folder (not the savestate + * folder) + */ + public void loadCurrentIndexFromFile() { + int index = -1; + List lines = new ArrayList(); + File tasmodDir = new File(savestateDirectory, "../" + server.getFolderName() + "/tasmod/"); + if (!tasmodDir.exists()) { + tasmodDir.mkdir(); + } + File savestateDat = new File(tasmodDir, "savestate.data"); + try { + lines = FileUtils.readLines(savestateDat, StandardCharsets.UTF_8); + } catch (IOException e) { + TASmod.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(); + } + } + } + } + setCurrentIndex(index); } - + + private void setCurrentIndex(int index) { + if (index < 0) { + currentIndex = latestIndex; + } else { + currentIndex = index; + } + TASmod.logger.info("Setting the savestate index to {}", currentIndex); + } + + public int getCurrentIndex() { + return currentIndex; + } + /** - * Event, that gets executed after a loadstate operation was carried out, get's called on the server side + * Event, that gets executed after a loadstate operation was carried out, get's + * called on the server side */ public static void playerLoadSavestateEventServer() { - EntityPlayerMP player=server.getPlayerList().getPlayers().get(0); - NBTTagCompound nbttagcompound = server.getPlayerList().getPlayerNBT(player); - SavestatePlayerLoading.reattachEntityToPlayer(nbttagcompound, player.getServerWorld(), player); + PlayerList playerList = TASmod.getServerInstance().getPlayerList(); + for (EntityPlayerMP player : playerList.getPlayers()) { + NBTTagCompound nbttagcompound = playerList.getPlayerNBT(player); + SavestatePlayerLoading.reattachEntityToPlayer(nbttagcompound, player.getServerWorld(), player); + } } - + @SideOnly(Side.CLIENT) public static void playerLoadSavestateEventClient() { SavestatesChunkControl.addPlayerToChunk(Minecraft.getMinecraft().player); diff --git a/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestatePacket.java b/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestatePacket.java index 1da8489a..e886e2f1 100644 --- a/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestatePacket.java +++ b/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestatePacket.java @@ -15,15 +15,32 @@ */ public class SavestatePacket implements IMessage{ + public int index; + + /** + * Make a savestate at the next index + */ public SavestatePacket() { + index=-1; + } + + /** + * Make a savestate at the specified index + * + * @param index The index where to make a savestate + */ + public SavestatePacket(int index) { + this.index=index; } @Override public void fromBytes(ByteBuf buf) { + index=buf.readInt(); } @Override public void toBytes(ByteBuf buf) { + buf.writeInt(index); } } diff --git a/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestatePacketHandler.java b/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestatePacketHandler.java index f63bcd87..3f3cc26f 100644 --- a/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestatePacketHandler.java +++ b/src/main/java/de/scribble/lp/tasmod/savestates/server/SavestatePacketHandler.java @@ -1,5 +1,6 @@ package de.scribble.lp.tasmod.savestates.server; +import de.scribble.lp.tasmod.TASmod; import de.scribble.lp.tasmod.savestates.server.exceptions.SavestateException; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.util.text.TextComponentString; @@ -30,7 +31,7 @@ public IMessage onMessage(SavestatePacket message, MessageContext ctx) { return; } try { - SavestateHandler.saveState(-1); + TASmod.savestateHandler.saveState(message.index); } catch (SavestateException e) { player.sendMessage(new TextComponentString(TextFormatting.RED+"Failed to create a savestate: "+ e.getMessage())); @@ -38,11 +39,11 @@ public IMessage onMessage(SavestatePacket message, MessageContext ctx) { e.printStackTrace(); player.sendMessage(new TextComponentString(TextFormatting.RED+"Failed to create a savestate: "+ e.getCause().toString())); } finally { - SavestateHandler.state=SavestateState.NONE; + TASmod.savestateHandler.state=SavestateState.NONE; } }); }else { - net.minecraft.client.Minecraft mc=net.minecraft.client.Minecraft.getMinecraft(); + net.minecraft.client.Minecraft mc=net.minecraft.client.Minecraft.getMinecraft(); //Forge will think this is executed on the server for some reason... workaround(mc); } return null; diff --git a/src/main/java/de/scribble/lp/tasmod/savestates/server/exceptions/SavestateDeleteException.java b/src/main/java/de/scribble/lp/tasmod/savestates/server/exceptions/SavestateDeleteException.java new file mode 100644 index 00000000..c3877f1f --- /dev/null +++ b/src/main/java/de/scribble/lp/tasmod/savestates/server/exceptions/SavestateDeleteException.java @@ -0,0 +1,12 @@ +package de.scribble.lp.tasmod.savestates.server.exceptions; + +public class SavestateDeleteException extends Exception { + /** + * + */ + private static final long serialVersionUID = -3117656644168896404L; + + public SavestateDeleteException(String s) { + super(s); + } +} diff --git a/src/main/java/de/scribble/lp/tasmod/savestates/server/playerloading/SavestatePlayerLoadingPacketHandler.java b/src/main/java/de/scribble/lp/tasmod/savestates/server/playerloading/SavestatePlayerLoadingPacketHandler.java index a7467e0c..0ca90e7d 100644 --- a/src/main/java/de/scribble/lp/tasmod/savestates/server/playerloading/SavestatePlayerLoadingPacketHandler.java +++ b/src/main/java/de/scribble/lp/tasmod/savestates/server/playerloading/SavestatePlayerLoadingPacketHandler.java @@ -1,5 +1,6 @@ package de.scribble.lp.tasmod.savestates.server.playerloading; +import de.pfannekuchen.tasmod.events.CameraInterpolationEvents; import de.scribble.lp.tasmod.savestates.server.chunkloading.SavestatesChunkControl; import net.minecraft.client.Minecraft; import net.minecraft.nbt.NBTTagCompound; @@ -61,6 +62,10 @@ private void workaround(SavestatePlayerLoadingPacket message) { GameType type=GameType.getByID(gamemode); Minecraft.getMinecraft().playerController.setGameType(type); + //TAS#?? Player rotation does not change when loading a savestate + CameraInterpolationEvents.rotationPitch=player.rotationPitch; + CameraInterpolationEvents.rotationYaw=player.rotationYaw+180f; + SavestatesChunkControl.keepPlayerInLoadedEntityList(player); SavestatePlayerLoading.wasLoading = true; } diff --git a/src/main/java/de/scribble/lp/tasmod/virtual/VirtualInput.java b/src/main/java/de/scribble/lp/tasmod/virtual/VirtualInput.java index 37ee1bfd..c286725e 100644 --- a/src/main/java/de/scribble/lp/tasmod/virtual/VirtualInput.java +++ b/src/main/java/de/scribble/lp/tasmod/virtual/VirtualInput.java @@ -179,7 +179,6 @@ public void clearNextKeyboard() { public boolean isKeyDown(int keycode) { if (keycode >= 0) return currentKeyboard.get(keycode).isKeyDown(); - else return currentMouse.get(keycode).isKeyDown(); } @@ -197,7 +196,6 @@ public boolean isKeyDown(String keyname) { public boolean willKeyBeDown(int keycode) { if (keycode >= 0) return nextKeyboard.get(keycode).isKeyDown(); - else return nextMouse.get(keycode).isKeyDown(); } @@ -264,15 +262,18 @@ public VirtualMouse getNextMouse() { public void updateNextMouse(int keycode, boolean keystate, int scrollwheel, int cursorX, int cursorY, boolean filter) { - if (VirtualKeybindings.isKeyCodeAlwaysBlocked(keycode)) { - return; - } boolean flag = true; if (filter) { flag = nextMouse.isSomethingDown() || scrollwheel != 0 || keycode != -1; } + VirtualKey key = nextMouse.get(keycode - 100); + if (VirtualKeybindings.isKeyCodeAlwaysBlocked(keycode - 100)) { + key.setPressed(false); + return; + } + key.setPressed(keystate); nextMouse.setScrollWheel(scrollwheel); @@ -427,34 +428,37 @@ public void setContainer(InputContainer container) { public void loadSavestate(InputContainer savestatecontainer) { if (this.container.isPlayingback()) { - preloadInput(this.container, savestatecontainer.size() - 1); // Preloading from the current container and from the second to last index of + preloadInput(this.container, savestatecontainer.size() - 1); // Preloading from the current container and from the second to last index of // the savestatecontainer. Since this is executed during playback, // we will only load the position of the savestate container and not replace the // container itself - this.container.setIndex(savestatecontainer.size()); // Set the "playback" index of the current container to the latest index of the - // savestatecontainer. Meaning this index will be played next + this.container.setIndex(savestatecontainer.size()); // Set the "playback" index of the current container to the latest index of the + // savestatecontainer. Meaning this index will be played next } else if (this.container.isRecording()) { - String start = savestatecontainer.getStartLocation(); // TODO Another start location thing to keep in mind - preloadInput(savestatecontainer, savestatecontainer.size() - 1); // Preload the input of the savestate + String start = savestatecontainer.getStartLocation(); // TODO Another start location thing to keep in mind + preloadInput(savestatecontainer, savestatecontainer.size() - 1); // Preload the input of the savestate - nextKeyboard = new VirtualKeyboard(); // Unpress the nextKeyboard and mouse to get rid of the preloaded inputs in the + nextKeyboard = new VirtualKeyboard(); // Unpress the nextKeyboard and mouse to get rid of the preloaded inputs in the // next keyboard. Note that these inputs are still loaded in the current // keyboard nextMouse = new VirtualMouse(); savestatecontainer.setIndex(savestatecontainer.size()); savestatecontainer.setTASState(TASstate.RECORDING); - savestatecontainer.setStartLocation(start); //TODO Another one - this.container = savestatecontainer; //Replace the current container with the savestated container + savestatecontainer.setStartLocation(start); // TODO Another one + this.container = savestatecontainer; // Replace the current container with the savestated container } } /** - * Preloads the specified index from, the container to {@link #nextMouse} and {@link #nextKeyboard} + * Preloads the specified index from, the container to {@link #nextMouse} and + * {@link #nextKeyboard} + * * @param container The container from which the inputs should be pre loaded - * @param index The index of the container from which the inputs should be loaded + * @param index The index of the container from which the inputs should be + * loaded */ private void preloadInput(InputContainer container, int index) { TickInputContainer tickcontainer = container.get(index); @@ -464,7 +468,8 @@ private void preloadInput(InputContainer container, int index) { nextKeyboard = tickcontainer.getKeyboard().clone(); nextMouse = tickcontainer.getMouse().clone(); - ((AccessorRunStuff) Minecraft.getMinecraft()).runTickKeyboardAccessor(); // Letting mouse and keyboard tick once to load inputs into the "currentKeyboard" + ((AccessorRunStuff) Minecraft.getMinecraft()).runTickKeyboardAccessor(); // Letting mouse and keyboard tick once to load inputs into the + // "currentKeyboard" ((AccessorRunStuff) Minecraft.getMinecraft()).runTickMouseAccessor(); } else { TASmod.logger.warn("Can't preload inputs, specified inputs are null!"); @@ -490,25 +495,30 @@ public InputEvent(int tick, List keyboardevent, List *
- * Container and input events differ in that input events are the events that get accepted by Minecraft in the runTickKeyboard.
- * The container however stores the current inputs and can calculate the corresponding input events from that, but it does it only when you are playing back or recording.
+ * Container and input events differ in that input events are the events that + * get accepted by Minecraft in the runTickKeyboard.
+ * The container however stores the current inputs and can calculate the + * corresponding input events from that, but it does it only when you are + * playing back or recording.
*
- * This however runs through the {@link VirtualInput#container} and generates the input events on for debug purposes. + * This however runs through the {@link VirtualInput#container} and generates + * the input events on for debug purposes. + * * @return */ public List getAllInputEvents() { - + List main = new ArrayList<>(); - + for (int i = 0; i < container.size(); i++) { - + TickInputContainer tick = container.get(i); TickInputContainer nextTick = container.get(i + 1); - + if (nextTick == null) { - nextTick = new TickInputContainer(i + 1); //Fills the last tick in the container with an empty TickInputContainer + nextTick = new TickInputContainer(i + 1); // Fills the last tick in the container with an empty TickInputContainer } - + VirtualKeyboard keyboard = tick.getKeyboard(); List keyboardEventsList = keyboard.getDifference(nextTick.getKeyboard()); diff --git a/src/main/java/de/scribble/lp/tasmod/virtual/VirtualKeybindings.java b/src/main/java/de/scribble/lp/tasmod/virtual/VirtualKeybindings.java index bf17fe53..877e4b5d 100644 --- a/src/main/java/de/scribble/lp/tasmod/virtual/VirtualKeybindings.java +++ b/src/main/java/de/scribble/lp/tasmod/virtual/VirtualKeybindings.java @@ -5,6 +5,7 @@ import java.util.List; import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; import com.google.common.collect.Maps; @@ -18,62 +19,71 @@ /** * Applies special rules to vanilla keybindings.
*
- * Using {@link #isKeyDown(KeyBinding)}, the registered keybindings will work inside of gui screens
+ * Using {@link #isKeyDown(KeyBinding)}, the registered keybindings will work + * inside of gui screens
*
- * {@link #isKeyDownExceptTextfield(KeyBinding)} does the same, but excludes textfields, certain guiscreens, and the keybinding options
+ * {@link #isKeyDownExceptTextfield(KeyBinding)} does the same, but excludes + * textfields, certain guiscreens, and the keybinding options
*
- * Keybindings registered with {@link #registerBlockedKeyBinding(KeyBinding)} will not be recorded during a recording or pressed in a playback + * Keybindings registered with {@link #registerBlockedKeyBinding(KeyBinding)} + * will not be recorded during a recording or pressed in a playback * * @author ScribbleLP * */ public class VirtualKeybindings { private static Minecraft mc = Minecraft.getMinecraft(); - private static long cooldown = 20; + private static long cooldown = 50*5; private static HashMap cooldownHashMap = Maps.newHashMap(); private static List blockedKeys = new ArrayList<>(); - private static long cooldowntimer = 0; public static boolean focused = false; - public static void increaseCooldowntimer() { - cooldowntimer++; - } /** * Checks whether the keycode is pressed, regardless of any gui screens + * * @param keybind * @return */ public static boolean isKeyDown(KeyBinding keybind) { + + int keycode = keybind.getKeyCode(); + + boolean down = false; + + if(mc.currentScreen instanceof GuiControls) { + return false; + } - int keycode=keybind.getKeyCode(); - boolean down = isKeyCodeAlwaysBlocked(keycode) ? Keyboard.isKeyDown(keycode) : ClientProxy.virtual.willKeyBeDown(keycode); - + if (isKeyCodeAlwaysBlocked(keycode)) { + down = keycode >= 0 ? Keyboard.isKeyDown(keycode) : Mouse.isButtonDown(keycode + 100); + } else { + down = ClientProxy.virtual.willKeyBeDown(keycode); + } + if (down) { if (cooldownHashMap.containsKey(keybind)) { - if (cooldown <= cooldowntimer - (long) cooldownHashMap.get(keybind)) { - cooldownHashMap.put(keybind, cooldowntimer); - cooldown=Minecraft.getDebugFPS()/3; + if (cooldown <= Minecraft.getSystemTime() - (long) cooldownHashMap.get(keybind)) { + cooldownHashMap.put(keybind, Minecraft.getSystemTime()); return true; } return false; } else { - cooldownHashMap.put(keybind, cooldowntimer); - cooldown=Minecraft.getDebugFPS()/3; + cooldownHashMap.put(keybind, Minecraft.getSystemTime()); return true; } } return false; } - + /** * Checks whether the key is down, but stops when certain conditions apply * - * @param keybind + * @param keybind * @return */ public static boolean isKeyDownExceptTextfield(KeyBinding keybind) { - if (mc.currentScreen instanceof GuiChat || mc.currentScreen instanceof GuiEditSign || (focused && mc.currentScreen != null) || mc.currentScreen instanceof GuiControls) { + if (mc.currentScreen instanceof GuiChat || mc.currentScreen instanceof GuiEditSign || (focused && mc.currentScreen != null)) { return false; } return isKeyDown(keybind); @@ -81,14 +91,16 @@ public static boolean isKeyDownExceptTextfield(KeyBinding keybind) { /** * Registers keybindings that should not be recorded or played back in a TAS + * * @param keybind */ public static void registerBlockedKeyBinding(KeyBinding keybind) { blockedKeys.add(keybind); } - + /** * Checks whether the keycode should not be recorded or played back in a TAS + * * @param keycode to block * @return Whether it should be blocked */ @@ -99,5 +111,5 @@ public static boolean isKeyCodeAlwaysBlocked(int keycode) { } return false; } - + } diff --git a/src/main/java/de/scribble/lp/tasmod/virtual/VirtualMouse.java b/src/main/java/de/scribble/lp/tasmod/virtual/VirtualMouse.java index 2abaef03..f284c242 100644 --- a/src/main/java/de/scribble/lp/tasmod/virtual/VirtualMouse.java +++ b/src/main/java/de/scribble/lp/tasmod/virtual/VirtualMouse.java @@ -54,19 +54,19 @@ public VirtualMouse() { keyList.put(-100, new VirtualKey("LC", -100)); keyList.put(-99, new VirtualKey("RC", -99)); keyList.put(-98, new VirtualKey("MC", -98)); - keyList.put(-97, new VirtualKey("MBUTTON3", -97)); - keyList.put(-96, new VirtualKey("MBUTTON4", -96)); - keyList.put(-95, new VirtualKey("MBUTTON5", -95)); - keyList.put(-94, new VirtualKey("MBUTTON6", -94)); - keyList.put(-93, new VirtualKey("MBUTTON7", -93)); - keyList.put(-92, new VirtualKey("MBUTTON8", -92)); - keyList.put(-91, new VirtualKey("MBUTTON9", -91)); - keyList.put(-90, new VirtualKey("MBUTTON10", -90)); - keyList.put(-89, new VirtualKey("MBUTTON11", -89)); - keyList.put(-88, new VirtualKey("MBUTTON12", -88)); - keyList.put(-87, new VirtualKey("MBUTTON13", -87)); - keyList.put(-86, new VirtualKey("MBUTTON14", -86)); - keyList.put(-85, new VirtualKey("MBUTTON15", -85)); + keyList.put(-97, new VirtualKey("MBUTTON4", -97)); + keyList.put(-96, new VirtualKey("MBUTTON5", -96)); + keyList.put(-95, new VirtualKey("MBUTTON6", -95)); + keyList.put(-94, new VirtualKey("MBUTTON7", -94)); + keyList.put(-93, new VirtualKey("MBUTTON8", -93)); + keyList.put(-92, new VirtualKey("MBUTTON9", -92)); + keyList.put(-91, new VirtualKey("MBUTTON10", -91)); + keyList.put(-90, new VirtualKey("MBUTTON11", -90)); + keyList.put(-89, new VirtualKey("MBUTTON12", -89)); + keyList.put(-88, new VirtualKey("MBUTTON13", -88)); + keyList.put(-87, new VirtualKey("MBUTTON14", -87)); + keyList.put(-86, new VirtualKey("MBUTTON15", -86)); + keyList.put(-85, new VirtualKey("MBUTTON16", -85)); this.scrollwheel = 0; diff --git a/src/main/resources/mixins.tasmod.json b/src/main/resources/mixins.tasmod.json index e84921c1..93628ffa 100644 --- a/src/main/resources/mixins.tasmod.json +++ b/src/main/resources/mixins.tasmod.json @@ -17,8 +17,9 @@ //Events "events.MixinPlayerList", - //Fixing forge stuff - "MixinDragonFightManager" + //Fixing forge and vanilla stuff + "fixes.MixinDragonFightManager", + "fixes.MixinMinecraftFullscreen" ], "client": [