diff --git a/build.gradle b/build.gradle index 53128ff5..52d14f78 100644 --- a/build.gradle +++ b/build.gradle @@ -56,7 +56,8 @@ dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.legacyfabric:yarn:${project.minecraft_version}+build.mcp" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + testImplementation 'org.junit.jupiter:junit-jupiter:5.13.3' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } // task for downloading KillTheRng diff --git a/src/main/java/com/minecrafttas/mctcommon/events/EventListenerRegistry.java b/src/main/java/com/minecrafttas/mctcommon/events/EventListenerRegistry.java index 0a42ea10..65aa9aa0 100644 --- a/src/main/java/com/minecrafttas/mctcommon/events/EventListenerRegistry.java +++ b/src/main/java/com/minecrafttas/mctcommon/events/EventListenerRegistry.java @@ -235,8 +235,7 @@ public static Object fireEvent(Class // Iterate through all methods for (Method method : methodsInListener) { - // Check if the current method has the same name as the method we are looking - // for + // Check if the current method has the same name as the method we are looking for if (!checkName(method, methodToFind.getName())) { continue; } @@ -252,7 +251,9 @@ public static Object fireEvent(Class toThrow = null; // Reset toThrow as the correct method was found method.setAccessible(true); try { - returnValue = method.invoke(eventListener, eventParams); // Call the method + Object newReturnValue = method.invoke(eventListener, eventParams); // Call the method + if (newReturnValue != null) + returnValue = newReturnValue; } catch (IllegalAccessException | InvocationTargetException e) { throw new EventException(eventClass, e); } catch (IllegalArgumentException e) { diff --git a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java index 5f8aaa4f..6acd7502 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java @@ -198,6 +198,8 @@ private void registerEventListeners() { EventListenerRegistry.register(TASmodAPIRegistry.PLAYBACK_FILE_COMMAND); EventListenerRegistry.register(new LoggerMarkers()); EventListenerRegistry.register(savestateHandlerClient); + + EventListenerRegistry.register(virtual.interpolationHandler); } @Override diff --git a/src/main/java/com/minecrafttas/tasmod/events/EventClient.java b/src/main/java/com/minecrafttas/tasmod/events/EventClient.java index fdd53517..7c4a4a6e 100644 --- a/src/main/java/com/minecrafttas/tasmod/events/EventClient.java +++ b/src/main/java/com/minecrafttas/tasmod/events/EventClient.java @@ -3,6 +3,7 @@ import com.minecrafttas.mctcommon.events.EventListenerRegistry.EventBase; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; /** * TASmod specific events fired on the client side @@ -22,6 +23,17 @@ public static interface EventDrawHotbar extends EventBase { public void onDrawHotbar(); } + /** + * Fired when a screen in a gui is drawn + */ + @FunctionalInterface + public static interface EventDrawScreen extends EventBase { + /** + * Fired when a screen in a gui is drawn + */ + public void onDrawScreen(GuiScreen screen, int xCoordinate, int yCoordinate); + } + /** * Fired when drawing something on screen. Ignores F1 */ diff --git a/src/main/java/com/minecrafttas/tasmod/gui/InfoHud.java b/src/main/java/com/minecrafttas/tasmod/gui/InfoHud.java index 12e7a38e..c7b04baf 100644 --- a/src/main/java/com/minecrafttas/tasmod/gui/InfoHud.java +++ b/src/main/java/com/minecrafttas/tasmod/gui/InfoHud.java @@ -18,14 +18,18 @@ import com.minecrafttas.tasmod.TASmod; import com.minecrafttas.tasmod.TASmodClient; import com.minecrafttas.tasmod.events.EventClient.EventDrawHotbar; +import com.minecrafttas.tasmod.playback.PlaybackControllerClient; import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; import com.minecrafttas.tasmod.playback.filecommands.builtin.DesyncMonitorFileCommandExtension; +import com.minecrafttas.tasmod.virtual.VirtualInput; +import com.minecrafttas.tasmod.virtual.VirtualInterpolationHandler.MouseInterpolation; import com.mojang.realmsclient.gui.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.ScaledResolution; import net.minecraft.util.math.MathHelper; +import net.minecraft.util.text.TextFormatting; /** * The info hud is a hud that is always being rendered ontop of the screen, it can show some stuff such as coordinates, etc., @@ -378,13 +382,25 @@ public boolean checkInit() { } })); - // title = "cursor"; - // y += 14; - // if (configuration.getProperty(title + "_x", "err").equals("err")) setDefaults(title, y); - // lists.add(new InfoLabel(title, Integer.parseInt(configuration.getProperty(title + "_x")), Integer.parseInt(configuration.getProperty(title + "_y")), Boolean.parseBoolean(configuration.getProperty(title + "_visible")), Boolean.parseBoolean(configuration.getProperty(title + "_rect")), () -> { - // if (Minecraft.getMinecraft().currentScreen == this) return "Mouse Position"; - // return String.format("Mouse Cursor: " + TASmodClient.virtual.getNextMouse().getPath().get(0).cursorX + " " + TASmodClient.virtual.getNextMouse().getPath().get(0).cursorY); - // })); TODO Remove? + title = "cursor"; + y += 14; + if (configuration.getProperty(title + "_x", "err").equals("err")) + setDefaults(title, y); + lists.add(new InfoLabel(title, Integer.parseInt(configuration.getProperty(title + "_x")), Integer.parseInt(configuration.getProperty(title + "_y")), Boolean.parseBoolean(configuration.getProperty(title + + "_visible")), Boolean.parseBoolean(configuration.getProperty(title + "_rect")), () -> { + if (Minecraft.getMinecraft().currentScreen == this) + return "Mouse Position"; + + MouseInterpolation mouseInterpolation = TASmodClient.virtual.interpolationHandler.getInterpolatedMouseCursor(0, false); + Integer xCursor = mouseInterpolation.getX(); + Integer yCursor = mouseInterpolation.getY(); + + if (Minecraft.getMinecraft().currentScreen != null) + return String.format("Mouse Cursor: %s %s", xCursor == null ? "null" : xCursor, yCursor == null ? "null" : yCursor); + else + return "Mouse Cursor: 0 0"; + + })); // title = "trajectories"; // y += 14; @@ -537,29 +553,27 @@ private void drawRectWithText(String text, int x, int y, boolean rect) { } private String keystrokes() { + boolean isPlayingBack = TASmodClient.controller.isPlayingback(); + VirtualInput virtual = TASmodClient.virtual; + PlaybackControllerClient controller = TASmodClient.controller; - String out1 = "" + ChatFormatting.WHITE; - for (String mouse : TASmodClient.virtual.getCurrentMousePresses()) { - out1 = out1.concat(mouse + " "); - } - if (Display.isActive() || TASmodClient.controller.isPlayingback()) { - out1 = out1.concat("" + ChatFormatting.GREEN); - for (String mouse : TASmodClient.virtual.getNextMousePresses()) { - out1 = out1.concat(mouse + " "); - } - } + String currentMousePresses = String.join(" ", virtual.getCurrentMousePresses()); - String out2 = "" + ChatFormatting.WHITE; - for (String key : TASmodClient.virtual.getCurrentKeyboardPresses()) { - out2 = out2.concat(key + " "); + String nextMousePresses = ""; + if (Display.isActive() || isPlayingBack) { + List mousePresses = isPlayingBack ? controller.getNextMousePresses() : virtual.getNextMousePresses(); + nextMousePresses = String.join(" ", mousePresses); } - if (Display.isActive() || TASmodClient.controller.isPlayingback()) { - out2 = out2.concat("" + ChatFormatting.GREEN); - for (String key : TASmodClient.virtual.getNextKeyboardPresses()) { - out2 = out2.concat(key + " "); - } + + String currentKeyboardPresses = String.join(" ", TASmodClient.virtual.getCurrentKeyboardPresses()); + + String nextKeyboardPresses = ""; + if (Display.isActive() || isPlayingBack) { + List keyboardPresses = isPlayingBack ? controller.getNextKeyboardPresses() : virtual.getNextKeyboardPresses(); + nextKeyboardPresses = String.join(" ", keyboardPresses); } - return out1 + out2; + + return String.format("%s%s %s%s %s%s %s%s", TextFormatting.WHITE, currentMousePresses, TextFormatting.GREEN, nextMousePresses, TextFormatting.WHITE, currentKeyboardPresses, TextFormatting.GREEN, nextKeyboardPresses); } private Pair getScreenOffset(int x, int y, InfoLabel label) { diff --git a/src/main/java/com/minecrafttas/tasmod/mixin/playbackhooks/MixinEntityRenderer.java b/src/main/java/com/minecrafttas/tasmod/mixin/playbackhooks/MixinEntityRenderer.java index daf1f405..5c1a36de 100644 --- a/src/main/java/com/minecrafttas/tasmod/mixin/playbackhooks/MixinEntityRenderer.java +++ b/src/main/java/com/minecrafttas/tasmod/mixin/playbackhooks/MixinEntityRenderer.java @@ -1,6 +1,5 @@ package com.minecrafttas.tasmod.mixin.playbackhooks; -import org.apache.commons.lang3.tuple.Triple; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -11,11 +10,14 @@ import com.llamalad7.mixinextras.sugar.Share; import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef; +import com.llamalad7.mixinextras.sugar.ref.LocalIntRef; import com.minecrafttas.mctcommon.events.EventListenerRegistry; import com.minecrafttas.tasmod.TASmodClient; import com.minecrafttas.tasmod.events.EventClient.EventDrawHotbarAlways; import com.minecrafttas.tasmod.util.Ducks.SubtickDuck; import com.minecrafttas.tasmod.virtual.VirtualInput; +import com.minecrafttas.tasmod.virtual.VirtualInterpolationHandler.CameraInterpolation; +import com.minecrafttas.tasmod.virtual.VirtualInterpolationHandler.MouseInterpolation; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; @@ -193,21 +195,31 @@ public void playback_updateOverlay(CallbackInfo ci) { EventListenerRegistry.fireEvent(EventDrawHotbarAlways.class); } + @Redirect(method = "updateCameraAndRender", at = @At(value = "INVOKE", target = "Lorg/lwjgl/input/Mouse;getX()I", remap = false)) + public int redirect_updateCameraAndRendererX(@Share(value = "interpolatedY") LocalIntRef shared) { + MouseInterpolation interpolated = TASmodClient.virtual.interpolationHandler.getInterpolatedMouseCursor(Minecraft.getMinecraft().timer.renderPartialTicks, TASmodClient.controller.isPlayingback()); + shared.set(interpolated.getY()); + return interpolated.getX(); + } + + @Redirect(method = "updateCameraAndRender", at = @At(value = "INVOKE", target = "Lorg/lwjgl/input/Mouse;getY()I", remap = false)) + public int redirect_updateCameraAndRendererY(@Share(value = "interpolatedY") LocalIntRef shared) { + return shared.get(); + } + /** * Turns the camera via GLStateManager - * @param pitch The pi + * @param pitch The pitch * @param yaw The yaw - * @see com.minecrafttas.tasmod.virtual.VirtualInput.VirtualCameraAngleInput#getInterpolatedState(float, float, float, boolean) + * @see com.minecrafttas.tasmod.virtual.VirtualInterpolationHandler#getInterpolatedState(float, float, float, boolean) * @return The redirected yaw */ private float redirectCam(float pitch, float yaw) { - Triple interpolated = TASmodClient.virtual.CAMERA_ANGLE.getInterpolatedState(Minecraft.getMinecraft().timer.renderPartialTicks, pitch, yaw, TASmodClient.controller.isPlayingback()); - float pitch2 = interpolated.getLeft(); - float yaw2 = interpolated.getMiddle(); + CameraInterpolation interpolated = TASmodClient.virtual.interpolationHandler.getInterpolatedState(Minecraft.getMinecraft().timer.renderPartialTicks, pitch, yaw, TASmodClient.controller.isPlayingback()); + float pitch2 = interpolated.getPitch(); + float yaw2 = interpolated.getYaw(); // Update pitch GlStateManager.rotate(pitch2, 1.0f, 0.0f, 0.0f); - // Update roll - GlStateManager.rotate(interpolated.getRight(), 0.0f, 0.0f, 1.0f); // Update yaw return yaw2; } diff --git a/src/main/java/com/minecrafttas/tasmod/mixin/playbackhooks/MixinGuiScreen.java b/src/main/java/com/minecrafttas/tasmod/mixin/playbackhooks/MixinGuiScreen.java index d4bb0893..ed386307 100644 --- a/src/main/java/com/minecrafttas/tasmod/mixin/playbackhooks/MixinGuiScreen.java +++ b/src/main/java/com/minecrafttas/tasmod/mixin/playbackhooks/MixinGuiScreen.java @@ -3,9 +3,13 @@ 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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import com.minecrafttas.mctcommon.events.EventListenerRegistry; import com.minecrafttas.tasmod.TASmodClient; +import com.minecrafttas.tasmod.events.EventClient.EventDrawScreen; import com.minecrafttas.tasmod.util.Ducks.GuiScreenDuck; import com.minecrafttas.tasmod.virtual.VirtualInput; import com.minecrafttas.tasmod.virtual.event.VirtualKeyboardEvent; @@ -103,6 +107,11 @@ private static boolean redirectIsAltKeyDown(int i) { // ===================================================================================================================================== + @Inject(method = "drawScreen", at = @At("HEAD")) + private void injectDrawScreen(int i, int j, float f, CallbackInfo ci) { + EventListenerRegistry.fireEvent(EventDrawScreen.class, (GuiScreen) (Object) this, i, j); + } + @Shadow private int width; diff --git a/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerClient.java b/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerClient.java index b275a921..07684e67 100644 --- a/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerClient.java +++ b/src/main/java/com/minecrafttas/tasmod/playback/PlaybackControllerClient.java @@ -38,6 +38,7 @@ import com.minecrafttas.tasmod.TASmod; import com.minecrafttas.tasmod.TASmodClient; import com.minecrafttas.tasmod.events.EventClient.EventClientTickPost; +import com.minecrafttas.tasmod.events.EventClient.EventDrawScreen; import com.minecrafttas.tasmod.events.EventPlaybackClient; import com.minecrafttas.tasmod.events.EventPlaybackClient.EventControllerStateChange; import com.minecrafttas.tasmod.events.EventPlaybackClient.EventPlaybackJoinedWorld; @@ -55,14 +56,12 @@ import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.util.Ducks.GuiScreenDuck; import com.minecrafttas.tasmod.util.LoggerMarkers; -import com.minecrafttas.tasmod.util.PointerNormalizer; import com.minecrafttas.tasmod.util.Scheduler.Task; import com.minecrafttas.tasmod.virtual.VirtualCameraAngle; import com.minecrafttas.tasmod.virtual.VirtualInput; import com.minecrafttas.tasmod.virtual.VirtualInput.VirtualCameraAngleInput; import com.minecrafttas.tasmod.virtual.VirtualKeyboard; import com.minecrafttas.tasmod.virtual.VirtualMouse; -import com.minecrafttas.tasmod.virtual.event.VirtualMouseEvent; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; @@ -97,12 +96,12 @@ public class PlaybackControllerClient implements EventClientInit, EventClientTickPost, + EventDrawScreen, EventVirtualInput.EventVirtualKeyboardTick, EventVirtualInput.EventVirtualMouseTick, - EventVirtualInput.EventVirtualCameraAngleTick, + EventVirtualInput.EventVirtualCameraAngleTick - EventVirtualInput.EventVirtualMouseSubtick //@formatter:on { private Logger logger = TASmod.LOGGER; @@ -122,12 +121,40 @@ public class PlaybackControllerClient implements */ private long index; - private VirtualKeyboard keyboard = new VirtualKeyboard(); - - private VirtualMouse mouse = new VirtualMouse(); - - private VirtualCameraAngle camera = new VirtualCameraAngle(); + /** + *

