diff --git a/src/main/java/com/minecrafttas/tasmod/TASmod.java b/src/main/java/com/minecrafttas/tasmod/TASmod.java index 69fb8eb3..6cb78d10 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmod.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmod.java @@ -29,6 +29,7 @@ import com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.SavestateHandlerServer; +import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler; import com.minecrafttas.tasmod.savestates.storage.builtin.SavestateMotionStorage; import com.minecrafttas.tasmod.tickratechanger.TickrateChangerServer; import com.minecrafttas.tasmod.ticksync.TickSyncServer; @@ -119,6 +120,9 @@ public void onInitialize() { 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); } diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java index 7abd7317..63d66b46 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java @@ -42,17 +42,41 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args } else if (args.length >= 1) { if ("save".equals(args[0])) { if (args.length == 1) { - saveLatest(); + TASmod.gameLoopSchedulerServer.add(() -> { + try { + saveLatest(); + } catch (CommandException e) { + e.printStackTrace(); + } + }); } else if (args.length == 2) { - saveWithIndex(args); + 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) { - loadLatest(); + TASmod.gameLoopSchedulerServer.add(() -> { + try { + loadLatest(); + } catch (CommandException e) { + e.printStackTrace(); + } + }); } else if (args.length == 2) { - loadLatest(args); + TASmod.gameLoopSchedulerServer.add(() -> { + try { + loadLatest(args); + } catch (CommandException e) { + e.printStackTrace(); + } + }); } else { throw new CommandException("Too many arguments!", new Object[] {}); } diff --git a/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraft.java b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraft.java new file mode 100644 index 00000000..2662e459 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraft.java @@ -0,0 +1,21 @@ +package com.minecrafttas.tasmod.mixin.savestates; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler; + +import net.minecraft.client.Minecraft; + +@Mixin(Minecraft.class) +public class MixinMinecraft { + + @Inject(method = "refreshResources", at = @At(value = "RETURN")) + public void inject_refreshResources(CallbackInfo ci) { + if (SavestateResourcePackHandler.clientRPLatch != null && SavestateResourcePackHandler.clientRPLatch.getCount() > 0) { + SavestateResourcePackHandler.clientRPLatch.countDown(); + } + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java b/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java index 7c8b2094..42f4e093 100644 --- a/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java +++ b/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java @@ -104,6 +104,12 @@ public enum TASmodPackets implements PacketID { Minecraft mc = Minecraft.getMinecraft(); ((ScoreboardDuck) mc.world.getScoreboard()).clearScoreboard(); }), + /** + *

Clears the resourcepack on the client side + *

SIDE: Client
+ * ARGS: none + */ + SAVESTATE_CLEAR_RESOURCEPACK, /** *

Notifies the client to clear all inputs from the input buffer in {@link PlaybackControllerClient} *

SIDE: Both
diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index 387078c5..05f91369 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -40,6 +40,7 @@ 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.SavestateResourcePackHandler; import com.minecrafttas.tasmod.savestates.handlers.SavestateWorldHandler; import com.minecrafttas.tasmod.util.LoggerMarkers; import com.minecrafttas.tasmod.util.Scheduler.Task; @@ -386,6 +387,9 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex // 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(); @@ -753,9 +757,9 @@ private int legacyIndexFile(Path savestateDat) { public PacketID[] getAcceptedPacketIDs() { return new TASmodPackets[] { //@formatter:off - TASmodPackets.SAVESTATE_SAVE, - TASmodPackets.SAVESTATE_LOAD, - TASmodPackets.SAVESTATE_SCREEN, + TASmodPackets.SAVESTATE_SAVE, + TASmodPackets.SAVESTATE_LOAD, + TASmodPackets.SAVESTATE_SCREEN, TASmodPackets.SAVESTATE_UNLOAD_CHUNKS //@formatter:on }; @@ -770,7 +774,7 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws switch (packet) { case SAVESTATE_SAVE: - Integer index = TASmodBufferBuilder.readInt(buf); + int index = TASmodBufferBuilder.readInt(buf); Task savestateTask = () -> { try { diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiResourcepackWarn.java b/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiResourcepackWarn.java new file mode 100644 index 00000000..bcf3f560 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiResourcepackWarn.java @@ -0,0 +1,43 @@ +package com.minecrafttas.tasmod.savestates.gui; + +import com.minecrafttas.tasmod.util.MessageUtils; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.resources.I18n; + +/** + * Screen for warning the player that a "reources.zip" is present in the world folder,
+ * which significantly slows down savestates + */ +public class GuiResourcepackWarn extends GuiScreen { + + /** + * Screen for warning the player that a "reources.zip" is present in the world folder,
+ * which significantly slows down savestates + */ + public GuiResourcepackWarn() { + 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(); + + MessageUtils.splitNewline(I18n.format("gui.tasmod.savestate.resourcepack"), 15, (line, y) -> { + drawCenteredString(fontRenderer, line, width / 2, height / 4 + 40 + y, 0xFF5555); + }); + + super.drawScreen(mouseX, mouseY, partialTicks); + } + + @Override + public boolean doesGuiPauseGame() { + return false; + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java new file mode 100644 index 00000000..520280b5 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java @@ -0,0 +1,162 @@ +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; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +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; +import com.minecrafttas.tasmod.TASmodClient; +import com.minecrafttas.tasmod.events.EventSavestate; +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.GuiResourcepackWarn; +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.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; + +/** + * Handles reloading server resourcepacks when loadstating. + * + * @author Scribble + */ +public class SavestateResourcePackHandler implements EventSavestate.EventServerLoadstate, ServerPacketHandler, ClientPacketHandler { + + /** + * The server future for waiting until the client is done unloading the RP + */ + private CompletableFuture serverRPFuture; + + /** + * The latch for waiting until the client RP is unloaded + */ + public static CountDownLatch clientRPLatch; + + @Override + public void onServerLoadstate(MinecraftServer server, int index, Path target, Path current) { + if (server.getResourcePackUrl().isEmpty() || server.isDedicatedServer()) + return; + + String serverOwnerName = server.getServerOwner(); + + try { + TASmod.server.sendTo(serverOwnerName, new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK)); + } catch (Exception e) { + TASmod.LOGGER.catching(e); + } + serverRPFuture = new CompletableFuture<>(); + + String playername = null; + try { + playername = serverRPFuture.get(2L, TimeUnit.MINUTES); + } catch (TimeoutException e) { + throw new SavestateException(e, "Clearing resourcepacks %s timed out!", serverOwnerName); + } catch (ExecutionException | InterruptedException e) { + throw new SavestateException(e, "Clearing resourcepacks %s", serverOwnerName); + } + + server.setResourcePack("", ""); + TASmod.LOGGER.debug(LoggerMarkers.Savestate, "Cleared resourcepack for player {}", playername); + } + + @Override + public PacketID[] getAcceptedPacketIDs() { + return new TASmodPackets[] { TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK }; + } + + @Environment(EnvType.CLIENT) + @Override + public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception { + TASmodPackets packetId = (TASmodPackets) id; + + Minecraft mc = Minecraft.getMinecraft(); + switch (packetId) { + case SAVESTATE_CLEAR_RESOURCEPACK: + + TASmod.LOGGER.debug(LoggerMarkers.Savestate, "Clearing server resource pack"); + + mc.displayGuiScreen(new GuiResourcepackWarn()); + + /** + * Using a countdown latch here, which is counted down in + * savestates.MixinMinecraft. + * + * Clearing the resourcepack is scheduled multiple times + * so for simplicity, I use a latch here. + */ + clientRPLatch = new CountDownLatch(1); + mc.getResourcePackRepository().clearResourcePack(); + + try { + clientRPLatch.await(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + /** + * At this point, "clearResourcePack" did remove the server RP + * however, the file association with the "resources.zip" in the + * save folder is still there, which causes loadstating to fail, + * as the system still thinks that the RP is still "in use". + * + * We have to run the garbage collector to remove it. + */ + System.gc(); + + /** + * Notify the server that savestates have been cleared and that savestating can continue + */ + try { + TASmodClient.client.send(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK)); + } catch (Exception e) { + TASmod.LOGGER.catching(e); + } + break; + + default: + throw new WrongSideException(packetId, Side.CLIENT); + } + } + + @Override + public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception { + TASmodPackets packetId = (TASmodPackets) id; + + switch (packetId) { + case SAVESTATE_CLEAR_RESOURCEPACK: + serverRPFuture.complete(username); + break; + + default: + throw new WrongSideException(packetId, Side.SERVER); + } + } + + /** + * Notifies all clients that a new server resourcepack should be downloaded if available + * + * @param server The Minecraft server + */ + public static void refreshServerResourcepack(MinecraftServer server) { + TASmod.LOGGER.debug(LoggerMarkers.Savestate, "Refreshing resourcepack"); + List players = server.getPlayerList().getPlayers(); + players.forEach((player) -> { + player.loadResourcePack(server.getResourcePackUrl(), server.getResourcePackHash()); + }); + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/util/MessageUtils.java b/src/main/java/com/minecrafttas/tasmod/util/MessageUtils.java new file mode 100644 index 00000000..6fc5c3ae --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/util/MessageUtils.java @@ -0,0 +1,23 @@ +package com.minecrafttas.tasmod.util; + +public class MessageUtils { + + public static void splitNewline(String phrase, GuiInterface method) { + splitNewline(phrase, 10, method); + } + + public static void splitNewline(String phrase, int distance, GuiInterface method) { + int y = 0; + String[] lines = phrase.split("\r?\n"); + for (String line : lines) { + method.draw(line, y); + y += distance; + } + } + + @FunctionalInterface + public interface GuiInterface { + + public void draw(String line, int y); + } +} diff --git a/src/main/resources/assets/tasmod/lang/en_us.json b/src/main/resources/assets/tasmod/lang/en_us.json index 42434be2..8c785e0a 100644 --- a/src/main/resources/assets/tasmod/lang/en_us.json +++ b/src/main/resources/assets/tasmod/lang/en_us.json @@ -1,4 +1,5 @@ { - "tickratechanger.tasmod.command.show": "Current tickrate: %s", - "tickratechanger.tasmod.command.usage": "/tickrate " + "tickratechanger.tasmod.command.show":"Current tickrate: %s", + "tickratechanger.tasmod.command.usage":"/tickrate ", + "gui.tasmod.savestate.resourcepack":"You have a resource pack (resources.zip) in your world folder.\nLoadstating will take severely longer!" } \ No newline at end of file diff --git a/src/main/resources/tasmod.mixin.json b/src/main/resources/tasmod.mixin.json index 6f004f1d..b500e7d6 100644 --- a/src/main/resources/tasmod.mixin.json +++ b/src/main/resources/tasmod.mixin.json @@ -32,6 +32,7 @@ // Savestates "savestates.MixinChunkProviderClient", "savestates.MixinWorldClient", + "savestates.MixinMinecraft", // Interpolation "MixinFrustum",