Skip to content

Commit 39f58a4

Browse files
authored
[Savestates] Fix input preloading when loadstating during a recording (#240)
2 parents ba7e004 + 3f4a2c4 commit 39f58a4

File tree

11 files changed

+123
-35
lines changed

11 files changed

+123
-35
lines changed

src/main/java/com/minecrafttas/mctcommon/events/EventClient.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import net.minecraft.client.Minecraft;
88
import net.minecraft.client.entity.EntityPlayerSP;
99
import net.minecraft.client.gui.GuiScreen;
10+
import net.minecraft.client.network.NetHandlerPlayClient;
1011

1112
/**
1213
* Contains all events fired on the client side
@@ -59,6 +60,21 @@ public static interface EventDoneLoadingWorld extends EventBase {
5960
public void onDoneLoadingWorld();
6061
}
6162

63+
/**
64+
* Fired when the player is done loading, after the position has been loaded from the server side
65+
* @author Scribble
66+
* @see NetHandlerPlayClient#handlePlayerPosLook(net.minecraft.network.play.server.SPacketPlayerPosLook)
67+
*/
68+
@FunctionalInterface
69+
public static interface EventDoneLoadingPlayer extends EventBase {
70+
71+
/**
72+
* Fired when the player is done loading, after the position has been loaded from the server side
73+
* @see NetHandlerPlayClient#handlePlayerPosLook(net.minecraft.network.play.server.SPacketPlayerPosLook)
74+
*/
75+
public void onDoneLoadingPlayer();
76+
}
77+
6278
/**
6379
* Fired when the client ticks
6480
* @author Scribble

src/main/java/com/minecrafttas/tasmod/handlers/LoadingScreenHandler.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
import static com.minecrafttas.tasmod.TASmod.LOGGER;
44

55
import com.minecrafttas.mctcommon.events.EventClient.EventClientGameLoop;
6+
import com.minecrafttas.mctcommon.events.EventClient.EventDoneLoadingPlayer;
67
import com.minecrafttas.mctcommon.events.EventClient.EventDoneLoadingWorld;
78
import com.minecrafttas.mctcommon.events.EventClient.EventLaunchIntegratedServer;
89
import com.minecrafttas.tasmod.TASmod;
910
import com.minecrafttas.tasmod.TASmodClient;
11+
import com.minecrafttas.tasmod.mixin.playbackhooks.MixinEntityRenderer;
1012
import com.minecrafttas.tasmod.playback.PlaybackControllerClient;
1113
import com.minecrafttas.tasmod.util.LoggerMarkers;
14+
import com.minecrafttas.tasmod.virtual.VirtualInput;
1215

1316
import net.minecraft.client.Minecraft;
1417

@@ -17,7 +20,7 @@
1720
*
1821
* @author Scribble
1922
*/
20-
public class LoadingScreenHandler implements EventLaunchIntegratedServer, EventClientGameLoop, EventDoneLoadingWorld {
23+
public class LoadingScreenHandler implements EventLaunchIntegratedServer, EventClientGameLoop, EventDoneLoadingWorld, EventDoneLoadingPlayer {
2124

2225
private boolean waszero;
2326
private boolean isLoading;
@@ -62,12 +65,32 @@ public void onDoneLoadingWorld() {
6265
LOGGER.debug(LoggerMarkers.Event, "Finished loading the world on the client");
6366
loadingScreenDelay = 1;
6467

65-
TASmodClient.virtual.clear();
6668
}
6769
}
6870

6971
public boolean isLoading() {
7072
return isLoading;
7173
}
7274

75+
/**
76+
* {@inheritDoc}
77+
*
78+
* <p>Fixes an issue, where the look position of the player is reset to 0 -180,<br>
79+
* As well as removing any keyboard inputs present in the main menu
80+
*
81+
* <p>{@link MixinEntityRenderer#runUpdate(float)} rewrites the camera input,<br>
82+
* So that it can be used with interpolation. <br>
83+
* However, when you start the game, this camera input needs to be initialised with the current look position from the server.<br>
84+
* So a special condition is set, that if the {@link VirtualInput#CAMERA_ANGLE} is null,<br>
85+
* it intialises the {@link VirtualInput#CAMERA_ANGLE CAMERA_ANGLE} with the current player camera angle.
86+
*
87+
* <p>So {@link VirtualInput#clear()} has to be called at the right moment in the player initialisation<br>
88+
* to set the correct values. Before that, the playerRotation defaults to 0 -180
89+
*/
90+
@Override
91+
public void onDoneLoadingPlayer() {
92+
LOGGER.debug(LoggerMarkers.Event, "Finished loading the player position on the client");
93+
TASmodClient.virtual.clear();
94+
}
95+
7396
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.minecrafttas.tasmod.mixin.events;
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.mctcommon.events.EventClient.EventDoneLoadingPlayer;
9+
import com.minecrafttas.mctcommon.events.EventListenerRegistry;
10+
11+
import net.minecraft.client.network.NetHandlerPlayClient;
12+
13+
@Mixin(NetHandlerPlayClient.class)
14+
public class MixinNetHandlerPlayClient {
15+
16+
@Inject(method = "handlePlayerPosLook", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;displayGuiScreen(Lnet/minecraft/client/gui/GuiScreen;)V"))
17+
public void event_handlePlayerPosLook(CallbackInfo ci) {
18+
EventListenerRegistry.fireEvent(EventDoneLoadingPlayer.class);
19+
}
20+
}

src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinWorldServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* @author Scribble
1818
*/
1919
@Mixin(WorldServer.class)
20-
public abstract class MixinWorldServer implements WorldServerDuck {
20+
public class MixinWorldServer implements WorldServerDuck {
2121

2222
@Shadow
2323
private PlayerChunkMap playerChunkMap;

src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerClient.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -539,12 +539,7 @@ public void setInputs(BigArrayList<InputContainer> inputs) {
539539
}
540540

541541
public void setInputs(BigArrayList<InputContainer> inputs, long index) {
542-
try {
543-
this.inputs.clearMemory();
544-
} catch (IOException e) {
545-
e.printStackTrace();
546-
}
547-
this.inputs = new BigArrayList<InputContainer>(tasFileDirectory + File.separator + "temp");
542+
clearInputList();
548543
SerialiserFlavorBase.addAll(this.inputs, inputs);
549544
setIndex(index);
550545
}
@@ -582,14 +577,19 @@ public InputContainer get() {
582577

583578
public void clear() {
584579
LOGGER.info(LoggerMarkers.Playback, "Clearing playback controller");
580+
clearInputList();
585581
EventListenerRegistry.fireEvent(EventPlaybackClient.EventRecordClear.class);
582+
583+
index = 0;
584+
}
585+
586+
private void clearInputList() {
586587
try {
587588
inputs.clearMemory();
588589
} catch (IOException e) {
589590
e.printStackTrace();
590591
}
591592
inputs = new BigArrayList<InputContainer>(tasFileDirectory + File.separator + "temp");
592-
index = 0;
593593
}
594594

595595
/**

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

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
import static com.minecrafttas.tasmod.TASmod.LOGGER;
44

5-
import java.io.File;
65
import java.io.IOException;
76
import java.nio.ByteBuffer;
7+
import java.nio.file.Files;
8+
import java.nio.file.Path;
89

910
import com.dselent.bigarraylist.BigArrayList;
1011
import com.minecrafttas.mctcommon.events.EventListenerRegistry;
@@ -50,7 +51,7 @@
5051
*/
5152
public class SavestateHandlerClient implements ClientPacketHandler, EventSavestate.EventClientCompleteLoadstate, EventSavestate.EventClientLoadPlayer {
5253

53-
public final static File savestateDirectory = TASmodClient.savestatedirectory.toFile(); //TODO Change to path... don't want to deal with this rn ._.
54+
public final static Path clientSavestateDirectory = TASmodClient.tasfiledirectory.resolve("savestates");
5455

5556
/**
5657
* A bug occurs when unloading the client world. The client world has a
@@ -122,15 +123,15 @@ public static void savestate(String nameOfSavestate) throws SavestateException,
122123
return;
123124
}
124125

125-
SavestateHandlerClient.savestateDirectory.mkdir();
126+
createClientSavestateDirectory();
126127

127-
File targetfile = new File(SavestateHandlerClient.savestateDirectory, nameOfSavestate + ".mctas");
128+
Path targetfile = clientSavestateDirectory.resolve(nameOfSavestate + ".mctas");
128129

129130
PlaybackControllerClient container = TASmodClient.controller;
130131
if (container.isRecording()) {
131-
PlaybackSerialiser.saveToFile(targetfile.toPath(), container, ""); // If the container is recording, store it entirely
132+
PlaybackSerialiser.saveToFile(targetfile, container, ""); // If the container is recording, store it entirely
132133
} else if (container.isPlayingback()) {
133-
PlaybackSerialiser.saveToFile(targetfile.toPath(), container, "", container.index()); // If the container is playing, store it until the current index
134+
PlaybackSerialiser.saveToFile(targetfile, container, "", container.index()); // If the container is playing, store it until the current index
134135
}
135136
}
136137

@@ -150,26 +151,27 @@ public static void loadstate(String nameOfSavestate) throws Exception {
150151
return;
151152
}
152153

153-
savestateDirectory.mkdir();
154-
155154
PlaybackControllerClient controller = TASmodClient.controller;
156155

157156
TASstate state = controller.getState();
158157

159158
if (state == TASstate.NONE) {
159+
TASmodClient.tickSchedulerClient.add(() -> {
160+
EventListenerRegistry.fireEvent(EventSavestate.EventClientCompleteLoadstate.class);
161+
});
160162
return;
161163
}
162164

163165
if (state == TASstate.PAUSED) {
164166
state = controller.getStateAfterPause();
165167
}
166168

167-
File targetfile = new File(savestateDirectory, nameOfSavestate + ".mctas");
169+
Path targetfile = clientSavestateDirectory.resolve(nameOfSavestate + ".mctas");
168170

169171
BigArrayList<InputContainer> savestateContainerList;
170172

171-
if (targetfile.exists()) {
172-
savestateContainerList = PlaybackSerialiser.loadFromFile(targetfile.toPath(), state != TASstate.PLAYBACK);
173+
if (Files.exists(targetfile)) {
174+
savestateContainerList = PlaybackSerialiser.loadFromFile(targetfile, state != TASstate.PLAYBACK);
173175
} else {
174176
controller.setTASStateClient(TASstate.NONE, false);
175177
Minecraft.getMinecraft().player.sendMessage(new TextComponentString(ChatFormatting.YELLOW + "Inputs could not be loaded for this savestate,"));
@@ -199,12 +201,14 @@ public static void loadstate(String nameOfSavestate) throws Exception {
199201
if (state == TASstate.RECORDING) {
200202
long index = savestateContainerList.size() - 1;
201203

204+
preload(savestateContainerList, index);
202205
controller.setInputs(savestateContainerList, index);
203-
204-
/*
205-
* When loading a savestate during a playback 2 different scenarios can happen.
206-
* */
207-
} else if (state == TASstate.PLAYBACK) {
206+
TASmodClient.virtual.clear();
207+
}
208+
/*
209+
* When loading a savestate during a playback 2 different scenarios can happen.
210+
*/
211+
else if (state == TASstate.PLAYBACK) {
208212

209213
/*
210214
* Scenario 1:
@@ -233,6 +237,7 @@ public static void loadstate(String nameOfSavestate) throws Exception {
233237

234238
preload(controller.getInputs(), index);
235239
controller.setIndex(index);
240+
TASmodClient.virtual.clear();
236241
}
237242
/*
238243
* Scenario 2:
@@ -254,7 +259,13 @@ public static void loadstate(String nameOfSavestate) throws Exception {
254259
});
255260
}
256261

262+
private static void createClientSavestateDirectory() throws IOException {
263+
LOGGER.trace(LoggerMarkers.Savestate, "Creating savestate directory at {}", clientSavestateDirectory);
264+
Files.createDirectories(clientSavestateDirectory);
265+
}
266+
257267
private static void preload(BigArrayList<InputContainer> containerList, long index) {
268+
LOGGER.trace(LoggerMarkers.Savestate, "Preloading container at index {}", index);
258269
InputContainer containerToPreload = containerList.get(index);
259270
TASmodClient.virtual.preloadInput(containerToPreload.getKeyboard(), containerToPreload.getMouse(), containerToPreload.getCameraAngle());
260271

@@ -289,8 +300,8 @@ public static void loadPlayer(NBTTagCompound compound) {
289300
double z = player.motionZ;
290301

291302
float rx = player.moveForward;
292-
float ry = player.moveStrafing;
293-
float rz = player.moveVertical;
303+
float ry = player.moveVertical;
304+
float rz = player.moveStrafing;
294305

295306
boolean sprinting = player.isSprinting();
296307
float jumpVector = player.jumpMovementFactor;

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,12 +410,17 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex
410410
TASmod.tickratechanger.pauseGame(false);
411411
}
412412

413+
// Unlock savestating
414+
state = SavestateState.NONE;
415+
416+
/*
417+
* TODO Savestates can be reloaded without a tick passing...
418+
* And since this scheduler is not cleared, it would execute the same task multiple times in the next tick
419+
* Rn it's not a problem, but this should be looked at...
420+
*/
413421
TASmod.tickSchedulerServer.add(() -> {
414422
EventListenerRegistry.fireEvent(EventSavestate.EventServerCompleteLoadstate.class);
415423
onLoadstateComplete();
416-
417-
// Unlock savestating
418-
state = SavestateState.NONE;
419424
});
420425
}
421426

src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateWorldHandler.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ public void loadAllWorlds(String string, String string2) {
239239
}
240240

241241
server.worlds[i].addEventListener(new ServerWorldEventHandler(server, server.worlds[i]));
242+
server.worlds[i].tick(); // TODO I give up...
242243
}
243244

244245
server.getPlayerList().setPlayerManager(server.worlds);

src/main/java/com/minecrafttas/tasmod/virtual/VirtualCameraAngle.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public void set(float pitch, float yaw) {
133133
public void getStates(List<VirtualCameraAngle> reference) {
134134
if (isParent()) {
135135
reference.addAll(subtickList);
136-
reference.add(this);
136+
reference.add(this.shallowClone()); // Fixes a crash where "this" can become null in the reference.
137137
}
138138
}
139139

src/main/java/com/minecrafttas/tasmod/virtual/VirtualInput.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,19 @@ public void clear() {
141141
}
142142

143143
public void preloadInput(VirtualKeyboard keyboardToPreload, VirtualMouse mouseToPreload, VirtualCameraAngle angleToPreload) {
144+
// Preload the nextKeyboard
144145
KEYBOARD.nextKeyboard.deepCopyFrom(keyboardToPreload);
145146
MOUSE.nextMouse.deepCopyFrom(mouseToPreload);
146147
CAMERA_ANGLE.nextCameraAngle.deepCopyFrom(angleToPreload);
148+
149+
// Preload the currentKeyboard
150+
KEYBOARD.nextKeyboardTick();
151+
MOUSE.nextMouseTick();
152+
153+
// Preload vanilla inputs
147154
Minecraft.getMinecraft().runTickKeyboard(); // Letting mouse and keyboard tick once to load inputs into the "currentKeyboard"
148155
Minecraft.getMinecraft().runTickMouse();
156+
149157
SubtickDuck entityRenderer = (SubtickDuck) Minecraft.getMinecraft().entityRenderer;
150158
entityRenderer.runUpdate(0);
151159
}
@@ -262,7 +270,7 @@ public void updateNextKeyboard(int keycode, boolean keystate, char character) {
262270
* @param repeatEventsEnabled If repeat events are enabled
263271
*/
264272
public void updateNextKeyboard(int keycode, boolean keystate, char character, boolean repeatEventsEnabled) {
265-
LOGGER.debug(LoggerMarkers.Keyboard, "Update: {}, {}, {}, {}", keycode, keystate, character); // Activate with -Dtasmod.marker.keyboard=ACCEPT in VM arguments (and -Dtasmod.log.level=debug)
273+
LOGGER.debug(LoggerMarkers.Keyboard, "Update: {}, {}, {}", keycode, keystate, character); // Activate with -Dtasmod.marker.keyboard=ACCEPT in VM arguments (and -Dtasmod.log.level=debug)
266274
nextKeyboard.updateFromEvent(keycode, keystate, character, repeatEventsEnabled);
267275
}
268276

@@ -277,6 +285,7 @@ public void nextKeyboardTick() {
277285
nextKeyboard.deepCopyFrom((VirtualKeyboard) EventListenerRegistry.fireEvent(EventVirtualInput.EventVirtualKeyboardTick.class, nextKeyboard));
278286
currentKeyboard.getVirtualEvents(nextKeyboard, keyboardEventQueue);
279287
currentKeyboard.moveFrom(nextKeyboard);
288+
LOGGER.debug(LoggerMarkers.Keyboard, "KeyboardTick: {}", currentKeyboard); // Activate with -Dtasmod.marker.keyboard=ACCEPT in VM arguments (and -Dtasmod.log.level=debug)
280289
}
281290

282291
/**
@@ -693,8 +702,10 @@ public Triple<Float, Float, Float> getInterpolatedState(float partialTick, float
693702
if (enable && !cameraAngleInterpolationStates.isEmpty()) {
694703
int index = (int) MathHelper.clampedLerp(0, cameraAngleInterpolationStates.size() - 1, partialTick); // Get interpolate index
695704

696-
interpolatedPitch = cameraAngleInterpolationStates.get(index).getPitch();
697-
interpolatedYaw = cameraAngleInterpolationStates.get(index).getYaw() + 180;
705+
VirtualCameraAngle interpolatedCamera = cameraAngleInterpolationStates.get(index);
706+
707+
interpolatedPitch = interpolatedCamera.getPitch();
708+
interpolatedYaw = interpolatedCamera.getYaw() + 180;
698709

699710
}
700711
return Triple.of(interpolatedPitch, interpolatedYaw, 0f);

0 commit comments

Comments
 (0)