The current keyboard used in the {@link PlaybackControllerClient PlaybackController} + *

Used during recording to store incoming inputs from the {@link VirtualInput#KEYBOARD}
+ * or stores inputs that are sent to the {@link VirtualInput#KEYBOARD} during playback + */ + private VirtualKeyboard currentPlaybackKeyboard = new VirtualKeyboard(); + /** + *

The current mouse used in the {@link PlaybackControllerClient PlaybackController} + *

Used during recording to store incoming inputs from the {@link VirtualInput#MOUSE}
+ * or stores inputs that are sent to the {@link VirtualInput#MOUSE} during playback + */ + private VirtualMouse currentPlaybackMouse = new VirtualMouse(); + /** + *

The current camera angle used in the {@link PlaybackControllerClient PlaybackController} + *

Used during recording to store incoming inputs from the {@link VirtualInput#CAMERA_ANGLE}
+ * or stores inputs that are sent to the {@link VirtualInput#CAMERA_ANGLE} during playback + */ + private VirtualCameraAngle currentPlaybackCameraAngle = new VirtualCameraAngle(); + /** + *

The keyboard in the next playback tick + *

These inputs will be fed into {@link #currentPlaybackKeyboard} after a tick + */ + private VirtualKeyboard nextPlaybackKeyboard = new VirtualKeyboard(); + /** + *

The mouse in the next playback tick + *

These inputs will be fed into {@link #currentPlaybackMouse} after a tick + */ + private VirtualMouse nextPlaybackMouse = new VirtualMouse(); + /** + *

The camera angle in the next playback tick + *

These inputs will be fed into {@link #currentPlaybackCameraAngle} after a tick + */ + private VirtualCameraAngle nextPlaybackCameraAngle = new VirtualCameraAngle(); /** * The directory where to store the tasfiles */ @@ -283,7 +310,7 @@ private void startRecording() { VirtualCameraAngleInput CAMERA_ANGLE = TASmodClient.virtual.CAMERA_ANGLE; Float pitch = CAMERA_ANGLE.getCurrentPitch(); Float yaw = CAMERA_ANGLE.getCurrentYaw(); - this.camera.set(pitch, yaw); + this.currentPlaybackCameraAngle.set(pitch, yaw); inputs.add(new InputContainer()); } @@ -374,91 +401,73 @@ public TASstate getStateAfterPause() { // running @Override - public VirtualMouse onVirtualMouseTick(VirtualMouse vmouse) { + public VirtualKeyboard onVirtualKeyboardTick(VirtualKeyboard vkeyboard) { if (state == TASstate.RECORDING) { - this.mouse.deepCopyFrom(vmouse); + this.currentPlaybackKeyboard.deepCopyFrom(vkeyboard); } else if (state == TASstate.PLAYBACK) { - vmouse.deepCopyFrom(this.mouse); + vkeyboard.deepCopyFrom(this.currentPlaybackKeyboard); } - return vmouse.clone(); + return vkeyboard.clone(); } @Override - public VirtualKeyboard onVirtualKeyboardTick(VirtualKeyboard vkeyboard) { + public VirtualMouse onVirtualMouseTick(VirtualMouse vmouse) { if (state == TASstate.RECORDING) { - this.keyboard.deepCopyFrom(vkeyboard); + this.currentPlaybackMouse.deepCopyFrom(vmouse); } else if (state == TASstate.PLAYBACK) { - vkeyboard.deepCopyFrom(this.keyboard); + vmouse.deepCopyFrom(this.currentPlaybackMouse); } - return vkeyboard.clone(); + return vmouse.clone(); } @Override public VirtualCameraAngle onVirtualCameraTick(VirtualCameraAngle vcamera) { if (state == TASstate.RECORDING) { - this.camera.deepCopyFrom(vcamera); + this.currentPlaybackCameraAngle.deepCopyFrom(vcamera); } else if (state == TASstate.PLAYBACK) { - vcamera.deepCopyFrom(this.camera); + vcamera.deepCopyFrom(this.currentPlaybackCameraAngle); } return vcamera.clone(); } /** - * Updates the cursor location on screen + * {@inheritDoc} + *

Updates the cursor location on screen */ @Override - public void onVirtualMouseSubtick(VirtualMouseEvent event) { - if (!isPlayingback() || event == null) + public void onDrawScreen(GuiScreen screen, int x, int y) { + if (!isPlayingback()) return; Minecraft mc = Minecraft.getMinecraft(); if (!mc.gameSettings.pauseOnLostFocus && !Display.isActive()) // If pause on lost focus is on and the display is not active don't set the cursor position return; - GuiScreen screen = mc.currentScreen; - if (screen == null) - return; - - GuiScreenDuck duckedScreen = (GuiScreenDuck) mc.currentScreen; - //@formatter:off - Mouse.setCursorPosition( - duckedScreen.rescaleX( - PointerNormalizer.reapplyScalingX( - event.getCursorX() - ) - ), - duckedScreen.rescaleY( - PointerNormalizer.reapplyScalingY( - event.getCursorY() - ) - - ) - ); - //@formatter:on + GuiScreenDuck duckedScreen = (GuiScreenDuck) screen; + Mouse.setCursorPosition(duckedScreen.rescaleX(x), duckedScreen.rescaleY(y)); } /** * Updates the input container.
*
- * During a recording this adds the {@linkplain #keyboard}, {@linkplain #mouse} - * and {@linkplain #camera} to {@linkplain #inputs} and increases the + * During a recording this adds the {@linkplain #currentPlaybackKeyboard}, {@linkplain #currentPlaybackMouse} + * and {@linkplain #currentPlaybackCameraAngle} to {@linkplain #inputs} and increases the * {@linkplain #index}.
*
* During playback the opposite is happening, getting the inputs from - * {@linkplain #inputs} and temporarily storing them in {@linkplain #keyboard}, - * {@linkplain #mouse} and {@linkplain #camera}.
+ * {@linkplain #inputs} and temporarily storing them in {@linkplain #currentPlaybackKeyboard}, + * {@linkplain #currentPlaybackMouse} and {@linkplain #currentPlaybackCameraAngle}.
*
- * Then in {@linkplain VirtualInput}, {@linkplain #keyboard}, - * {@linkplain #mouse} and {@linkplain #camera} are retrieved and emulated as + * Then in {@linkplain VirtualInput}, {@linkplain #currentPlaybackKeyboard}, + * {@linkplain #currentPlaybackMouse} and {@linkplain #currentPlaybackCameraAngle} are retrieved and emulated as * the next inputs */ @Override public void onClientTickPost(Minecraft mc) { /* Stop the playback while player is still loading */ EntityPlayerSP player = mc.player; - if (player != null && player.addedToChunk) { - if (isPaused() && stateAfterPause != TASstate.NONE) { + if (isPaused() && stateAfterPause != TASstate.NONE) { // TODO Find a better solution... setTASState(stateAfterPause); // The recording is paused in LoadWorldEvents#startLaunchServer pause(false); EventListenerRegistry.fireEvent(EventPlaybackJoinedWorld.class, state); @@ -479,7 +488,7 @@ public void onClientTickPost(Minecraft mc) { private void recordNextTick() { index++; - InputContainer container = new InputContainer(keyboard.clone(), mouse.clone(), camera.clone()); + InputContainer container = new InputContainer(currentPlaybackKeyboard.clone(), currentPlaybackMouse.clone(), currentPlaybackCameraAngle.clone()); if (inputs.size() <= index) { if (inputs.size() < index) { LOGGER.warn("Index is {} inputs bigger than the container!", index - inputs.size()); @@ -512,10 +521,24 @@ private void playbackNextTick() { } /* Continue condition */ else { - InputContainer container = inputs.get(index); // Loads the new inputs from the container - this.keyboard = container.getKeyboard().clone(); - this.mouse = container.getMouse().clone(); - this.camera = container.getCameraAngle().clone(); + InputContainer container = null; + if (index + 1 < inputs.size()) { + container = inputs.get(index + 1); // Loads the new inputs from the container + + this.currentPlaybackKeyboard = this.nextPlaybackKeyboard.clone(); + this.currentPlaybackMouse = this.nextPlaybackMouse.clone(); + this.currentPlaybackCameraAngle = this.nextPlaybackCameraAngle.clone(); + + this.nextPlaybackKeyboard = container.getKeyboard().clone(); + this.nextPlaybackMouse = container.getMouse().clone(); + this.nextPlaybackCameraAngle = container.getCameraAngle().clone(); + } else { + container = inputs.get(index); // Loads the new inputs from the container + this.currentPlaybackKeyboard = container.getKeyboard().clone(); + this.currentPlaybackMouse = container.getMouse().clone(); + this.currentPlaybackCameraAngle = container.getCameraAngle().clone(); + } + EventListenerRegistry.fireEvent(EventPlaybackTick.class, index, container); } } @@ -558,9 +581,9 @@ public void setIndex(long index) throws IndexOutOfBoundsException { this.index = index; if (state == TASstate.PLAYBACK) { InputContainer inputcontainer = inputs.get(index); - this.keyboard = inputcontainer.getKeyboard(); - this.mouse = inputcontainer.getMouse(); - this.camera = inputcontainer.getCameraAngle(); + this.currentPlaybackKeyboard = inputcontainer.getKeyboard(); + this.currentPlaybackMouse = inputcontainer.getMouse(); + this.currentPlaybackCameraAngle = inputcontainer.getCameraAngle(); } } else { throw new IndexOutOfBoundsException("Index is bigger than the container"); @@ -601,6 +624,30 @@ private void clearInputList() { inputs = new BigArrayList(tasFileDirectory + File.separator + "temp"); } + public VirtualKeyboard getNextPlaybackKeyboard() { + return nextPlaybackKeyboard; + } + + public VirtualMouse getNextPlaybackMouse() { + return nextPlaybackMouse; + } + + public VirtualCameraAngle getNextPlaybackCameraAngle() { + return nextPlaybackCameraAngle; + } + + public List getNextKeyboardPresses() { + return nextPlaybackKeyboard.getCurrentPresses(); + } + + public List getNextMousePresses() { + return nextPlaybackMouse.getCurrentPresses(); + } + + public VirtualMouse getNextMouse() { + return nextPlaybackMouse; + } + /** * Used for displaying the rought contents of the input container */ @@ -620,12 +667,12 @@ public String toString() { // ============================================================== /** - * Clears {@link #keyboard} and {@link #mouse} + * Clears {@link #currentPlaybackKeyboard} and {@link #currentPlaybackMouse} */ public void unpressContainer() { LOGGER.trace(LoggerMarkers.Playback, "Unpressing container"); - keyboard.clear(); - mouse.clear(); + currentPlaybackKeyboard.clear(); + currentPlaybackMouse.clear(); } // ============================================================== diff --git a/src/main/java/com/minecrafttas/tasmod/virtual/VirtualInput.java b/src/main/java/com/minecrafttas/tasmod/virtual/VirtualInput.java index ff615b56..377f4c35 100644 --- a/src/main/java/com/minecrafttas/tasmod/virtual/VirtualInput.java +++ b/src/main/java/com/minecrafttas/tasmod/virtual/VirtualInput.java @@ -1,11 +1,9 @@ package com.minecrafttas.tasmod.virtual; -import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; -import org.apache.commons.lang3.tuple.Triple; import org.apache.logging.log4j.Logger; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; @@ -24,7 +22,6 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; -import net.minecraft.util.math.MathHelper; /** * Main component for redirecting inputs.
@@ -49,6 +46,8 @@ public class VirtualInput { */ public final VirtualCameraAngleInput CAMERA_ANGLE; + public final VirtualInterpolationHandler interpolationHandler = new VirtualInterpolationHandler(); + /** * Creates a new virtual input with an empty {@link VirtualKeyboardInput}, {@link VirtualMouseInput} and {@link VirtualCameraAngleInput} * @param logger The logger instance @@ -295,7 +294,11 @@ public void nextKeyboardTick() { * @return If a keyboard event is in {@link #keyboardEventQueue} */ public boolean nextKeyboardSubtick() { - boolean isPolled = (currentKeyboardEvent = keyboardEventQueue.poll()) != null; + VirtualKeyboardEvent newKeyboardEvent = keyboardEventQueue.poll(); + boolean isPolled = newKeyboardEvent != null; + if (isPolled) { + currentKeyboardEvent = newKeyboardEvent; + } EventListenerRegistry.fireEvent(EventVirtualInput.EventVirtualKeyboardSubtick.class, currentKeyboardEvent); return isPolled; } @@ -464,7 +467,11 @@ public void nextMouseTick() { * @return If a mouse event is in {@link #mouseEventQueue} */ public boolean nextMouseSubtick() { - boolean isPolled = (currentMouseEvent = mouseEventQueue.poll()) != null; + VirtualMouseEvent newMouseEvent = mouseEventQueue.poll(); + boolean isPolled = newMouseEvent != null; + if (isPolled) { + currentMouseEvent = newMouseEvent; + } EventListenerRegistry.fireEvent(EventVirtualInput.EventVirtualMouseSubtick.class, currentMouseEvent); return isPolled; } @@ -608,11 +615,6 @@ public class VirtualCameraAngleInput { * and updates {@link #currentCameraAngle} in {@link #nextCameraTick()} */ private final VirtualCameraAngle nextCameraAngle = new VirtualCameraAngle(); - /** - * States of the {@link #nextCameraAngle} made during the tick.
- * Is updated in {@link #nextCameraTick()} - */ - private final List cameraAngleInterpolationStates = new ArrayList<>(); /** * Constructor to preload the {@link #currentCameraAngle} with an existing @@ -660,8 +662,6 @@ public void updateNextCameraAngle(float pitchDelta, float yawDelta, boolean upda */ public void nextCameraTick() { nextCameraAngle.deepCopyFrom((VirtualCameraAngle) EventListenerRegistry.fireEvent(EventVirtualInput.EventVirtualCameraAngleTick.class, nextCameraAngle)); - cameraAngleInterpolationStates.clear(); - nextCameraAngle.getStates(cameraAngleInterpolationStates); currentCameraAngle.moveFrom(nextCameraAngle); } @@ -697,32 +697,6 @@ public Float getCurrentYaw() { return currentCameraAngle.getYaw(); } - /** - * Gets the absolute coordinates of the camera angle - * - * @param partialTick The partial ticks of the timer - * @param pitch The original pitch of the camera - * @param yaw The original yaw of the camera - * @param enable Whether the custom interpolation is enabled. Enabled during playback. - * @return A triple of pitch, yaw and roll, as left, middle and right respectively - */ - public Triple getInterpolatedState(float partialTick, float pitch, float yaw, boolean enable) { - - float interpolatedPitch = nextCameraAngle.getPitch() == null ? pitch : nextCameraAngle.getPitch(); - float interpolatedYaw = nextCameraAngle.getYaw() == null ? yaw : nextCameraAngle.getYaw() + 180; - - if (enable && !cameraAngleInterpolationStates.isEmpty()) { - int index = (int) MathHelper.clampedLerp(0, cameraAngleInterpolationStates.size() - 1, partialTick); // Get interpolate index - - VirtualCameraAngle interpolatedCamera = cameraAngleInterpolationStates.get(index); - - interpolatedPitch = interpolatedCamera.getPitch(); - interpolatedYaw = interpolatedCamera.getYaw() + 180; - - } - return Triple.of(interpolatedPitch, interpolatedYaw, 0f); - } - /** * Clears the {@link #nextCameraAngle} */ diff --git a/src/main/java/com/minecrafttas/tasmod/virtual/VirtualInterpolationHandler.java b/src/main/java/com/minecrafttas/tasmod/virtual/VirtualInterpolationHandler.java new file mode 100644 index 00000000..9b861940 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/virtual/VirtualInterpolationHandler.java @@ -0,0 +1,189 @@ +package com.minecrafttas.tasmod.virtual; + +import java.util.ArrayList; +import java.util.List; + +import com.minecrafttas.tasmod.TASmodClient; +import com.minecrafttas.tasmod.events.EventVirtualInput; +import com.minecrafttas.tasmod.playback.PlaybackControllerClient; +import com.minecrafttas.tasmod.util.Ducks.GuiScreenDuck; +import com.minecrafttas.tasmod.util.PointerNormalizer; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.math.MathHelper; + +public class VirtualInterpolationHandler implements EventVirtualInput.EventVirtualMouseTick, EventVirtualInput.EventVirtualCameraAngleTick { + + /** + * Copy of the {@link PlaybackControllerClient#nextPlaybackMouse} + */ + private VirtualMouse nextMouse = new VirtualMouse(); + /** + * Copy of the {@link VirtualInput#CAMERA_ANGLE#nextCameraAngle} + */ + private VirtualCameraAngle nextCameraAngle = new VirtualCameraAngle(); + + /** + * States of the {@link #nextMouse} made during the tick.
+ * Is updated in {@link #onVirtualMouseTick()} + */ + private final List mousePointerStates = new ArrayList<>(); + /** + * States of the {@link #nextCameraAngle} made during the tick.
+ * Is updated in {@link #onVirtualCameraTick()} + */ + private final List cameraAngleStates = new ArrayList<>(); +// private int debugFinalIndex = 0; + + @Override + public VirtualMouse onVirtualMouseTick(VirtualMouse vmouse) { + this.nextMouse = vmouse; +// if (TASmodClient.controller.isPlayingback()) { +// System.out.println(debugFinalIndex == mousePointerInterpolationStates.size() - 1); +// } + mousePointerStates.clear(); + TASmodClient.controller.getNextMouse().getStates(mousePointerStates); + return null; + } + + @Override + public VirtualCameraAngle onVirtualCameraTick(VirtualCameraAngle vcamera) { + this.nextCameraAngle = vcamera; + cameraAngleStates.clear(); + nextCameraAngle.getStates(cameraAngleStates); + return null; + } + + /** + * Interpolates the mouse cursor inbetween ticks based on the data from the next tick + * + * @param partialTick The partial ticks used for interpolating + * @param enable If the interpolation should be enabled. Basically if {@link PlaybackControllerClient#isPlayingback()} + * @return A {@link MouseInterpolation} object with x and y coordinates + */ + public MouseInterpolation getInterpolatedMouseCursor(float partialTick, boolean enable) { + + int interpolatedPointerX = nextMouse.getCursorX(); + int interpolatedPointerY = nextMouse.getCursorY(); + + if (enable && !mousePointerStates.isEmpty()) { + partialTick = dynamicallyRound(partialTick, TASmodClient.tickratechanger.ticksPerSecond); + int index = (int) MathHelper.clampedLerp(0, mousePointerStates.size() - 1, partialTick); // Get interpolate index +// debugFinalIndex = index; + VirtualMouse interpolatedCamera = mousePointerStates.get(index); + + interpolatedPointerX = interpolatedCamera.getCursorX(); + interpolatedPointerY = interpolatedCamera.getCursorY(); + + } + Minecraft mc = Minecraft.getMinecraft(); + GuiScreenDuck gui = (GuiScreenDuck) mc.currentScreen; + + if (gui != null) { + interpolatedPointerX = gui.rescaleX(PointerNormalizer.reapplyScalingX(interpolatedPointerX)); + interpolatedPointerY = gui.rescaleY(PointerNormalizer.reapplyScalingY(interpolatedPointerY)); + } + + return new MouseInterpolation(interpolatedPointerX, interpolatedPointerY); + } + + /** + * Rounds the partial tick to 1 depending on the tickrate + * + * To correctly play back the mouse cursor, the partial ticks have to reach 1 at some point. + * However this is not the case in higher tickrates. + * The solution is to round the partial ticks to 1 after a certain threshold. + * + * The higher the tps, the lower the threshold for rounding. + * + * @param partialTick The partial ticks to round + * @param tps The ticks per second used for setting the threshold + * @return The rounded partial ticks + */ + private float dynamicallyRound(float partialTick, float tps) { + float percent = tps / 100; + if (partialTick > 1 - percent) + partialTick = 1; + return partialTick; + } + + /** + * Gets the interpolated coordinates of the camera angle + * + * @param partialTick The partial ticks of the timer + * @param pitch The original pitch of the camera + * @param yaw The original yaw of the camera + * @param enable Whether the custom interpolation is enabled. Enabled during playback. + * @return A triple of pitch, yaw and roll, as left, middle and right respectively + */ + public CameraInterpolation getInterpolatedState(float partialTick, float pitch, float yaw, boolean enable) { + + float interpolatedPitch = nextCameraAngle.getPitch() == null ? pitch : nextCameraAngle.getPitch(); + float interpolatedYaw = nextCameraAngle.getYaw() == null ? yaw : nextCameraAngle.getYaw() + 180; + + if (enable && !cameraAngleStates.isEmpty()) { + int index = (int) MathHelper.clampedLerp(0, cameraAngleStates.size() - 1, partialTick); // Get interpolate index + + VirtualCameraAngle interpolatedCamera = cameraAngleStates.get(index); + + interpolatedPitch = interpolatedCamera.getPitch() == null ? 0 : interpolatedCamera.getPitch(); + interpolatedYaw = interpolatedCamera.getYaw() == null ? 0 : interpolatedCamera.getYaw() + 180; + + } + return new CameraInterpolation(interpolatedPitch, interpolatedYaw); + } + + public static class MouseInterpolation { + final Integer x; + final Integer y; + + public MouseInterpolation(Integer x, Integer y) { + this.x = x; + this.y = y; + } + + public Integer getX() { + return x; + } + + public Integer getY() { + return y; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof MouseInterpolation) { + MouseInterpolation other = (MouseInterpolation) obj; + return this.x.equals(other.x) && this.y.equals(other.y); + } + return super.equals(obj); + } + } + + public static class CameraInterpolation { + final Float pitch; + final Float yaw; + + public CameraInterpolation(Float pitch, Float yaw) { + this.pitch = pitch; + this.yaw = yaw; + } + + public Float getPitch() { + return pitch; + } + + public Float getYaw() { + return yaw; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof CameraInterpolation) { + CameraInterpolation other = (CameraInterpolation) obj; + return this.pitch.equals(other.pitch) && this.yaw.equals(other.yaw); + } + return super.equals(obj); + } + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/virtual/VirtualMouse.java b/src/main/java/com/minecrafttas/tasmod/virtual/VirtualMouse.java index 76bfc0f0..1a475b77 100644 --- a/src/main/java/com/minecrafttas/tasmod/virtual/VirtualMouse.java +++ b/src/main/java/com/minecrafttas/tasmod/virtual/VirtualMouse.java @@ -358,4 +358,11 @@ public int getCursorY() { public boolean isEmpty() { return super.isEmpty() && scrollWheel == 0 && cursorX == 0 && cursorY == 0; } + + public void getStates(List reference) { + if (isParent()) { + reference.addAll(subtickList); + reference.add(this.shallowClone()); + } + } } diff --git a/src/test/java/tasmod/virtual/VirtualInputTest.java b/src/test/java/tasmod/virtual/VirtualInputTest.java index cb2b4582..e7ca7aaf 100644 --- a/src/test/java/tasmod/virtual/VirtualInputTest.java +++ b/src/test/java/tasmod/virtual/VirtualInputTest.java @@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.apache.commons.lang3.tuple.Triple; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.BeforeAll; @@ -15,6 +14,7 @@ import com.minecrafttas.tasmod.events.EventVirtualInput; import com.minecrafttas.tasmod.virtual.VirtualCameraAngle; import com.minecrafttas.tasmod.virtual.VirtualInput; +import com.minecrafttas.tasmod.virtual.VirtualInterpolationHandler.CameraInterpolation; import com.minecrafttas.tasmod.virtual.VirtualKey; import com.minecrafttas.tasmod.virtual.VirtualKeyboard; import com.minecrafttas.tasmod.virtual.VirtualMouse; @@ -274,15 +274,18 @@ void testCurrentCameraAngles() { @Test void testInterpolationDisabled() { VirtualInput virtual = new VirtualInput(LOGGER); + EventListenerRegistry.register(virtual.interpolationHandler); virtual.CAMERA_ANGLE.setCamera(0f, 0f); virtual.CAMERA_ANGLE.updateNextCameraAngle(10f, 20f); virtual.CAMERA_ANGLE.updateNextCameraAngle(20f, 30f); + virtual.CAMERA_ANGLE.nextCameraTick(); - Triple expected = Triple.of(30f, 50f + 180f, 0f); - Triple actual = virtual.CAMERA_ANGLE.getInterpolatedState(0f, 1f, 2f, false); + CameraInterpolation expected = new CameraInterpolation(30f, 50f + 180f); + CameraInterpolation actual = virtual.interpolationHandler.getInterpolatedState(0f, 1f, 2f, false); assertEquals(expected, actual); + EventListenerRegistry.unregister(virtual.interpolationHandler); } /** @@ -291,6 +294,7 @@ void testInterpolationDisabled() { @Test void testInterpolationEnabled() { VirtualInput virtual = new VirtualInput(LOGGER); + EventListenerRegistry.register(virtual.interpolationHandler); virtual.CAMERA_ANGLE.setCamera(0f, 0f); virtual.CAMERA_ANGLE.updateNextCameraAngle(0f, 0f); @@ -306,36 +310,38 @@ void testInterpolationEnabled() { virtual.CAMERA_ANGLE.nextCameraTick(); - Triple expected = Triple.of(0f, 180f, 0f); - Triple actual = virtual.CAMERA_ANGLE.getInterpolatedState(0f, 0f, 0f, true); + CameraInterpolation expected = new CameraInterpolation(0f, 180f); + CameraInterpolation actual = virtual.interpolationHandler.getInterpolatedState(0f, 0f, 0f, true); assertEquals(expected, actual); - expected = Triple.of(0f, 180f, 0f); - actual = virtual.CAMERA_ANGLE.getInterpolatedState(0.1f, 0f, 0f, true); + expected = new CameraInterpolation(0f, 180f); + actual = virtual.interpolationHandler.getInterpolatedState(0.1f, 0f, 0f, true); assertEquals(expected, actual); - expected = Triple.of(10f, 190f, 0f); - actual = virtual.CAMERA_ANGLE.getInterpolatedState(0.199f, 0f, 0f, true); + expected = new CameraInterpolation(10f, 190f); + actual = virtual.interpolationHandler.getInterpolatedState(0.199f, 0f, 0f, true); assertEquals(expected, actual); - expected = Triple.of(10f, 190f, 0f); - actual = virtual.CAMERA_ANGLE.getInterpolatedState(0.2f, 0f, 0f, true); + expected = new CameraInterpolation(10f, 190f); + actual = virtual.interpolationHandler.getInterpolatedState(0.2f, 0f, 0f, true); assertEquals(expected, actual); - expected = Triple.of(20f, 200f, 0f); - actual = virtual.CAMERA_ANGLE.getInterpolatedState(0.3f, 0f, 0f, true); + expected = new CameraInterpolation(20f, 200f); + actual = virtual.interpolationHandler.getInterpolatedState(0.3f, 0f, 0f, true); assertEquals(expected, actual); - expected = Triple.of(30f, 210f, 0f); - actual = virtual.CAMERA_ANGLE.getInterpolatedState(0.4f, 0f, 0f, true); + expected = new CameraInterpolation(30f, 210f); + actual = virtual.interpolationHandler.getInterpolatedState(0.4f, 0f, 0f, true); assertEquals(expected, actual); - expected = Triple.of(40f, 220f, 0f); - actual = virtual.CAMERA_ANGLE.getInterpolatedState(0.5f, 0f, 0f, true); + expected = new CameraInterpolation(40f, 220f); + actual = virtual.interpolationHandler.getInterpolatedState(0.5f, 0f, 0f, true); assertEquals(expected, actual); - expected = Triple.of(50f, 230f, 0f); - actual = virtual.CAMERA_ANGLE.getInterpolatedState(0.6f, 0f, 0f, true); + expected = new CameraInterpolation(50f, 230f); + actual = virtual.interpolationHandler.getInterpolatedState(0.6f, 0f, 0f, true); assertEquals(expected, actual); + + EventListenerRegistry.unregister(virtual.interpolationHandler); } }