Skip to content

Commit 4b11029

Browse files
authored
[Savestates] Fix world resourcepacks not correctly applying after savestate (#255)
2 parents b26a9b4 + 4274829 commit 4b11029

File tree

10 files changed

+299
-10
lines changed

10 files changed

+299
-10
lines changed

src/main/java/com/minecrafttas/tasmod/TASmod.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension;
3030
import com.minecrafttas.tasmod.registries.TASmodPackets;
3131
import com.minecrafttas.tasmod.savestates.SavestateHandlerServer;
32+
import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler;
3233
import com.minecrafttas.tasmod.savestates.storage.builtin.SavestateMotionStorage;
3334
import com.minecrafttas.tasmod.tickratechanger.TickrateChangerServer;
3435
import com.minecrafttas.tasmod.ticksync.TickSyncServer;
@@ -119,6 +120,9 @@ public void onInitialize() {
119120
SavestateMotionStorage motionStorage = new SavestateMotionStorage();
120121
PacketHandlerRegistry.register(motionStorage);
121122
EventListenerRegistry.register(motionStorage);
123+
SavestateResourcePackHandler resourcepackHandler = new SavestateResourcePackHandler();
124+
PacketHandlerRegistry.register(resourcepackHandler);
125+
EventListenerRegistry.register(resourcepackHandler);
122126
PacketHandlerRegistry.register(playUntil);
123127
EventListenerRegistry.register(playUntil);
124128
}

src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,41 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args
4242
} else if (args.length >= 1) {
4343
if ("save".equals(args[0])) {
4444
if (args.length == 1) {
45-
saveLatest();
45+
TASmod.gameLoopSchedulerServer.add(() -> {
46+
try {
47+
saveLatest();
48+
} catch (CommandException e) {
49+
e.printStackTrace();
50+
}
51+
});
4652
} else if (args.length == 2) {
47-
saveWithIndex(args);
53+
TASmod.gameLoopSchedulerServer.add(() -> {
54+
try {
55+
saveWithIndex(args);
56+
} catch (CommandException e) {
57+
e.printStackTrace();
58+
}
59+
});
4860
} else {
4961
throw new CommandException("Too many arguments!", new Object[] {});
5062
}
5163
} else if ("load".equals(args[0])) {
5264
if (args.length == 1) {
53-
loadLatest();
65+
TASmod.gameLoopSchedulerServer.add(() -> {
66+
try {
67+
loadLatest();
68+
} catch (CommandException e) {
69+
e.printStackTrace();
70+
}
71+
});
5472
} else if (args.length == 2) {
55-
loadLatest(args);
73+
TASmod.gameLoopSchedulerServer.add(() -> {
74+
try {
75+
loadLatest(args);
76+
} catch (CommandException e) {
77+
e.printStackTrace();
78+
}
79+
});
5680
} else {
5781
throw new CommandException("Too many arguments!", new Object[] {});
5882
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.minecrafttas.tasmod.mixin.savestates;
2+
3+
import org.spongepowered.asm.mixin.Mixin;
4+
import org.spongepowered.asm.mixin.injection.At;
5+
import org.spongepowered.asm.mixin.injection.Inject;
6+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
7+
8+
import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler;
9+
10+
import net.minecraft.client.Minecraft;
11+
12+
@Mixin(Minecraft.class)
13+
public class MixinMinecraft {
14+
15+
@Inject(method = "refreshResources", at = @At(value = "RETURN"))
16+
public void inject_refreshResources(CallbackInfo ci) {
17+
if (SavestateResourcePackHandler.clientRPLatch != null && SavestateResourcePackHandler.clientRPLatch.getCount() > 0) {
18+
SavestateResourcePackHandler.clientRPLatch.countDown();
19+
}
20+
}
21+
}

src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ public enum TASmodPackets implements PacketID {
104104
Minecraft mc = Minecraft.getMinecraft();
105105
((ScoreboardDuck) mc.world.getScoreboard()).clearScoreboard();
106106
}),
107+
/**
108+
* <p>Clears the resourcepack on the client side
109+
* <p>SIDE: Client<br>
110+
* ARGS: none
111+
*/
112+
SAVESTATE_CLEAR_RESOURCEPACK,
107113
/**
108114
* <p>Notifies the client to clear all inputs from the input buffer in {@link PlaybackControllerClient}
109115
* <p>SIDE: Both<br>

src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import com.minecrafttas.tasmod.savestates.files.SavestateDataFile.DataValues;
4141
import com.minecrafttas.tasmod.savestates.files.SavestateTrackerFile;
4242
import com.minecrafttas.tasmod.savestates.handlers.SavestatePlayerHandler;
43+
import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler;
4344
import com.minecrafttas.tasmod.savestates.handlers.SavestateWorldHandler;
4445
import com.minecrafttas.tasmod.util.LoggerMarkers;
4546
import com.minecrafttas.tasmod.util.Scheduler.Task;
@@ -386,6 +387,9 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex
386387
// Reenable level saving
387388
worldHandler.enableLevelSaving();
388389

390+
// Refresh server resourcepacks on the client
391+
SavestateResourcePackHandler.refreshServerResourcepack(server);
392+
389393
// Incrementing info file
390394
SavestateTrackerFile tracker = new SavestateTrackerFile(savestateDirectory.resolve(worldname + "-info.txt"));
391395
tracker.increaseLoadstateCount();
@@ -753,9 +757,9 @@ private int legacyIndexFile(Path savestateDat) {
753757
public PacketID[] getAcceptedPacketIDs() {
754758
return new TASmodPackets[] {
755759
//@formatter:off
756-
TASmodPackets.SAVESTATE_SAVE,
757-
TASmodPackets.SAVESTATE_LOAD,
758-
TASmodPackets.SAVESTATE_SCREEN,
760+
TASmodPackets.SAVESTATE_SAVE,
761+
TASmodPackets.SAVESTATE_LOAD,
762+
TASmodPackets.SAVESTATE_SCREEN,
759763
TASmodPackets.SAVESTATE_UNLOAD_CHUNKS
760764
//@formatter:on
761765
};
@@ -770,7 +774,7 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws
770774

771775
switch (packet) {
772776
case SAVESTATE_SAVE:
773-
Integer index = TASmodBufferBuilder.readInt(buf);
777+
int index = TASmodBufferBuilder.readInt(buf);
774778

775779
Task savestateTask = () -> {
776780
try {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.minecrafttas.tasmod.savestates.gui;
2+
3+
import com.minecrafttas.tasmod.util.MessageUtils;
4+
5+
import net.minecraft.client.Minecraft;
6+
import net.minecraft.client.gui.GuiScreen;
7+
import net.minecraft.client.gui.ScaledResolution;
8+
import net.minecraft.client.resources.I18n;
9+
10+
/**
11+
* Screen for warning the player that a "reources.zip" is present in the world folder,<br>
12+
* which significantly slows down savestates
13+
*/
14+
public class GuiResourcepackWarn extends GuiScreen {
15+
16+
/**
17+
* Screen for warning the player that a "reources.zip" is present in the world folder,<br>
18+
* which significantly slows down savestates
19+
*/
20+
public GuiResourcepackWarn() {
21+
this.mc = Minecraft.getMinecraft();
22+
}
23+
24+
@Override
25+
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
26+
this.drawDefaultBackground();
27+
28+
ScaledResolution scaled = new ScaledResolution(Minecraft.getMinecraft());
29+
int width = scaled.getScaledWidth();
30+
int height = scaled.getScaledHeight();
31+
32+
MessageUtils.splitNewline(I18n.format("gui.tasmod.savestate.resourcepack"), 15, (line, y) -> {
33+
drawCenteredString(fontRenderer, line, width / 2, height / 4 + 40 + y, 0xFF5555);
34+
});
35+
36+
super.drawScreen(mouseX, mouseY, partialTicks);
37+
}
38+
39+
@Override
40+
public boolean doesGuiPauseGame() {
41+
return false;
42+
}
43+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package com.minecrafttas.tasmod.savestates.handlers;
2+
3+
import java.nio.ByteBuffer;
4+
import java.nio.file.Path;
5+
import java.util.List;
6+
import java.util.concurrent.CompletableFuture;
7+
import java.util.concurrent.CountDownLatch;
8+
import java.util.concurrent.ExecutionException;
9+
import java.util.concurrent.TimeUnit;
10+
import java.util.concurrent.TimeoutException;
11+
12+
import com.minecrafttas.mctcommon.networking.Client.Side;
13+
import com.minecrafttas.mctcommon.networking.exception.PacketNotImplementedException;
14+
import com.minecrafttas.mctcommon.networking.exception.WrongSideException;
15+
import com.minecrafttas.mctcommon.networking.interfaces.ClientPacketHandler;
16+
import com.minecrafttas.mctcommon.networking.interfaces.PacketID;
17+
import com.minecrafttas.mctcommon.networking.interfaces.ServerPacketHandler;
18+
import com.minecrafttas.tasmod.TASmod;
19+
import com.minecrafttas.tasmod.TASmodClient;
20+
import com.minecrafttas.tasmod.events.EventSavestate;
21+
import com.minecrafttas.tasmod.networking.TASmodBufferBuilder;
22+
import com.minecrafttas.tasmod.registries.TASmodPackets;
23+
import com.minecrafttas.tasmod.savestates.exceptions.SavestateException;
24+
import com.minecrafttas.tasmod.savestates.gui.GuiResourcepackWarn;
25+
import com.minecrafttas.tasmod.util.LoggerMarkers;
26+
27+
import net.fabricmc.api.EnvType;
28+
import net.fabricmc.api.Environment;
29+
import net.minecraft.client.Minecraft;
30+
import net.minecraft.entity.player.EntityPlayerMP;
31+
import net.minecraft.server.MinecraftServer;
32+
33+
/**
34+
* Handles reloading server resourcepacks when loadstating.
35+
*
36+
* @author Scribble
37+
*/
38+
public class SavestateResourcePackHandler implements EventSavestate.EventServerLoadstate, ServerPacketHandler, ClientPacketHandler {
39+
40+
/**
41+
* The server future for waiting until the client is done unloading the RP
42+
*/
43+
private CompletableFuture<String> serverRPFuture;
44+
45+
/**
46+
* The latch for waiting until the client RP is unloaded
47+
*/
48+
public static CountDownLatch clientRPLatch;
49+
50+
@Override
51+
public void onServerLoadstate(MinecraftServer server, int index, Path target, Path current) {
52+
if (server.getResourcePackUrl().isEmpty() || server.isDedicatedServer())
53+
return;
54+
55+
String serverOwnerName = server.getServerOwner();
56+
57+
try {
58+
TASmod.server.sendTo(serverOwnerName, new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK));
59+
} catch (Exception e) {
60+
TASmod.LOGGER.catching(e);
61+
}
62+
serverRPFuture = new CompletableFuture<>();
63+
64+
String playername = null;
65+
try {
66+
playername = serverRPFuture.get(2L, TimeUnit.MINUTES);
67+
} catch (TimeoutException e) {
68+
throw new SavestateException(e, "Clearing resourcepacks %s timed out!", serverOwnerName);
69+
} catch (ExecutionException | InterruptedException e) {
70+
throw new SavestateException(e, "Clearing resourcepacks %s", serverOwnerName);
71+
}
72+
73+
server.setResourcePack("", "");
74+
TASmod.LOGGER.debug(LoggerMarkers.Savestate, "Cleared resourcepack for player {}", playername);
75+
}
76+
77+
@Override
78+
public PacketID[] getAcceptedPacketIDs() {
79+
return new TASmodPackets[] { TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK };
80+
}
81+
82+
@Environment(EnvType.CLIENT)
83+
@Override
84+
public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception {
85+
TASmodPackets packetId = (TASmodPackets) id;
86+
87+
Minecraft mc = Minecraft.getMinecraft();
88+
switch (packetId) {
89+
case SAVESTATE_CLEAR_RESOURCEPACK:
90+
91+
TASmod.LOGGER.debug(LoggerMarkers.Savestate, "Clearing server resource pack");
92+
93+
mc.displayGuiScreen(new GuiResourcepackWarn());
94+
95+
/**
96+
* Using a countdown latch here, which is counted down in
97+
* savestates.MixinMinecraft.
98+
*
99+
* Clearing the resourcepack is scheduled multiple times
100+
* so for simplicity, I use a latch here.
101+
*/
102+
clientRPLatch = new CountDownLatch(1);
103+
mc.getResourcePackRepository().clearResourcePack();
104+
105+
try {
106+
clientRPLatch.await(30, TimeUnit.SECONDS);
107+
} catch (InterruptedException e) {
108+
e.printStackTrace();
109+
}
110+
111+
/**
112+
* At this point, "clearResourcePack" did remove the server RP
113+
* however, the file association with the "resources.zip" in the
114+
* save folder is still there, which causes loadstating to fail,
115+
* as the system still thinks that the RP is still "in use".
116+
*
117+
* We have to run the garbage collector to remove it.
118+
*/
119+
System.gc();
120+
121+
/**
122+
* Notify the server that savestates have been cleared and that savestating can continue
123+
*/
124+
try {
125+
TASmodClient.client.send(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK));
126+
} catch (Exception e) {
127+
TASmod.LOGGER.catching(e);
128+
}
129+
break;
130+
131+
default:
132+
throw new WrongSideException(packetId, Side.CLIENT);
133+
}
134+
}
135+
136+
@Override
137+
public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception {
138+
TASmodPackets packetId = (TASmodPackets) id;
139+
140+
switch (packetId) {
141+
case SAVESTATE_CLEAR_RESOURCEPACK:
142+
serverRPFuture.complete(username);
143+
break;
144+
145+
default:
146+
throw new WrongSideException(packetId, Side.SERVER);
147+
}
148+
}
149+
150+
/**
151+
* Notifies all clients that a new server resourcepack should be downloaded if available
152+
*
153+
* @param server The Minecraft server
154+
*/
155+
public static void refreshServerResourcepack(MinecraftServer server) {
156+
TASmod.LOGGER.debug(LoggerMarkers.Savestate, "Refreshing resourcepack");
157+
List<EntityPlayerMP> players = server.getPlayerList().getPlayers();
158+
players.forEach((player) -> {
159+
player.loadResourcePack(server.getResourcePackUrl(), server.getResourcePackHash());
160+
});
161+
}
162+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.minecrafttas.tasmod.util;
2+
3+
public class MessageUtils {
4+
5+
public static void splitNewline(String phrase, GuiInterface method) {
6+
splitNewline(phrase, 10, method);
7+
}
8+
9+
public static void splitNewline(String phrase, int distance, GuiInterface method) {
10+
int y = 0;
11+
String[] lines = phrase.split("\r?\n");
12+
for (String line : lines) {
13+
method.draw(line, y);
14+
y += distance;
15+
}
16+
}
17+
18+
@FunctionalInterface
19+
public interface GuiInterface {
20+
21+
public void draw(String line, int y);
22+
}
23+
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2-
"tickratechanger.tasmod.command.show": "Current tickrate: %s",
3-
"tickratechanger.tasmod.command.usage": "/tickrate <ticks per second>"
2+
"tickratechanger.tasmod.command.show":"Current tickrate: %s",
3+
"tickratechanger.tasmod.command.usage":"/tickrate <ticks per second>",
4+
"gui.tasmod.savestate.resourcepack":"You have a resource pack (resources.zip) in your world folder.\nLoadstating will take severely longer!"
45
}

src/main/resources/tasmod.mixin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
// Savestates
3333
"savestates.MixinChunkProviderClient",
3434
"savestates.MixinWorldClient",
35+
"savestates.MixinMinecraft",
3536

3637
// Interpolation
3738
"MixinFrustum",

0 commit comments

Comments
 (0)