From 3d009e45c74fd8bb152070016548d1dd297833f1 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 4 May 2019 14:37:00 +0200 Subject: [PATCH] Update to MC 1.14 / Fabric --- build.gradle | 5 +- jGui | 2 +- settings.gradle | 6 +- .../com/replaymod/compat/ReplayModCompat.java | 17 +- .../compat/optifine/DisableFastRender.java | 14 +- .../HideInvisibleEntities.java | 17 +- .../compat/shaders/ShaderBeginRender.java | 17 +- .../mixin/MixinShaderEntityRenderer.java | 4 + .../shaders/mixin/MixinShaderRenderChunk.java | 2 +- .../mixin/MixinShaderRenderGlobal.java | 33 +- .../shaders/mixin/MixinShadersRender.java | 11 +- .../replaymod/core/KeyBindingRegistry.java | 79 ++- .../com/replaymod/core/LoadingPlugin.java | 1 + .../java/com/replaymod/core/ReplayMod.java | 145 ++++- .../com/replaymod/core/SettingsRegistry.java | 99 +++- .../core/events/KeyBindingEventCallback.java | 19 + .../core/events/KeyEventCallback.java | 19 + .../core/events/PostRenderCallback.java | 19 + .../core/events/PostRenderWorldCallback.java | 19 + .../core/events/PreRenderCallback.java | 19 + .../core/events/PreRenderHandCallback.java | 22 + .../core/events/SettingsChangedCallback.java | 18 + .../core/events/SettingsChangedEvent.java | 24 - .../core/gui/GuiBackgroundProcesses.java | 36 +- .../com/replaymod/core/gui/GuiFactory.java | 2 + .../core/handler/MainMenuHandler.java | 44 +- .../mixin/AbstractButtonWidgetAccessor.java | 13 + .../core/mixin/GuiScreenAccessor.java | 32 + .../core/mixin/MinecraftAccessor.java | 8 + .../core/mixin/MixinGameRenderer.java | 59 ++ .../core/mixin/MixinKeyboardListener.java | 61 ++ .../replaymod/core/mixin/MixinMinecraft.java | 54 +- .../core/mixin/MixinModResourcePackUtil.java | 37 ++ .../core/mixin/MixinMouseListener.java | 60 ++ .../com/replaymod/core/mixin/MixinScreen.java | 27 + .../core/utils/EventRegistration.java | 64 -- .../core/utils/EventRegistrations.java | 31 - .../com/replaymod/core/utils/ModCompat.java | 16 + .../core/versions/LangResourcePack.java | 92 ++- .../com/replaymod/core/versions/MCVer.java | 160 ++++- .../com/replaymod/editor/ReplayModEditor.java | 3 - .../replaymod/editor/handler/GuiHandler.java | 32 +- .../com/replaymod/extras/FullBrightness.java | 56 +- .../com/replaymod/extras/HotkeyButtons.java | 28 +- .../com/replaymod/extras/OpenEyeExtra.java | 17 +- .../AdvancedScreenshots.java | 6 +- .../ScreenshotRenderer.java | 6 +- .../extras/playeroverview/PlayerOverview.java | 47 +- .../extras/youtube/GuiYoutubeUpload.java | 4 + .../extras/youtube/YoutubeUpload.java | 31 +- .../com/replaymod/online/ReplayModOnline.java | 32 +- .../api/replay/holders/MinecraftVersion.java | 3 +- .../replaymod/online/gui/GuiLoginPrompt.java | 8 +- .../com/replaymod/online/gui/GuiRegister.java | 8 +- .../replaymod/online/gui/GuiReplayCenter.java | 8 +- .../online/gui/GuiReplayDownloading.java | 18 +- .../replaymod/online/handler/GuiHandler.java | 87 +-- .../player/AbstractTimelinePlayer.java | 21 +- .../player/RealtimeTimelinePlayer.java | 11 +- .../replaymod/pathing/player/ReplayTimer.java | 30 +- .../pathing/properties/SpectatorProperty.java | 3 +- .../recording/ReplayModRecording.java | 30 +- .../recording/gui/GuiRecordingControls.java | 24 +- .../recording/gui/GuiRecordingOverlay.java | 35 +- .../handler/ConnectionEventHandler.java | 5 +- .../recording/handler/GuiHandler.java | 25 +- .../handler/RecordingEventHandler.java | 164 +++-- .../mixin/MixinDownloadingPackFinder.java | 23 +- .../mixin/MixinKeyboardListener.java | 47 -- .../recording/mixin/MixinMouseHelper.java | 15 +- .../mixin/MixinNetHandlerLoginClient.java | 24 +- .../mixin/MixinNetHandlerPlayClient.java | 2 +- .../recording/mixin/MixinWorldClient.java | 29 +- .../mixin/NetworkManagerAccessor.java | 12 + .../packet/ResourcePackRecorder.java | 59 +- .../com/replaymod/render/ReplayModRender.java | 10 +- .../render/blend/BlendMeshBuilder.java | 2 +- .../java/com/replaymod/render/blend/Util.java | 21 + .../blend/exporters/EntityExporter.java | 8 +- .../render/blend/exporters/ItemExporter.java | 14 +- .../blend/exporters/ParticlesExporter.java | 21 +- .../blend/exporters/TileEntityExporter.java | 6 + ...ntainerLocalRenderInformationAccessor.java | 8 + .../blend/mixin/MixinEffectRenderer.java | 29 +- .../render/blend/mixin/MixinRenderGlobal.java | 39 +- .../render/blend/mixin/MixinRenderItem.java | 4 + .../blend/mixin/MixinRenderManager.java | 8 + .../MixinTileEntityRendererDispatcher.java | 10 + .../render/capturer/ODSFrameCapturer.java | 8 +- .../render/capturer/OpenGlFrameCapturer.java | 12 +- .../render/events/ReplayRenderCallback.java | 29 + .../render/events/ReplayRenderEvent.java | 33 -- .../replaymod/render/gui/GuiRenderQueue.java | 16 +- .../render/gui/GuiRenderSettings.java | 16 +- .../hooks/ChunkLoadingRenderGlobal.java | 9 +- .../render/hooks/EntityRendererHandler.java | 6 + .../replaymod/render/mixin/MixinCamera.java | 168 ++++++ .../render/mixin/MixinEntityRenderer.java | 136 ++--- .../render/mixin/MixinFogRenderer.java | 15 +- .../render/mixin/MixinGlStateManager.java | 15 +- .../render/mixin/MixinParticleManager.java | 46 +- .../replaymod/render/mixin/MixinRender.java | 4 + .../render/mixin/MixinRenderGlobal.java | 31 +- .../render/mixin/MixinRenderManager.java | 8 +- .../render/rendering/VideoRenderer.java | 44 +- .../render/utils/PixelBufferObject.java | 4 +- .../replaymod/replay/FullReplaySender.java | 154 +++-- .../replaymod/replay/InputReplayTimer.java | 5 + .../com/replaymod/replay/NoGuiScreenshot.java | 20 +- .../replaymod/replay/QuickReplaySender.java | 50 +- .../com/replaymod/replay/ReplayHandler.java | 45 +- .../replaymod/replay/camera/CameraEntity.java | 241 +++++--- .../camera/ClassicCameraController.java | 10 +- .../camera/SpectatorCameraController.java | 11 +- .../camera/VanillaCameraController.java | 7 +- .../RenderSpectatorCrosshairCallback.java | 24 + .../replay/events/ReplayChatMessageEvent.java | 2 + .../replay/events/ReplayCloseEvent.java | 24 - .../replay/events/ReplayClosedCallback.java | 16 + .../replay/events/ReplayClosingCallback.java | 18 + .../events/ReplayDispatchKeypressesEvent.java | 22 +- .../replay/events/ReplayOpenEvent.java | 24 - .../replay/events/ReplayOpenedCallback.java | 18 + .../replay/gui/overlay/GuiReplayOverlay.java | 71 ++- .../replay/gui/screen/GuiReplayViewer.java | 19 +- .../replaymod/replay/handler/GuiHandler.java | 256 +++++--- .../replaymod/replay/mixin/MixinCamera.java | 30 + .../replay/mixin/MixinGuiSpectator.java | 9 +- .../replay/mixin/MixinInGameHud.java | 22 + .../replay/mixin/MixinKeyboardListener.java | 56 ++ .../replay/mixin/MixinParticleManager.java | 4 +- .../replay/mixin/MixinPlayerControllerMP.java | 32 +- .../replay/mixin/MixinRenderItem.java | 4 + .../replay/mixin/MixinRenderLivingBase.java | 12 +- .../replay/mixin/MixinRenderManager.java | 3 +- .../replay/mixin/MixinViewFrustum.java | 27 +- .../replay/mixin/MixinWorldClient.java | 3 + .../simplepathing/ReplayModSimplePathing.java | 62 +- .../simplepathing/preview/PathPreview.java | 60 +- .../preview/PathPreviewRenderer.java | 32 +- src/main/resources/fabric.mod.json | 39 ++ src/main/resources/mixins.core.replaymod.json | 15 +- .../resources/mixins.recording.replaymod.json | 2 +- .../resources/mixins.render.replaymod.json | 1 + .../resources/mixins.replay.replaymod.json | 9 + versions/1.11/mapping.txt | 1 + versions/1.12/mapping.txt | 5 +- versions/1.13.2/mapping.txt | 4 +- versions/1.14/mapping.txt | 558 ++++++++++++++++++ versions/1.9.4/mapping.txt | 9 +- versions/common.gradle | 242 +++++--- 151 files changed, 3963 insertions(+), 1455 deletions(-) create mode 100644 src/main/java/com/replaymod/core/events/KeyBindingEventCallback.java create mode 100644 src/main/java/com/replaymod/core/events/KeyEventCallback.java create mode 100644 src/main/java/com/replaymod/core/events/PostRenderCallback.java create mode 100644 src/main/java/com/replaymod/core/events/PostRenderWorldCallback.java create mode 100644 src/main/java/com/replaymod/core/events/PreRenderCallback.java create mode 100644 src/main/java/com/replaymod/core/events/PreRenderHandCallback.java create mode 100644 src/main/java/com/replaymod/core/events/SettingsChangedCallback.java delete mode 100644 src/main/java/com/replaymod/core/events/SettingsChangedEvent.java create mode 100644 src/main/java/com/replaymod/core/mixin/AbstractButtonWidgetAccessor.java create mode 100644 src/main/java/com/replaymod/core/mixin/GuiScreenAccessor.java create mode 100644 src/main/java/com/replaymod/core/mixin/MixinGameRenderer.java create mode 100644 src/main/java/com/replaymod/core/mixin/MixinKeyboardListener.java create mode 100644 src/main/java/com/replaymod/core/mixin/MixinModResourcePackUtil.java create mode 100644 src/main/java/com/replaymod/core/mixin/MixinMouseListener.java create mode 100644 src/main/java/com/replaymod/core/mixin/MixinScreen.java delete mode 100644 src/main/java/com/replaymod/core/utils/EventRegistration.java delete mode 100644 src/main/java/com/replaymod/core/utils/EventRegistrations.java delete mode 100644 src/main/java/com/replaymod/recording/mixin/MixinKeyboardListener.java create mode 100644 src/main/java/com/replaymod/recording/mixin/NetworkManagerAccessor.java create mode 100644 src/main/java/com/replaymod/render/events/ReplayRenderCallback.java delete mode 100644 src/main/java/com/replaymod/render/events/ReplayRenderEvent.java create mode 100644 src/main/java/com/replaymod/render/mixin/MixinCamera.java create mode 100644 src/main/java/com/replaymod/replay/events/RenderSpectatorCrosshairCallback.java delete mode 100644 src/main/java/com/replaymod/replay/events/ReplayCloseEvent.java create mode 100644 src/main/java/com/replaymod/replay/events/ReplayClosedCallback.java create mode 100644 src/main/java/com/replaymod/replay/events/ReplayClosingCallback.java delete mode 100644 src/main/java/com/replaymod/replay/events/ReplayOpenEvent.java create mode 100644 src/main/java/com/replaymod/replay/events/ReplayOpenedCallback.java create mode 100644 src/main/java/com/replaymod/replay/mixin/MixinCamera.java create mode 100644 src/main/java/com/replaymod/replay/mixin/MixinInGameHud.java create mode 100644 src/main/java/com/replaymod/replay/mixin/MixinKeyboardListener.java create mode 100755 src/main/resources/fabric.mod.json create mode 100644 versions/1.11/mapping.txt create mode 100644 versions/1.14/mapping.txt diff --git a/build.gradle b/build.gradle index 05149237..13f45c2c 100755 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,9 @@ if (gitDescribe().endsWith('*')) { group= "com.replaymod" +// Fabric's remapJar task assumes the compile configuration to exist +configurations { compile } + subprojects { buildscript { repositories { @@ -44,7 +47,7 @@ subprojects { apply plugin: 'idea' } -evaluationDependsOnChildren() +//evaluationDependsOnChildren() task shadowJar(type: Copy) { subprojects.forEach { def shadowJar = it.tasks.findByName('shadowJar') diff --git a/jGui b/jGui index 8f2e1c72..f7ffcbd4 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 8f2e1c7218f3fe6fa31a4476be324355633e3cc9 +Subproject commit f7ffcbd41a008c935e5d48cf20d628a3fcb4d40f diff --git a/settings.gradle b/settings.gradle index 30dd1b1c..39144031 100644 --- a/settings.gradle +++ b/settings.gradle @@ -26,7 +26,8 @@ if (gradle.gradleVersion.startsWith('3.')) { '1.8.9', '1.9.4', '1.12', - '1.13.2' + '1.13.2', + '1.14', ] replayModVersions += [ '1.8', @@ -38,7 +39,8 @@ if (gradle.gradleVersion.startsWith('3.')) { '1.12', '1.12.1', '1.12.2', - '1.13.2' + '1.13.2', + '1.14', ] } diff --git a/src/main/java/com/replaymod/compat/ReplayModCompat.java b/src/main/java/com/replaymod/compat/ReplayModCompat.java index e124b520..2e952481 100644 --- a/src/main/java/com/replaymod/compat/ReplayModCompat.java +++ b/src/main/java/com/replaymod/compat/ReplayModCompat.java @@ -1,12 +1,14 @@ package com.replaymod.compat; import com.replaymod.compat.optifine.DisableFastRender; -import com.replaymod.compat.oranges17animations.HideInvisibleEntities; import com.replaymod.core.Module; -import net.minecraftforge.eventbus.api.IEventBus; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +//#if MC<11400 +import com.replaymod.compat.oranges17animations.HideInvisibleEntities; +//#endif + //#if MC<11300 //$$ import com.replaymod.compat.bettersprinting.DisableBetterSprinting; //#endif @@ -15,19 +17,18 @@ import org.apache.logging.log4j.Logger; import com.replaymod.compat.shaders.ShaderBeginRender; //#endif -import static com.replaymod.core.versions.MCVer.*; - public class ReplayModCompat implements Module { public static Logger LOGGER = LogManager.getLogger(); @Override public void initClient() { - IEventBus bus = FML_BUS; //#if MC>=10800 - bus.register(new ShaderBeginRender()); + new ShaderBeginRender().register(); + //#endif + new DisableFastRender().register(); + //#if MC<11400 + new HideInvisibleEntities().register(); //#endif - bus.register(new DisableFastRender()); - bus.register(new HideInvisibleEntities()); //#if MC<11300 //$$ DisableBetterSprinting.register(); //#endif diff --git a/src/main/java/com/replaymod/compat/optifine/DisableFastRender.java b/src/main/java/com/replaymod/compat/optifine/DisableFastRender.java index 35b221af..86be2bb0 100644 --- a/src/main/java/com/replaymod/compat/optifine/DisableFastRender.java +++ b/src/main/java/com/replaymod/compat/optifine/DisableFastRender.java @@ -1,18 +1,18 @@ package com.replaymod.compat.optifine; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import com.replaymod.core.versions.MCVer; -import com.replaymod.render.events.ReplayRenderEvent; +import com.replaymod.render.events.ReplayRenderCallback; import net.minecraft.client.Minecraft; -import net.minecraftforge.eventbus.api.SubscribeEvent; -public class DisableFastRender { +public class DisableFastRender extends EventRegistrations { private final Minecraft mc = Minecraft.getInstance(); private boolean wasFastRender = false; - @SubscribeEvent - public void onRenderBegin(ReplayRenderEvent.Pre event) { + { on(ReplayRenderCallback.Pre.EVENT, renderer -> onRenderBegin()); } + private void onRenderBegin() { if (!MCVer.hasOptifine()) return; try { @@ -23,8 +23,8 @@ public class DisableFastRender { } } - @SubscribeEvent - public void onRenderEnd(ReplayRenderEvent.Post event){ + { on(ReplayRenderCallback.Post.EVENT, renderer -> onRenderEnd()); } + private void onRenderEnd() { if (!MCVer.hasOptifine()) return; try { diff --git a/src/main/java/com/replaymod/compat/oranges17animations/HideInvisibleEntities.java b/src/main/java/com/replaymod/compat/oranges17animations/HideInvisibleEntities.java index 64729f18..bafc23f2 100644 --- a/src/main/java/com/replaymod/compat/oranges17animations/HideInvisibleEntities.java +++ b/src/main/java/com/replaymod/compat/oranges17animations/HideInvisibleEntities.java @@ -1,17 +1,13 @@ +//#if MC<11400 package com.replaymod.compat.oranges17animations; import com.replaymod.replay.camera.CameraEntity; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import net.minecraft.client.Minecraft; import net.minecraftforge.client.event.RenderLivingEvent; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; -//#if MC>=11300 -import net.minecraftforge.fml.ModList; -//#else -//$$ import net.minecraftforge.fml.common.Loader; -//#endif - import static com.replaymod.core.versions.MCVer.*; /** @@ -20,13 +16,9 @@ import static com.replaymod.core.versions.MCVer.*; * * To fix this issue, we simply cancel the RenderLivingEvent.Pre before it gets to ArmorAnimation if the entity is invisible. */ -public class HideInvisibleEntities { +public class HideInvisibleEntities extends EventRegistrations { private final Minecraft mc = Minecraft.getInstance(); - //#if MC>=11300 - private final boolean modLoaded = ModList.get().isLoaded("animations"); - //#else - //$$ private final boolean modLoaded = Loader.isModLoaded("animations"); - //#endif + private final boolean modLoaded = isModLoaded("animations"); @SubscribeEvent(priority = EventPriority.HIGH) public void preRenderLiving(RenderLivingEvent.Pre event) { @@ -37,3 +29,4 @@ public class HideInvisibleEntities { } } } +//#endif diff --git a/src/main/java/com/replaymod/compat/shaders/ShaderBeginRender.java b/src/main/java/com/replaymod/compat/shaders/ShaderBeginRender.java index 9ed1b651..16cad146 100644 --- a/src/main/java/com/replaymod/compat/shaders/ShaderBeginRender.java +++ b/src/main/java/com/replaymod/compat/shaders/ShaderBeginRender.java @@ -2,15 +2,21 @@ package com.replaymod.compat.shaders; import com.replaymod.render.hooks.EntityRendererHandler; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import net.minecraft.client.Minecraft; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.gameevent.TickEvent; import java.lang.reflect.InvocationTargetException; import static com.replaymod.core.versions.MCVer.getRenderPartialTicks; -public class ShaderBeginRender { +//#if MC>=11400 +//$$ import com.replaymod.core.events.PreRenderCallback; +//#else +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +//#endif + +public class ShaderBeginRender extends EventRegistrations { private final Minecraft mc = Minecraft.getInstance(); @@ -19,9 +25,14 @@ public class ShaderBeginRender { * as this would usually get called by EntityRenderer#renderWorld, * which we're not calling during rendering. */ + //#if MC>=11400 + //$$ { on(PreRenderCallback.EVENT, this::onRenderTickStart); } + //$$ private void onRenderTickStart() { + //#else @SubscribeEvent public void onRenderTickStart(TickEvent.RenderTickEvent event) { if (event.phase != TickEvent.Phase.START) return; + //#endif if (ShaderReflection.shaders_beginRender == null) return; if (ShaderReflection.config_isShaders == null) return; diff --git a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderEntityRenderer.java b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderEntityRenderer.java index 4b5e2998..8491acc4 100644 --- a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderEntityRenderer.java +++ b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderEntityRenderer.java @@ -13,11 +13,15 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(GameRenderer.class) public abstract class MixinShaderEntityRenderer { + //#if MC>=11400 + //$$ @Inject(method = "renderCenter", at = @At("HEAD")) + //#else //#if MC>=11300 @Inject(method = "updateCameraAndRender(FJ)V", at = @At("HEAD")) //#else //$$ @Inject(method = "renderWorldPass", at = @At("HEAD")) //#endif + //#endif private void replayModCompat_updateShaderFrameTimeCounter(CallbackInfo ignore) { if (ReplayModReplay.instance.getReplayHandler() == null) return; if (ShaderReflection.shaders_frameTimeCounter == null) return; diff --git a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderChunk.java b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderChunk.java index d46a41c8..45b59d39 100644 --- a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderChunk.java +++ b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderChunk.java @@ -19,7 +19,7 @@ public abstract class MixinShaderRenderChunk { * to always return true while rendering so no chunks are being added * to a separate rendering queue */ - @Inject(method = "isPlayerUpdate", at = @At("HEAD"), cancellable = true) + @Inject(method = "isPlayerUpdate", at = @At("HEAD"), cancellable = true, remap = false) private void replayModCompat_disableIsPlayerUpdate(CallbackInfoReturnable ci) { // TODO: Update to 1.12 once optifine is available // TODO: We're on 1.13 now and haven't gotten any 1.12 complaints, so 1.12 is probably working but 1.13? diff --git a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderGlobal.java b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderGlobal.java index 4f1e04be..9cb30ac4 100644 --- a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderGlobal.java +++ b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderGlobal.java @@ -6,13 +6,18 @@ import com.replaymod.render.hooks.EntityRendererHandler; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.WorldRenderer; import net.minecraft.client.renderer.culling.ICamera; -import net.minecraft.entity.Entity; 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; +//#if MC>=11400 +//$$ import net.minecraft.client.render.Camera; +//#else +import net.minecraft.entity.Entity; +//#endif + @Mixin(WorldRenderer.class) public abstract class MixinShaderRenderGlobal { @@ -22,14 +27,22 @@ public abstract class MixinShaderRenderGlobal { public boolean displayListEntitiesDirty; @Inject(method = "setupTerrain", at = @At("HEAD"), cancellable = true) - public void replayModCompat_setupTerrain(Entity viewEntity, - //#if MC>=11300 - float partialTicks, - //#else - //$$ double partialTicks, - //#endif - ICamera camera, - int frameCount, boolean playerSpectator, CallbackInfo ci) { + public void replayModCompat_setupTerrain( + //#if MC>=11400 + //$$ Camera viewEntity, + //#else + Entity viewEntity, + //#if MC>=11300 + float partialTicks, + //#else + //$$ double partialTicks, + //#endif + //#endif + ICamera camera, + int frameCount, + boolean playerSpectator, + CallbackInfo ci + ) { if (((EntityRendererHandler.IEntityRenderer) mc.entityRenderer).replayModRender_getHandler() == null) return; if (ShaderReflection.shaders_isShadowPass == null) return; @@ -38,7 +51,7 @@ public abstract class MixinShaderRenderGlobal { // set it to false manually to exit the loop imposed by MixinRenderGlobal#replayModRender_setupTerrain. try { if ((boolean) ShaderReflection.shaders_isShadowPass.get(null) == true) { - displayListEntitiesDirty = false; + this.displayListEntitiesDirty = false; } } catch (IllegalAccessException ignore) {} diff --git a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShadersRender.java b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShadersRender.java index 51df262b..4ad55ec9 100644 --- a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShadersRender.java +++ b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShadersRender.java @@ -1,13 +1,18 @@ package com.replaymod.compat.shaders.mixin; import net.minecraft.client.renderer.GameRenderer; -import net.minecraftforge.client.ForgeHooksClient; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=11400 +//$$ import com.replaymod.core.events.PreRenderHandCallback; +//#else +import net.minecraftforge.client.ForgeHooksClient; +//#endif + @Pseudo @Mixin(targets = { "shadersmod/client/ShadersRender", // Pre Optifine 1.12.2 E1 @@ -17,10 +22,14 @@ public abstract class MixinShadersRender { @Inject(method = "renderHand0", at = @At("HEAD"), cancellable = true) private static void replayModCompat_disableRenderHand0(GameRenderer er, float partialTicks, int renderPass, CallbackInfo ci) { + //#if MC>=11400 + //$$ if (PreRenderHandCallback.EVENT.invoker().preRenderHand()) { + //#else //#if MC>=11300 if (ForgeHooksClient.renderFirstPersonHand(er.getMinecraft().renderGlobal, partialTicks)) { //#else //$$ if (ForgeHooksClient.renderFirstPersonHand(er.mc.renderGlobal, partialTicks, renderPass)) { + //#endif //#endif ci.cancel(); } diff --git a/src/main/java/com/replaymod/core/KeyBindingRegistry.java b/src/main/java/com/replaymod/core/KeyBindingRegistry.java index 79a27aba..efa2ae76 100644 --- a/src/main/java/com/replaymod/core/KeyBindingRegistry.java +++ b/src/main/java/com/replaymod/core/KeyBindingRegistry.java @@ -2,19 +2,33 @@ package com.replaymod.core; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import com.replaymod.core.versions.MCVer; import net.minecraft.client.settings.KeyBinding; import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; import net.minecraft.crash.ReportedException; -import net.minecraftforge.eventbus.api.SubscribeEvent; + +//#if MC>=11400 +//$$ import com.replaymod.core.versions.LangResourcePack; +//$$ import net.fabricmc.fabric.api.client.keybinding.FabricKeyBinding; +//$$ import net.minecraft.client.util.InputUtil; +//$$ import net.minecraft.util.Identifier; +//$$ import static com.replaymod.core.ReplayMod.MOD_ID; +//#else import net.minecraftforge.fml.client.registry.ClientRegistry; -import net.minecraftforge.fml.common.gameevent.InputEvent; -import net.minecraftforge.fml.common.gameevent.TickEvent; +//#endif //#if MC>=11300 +import com.replaymod.core.events.KeyBindingEventCallback; +import com.replaymod.core.events.KeyEventCallback; +import com.replaymod.core.events.PreRenderCallback; //#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//$$ import net.minecraftforge.fml.common.gameevent.InputEvent; +//$$ import net.minecraftforge.fml.common.gameevent.TickEvent; //$$ import org.lwjgl.input.Keyboard; +//$$ import static com.replaymod.core.versions.MCVer.FML_BUS; //#endif import java.util.Collection; @@ -22,7 +36,12 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -public class KeyBindingRegistry { +public class KeyBindingRegistry extends EventRegistrations { + private static final String CATEGORY = "replaymod.title"; + //#if MC>=11400 + //$$ static { net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry.INSTANCE.addCategory(CATEGORY); } + //#endif + private Map keyBindings = new HashMap(); private Multimap keyBindingHandlers = ArrayListMultimap.create(); private Multimap repeatedKeyBindingHandlers = ArrayListMultimap.create(); @@ -39,9 +58,19 @@ public class KeyBindingRegistry { private KeyBinding registerKeyBinding(String name, int keyCode) { KeyBinding keyBinding = keyBindings.get(name); if (keyBinding == null) { - keyBinding = new KeyBinding(name, keyCode, "replaymod.title"); - keyBindings.put(name, keyBinding); + //#if MC>=11400 + //$$ if (keyCode == 0) { + //$$ keyCode = -1; + //$$ } + //$$ Identifier id = new Identifier(MOD_ID, name.substring(LangResourcePack.LEGACY_KEY_PREFIX.length())); + //$$ FabricKeyBinding fabricKeyBinding = FabricKeyBinding.Builder.create(id, InputUtil.Type.KEYSYM, keyCode, CATEGORY).build(); + //$$ net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry.INSTANCE.register(fabricKeyBinding); + //$$ keyBinding = fabricKeyBinding; + //#else + keyBinding = new KeyBinding(name, keyCode, CATEGORY); ClientRegistry.registerKeyBinding(keyBinding); + //#endif + keyBindings.put(name, keyBinding); } return keyBinding; } @@ -54,17 +83,23 @@ public class KeyBindingRegistry { return Collections.unmodifiableMap(keyBindings); } - @SubscribeEvent - public void onKeyInput(InputEvent.KeyInputEvent event) { - handleKeyBindings(); - handleRaw(); - } - - @SubscribeEvent - public void onTick(TickEvent.RenderTickEvent event) { - if (event.phase != TickEvent.Phase.START) return; - handleRepeatedKeyBindings(); - } + //#if MC>=11300 + { on(KeyBindingEventCallback.EVENT, this::handleKeyBindings); } + { on(KeyEventCallback.EVENT, (keyCode, scanCode, action, modifiers) -> handleRaw(keyCode, action)); } + { on(PreRenderCallback.EVENT, this::handleRepeatedKeyBindings); } + //#else + //$$ @SubscribeEvent + //$$ public void onKeyInput(InputEvent.KeyInputEvent event) { + //$$ handleKeyBindings(); + //$$ handleRaw(); + //$$ } + //$$ + //$$ @SubscribeEvent + //$$ public void onTick(TickEvent.RenderTickEvent event) { + //$$ if (event.phase != TickEvent.Phase.START) return; + //$$ handleRepeatedKeyBindings(); + //$$ } + //#endif public void handleRepeatedKeyBindings() { for (Map.Entry> entry : repeatedKeyBindingHandlers.asMap().entrySet()) { @@ -96,9 +131,13 @@ public class KeyBindingRegistry { } } - public void handleRaw() { - //int keyCode = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() + 256 : Keyboard.getEventKey(); - int keyCode = -1; // FIXME + //#if MC>=11300 + private void handleRaw(int keyCode, int action) { + if (action != 0) return; + //#else + //$$ private void handleRaw() { + //$$ int keyCode = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() + 256 : Keyboard.getEventKey(); + //#endif for (final Runnable runnable : rawHandlers.get(keyCode)) { try { runnable.run(); diff --git a/src/main/java/com/replaymod/core/LoadingPlugin.java b/src/main/java/com/replaymod/core/LoadingPlugin.java index 63356137..021e9a03 100755 --- a/src/main/java/com/replaymod/core/LoadingPlugin.java +++ b/src/main/java/com/replaymod/core/LoadingPlugin.java @@ -26,6 +26,7 @@ package com.replaymod.core; //$$ //$$ public LoadingPlugin() { //$$ MixinBootstrap.init(); +//$$ Mixins.addConfiguration("mixins.core.replaymod.json"); //$$ Mixins.addConfiguration("mixins.recording.replaymod.json"); //$$ Mixins.addConfiguration("mixins.render.replaymod.json"); //$$ Mixins.addConfiguration("mixins.render.blend.replaymod.json"); diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 653833c2..cc32d8df 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -21,10 +21,26 @@ import de.johni0702.minecraft.gui.container.GuiScreen; import lombok.Getter; import net.minecraft.client.Minecraft; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import org.apache.commons.io.Charsets; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.util.text.TextFormatting; import org.apache.commons.io.FileUtils; +//#if MC>=11400 +//$$ import com.github.steveice10.mc.protocol.MinecraftConstants; +//$$ import com.replaymod.core.versions.LangResourcePack; +//$$ import net.fabricmc.api.ClientModInitializer; +//$$ import net.fabricmc.loader.api.FabricLoader; +//$$ import net.minecraft.SharedConstants; +//$$ import net.minecraft.client.options.GameOption; +//$$ import net.minecraft.resource.DirectoryResourcePack; +//$$ import net.minecraft.resource.ResourcePackCreator; +//$$ import net.minecraft.resource.ResourcePackContainer; +//#else +import net.minecraftforge.eventbus.api.SubscribeEvent; + //#if MC>=11300 import com.replaymod.core.versions.LangResourcePack; import net.minecraft.resources.FolderPack; @@ -41,28 +57,16 @@ import net.minecraftforge.versions.mcp.MCPVersion; //$$ import net.minecraftforge.common.config.Configuration; //#endif -//#if MC>=10904 -import net.minecraft.util.text.*; -//#else -//$$ import net.minecraft.util.ChatComponentText; -//$$ import net.minecraft.util.ChatComponentTranslation; -//$$ import net.minecraft.util.ChatStyle; -//$$ import net.minecraft.util.EnumChatFormatting; -//$$ import net.minecraft.util.IChatComponent; -//#endif - //#if MC>=10800 import net.minecraft.client.GameSettings; //#endif //#if MC>=11300 -import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.ModList; //#else //$$ import net.minecraftforge.fml.common.Loader; //$$ import net.minecraftforge.fml.common.Mod.EventHandler; //$$ import net.minecraftforge.fml.common.Mod.Instance; -//$$ import net.minecraftforge.fml.common.ModContainer; //$$ import net.minecraftforge.fml.common.event.FMLInitializationEvent; //$$ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; //#if MC>=10800 @@ -75,11 +79,13 @@ import net.minecraftforge.fml.ModList; //#endif import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.gameevent.TickEvent; +//#endif import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -88,6 +94,7 @@ import java.util.concurrent.FutureTask; import static com.replaymod.core.versions.MCVer.*; +//#if MC<11400 //#if MC>=11300 @Mod(ReplayMod.MOD_ID) //#else @@ -102,22 +109,24 @@ import static com.replaymod.core.versions.MCVer.*; //#endif //$$ guiFactory = "com.replaymod.core.gui.GuiFactory") //#endif -public class ReplayMod implements Module { - - public static ModContainer getContainer() { - //#if MC>=11300 - return ModList.get().getModContainerById(MOD_ID).get(); - //#else - //$$ return Loader.instance().getIndexedModList().get(MOD_ID); +//#endif +public class ReplayMod implements + //#if MC>=11400 + //$$ ClientModInitializer, //#endif - } + Module +{ @Getter(lazy = true) + //#if MC>=11400 + //$$ private static final String minecraftVersion = MinecraftClient.getInstance().getGame().getVersion().getName(); + //#else //#if MC>=11300 private static final String minecraftVersion = MCPVersion.getMCVersion(); //#else //$$ private static final String minecraftVersion = Loader.MC_VERSION; //#endif + //#endif public static final String MOD_ID = "replaymod"; @@ -152,9 +161,26 @@ public class ReplayMod implements Module { public ReplayMod() { I18n.setI18n(net.minecraft.client.resources.I18n::format); + //#if MC>=11400 + //$$ // Check Minecraft protocol version for compatibility + //$$ int supportedProtocol = MinecraftConstants.PROTOCOL_VERSION; + //$$ int actualProtocol = SharedConstants.getGameVersion().getProtocolVersion(); + //$$ if (supportedProtocol != actualProtocol) { + //$$ throw new UnsupportedOperationException(String.format( + //$$ "Unsupported Minecraft version, supporting protocol version %s (%s) but actual version is %s (%s).", + //$$ supportedProtocol, MinecraftConstants.GAME_VERSION, + //$$ actualProtocol, SharedConstants.getGameVersion().getName() + //$$ )); + //$$ } + //#endif + + //#if MC>=11400 + //$$ // Not needed on fabric, using MixinModResourcePackUtil instead. Could in theory also use it on 1.13 but it already works as is. + //#else //#if MC>=11300 DeferredWorkQueue.runLater(() -> MCVer.getMinecraft().getResourcePackList().addPackFinder(new LangResourcePack.Finder())); //#endif + //#endif // Register all RM modules modules.add(this); @@ -198,8 +224,14 @@ public class ReplayMod implements Module { return folder; } - //#ifdef DEV_ENV static { // Note: even preInit is too late and we'd have to issue another resource reload + //#ifdef DEV_ENV + //noinspection ConstantConditions + if (true) { + //#else + //$$ //noinspection ConstantConditions + //$$ if (false) { + //#endif @SuppressWarnings("unchecked") //#if MC>=11300 FolderPack jGuiResourcePack = new FolderPack(new File("../jGui/src/main/resources")) { @@ -217,7 +249,13 @@ public class ReplayMod implements Module { //#endif } catch (IOException e) { if ("pack.mcmeta".equals(resourceName)) { - return new ByteArrayInputStream(("{\"pack\": {\"description\": \"dummy pack for jGui resources in dev-env\", \"pack_format\": 1}}").getBytes(Charsets.UTF_8)); + //#if MC>=11400 + //$$ int version = 4; + //#else + int version = 1; + //#endif + return new ByteArrayInputStream(("{\"pack\": {\"description\": \"dummy pack for jGui resources in dev-env\", \"pack_format\": " + + version + "}}").getBytes(StandardCharsets.UTF_8)); } throw e; } @@ -241,7 +279,7 @@ public class ReplayMod implements Module { //$$ return super.getInputStreamByName(resourceName); //$$ } catch (IOException e) { //$$ if ("pack.mcmeta".equals(resourceName)) { - //$$ return new ByteArrayInputStream(("{\"pack\": {\"description\": \"dummy pack for mod resources in dev-env\", \"pack_format\": 1}}").getBytes(Charsets.UTF_8)); + //$$ return new ByteArrayInputStream(("{\"pack\": {\"description\": \"dummy pack for mod resources in dev-env\", \"pack_format\": 1}}").getBytes(StandardCharsets.UTF_8)); //$$ } //$$ throw e; //$$ } @@ -249,9 +287,16 @@ public class ReplayMod implements Module { //$$ }; //$$ defaultResourcePacks.add(mainResourcePack); //#endif - } - //#endif + }} + //#if MC>=11400 + //$$ @Override + //$$ public void onInitializeClient() { + //$$ modules.forEach(Module::initCommon); + //$$ modules.forEach(Module::initClient); + //$$ modules.forEach(m -> m.registerKeyBindings(keyBindingRegistry)); + //$$ } + //#else //#if MC>=11300 { FMLJavaModLoadingContext.get().getModEventBus().addListener((FMLCommonSetupEvent event) -> modules.forEach(Module::initCommon)); @@ -266,6 +311,7 @@ public class ReplayMod implements Module { //$$ modules.forEach(m -> m.registerKeyBindings(keyBindingRegistry)); //$$ } //#endif + //#endif @Override public void registerKeyBindings(KeyBindingRegistry registry) { @@ -282,13 +328,17 @@ public class ReplayMod implements Module { //$$ FML_BUS.register(this); // For runLater(Runnable) //#endif - FML_BUS.register(keyBindingRegistry); - FORGE_BUS.register(backgroundProcesses); + backgroundProcesses.register(); + keyBindingRegistry.register(); // 1.7.10 crashes when render distance > 16 //#if MC>=10800 if (!MCVer.hasOptifine()) { + //#if MC>=11400 + //$$ GameOption.RENDER_DISTANCE.setMax(64f); + //#else GameSettings.Options.RENDER_DISTANCE.setValueMax(64f); + //#endif } //#endif @@ -334,8 +384,32 @@ public class ReplayMod implements Module { * processed, otherwise a livelock may occur. */ private boolean inRunLater = false; + //#if MC>=11400 + //$$ private boolean inRenderTaskQueue = false; + //#endif public void runLater(Runnable runnable) { + //#if MC>=11400 + //$$ if (mc.isOnThread() && inRunLater && !inRenderTaskQueue) { + //$$ ((MinecraftAccessor) mc).getRenderTaskQueue().offer(() -> { + //$$ inRenderTaskQueue = true; + //$$ try { + //$$ runLater(runnable); + //$$ } finally { + //$$ inRenderTaskQueue = false; + //$$ } + //$$ }); + //$$ } else { + //$$ mc.method_18858(() -> { + //$$ inRunLater = true; + //$$ try { + //$$ runnable.run(); + //$$ } finally { + //$$ inRunLater = false; + //$$ } + //$$ }); + //$$ } + //#else if (mc.isCallingFromMinecraftThread() && inRunLater) { //#if MC>=10800 FORGE_BUS.register(new Object() { @@ -369,6 +443,7 @@ public class ReplayMod implements Module { } }, null)); } + //#endif } //#if MC<=10710 @@ -403,10 +478,16 @@ public class ReplayMod implements Module { //#endif public String getVersion() { - //#if MC>=11300 - return getContainer().getModInfo().getVersion().toString(); + //#if MC>=11400 + //$$ return FabricLoader.getInstance().getModContainer(MOD_ID) + //$$ .orElseThrow(IllegalStateException::new) + //$$ .getMetadata().getVersion().toString(); //#else - //$$ return getContainer().getVersion(); + //#if MC>=11300 + return ModList.get().getModContainerById(MOD_ID).get().getModInfo().getVersion().toString(); + //#else + //$$ return Loader.instance().getIndexedModList().get(MOD_ID).getVersion(); + //#endif //#endif } diff --git a/src/main/java/com/replaymod/core/SettingsRegistry.java b/src/main/java/com/replaymod/core/SettingsRegistry.java index 99885bf7..4f06eb7c 100644 --- a/src/main/java/com/replaymod/core/SettingsRegistry.java +++ b/src/main/java/com/replaymod/core/SettingsRegistry.java @@ -1,12 +1,23 @@ package com.replaymod.core; -import com.replaymod.core.events.SettingsChangedEvent; +import com.replaymod.core.events.SettingsChangedCallback; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +//#if MC>=11400 +//$$ import com.google.gson.Gson; +//$$ import com.google.gson.GsonBuilder; +//$$ import com.google.gson.JsonElement; +//$$ import com.google.gson.JsonObject; +//$$ import com.google.gson.JsonPrimitive; +//$$ import java.io.IOException; +//$$ import java.nio.charset.StandardCharsets; +//$$ import java.nio.file.Files; +//$$ import java.nio.file.Path; +//#else //#if MC>=11300 import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.fml.ModLoadingContext; @@ -15,19 +26,63 @@ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; //#else //$$ import net.minecraftforge.common.config.Configuration; //#endif +//#endif import static com.replaymod.core.versions.MCVer.*; public class SettingsRegistry { - private static final Object NULL_OBJECT = new Object(); private Map, Object> settings = new ConcurrentHashMap<>(); + //#if MC>=11400 + //$$ private final Path configFile = getMinecraft().runDirectory.toPath().resolve("configs/replaymod.json"); + //#else + private static final Object NULL_OBJECT = new Object(); //#if MC>=11300 private ForgeConfigSpec spec; private ModConfig config; //#else //$$ private Configuration configuration; //#endif + //#endif + //#if MC>=11400 + //$$ public void register() { + //$$ String config; + //$$ if (Files.exists(configFile)) { + //$$ try { + //$$ config = new String(Files.readAllBytes(configFile), StandardCharsets.UTF_8); + //$$ } catch (IOException e) { + //$$ e.printStackTrace(); + //$$ return; + //$$ } + //$$ } else { + //$$ save(); + //$$ return; + //$$ } + //$$ Gson gson = new Gson(); + //$$ JsonObject root = gson.fromJson(config, JsonObject.class); + //$$ for (Map.Entry, Object> entry : settings.entrySet()) { + //$$ SettingKey key = entry.getKey(); + //$$ JsonElement category = root.get(key.getCategory()); + //$$ if (category != null && category.isJsonObject()) { + //$$ JsonElement valueElem = category.getAsJsonObject().get(key.getKey()); + //$$ if (!valueElem.isJsonPrimitive()) continue; + //$$ JsonPrimitive value = valueElem.getAsJsonPrimitive(); + //$$ if (key.getDefault() instanceof Boolean && value.isBoolean()) { + //$$ entry.setValue(value.getAsBoolean()); + //$$ } + //$$ if (key.getDefault() instanceof Integer && value.isNumber()) { + //$$ entry.setValue(value.getAsNumber().intValue()); + //$$ } + //$$ if (key.getDefault() instanceof Double && value.isNumber()) { + //$$ entry.setValue(value.getAsNumber().doubleValue()); + //$$ } + //$$ if (key.getDefault() instanceof String && value.isString()) { + //$$ entry.setValue(value.getAsString()); + //$$ } + //$$ } + //$$ } + //$$ } + //#else //#if MC>=11300 public void register() { if (spec == null) { @@ -62,6 +117,7 @@ public class SettingsRegistry { //$$ } //$$ } //#endif + //#endif public void register(Class settingsClass) { for (Field field : settingsClass.getDeclaredFields()) { @@ -77,6 +133,9 @@ public class SettingsRegistry { } public void register(SettingKey key) { + //#if MC>=11400 + //$$ settings.put(key, key.getDefault()); + //#else //#if MC>=11300 if (spec != null) { throw new IllegalStateException("Cannot register more settings are spec has been built."); @@ -101,6 +160,7 @@ public class SettingsRegistry { //$$ } //$$ settings.put(key, value); //#endif + //#endif } public Set> getSettings() { @@ -116,6 +176,7 @@ public class SettingsRegistry { } public void set(SettingKey key, T value) { + //#if MC<11400 //#if MC>=11300 if (config != null) { config.getConfigData().set(key.getCategory() + "." + key.getKey(), value); @@ -133,11 +194,42 @@ public class SettingsRegistry { //$$ throw new IllegalArgumentException("Default type " + key.getDefault().getClass() + " not supported."); //$$ } //#endif + //#endif settings.put(key, value); - FML_BUS.post(new SettingsChangedEvent(this, key)); + SettingsChangedCallback.EVENT.invoker().onSettingsChanged(this, key); } public void save() { + //#if MC>=11400 + //$$ JsonObject root = new JsonObject(); + //$$ for (Map.Entry, Object> entry : settings.entrySet()) { + //$$ SettingKey key = entry.getKey(); + //$$ JsonObject category = root.getAsJsonObject(key.getCategory()); + //$$ if (category == null) { + //$$ category = new JsonObject(); + //$$ root.add(key.getCategory(), category); + //$$ } + //$$ + //$$ Object value = entry.getValue(); + //$$ if (value instanceof Boolean) { + //$$ category.addProperty(key.getKey(), (Boolean) value); + //$$ } + //$$ if (value instanceof Number) { + //$$ category.addProperty(key.getKey(), (Number) value); + //$$ } + //$$ if (value instanceof String) { + //$$ category.addProperty(key.getKey(), (String) value); + //$$ } + //$$ } + //$$ Gson gson = new GsonBuilder().setPrettyPrinting().create(); + //$$ String config = gson.toJson(gson); + //$$ try { + //$$ Files.createDirectories(configFile.getParent()); + //$$ Files.write(configFile, config.getBytes(StandardCharsets.UTF_8)); + //$$ } catch (IOException e) { + //$$ e.printStackTrace(); + //$$ } + //#else //#if MC>=11300 if (config != null) { config.save(); @@ -145,6 +237,7 @@ public class SettingsRegistry { //#else //$$ configuration.save(); //#endif + //#endif } public interface SettingKey { diff --git a/src/main/java/com/replaymod/core/events/KeyBindingEventCallback.java b/src/main/java/com/replaymod/core/events/KeyBindingEventCallback.java new file mode 100644 index 00000000..fef27d8c --- /dev/null +++ b/src/main/java/com/replaymod/core/events/KeyBindingEventCallback.java @@ -0,0 +1,19 @@ +//#if MC>=11300 +package com.replaymod.core.events; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +public interface KeyBindingEventCallback { + Event EVENT = EventFactory.createArrayBacked( + KeyBindingEventCallback.class, + (listeners) -> () -> { + for (KeyBindingEventCallback listener : listeners) { + listener.onKeybindingEvent(); + } + } + ); + + void onKeybindingEvent(); +} +//#endif diff --git a/src/main/java/com/replaymod/core/events/KeyEventCallback.java b/src/main/java/com/replaymod/core/events/KeyEventCallback.java new file mode 100644 index 00000000..6ed28864 --- /dev/null +++ b/src/main/java/com/replaymod/core/events/KeyEventCallback.java @@ -0,0 +1,19 @@ +//#if MC>=11300 +package com.replaymod.core.events; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +public interface KeyEventCallback { + Event EVENT = EventFactory.createArrayBacked( + KeyEventCallback.class, + (listeners) -> (key, scanCode, action, modifiers) -> { + for (KeyEventCallback listener : listeners) { + listener.onKeyEvent(key, scanCode, action, modifiers); + } + } + ); + + void onKeyEvent(int key, int scanCode, int action, int modifiers); +} +//#endif diff --git a/src/main/java/com/replaymod/core/events/PostRenderCallback.java b/src/main/java/com/replaymod/core/events/PostRenderCallback.java new file mode 100644 index 00000000..3e7d6a13 --- /dev/null +++ b/src/main/java/com/replaymod/core/events/PostRenderCallback.java @@ -0,0 +1,19 @@ +//#if MC>=11300 +package com.replaymod.core.events; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +public interface PostRenderCallback { + Event EVENT = EventFactory.createArrayBacked( + PostRenderCallback.class, + (listeners) -> () -> { + for (PostRenderCallback listener : listeners) { + listener.postRender(); + } + } + ); + + void postRender(); +} +//#endif diff --git a/src/main/java/com/replaymod/core/events/PostRenderWorldCallback.java b/src/main/java/com/replaymod/core/events/PostRenderWorldCallback.java new file mode 100644 index 00000000..f67d21c1 --- /dev/null +++ b/src/main/java/com/replaymod/core/events/PostRenderWorldCallback.java @@ -0,0 +1,19 @@ +//#if MC>=11400 +//$$ package com.replaymod.core.events; +//$$ +//$$ import net.fabricmc.fabric.api.event.Event; +//$$ import net.fabricmc.fabric.api.event.EventFactory; +//$$ +//$$ public interface PostRenderWorldCallback { +//$$ Event EVENT = EventFactory.createArrayBacked( +//$$ PostRenderWorldCallback.class, +//$$ (listeners) -> () -> { +//$$ for (PostRenderWorldCallback listener : listeners) { +//$$ listener.postRenderWorld(); +//$$ } +//$$ } +//$$ ); +//$$ +//$$ void postRenderWorld(); +//$$ } +//#endif diff --git a/src/main/java/com/replaymod/core/events/PreRenderCallback.java b/src/main/java/com/replaymod/core/events/PreRenderCallback.java new file mode 100644 index 00000000..f8e8fa53 --- /dev/null +++ b/src/main/java/com/replaymod/core/events/PreRenderCallback.java @@ -0,0 +1,19 @@ +//#if MC>=11300 +package com.replaymod.core.events; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +public interface PreRenderCallback { + Event EVENT = EventFactory.createArrayBacked( + PreRenderCallback.class, + (listeners) -> () -> { + for (PreRenderCallback listener : listeners) { + listener.preRender(); + } + } + ); + + void preRender(); +} +//#endif diff --git a/src/main/java/com/replaymod/core/events/PreRenderHandCallback.java b/src/main/java/com/replaymod/core/events/PreRenderHandCallback.java new file mode 100644 index 00000000..57434a92 --- /dev/null +++ b/src/main/java/com/replaymod/core/events/PreRenderHandCallback.java @@ -0,0 +1,22 @@ +//#if MC>=11300 +package com.replaymod.core.events; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +public interface PreRenderHandCallback { + Event EVENT = EventFactory.createArrayBacked( + PreRenderHandCallback.class, + (listeners) -> () -> { + for (PreRenderHandCallback listener : listeners) { + if (listener.preRenderHand()) { + return true; + } + } + return false; + } + ); + + boolean preRenderHand(); +} +//#endif diff --git a/src/main/java/com/replaymod/core/events/SettingsChangedCallback.java b/src/main/java/com/replaymod/core/events/SettingsChangedCallback.java new file mode 100644 index 00000000..d4bc5edc --- /dev/null +++ b/src/main/java/com/replaymod/core/events/SettingsChangedCallback.java @@ -0,0 +1,18 @@ +package com.replaymod.core.events; + +import com.replaymod.core.SettingsRegistry; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +public interface SettingsChangedCallback { + Event EVENT = EventFactory.createArrayBacked( + SettingsChangedCallback.class, + (listeners) -> (registry, key) -> { + for (SettingsChangedCallback listener : listeners) { + listener.onSettingsChanged(registry, key); + } + } + ); + + void onSettingsChanged(SettingsRegistry registry, SettingsRegistry.SettingKey key); +} diff --git a/src/main/java/com/replaymod/core/events/SettingsChangedEvent.java b/src/main/java/com/replaymod/core/events/SettingsChangedEvent.java deleted file mode 100644 index 1c8f00c5..00000000 --- a/src/main/java/com/replaymod/core/events/SettingsChangedEvent.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.replaymod.core.events; - -import com.replaymod.core.SettingsRegistry; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -//#if MC>=10800 -//#if MC>=11300 -import net.minecraftforge.eventbus.api.Event; -//#else -//$$ import net.minecraftforge.fml.common.eventhandler.Event; -//#endif -//#else -//$$ import cpw.mods.fml.common.eventhandler.Event; -//#endif - -@RequiredArgsConstructor -public class SettingsChangedEvent extends Event { - @Getter - private final SettingsRegistry settingsRegistry; - - @Getter - private final SettingsRegistry.SettingKey key; -} diff --git a/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java b/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java index d0754278..09ba3e5e 100644 --- a/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java +++ b/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java @@ -9,40 +9,36 @@ import de.johni0702.minecraft.gui.container.VanillaGuiScreen; import de.johni0702.minecraft.gui.element.GuiElement; import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; + +//#if MC>=11400 +//$$ import de.johni0702.minecraft.gui.versions.callbacks.InitScreenCallback; +//$$ import net.minecraft.client.gui.Screen; +//#else import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.common.MinecraftForge; - -//#if MC>=10800 -//#if MC>=11300 import net.minecraftforge.eventbus.api.SubscribeEvent; -//#else -//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -//#endif -//#else -//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import static com.replaymod.core.versions.MCVer.getGui; //#endif -import static com.replaymod.core.versions.MCVer.getGui; import static com.replaymod.core.versions.MCVer.getMinecraft; -public class GuiBackgroundProcesses { +public class GuiBackgroundProcesses extends EventRegistrations { private GuiPanel panel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(10)); - public void register() { - MinecraftForge.EVENT_BUS.register(this); - } - - public void unregister() { - MinecraftForge.EVENT_BUS.unregister(this); - } - + //#if MC>=11400 + //$$ { on(InitScreenCallback.EVENT, (screen, buttons) -> onGuiInit(screen)); } + //$$ private void onGuiInit(Screen guiScreen) { + //#else @SubscribeEvent public void onGuiInit(GuiScreenEvent.InitGuiEvent.Post event) { - if (getGui(event) != getMinecraft().currentScreen) return; // people tend to construct GuiScreens without opening them + net.minecraft.client.gui.GuiScreen guiScreen = getGui(event); + //#endif + if (guiScreen != getMinecraft().currentScreen) return; // people tend to construct GuiScreens without opening them - VanillaGuiScreen.setup(getGui(event)).setLayout(new CustomLayout() { + VanillaGuiScreen.setup(guiScreen).setLayout(new CustomLayout() { @Override protected void layout(GuiScreen container, int width, int height) { pos(panel, width - 5 - width(panel), 5); diff --git a/src/main/java/com/replaymod/core/gui/GuiFactory.java b/src/main/java/com/replaymod/core/gui/GuiFactory.java index e2ac1ed4..bb6b88e8 100644 --- a/src/main/java/com/replaymod/core/gui/GuiFactory.java +++ b/src/main/java/com/replaymod/core/gui/GuiFactory.java @@ -1,3 +1,4 @@ +//#if MC<11400 package com.replaymod.core.gui; import com.replaymod.core.ReplayMod; @@ -59,3 +60,4 @@ public class GuiFactory implements IModGuiFactory { return null; } } +//#endif diff --git a/src/main/java/com/replaymod/core/handler/MainMenuHandler.java b/src/main/java/com/replaymod/core/handler/MainMenuHandler.java index 18648763..bfe27125 100644 --- a/src/main/java/com/replaymod/core/handler/MainMenuHandler.java +++ b/src/main/java/com/replaymod/core/handler/MainMenuHandler.java @@ -1,35 +1,50 @@ package com.replaymod.core.handler; import com.replaymod.core.mixin.GuiMainMenuAccessor; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiMainMenu; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiScreenRealmsProxy; +import org.lwjgl.opengl.GL11; + +import java.util.List; + +import static com.replaymod.core.versions.MCVer.*; + +//#if MC>=11400 +//$$ import de.johni0702.minecraft.gui.versions.callbacks.InitScreenCallback; +//$$ import net.minecraft.client.gui.widget.AbstractButtonWidget; +//#else +import net.minecraft.client.gui.GuiButton; import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.SubscribeEvent; -import org.lwjgl.opengl.GL11; - -import static com.replaymod.core.versions.MCVer.*; +//#endif /** * Moves certain buttons on the main menu upwards so we can inject our own. */ -public class MainMenuHandler { - public void register() { - MinecraftForge.EVENT_BUS.register(this); - } - +public class MainMenuHandler extends EventRegistrations { + //#if MC>=11400 + //$$ { on(InitScreenCallback.EVENT, this::onInit); } + //$$ public void onInit(Screen guiScreen, List buttonList) { + //#else @SubscribeEvent public void onInit(GuiScreenEvent.InitGuiEvent.Post event) { GuiScreen guiScreen = getGui(event); + List buttonList = getButtonList(event); + //#endif if (guiScreen instanceof GuiMainMenu) { GuiMainMenu gui = (GuiMainMenu) guiScreen; int realmsOffset = 0; - for (GuiButton button : getButtonList(event)) { + //#if MC>=11400 + //$$ for (AbstractButtonWidget button : buttonList) { + //#else + for (GuiButton button : buttonList) { + //#endif // Buttons that aren't in a rectangle directly above our space don't need moving - if (button.x + button.width < gui.width / 2 - 100 + if (button.x + width(button) < gui.width / 2 - 100 || button.x > gui.width / 2 + 100 || button.y > gui.height / 4 + 10 + 4 * 24) continue; // Move button up to make space for one rows of buttons @@ -37,11 +52,15 @@ public class MainMenuHandler { int offset = -1 * 24 + 10; button.y += offset; + //#if MC>=11400 + //$$ // FIXME looks like the button has moved into the realms lib? + //#else //#if MC>=11300 if (button.id == 14) { realmsOffset = offset; } //#endif + //#endif } //#if MC>=11300 GuiMainMenuAccessor guiA = (GuiMainMenuAccessor) gui; @@ -58,6 +77,9 @@ public class MainMenuHandler { private final int offset; private RealmsNotificationProxy(GuiScreenRealmsProxy proxy, int offset) { + //#if MC>=11400 + //$$ super(null); + //#endif this.proxy = proxy; this.offset = offset; } diff --git a/src/main/java/com/replaymod/core/mixin/AbstractButtonWidgetAccessor.java b/src/main/java/com/replaymod/core/mixin/AbstractButtonWidgetAccessor.java new file mode 100644 index 00000000..947d32c7 --- /dev/null +++ b/src/main/java/com/replaymod/core/mixin/AbstractButtonWidgetAccessor.java @@ -0,0 +1,13 @@ +//#if MC>=11400 +//$$ package com.replaymod.core.mixin; +//$$ +//$$ import net.minecraft.client.gui.widget.AbstractButtonWidget; +//$$ import org.spongepowered.asm.mixin.Mixin; +//$$ import org.spongepowered.asm.mixin.gen.Accessor; +//$$ +//$$ @Mixin(AbstractButtonWidget.class) +//$$ public interface AbstractButtonWidgetAccessor { +//$$ @Accessor +//$$ int getHeight(); +//$$ } +//#endif diff --git a/src/main/java/com/replaymod/core/mixin/GuiScreenAccessor.java b/src/main/java/com/replaymod/core/mixin/GuiScreenAccessor.java new file mode 100644 index 00000000..e2b450ec --- /dev/null +++ b/src/main/java/com/replaymod/core/mixin/GuiScreenAccessor.java @@ -0,0 +1,32 @@ +package com.replaymod.core.mixin; + +import net.minecraft.client.gui.GuiScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +//#if MC>=11400 +//$$ import net.minecraft.client.gui.widget.AbstractButtonWidget; +//#else +import net.minecraft.client.gui.GuiButton; +//#endif + +//#if MC>=11300 +import net.minecraft.client.gui.IGuiEventListener; +//#endif + +@Mixin(GuiScreen.class) +public interface GuiScreenAccessor { + @Accessor + //#if MC>=11400 + //$$ List getButtons(); + //#else + List getButtons(); + //#endif + + //#if MC>=11300 + @Accessor + List getChildren(); + //#endif +} diff --git a/src/main/java/com/replaymod/core/mixin/MinecraftAccessor.java b/src/main/java/com/replaymod/core/mixin/MinecraftAccessor.java index 95a8a000..9c8fbaa8 100644 --- a/src/main/java/com/replaymod/core/mixin/MinecraftAccessor.java +++ b/src/main/java/com/replaymod/core/mixin/MinecraftAccessor.java @@ -7,7 +7,10 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; import java.util.Queue; + +//#if MC<11400 import java.util.concurrent.FutureTask; +//#endif @Mixin(Minecraft.class) public interface MinecraftAccessor { @@ -16,8 +19,13 @@ public interface MinecraftAccessor { @Accessor void setTimer(Timer value); + //#if MC>=11400 + //$$ @Accessor + //$$ Queue getRenderTaskQueue(); + //#else @Accessor Queue> getScheduledTasks(); + //#endif @Accessor("hasCrashed") boolean hasCrashed(); diff --git a/src/main/java/com/replaymod/core/mixin/MixinGameRenderer.java b/src/main/java/com/replaymod/core/mixin/MixinGameRenderer.java new file mode 100644 index 00000000..b8d1e329 --- /dev/null +++ b/src/main/java/com/replaymod/core/mixin/MixinGameRenderer.java @@ -0,0 +1,59 @@ +//#if MC>=11400 +//$$ package com.replaymod.core.mixin; +//$$ +//$$ import com.replaymod.core.events.PostRenderWorldCallback; +//$$ import com.replaymod.core.events.PreRenderHandCallback; +//$$ import de.johni0702.minecraft.gui.versions.callbacks.PostRenderHudCallback; +//$$ import de.johni0702.minecraft.gui.versions.callbacks.PostRenderScreenCallback; +//$$ import net.minecraft.client.render.Camera; +//$$ import net.minecraft.client.render.GameRenderer; +//$$ 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; +//$$ +//$$ @Mixin(GameRenderer.class) +//$$ public class MixinGameRenderer { +//$$ @Inject( +//$$ method = "render", +//$$ at = @At( +//$$ value = "INVOKE", +//$$ target = "Lnet/minecraft/client/gui/hud/InGameHud;draw(F)V", +//$$ shift = At.Shift.AFTER +//$$ ) +//$$ ) +//$$ private void postRenderOverlay(float partialTicks, long nanoTime, boolean renderWorld, CallbackInfo ci) { +//$$ PostRenderHudCallback.EVENT.invoker().postRenderHud(partialTicks); +//$$ } +//$$ +//$$ @Inject( +//$$ method = "render", +//$$ at = @At( +//$$ value = "INVOKE", +//$$ target = "Lnet/minecraft/client/gui/Screen;render(IIF)V", +//$$ shift = At.Shift.AFTER +//$$ ) +//$$ ) +//$$ private void postRenderScreen(float partialTicks, long nanoTime, boolean renderWorld, CallbackInfo ci) { +//$$ PostRenderScreenCallback.EVENT.invoker().postRenderScreen(partialTicks); +//$$ } +//$$ +//$$ @Inject( +//$$ method = "renderCenter", +//$$ at = @At( +//$$ value = "FIELD", +//$$ target = "Lnet/minecraft/client/render/GameRenderer;renderHand:Z" +//$$ ) +//$$ ) +//$$ private void postRenderWorld(float partialTicks, long nanoTime, CallbackInfo ci) { +//$$ PostRenderWorldCallback.EVENT.invoker().postRenderWorld(); +//$$ } +//$$ +//$$ @Inject(method = "renderHand", at = @At("HEAD"), cancellable = true) +//$$ private void preRenderHand(Camera camera, float partialTicks, CallbackInfo ci) { +//$$ if (PreRenderHandCallback.EVENT.invoker().preRenderHand()) { +//$$ ci.cancel(); +//$$ } +//$$ } +//$$ } +//#endif diff --git a/src/main/java/com/replaymod/core/mixin/MixinKeyboardListener.java b/src/main/java/com/replaymod/core/mixin/MixinKeyboardListener.java new file mode 100644 index 00000000..a20006b7 --- /dev/null +++ b/src/main/java/com/replaymod/core/mixin/MixinKeyboardListener.java @@ -0,0 +1,61 @@ +//#if MC>=11300 +package com.replaymod.core.mixin; + +import com.replaymod.core.events.KeyBindingEventCallback; +import com.replaymod.core.events.KeyEventCallback; +import net.minecraft.client.KeyboardListener; +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; + +@Mixin(KeyboardListener.class) +public class MixinKeyboardListener { + @Inject(method = "onKeyEvent", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/settings/KeyBinding;onTick(Lnet/minecraft/client/util/InputMappings$Input;)V", shift = At.Shift.AFTER)) + private void afterKeyBindingTick(long windowPointer, int key, int scanCode, int action, int modifiers, CallbackInfo ci) { + KeyBindingEventCallback.EVENT.invoker().onKeybindingEvent(); + KeyEventCallback.EVENT.invoker().onKeyEvent(key, scanCode, action, modifiers); + } + + //#if MC>=11400 + //$$ /* FIXME we currently don't use these but they also don't work since their target is wrapped in a lambda, + //$$ see MixinMouseListener for working examples + //$$ @Redirect( + //$$ method = "onKey", + //$$ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ParentElement;keyPressed(III)Z") + //$$ ) + //$$ private boolean keyPressed(ParentElement element, int keyCode, int modifiers, int scanCode) { + //$$ if (KeyboardCallback.EVENT.invoker().keyPressed(keyCode, modifiers, scanCode)) { + //$$ return true; + //$$ } else { + //$$ return element.keyPressed(keyCode, modifiers, scanCode); + //$$ } + //$$ } + //$$ + //$$ @Redirect( + //$$ method = "onKey", + //$$ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ParentElement;keyReleased(III)Z") + //$$ ) + //$$ private boolean keyReleased(ParentElement element, int keyCode, int modifiers, int scanCode) { + //$$ if (KeyboardCallback.EVENT.invoker().keyReleased(keyCode, modifiers, scanCode)) { + //$$ return true; + //$$ } else { + //$$ return element.keyReleased(keyCode, modifiers, scanCode); + //$$ } + //$$ } + //$$ + //$$ @Redirect( + //$$ method = "onKey", + //$$ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ParentElement;charTyped(CI)Z") + //$$ ) + //$$ private boolean charTyped(ParentElement element, char keyChar, int modifiers) { + //$$ if (KeyboardCallback.EVENT.invoker().charTyped(keyChar, modifiers)) { + //$$ return true; + //$$ } else { + //$$ return element.charTyped(keyChar, modifiers); + //$$ } + //$$ } + //$$ */ + //#endif +} +//#endif diff --git a/src/main/java/com/replaymod/core/mixin/MixinMinecraft.java b/src/main/java/com/replaymod/core/mixin/MixinMinecraft.java index b74ef375..ded66a3b 100644 --- a/src/main/java/com/replaymod/core/mixin/MixinMinecraft.java +++ b/src/main/java/com/replaymod/core/mixin/MixinMinecraft.java @@ -1,18 +1,70 @@ //#if MC>=11300 package com.replaymod.core.mixin; +import com.replaymod.core.events.PostRenderCallback; +import com.replaymod.core.events.PreRenderCallback; import com.replaymod.core.versions.MCVer; +import de.johni0702.minecraft.gui.versions.callbacks.OpenGuiScreenCallback; +import de.johni0702.minecraft.gui.versions.callbacks.PreTickCallback; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; 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; + +//#if MC>=11400 +//$$ import net.minecraft.util.NonBlockingThreadExecutor; +//#endif @Mixin(Minecraft.class) -public abstract class MixinMinecraft implements MCVer.MinecraftMethodAccessor { +public abstract class MixinMinecraft + //#if MC>=11400 + //$$ extends NonBlockingThreadExecutor + //#endif + implements MCVer.MinecraftMethodAccessor { + //#if MC>=11400 + //$$ public MixinMinecraft(String string_1) { super(string_1); } + //#endif + @Shadow protected abstract void processKeyBinds(); @Override public void replayModProcessKeyBinds() { processKeyBinds(); } + + //#if MC>=11400 + //$$ @Override + //$$ public void replayModExecuteTaskQueue() { + //$$ executeTaskQueue(); + //$$ } + //#endif + + @Inject(method = "runGameLoop", + at = @At(value = "INVOKE", + target = "Lnet/minecraft/client/renderer/GameRenderer;updateCameraAndRender(FJZ)V")) + private void preRender(boolean unused, CallbackInfo ci) { + PreRenderCallback.EVENT.invoker().preRender(); + } + + @Inject(method = "runGameLoop", + at = @At(value = "INVOKE", + target = "Lnet/minecraft/client/renderer/GameRenderer;updateCameraAndRender(FJZ)V", + shift = At.Shift.AFTER)) + private void postRender(boolean unused, CallbackInfo ci) { + PostRenderCallback.EVENT.invoker().postRender(); + } + + @Inject(method = "runTick", at = @At("HEAD")) + private void preTick(CallbackInfo ci) { + PreTickCallback.EVENT.invoker().preTick(); + } + + @Inject(method = "displayGuiScreen", at = @At(value = "FIELD", target = "Lnet/minecraft/client/Minecraft;currentScreen:Lnet/minecraft/client/gui/GuiScreen;")) + private void openGuiScreen(GuiScreen newGuiScreen, CallbackInfo ci) { + OpenGuiScreenCallback.EVENT.invoker().openGuiScreen(newGuiScreen); + } } //#endif diff --git a/src/main/java/com/replaymod/core/mixin/MixinModResourcePackUtil.java b/src/main/java/com/replaymod/core/mixin/MixinModResourcePackUtil.java new file mode 100644 index 00000000..625e88ff --- /dev/null +++ b/src/main/java/com/replaymod/core/mixin/MixinModResourcePackUtil.java @@ -0,0 +1,37 @@ +//#if MC>=11400 +//$$ package com.replaymod.core.mixin; +//$$ +//$$ import com.replaymod.core.ReplayMod; +//$$ import com.replaymod.core.versions.LangResourcePack; +//$$ import net.fabricmc.fabric.api.resource.ModResourcePack; +//$$ import net.fabricmc.fabric.impl.resources.ModResourcePackUtil; +//$$ import net.fabricmc.loader.api.FabricLoader; +//$$ import net.fabricmc.loader.api.ModContainer; +//$$ import net.minecraft.resource.ResourcePack; +//$$ import net.minecraft.resource.ResourceType; +//$$ 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 java.util.List; +//$$ +//$$ @Mixin(value = ModResourcePackUtil.class, remap = false) +//$$ public class MixinModResourcePackUtil { +//$$ @Inject(method = "appendModResourcePacks", at = @At("RETURN"), remap = false) +//$$ private static void injectRMLangPack(List packList, ResourceType type, CallbackInfo ci) { +//$$ if (type != ResourceType.ASSETS) return; +//$$ +//$$ for (int i = 0; i < packList.size(); i++) { +//$$ ResourcePack pack = packList.get(i); +//$$ if (pack instanceof ModResourcePack && ((ModResourcePack) pack).getFabricModMetadata().getId().equals(ReplayMod.MOD_ID)) { +//$$ ModContainer container = FabricLoader.getInstance().getModContainer(ReplayMod.MOD_ID).orElseThrow(IllegalAccessError::new); +//$$ packList.add(i, new LangResourcePack(container.getRootPath())); +//$$ return; +//$$ } +//$$ } +//$$ +//$$ throw new IllegalStateException("Could not find ReplayMod resource pack."); +//$$ } +//$$ } +//#endif diff --git a/src/main/java/com/replaymod/core/mixin/MixinMouseListener.java b/src/main/java/com/replaymod/core/mixin/MixinMouseListener.java new file mode 100644 index 00000000..9911f195 --- /dev/null +++ b/src/main/java/com/replaymod/core/mixin/MixinMouseListener.java @@ -0,0 +1,60 @@ +//#if MC>=11400 +//$$ package com.replaymod.core.mixin; +//$$ +//$$ import com.replaymod.core.events.KeyBindingEventCallback; +//$$ import de.johni0702.minecraft.gui.versions.callbacks.MouseCallback; +//$$ import net.minecraft.client.Mouse; +//$$ import net.minecraft.client.gui.Element; +//$$ import net.minecraft.client.gui.ParentElement; +//$$ 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; +//$$ +//$$ @Mixin(Mouse.class) +//$$ public class MixinMouseListener { +//$$ @Shadow private int activeButton; +//$$ +//$$ @Inject(method = "onMouseButton", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/options/KeyBinding;onKeyPressed(Lnet/minecraft/client/util/InputUtil$KeyCode;)V", shift = At.Shift.AFTER)) +//$$ private void afterKeyBindingTick(CallbackInfo ci) { +//$$ KeyBindingEventCallback.EVENT.invoker().onKeybindingEvent(); +//$$ } +//$$ +//$$ @Inject(method = "method_1611", at = @At("HEAD"), cancellable = true) +//$$ private void mouseDown(boolean[] result, double x, double y, int button, CallbackInfo ci) { +//$$ if (MouseCallback.EVENT.invoker().mouseDown(x, y, button)) { +//$$ result[0] = true; +//$$ ci.cancel(); +//$$ } +//$$ } +//$$ +//$$ @Inject(method = "method_1605", at = @At("HEAD"), cancellable = true) +//$$ private void mouseUp(boolean[] result, double x, double y, int button, CallbackInfo ci) { +//$$ if (MouseCallback.EVENT.invoker().mouseUp(x, y, button)) { +//$$ result[0] = true; +//$$ ci.cancel(); +//$$ } +//$$ } +//$$ +//$$ @Inject(method = "method_1602", at = @At("HEAD"), cancellable = true) +//$$ private void mouseDrag(Element element, double x, double y, double dx, double dy, CallbackInfo ci) { +//$$ if (MouseCallback.EVENT.invoker().mouseDrag(x, y, this.activeButton, dx, dy)) { +//$$ ci.cancel(); +//$$ } +//$$ } +//$$ +//$$ @Redirect( +//$$ method = "onMouseScroll", +//$$ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ParentElement;mouseScrolled(DDD)Z") +//$$ ) +//$$ private boolean mouseScroll(ParentElement element, double x, double y, double scroll) { +//$$ if (MouseCallback.EVENT.invoker().mouseScroll(x, y, scroll)) { +//$$ return true; +//$$ } else { +//$$ return element.mouseScrolled(x, y, scroll); +//$$ } +//$$ } +//$$ } +//#endif diff --git a/src/main/java/com/replaymod/core/mixin/MixinScreen.java b/src/main/java/com/replaymod/core/mixin/MixinScreen.java new file mode 100644 index 00000000..499c6bef --- /dev/null +++ b/src/main/java/com/replaymod/core/mixin/MixinScreen.java @@ -0,0 +1,27 @@ +//#if MC>=11400 +//$$ package com.replaymod.core.mixin; +//$$ +//$$ import de.johni0702.minecraft.gui.versions.callbacks.InitScreenCallback; +//$$ import net.minecraft.client.MinecraftClient; +//$$ import net.minecraft.client.gui.Screen; +//$$ import net.minecraft.client.gui.widget.AbstractButtonWidget; +//$$ import org.spongepowered.asm.mixin.Final; +//$$ 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 java.util.List; +//$$ +//$$ @Mixin(Screen.class) +//$$ public class MixinScreen { +//$$ @Shadow +//$$ protected @Final List buttons; +//$$ +//$$ @Inject(method = "init(Lnet/minecraft/client/MinecraftClient;II)V", at = @At("RETURN")) +//$$ private void init(MinecraftClient minecraftClient_1, int int_1, int int_2, CallbackInfo ci) { +//$$ InitScreenCallback.EVENT.invoker().initScreen((Screen) (Object) this, buttons); +//$$ } +//$$ } +//#endif diff --git a/src/main/java/com/replaymod/core/utils/EventRegistration.java b/src/main/java/com/replaymod/core/utils/EventRegistration.java deleted file mode 100644 index d26aefaa..00000000 --- a/src/main/java/com/replaymod/core/utils/EventRegistration.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.replaymod.core.utils; - -import com.replaymod.replaystudio.us.myles.ViaVersion.util.ReflectionUtil; -import net.fabricmc.fabric.api.event.Event; - -import java.lang.reflect.InvocationTargetException; - -public class EventRegistration { - public static EventRegistration create(Event event, T callback) { - return new EventRegistration<>(event, callback); - } - - public static EventRegistration register(Event event, T callback) { - EventRegistration registration = new EventRegistration<>(event, callback); - registration.register(); - return registration; - } - - private final Event event; - private final T listener; - private boolean registered; - - private EventRegistration(Event event, T listener) { - this.event = event; - this.listener = listener; - - event.register(listener); - } - - public void register() { - if (registered) { - throw new IllegalStateException(); - } - - event.register(listener); - registered = true; - } - - @SuppressWarnings("unchecked") - public void unregister() { - if (!registered) { - throw new IllegalStateException(); - } - - try { - T[] handlers = (T[]) ReflectionUtil.get(event, "handlers", Object[].class); - T[] copy = (T[]) new Object[handlers.length - 1]; - for (int from = 0, to = 0; from < handlers.length; from++) { - if (handlers[from] == listener) { - continue; - } - copy[to++] = handlers[from]; - } - if (copy.length == 0) { - copy = null; - } - ReflectionUtil.set(event, "handlers", copy); - ReflectionUtil.invoke(event, "update"); - } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { - throw new RuntimeException(e); - } - registered = false; - } -} diff --git a/src/main/java/com/replaymod/core/utils/EventRegistrations.java b/src/main/java/com/replaymod/core/utils/EventRegistrations.java deleted file mode 100644 index 29e45a27..00000000 --- a/src/main/java/com/replaymod/core/utils/EventRegistrations.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.replaymod.core.utils; - -import net.fabricmc.fabric.api.event.Event; - -import java.util.ArrayList; -import java.util.List; - -public class EventRegistrations { - private List> registrations = new ArrayList<>(); - - public EventRegistrations on(EventRegistration registration) { - registrations.add(registration); - return this; - } - - public EventRegistrations on(Event event, T listener) { - return on(EventRegistration.create(event, listener)); - } - - public void register() { - for (EventRegistration registration : registrations) { - registration.register(); - } - } - - public void unregister() { - for (EventRegistration registration : registrations) { - registration.unregister(); - } - } -} diff --git a/src/main/java/com/replaymod/core/utils/ModCompat.java b/src/main/java/com/replaymod/core/utils/ModCompat.java index 41813c1c..aff15817 100644 --- a/src/main/java/com/replaymod/core/utils/ModCompat.java +++ b/src/main/java/com/replaymod/core/utils/ModCompat.java @@ -2,6 +2,12 @@ package com.replaymod.core.utils; import com.replaymod.replaystudio.data.ModInfo; import net.minecraft.util.ResourceLocation; + +//#if MC>=11400 +//$$ import net.fabricmc.loader.api.FabricLoader; +//$$ import net.fabricmc.loader.api.ModContainer; +//$$ import net.minecraft.util.registry.Registry; +//#else //#if MC>=11200 import net.minecraftforge.registries.ForgeRegistry; import net.minecraftforge.registries.RegistryManager; @@ -25,6 +31,7 @@ import net.minecraftforge.fml.ModList; //$$ import cpw.mods.fml.common.Loader; //$$ import cpw.mods.fml.common.ModContainer; //#endif +//#endif import java.util.*; import java.util.function.Function; @@ -34,12 +41,21 @@ public class ModCompat { @SuppressWarnings("unchecked") public static Collection getInstalledNetworkMods() { //#if MC>=11300 + //#if MC>=11400 + //$$ Map modInfoMap = FabricLoader.getInstance().getAllMods().stream() + //$$ .map(ModContainer::getMetadata) + //$$ .map(m -> new ModInfo(m.getId(), m.getName(), m.getVersion().toString())) + //$$ .collect(Collectors.toMap(ModInfo::getId, Function.identity())); + //$$ return Registry.REGISTRIES.stream() + //$$ .map(Registry::getIds).flatMap(Set::stream) + //#else Map modInfoMap = ModList.get().getMods().stream() .map(m -> new ModInfo(m.getModId(), m.getDisplayName(), m.getVersion().toString())) .collect(Collectors.toMap(ModInfo::getId, Function.identity())); return RegistryManager.ACTIVE.takeSnapshot(false).keySet().stream() .map(RegistryManager.ACTIVE::getRegistry) .map(ForgeRegistry::getKeys).flatMap(Set::stream) + //#endif .map(ResourceLocation::getNamespace).filter(s -> !s.equals("minecraft")).distinct() .map(modInfoMap::get).filter(Objects::nonNull) .collect(Collectors.toList()); diff --git a/src/main/java/com/replaymod/core/versions/LangResourcePack.java b/src/main/java/com/replaymod/core/versions/LangResourcePack.java index 61038bb5..d678d358 100644 --- a/src/main/java/com/replaymod/core/versions/LangResourcePack.java +++ b/src/main/java/com/replaymod/core/versions/LangResourcePack.java @@ -4,22 +4,15 @@ package com.replaymod.core.versions; import com.google.gson.Gson; import com.replaymod.core.ReplayMod; import net.minecraft.resources.AbstractResourcePack; -import net.minecraft.resources.IPackFinder; -import net.minecraft.resources.IResourcePack; import net.minecraft.resources.ResourcePackFileNotFoundException; -import net.minecraft.resources.ResourcePackInfo; import net.minecraft.resources.ResourcePackType; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.loading.moddiscovery.ModFile; -import net.minecraftforge.fml.packs.ModFileResourcePack; -import net.minecraftforge.fml.packs.ResourcePackLoader; import org.apache.commons.io.IOUtils; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.StringBufferInputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -33,8 +26,18 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +//#if MC>=11400 +//#else +import net.minecraft.resources.IPackFinder; +import net.minecraft.resources.ResourcePackInfo; +import net.minecraftforge.fml.loading.moddiscovery.ModFile; +import net.minecraftforge.fml.packs.ModFileResourcePack; +import net.minecraftforge.fml.packs.ResourcePackLoader; +//#endif + /** * Resource pack which on-the-fly converts pre-1.13 language files into 1.13 json format. + * Also remaps `replaymod.input.*` bindings to `key.replaymod.*` as convention on Fabric. */ public class LangResourcePack extends AbstractResourcePack { private static final Gson GSON = new Gson(); @@ -42,13 +45,24 @@ public class LangResourcePack extends AbstractResourcePack { private static final Pattern JSON_FILE_PATTERN = Pattern.compile("^assets/" + ReplayMod.MOD_ID + "/lang/([a-z][a-z])_([a-z][a-z]).json$"); private static final Pattern LANG_FILE_NAME_PATTERN = Pattern.compile("^([a-z][a-z])_([a-z][a-z]).lang$"); - LangResourcePack() { + //#if MC>=11400 + //$$ public static final String LEGACY_KEY_PREFIX = "replaymod.input."; + //$$ private static final String FABRIC_KEY_FORMAT = "key." + ReplayMod.MOD_ID + ".%s"; + //$$ + //$$ private final Path basePath; + //$$ public LangResourcePack(Path basePath) { + //$$ super(new File(NAME)); + //$$ this.basePath = basePath; + //$$ } + //#else + public LangResourcePack() { super(new File(NAME)); } private ModFileResourcePack getParent() { return ResourcePackLoader.getResourcePackFor(ReplayMod.MOD_ID).orElseThrow(() -> new RuntimeException("Failed to get ReplayMod resource pack!")); } + //#endif private String langName(String path) { Matcher matcher = JSON_FILE_PATTERN.matcher(path); @@ -56,14 +70,27 @@ public class LangResourcePack extends AbstractResourcePack { return String.format("%s_%s.lang", matcher.group(1), matcher.group(2).toUpperCase()); } - private Path langPath(String path) { + //#if MC>=11400 + //$$ private Path baseLangPath() { + //$$ return basePath.resolve("assets").resolve(ReplayMod.MOD_ID).resolve("lang"); + //$$ } + //#else + private Path baseLangPath() { ModFileResourcePack parent = getParent(); if (parent == null) return null; ModFile modFile = parent.getModFile(); + return modFile.getLocator().findPath(modFile, "assets", ReplayMod.MOD_ID, "lang"); + } + //#endif + private Path langPath(String path) { String langName = langName(path); if (langName == null) return null; - return modFile.getLocator().findPath(modFile, "assets", ReplayMod.MOD_ID, "lang", langName); + Path basePath = baseLangPath(); + //#if MC<11400 + if (basePath == null) return null; + //#endif + return basePath.resolve(langName); } private String convertValue(String value) { @@ -73,11 +100,11 @@ public class LangResourcePack extends AbstractResourcePack { @Override protected InputStream getInputStream(String path) throws IOException { if ("pack.mcmeta".equals(path)) { - return new StringBufferInputStream("{\"pack\": {\"description\": \"ReplayMod language files\", \"pack_format\": 4}}"); + return new ByteArrayInputStream("{\"pack\": {\"description\": \"ReplayMod language files\", \"pack_format\": 4}}".getBytes(StandardCharsets.UTF_8)); } Path langPath = langPath(path); - if (langPath == null) throw new ResourcePackFileNotFoundException(file, path); + if (langPath == null) throw new ResourcePackFileNotFoundException(this.file, path); String langFile; try (InputStream in = Files.newInputStream(langPath)) { @@ -91,6 +118,11 @@ public class LangResourcePack extends AbstractResourcePack { String key = line.substring(0, i); String value = line.substring(i + 1); value = convertValue(value); + //#if MC>=11400 + //$$ if (key.startsWith(LEGACY_KEY_PREFIX)) { + //$$ key = String.format(FABRIC_KEY_FORMAT, key.substring(LEGACY_KEY_PREFIX.length())); + //$$ } + //#endif properties.put(key, value); } @@ -103,22 +135,29 @@ public class LangResourcePack extends AbstractResourcePack { return langPath != null && Files.exists(langPath); } + @Override public Collection getAllResourceLocations(ResourcePackType resourcePackType, String path, int maxDepth, Predicate filter) { if (resourcePackType == ResourcePackType.CLIENT_RESOURCES && "lang".equals(path)) { - IResourcePack parent = getParent(); - if (parent == null) return Collections.emptyList(); - return parent.getAllResourceLocations(resourcePackType, path, maxDepth, name -> { - Matcher matcher = LANG_FILE_NAME_PATTERN.matcher(name); - if (matcher.matches()) { - return filter.test(String.format("%s_%s.json", matcher.group(1), matcher.group(1))); - } else { - return false; - } - }).stream().map(resourceLocation -> { - String p = resourceLocation.getPath().substring(0, "assets/lang/xx_XX".length()) + ".json"; - return new ResourceLocation(resourceLocation.getNamespace(), p); - }).collect(Collectors.toList()); + Path base = baseLangPath(); + //#if MC<11400 + if (base == null) return Collections.emptyList(); + //#endif + try { + return Files.walk(base, 1) + .skip(1) + .filter(Files::isRegularFile) + .map(Path::getFileName).map(Path::toString) + .map(LANG_FILE_NAME_PATTERN::matcher) + .filter(Matcher::matches) + .map(matcher -> String.format("%s_%s.json", matcher.group(1), matcher.group(1))) + .filter(filter::test) + .map(name -> new ResourceLocation(ReplayMod.MOD_ID, "lang/" + name)) + .collect(Collectors.toList()); + } catch (IOException e) { + e.printStackTrace(); + return Collections.emptyList(); + } } else { return Collections.emptyList(); } @@ -136,11 +175,14 @@ public class LangResourcePack extends AbstractResourcePack { @Override public void close() {} + // Not needed on fabric, using MixinModResourcePackUtil instead. + //#if MC<11400 public static class Finder implements IPackFinder { @Override public void addPackInfosToMap(Map packList, ResourcePackInfo.IFactory factory) { packList.put(NAME, ResourcePackInfo.func_195793_a(NAME, true, LangResourcePack::new, factory, ResourcePackInfo.Priority.BOTTOM)); } } + //#endif } //#endif diff --git a/src/main/java/com/replaymod/core/versions/MCVer.java b/src/main/java/com/replaymod/core/versions/MCVer.java index 8d231d13..184dbe05 100644 --- a/src/main/java/com/replaymod/core/versions/MCVer.java +++ b/src/main/java/com/replaymod/core/versions/MCVer.java @@ -1,10 +1,11 @@ package com.replaymod.core.versions; -import com.google.common.util.concurrent.ListenableFuture; +import com.replaymod.core.mixin.GuiScreenAccessor; import com.replaymod.core.mixin.MinecraftAccessor; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.multiplayer.WorldClient; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.entity.model.ModelBox; @@ -12,7 +13,6 @@ import net.minecraft.client.renderer.entity.model.ModelRenderer; import net.minecraft.client.renderer.texture.TextureMap; import net.minecraft.crash.CrashReportCategory; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.network.PacketBuffer; import net.minecraft.util.ResourceLocation; @@ -20,11 +20,22 @@ import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; + +//#if MC>=11400 +//$$ import com.replaymod.core.mixin.AbstractButtonWidgetAccessor; +//$$ import net.minecraft.client.gui.widget.AbstractButtonWidget; +//$$ import java.util.concurrent.CompletableFuture; +//#else +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import net.minecraft.entity.EntityLivingBase; import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.client.event.RenderLivingEvent; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.IEventBus; +//#endif //#if MC>=11300 import net.minecraft.client.MainWindow; @@ -40,6 +51,12 @@ import org.lwjgl.glfw.GLFW; //$$ import java.io.IOException; //#endif +//#if MC>=10904 +import com.replaymod.render.blend.mixin.ParticleAccessor; +import net.minecraft.client.particle.Particle; +import net.minecraft.util.math.Vec3d; +//#endif + //#if MC>=10809 import net.minecraft.client.renderer.vertex.DefaultVertexFormats; //#else @@ -60,21 +77,46 @@ import net.minecraft.world.WorldType; //$$ import static org.lwjgl.opengl.GL11.*; //#endif +//#if MC>=11400 +//$$ import net.fabricmc.loader.api.FabricLoader; +//#else +//#if MC>=11300 +import net.minecraftforge.fml.ModList; +//#else +//$$ import net.minecraftforge.fml.common.Loader; +//#endif +//#endif + import java.io.File; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; +import java.util.function.Consumer; /** * Abstraction over things that have changed between different MC versions. */ public class MCVer { + //#if MC<11400 public static IEventBus FORGE_BUS = MinecraftForge.EVENT_BUS; //#if MC>=10809 public static IEventBus FML_BUS = FORGE_BUS; //#else //$$ public static EventBus FML_BUS = FMLCommonHandler.instance().bus(); //#endif + //#endif + + public static boolean isModLoaded(String id) { + //#if MC>=11400 + //$$ return FabricLoader.getInstance().isModLoaded(id.toLowerCase()); + //#else + //#if MC>=11300 + return ModList.get().isLoaded(id.toLowerCase()); + //#else + //$$ return Loader.isModLoaded(id); + //#endif + //#endif + } public static void addDetail(CrashReportCategory category, String name, Callable callable) { //#if MC>=10904 @@ -88,6 +130,33 @@ public class MCVer { //#endif } + //#if MC>=11400 + //$$ public static void width(AbstractButtonWidget button, int value) { + //$$ button.setWidth(value); + //$$ } + //$$ + //$$ public static int width(AbstractButtonWidget button) { + //$$ return button.getWidth(); + //$$ } + //$$ + //$$ public static int height(AbstractButtonWidget button) { + //$$ return ((AbstractButtonWidgetAccessor) button).getHeight(); + //$$ } + //#else + public static void width(GuiButton button, int value) { + button.width = value; + } + + public static int width(GuiButton button) { + return button.width; + } + + public static int height(GuiButton button) { + return button.height; + } + //#endif + + //#if MC<11400 public static void addButton(GuiScreenEvent.InitGuiEvent event, GuiButton button) { //#if MC>=11300 event.addButton(button); @@ -136,6 +205,7 @@ public class MCVer { //$$ return event.entity; //#endif } + //#endif public static String readString(PacketBuffer buffer, int max) { //#if MC>=11102 @@ -153,6 +223,7 @@ public class MCVer { //#endif } + //#if MC<11400 public static RenderGameOverlayEvent.ElementType getType(RenderGameOverlayEvent event) { //#if MC>=10904 return event.getType(); @@ -160,6 +231,7 @@ public class MCVer { //$$ return event.type; //#endif } + //#endif //#if MC>=10800 public static WorldType WorldType_DEBUG_ALL_BLOCK_STATES @@ -197,7 +269,7 @@ public class MCVer { //$$ = TextureMap.locationBlocksTexture; //#endif - public static Entity getRidingEntity(Entity ridden) { + public static Entity getRiddenEntity(Entity ridden) { //#if MC>=10904 return ridden.getRidingEntity(); //#else @@ -205,9 +277,12 @@ public class MCVer { //#endif } - @SuppressWarnings("unchecked") - public static List loadedEntityList(World world) { + public static Iterable loadedEntityList(WorldClient world) { + //#if MC>=11400 + //$$ return world.getEntities(); + //#else return world.loadedEntityList; + //#endif } @SuppressWarnings("unchecked") @@ -226,7 +301,19 @@ public class MCVer { @SuppressWarnings("unchecked") public static List playerEntities(World world) { + //#if MC>=11400 + //$$ return (List) world.getPlayers(); + //#else return world.playerEntities; + //#endif + } + + public static boolean isOnMainThread() { + //#if MC>=11400 + //$$ return getMinecraft().isOnThread(); + //#else + return getMinecraft().isCallingFromMinecraftThread(); + //#endif } //#if MC>=11300 @@ -243,7 +330,13 @@ public class MCVer { //$$ } //#endif - public static ListenableFuture setServerResourcePack(File file) { + public static + //#if MC>=11400 + //$$ CompletableFuture + //#else + ListenableFuture + //#endif + setServerResourcePack(File file) { //#if MC>=11300 return getMinecraft().getPackFinder().func_195741_a(file); //#else @@ -267,6 +360,35 @@ public class MCVer { //#endif } + public static void addCallback( + //#if MC>=11400 + //$$ CompletableFuture future, + //#else + ListenableFuture future, + //#endif + Consumer success, + Consumer failure + ) { + //#if MC>=11400 + //$$ future.thenAccept(success).exceptionally(throwable -> { + //$$ failure.accept(throwable); + //$$ return null; + //$$ }); + //#else + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(T result) { + success.accept(result); + } + + @Override + public void onFailure(Throwable throwable) { + failure.accept(throwable); + } + }); + //#endif + } + public static void BufferBuilder_setTranslation(double x, double y, double z) { //#if MC>=10800 Tessellator.getInstance().getBuffer().setTranslation(x, y, z); @@ -386,12 +508,23 @@ public class MCVer { return ((MinecraftAccessor) getMinecraft()).getTimer().renderPartialTicks; } + public static void addButton(GuiScreen screen, GuiButton button) { + GuiScreenAccessor acc = (GuiScreenAccessor) screen; + acc.getButtons().add(button); + //#if MC>=11300 + acc.getChildren().add(button); + //#endif + } + //#if MC>=11300 public static void processKeyBinds() { ((MinecraftMethodAccessor) getMinecraft()).replayModProcessKeyBinds(); } public interface MinecraftMethodAccessor { void replayModProcessKeyBinds(); + //#if MC>=11400 + //$$ void replayModExecuteTaskQueue(); + //#endif } //#endif @@ -419,6 +552,17 @@ public class MCVer { return MathHelper.sin(val); } + //#if MC>=10904 + // TODO: this can be inlined once https://github.com/SpongePowered/Mixin/issues/305 is fixed + public static Vec3d getPosition(Particle particle, float partialTicks) { + ParticleAccessor acc = (ParticleAccessor) particle; + double x = acc.getPrevPosX() + (acc.getPosX() - acc.getPrevPosX()) * partialTicks; + double y = acc.getPrevPosY() + (acc.getPosY() - acc.getPrevPosY()) * partialTicks; + double z = acc.getPrevPosZ() + (acc.getPosZ() - acc.getPrevPosZ()) * partialTicks; + return new Vec3d(x, y, z); + } + //#endif + public static void openFile(File file) { //#if MC>=11300 Util.getOSType().openFile(file); @@ -535,7 +679,11 @@ public class MCVer { public static final int KEY_Z = GLFW.GLFW_KEY_Z; public static boolean isKeyDown(int keyCode) { + //#if MC>=11400 + //$$ return InputUtil.isKeyPressed(getMinecraft().window.getHandle(), keyCode); + //#else return InputMappings.isKeyDown(keyCode); + //#endif } } //#endif diff --git a/src/main/java/com/replaymod/editor/ReplayModEditor.java b/src/main/java/com/replaymod/editor/ReplayModEditor.java index 6e5b0304..bee459b5 100644 --- a/src/main/java/com/replaymod/editor/ReplayModEditor.java +++ b/src/main/java/com/replaymod/editor/ReplayModEditor.java @@ -3,7 +3,6 @@ package com.replaymod.editor; import com.replaymod.core.Module; import com.replaymod.core.ReplayMod; import com.replaymod.editor.handler.GuiHandler; -import com.replaymod.online.Setting; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -17,8 +16,6 @@ public class ReplayModEditor implements Module { public ReplayModEditor(ReplayMod core) { this.core = core; - - core.getSettingsRegistry().register(Setting.class); } @Override diff --git a/src/main/java/com/replaymod/editor/handler/GuiHandler.java b/src/main/java/com/replaymod/editor/handler/GuiHandler.java index 88aa69cc..e58f758b 100644 --- a/src/main/java/com/replaymod/editor/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/editor/handler/GuiHandler.java @@ -1,5 +1,6 @@ package com.replaymod.editor.handler; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import com.replaymod.core.utils.Utils; import com.replaymod.editor.ReplayModEditor; import com.replaymod.editor.gui.GuiEditReplay; @@ -8,36 +9,37 @@ import de.johni0702.minecraft.gui.container.AbstractGuiScreen; import de.johni0702.minecraft.gui.container.GuiScreen; import de.johni0702.minecraft.gui.element.GuiButton; import net.minecraft.crash.CrashReport; -import net.minecraftforge.client.event.GuiScreenEvent; -//#if MC>=10800 -//#if MC>=11300 +//#if MC>=11400 +//$$ import de.johni0702.minecraft.gui.versions.callbacks.InitScreenCallback; +//$$ import net.minecraft.client.gui.Screen; +//$$ import net.minecraft.client.gui.widget.AbstractButtonWidget; +//$$ import java.util.List; +//#else +import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; -//#else -//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -//#endif -//#else -//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif import java.io.IOException; import static com.replaymod.core.versions.MCVer.*; -public class GuiHandler { - public void register() { - FML_BUS.register(this); - FORGE_BUS.register(this); - } - +public class GuiHandler extends EventRegistrations { + //#if MC>=11400 + //$$ { on(InitScreenCallback.EVENT, this::injectIntoReplayViewer); } + //$$ public void injectIntoReplayViewer(Screen vanillaGuiScreen, List buttonList) { + //#else @SubscribeEvent public void injectIntoReplayViewer(GuiScreenEvent.InitGuiEvent.Post event) { - AbstractGuiScreen guiScreen = GuiScreen.from(getGui(event)); + net.minecraft.client.gui.GuiScreen vanillaGuiScreen = getGui(event); + //#endif + AbstractGuiScreen guiScreen = GuiScreen.from(vanillaGuiScreen); if (!(guiScreen instanceof GuiReplayViewer)) { return; } final GuiReplayViewer replayViewer = (GuiReplayViewer) guiScreen; // Inject Edit button + if (!replayViewer.editorButton.getChildren().isEmpty()) return; replayViewer.replaySpecificButtons.add(new GuiButton(replayViewer.editorButton).onClick(() -> { try { new GuiEditReplay(replayViewer, replayViewer.list.getSelected().file.toPath()) { diff --git a/src/main/java/com/replaymod/extras/FullBrightness.java b/src/main/java/com/replaymod/extras/FullBrightness.java index b06a8aa7..7edc63fb 100644 --- a/src/main/java/com/replaymod/extras/FullBrightness.java +++ b/src/main/java/com/replaymod/extras/FullBrightness.java @@ -1,37 +1,26 @@ package com.replaymod.extras; import com.replaymod.core.ReplayMod; +import com.replaymod.core.versions.MCVer.Keyboard; import com.replaymod.replay.ReplayHandler; import com.replaymod.replay.ReplayModReplay; -import com.replaymod.replay.events.ReplayOpenEvent; +import com.replaymod.replay.events.ReplayOpenedCallback; import com.replaymod.replay.gui.overlay.GuiReplayOverlay; import de.johni0702.minecraft.gui.element.GuiImage; import de.johni0702.minecraft.gui.element.IGuiImage; import de.johni0702.minecraft.gui.layout.HorizontalLayout; - -//#if MC>=11300 -import com.replaymod.core.versions.MCVer.Keyboard; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import net.minecraft.client.GameSettings; -//#else -//$$ import net.minecraft.client.settings.GameSettings; -//$$ import org.lwjgl.input.Keyboard; -//#endif -//#if MC>=10800 -//#if MC>=11300 +//#if MC>=11400 +//$$ import com.replaymod.core.events.PreRenderCallback; +//$$ import com.replaymod.core.events.PostRenderCallback; +//#else import net.minecraftforge.eventbus.api.SubscribeEvent; -//#else -//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -//#endif import net.minecraftforge.fml.common.gameevent.TickEvent; -//#else -//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; -//$$ import cpw.mods.fml.common.gameevent.TickEvent; //#endif -import static com.replaymod.core.versions.MCVer.*; - -public class FullBrightness implements Extra { +public class FullBrightness extends EventRegistrations implements Extra { private ReplayModReplay module; private final IGuiImage indicator = new GuiImage().setTexture(ReplayMod.TEXTURE, 90, 20, 19, 13).setSize(19, 13); @@ -65,26 +54,37 @@ public class FullBrightness implements Extra { } }); - FML_BUS.register(this); + register(); } + //#if MC>=11400 + //$$ { on(PreRenderCallback.EVENT, this::preRender); } + //$$ private void preRender() { + //#else @SubscribeEvent public void preRender(TickEvent.RenderTickEvent event) { + if (event.phase != TickEvent.Phase.START) return; + //#endif if (active && module.getReplayHandler() != null) { - if (event.phase == TickEvent.Phase.START) { - originalGamma = gameSettings.gammaSetting; - gameSettings.gammaSetting = 1000; - } else if (event.phase == TickEvent.Phase.END) { - gameSettings.gammaSetting = originalGamma; - } + originalGamma = gameSettings.gammaSetting; + gameSettings.gammaSetting = 1000; } } + //#if MC>=11400 + //$$ { on(PostRenderCallback.EVENT, this::postRender); } + //$$ private void postRender() { + //#else @SubscribeEvent - public void replayOpened(ReplayOpenEvent.Post event) { - updateIndicator(event.getReplayHandler().getOverlay()); + public void postRender(TickEvent.RenderTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + //#endif + if (active && module.getReplayHandler() != null) { + gameSettings.gammaSetting = originalGamma; + } } + { on(ReplayOpenedCallback.EVENT, replayHandler -> updateIndicator(replayHandler.getOverlay())); } private void updateIndicator(GuiReplayOverlay overlay) { if (active) { overlay.statusIndicatorPanel.addElements(new HorizontalLayout.Data(1), indicator); diff --git a/src/main/java/com/replaymod/extras/HotkeyButtons.java b/src/main/java/com/replaymod/extras/HotkeyButtons.java index c60df3f4..8e0137d8 100644 --- a/src/main/java/com/replaymod/extras/HotkeyButtons.java +++ b/src/main/java/com/replaymod/extras/HotkeyButtons.java @@ -3,7 +3,7 @@ package com.replaymod.extras; import com.replaymod.core.KeyBindingRegistry; import com.replaymod.core.ReplayMod; import com.replaymod.core.mixin.KeyBindingAccessor; -import com.replaymod.replay.events.ReplayOpenEvent; +import com.replaymod.replay.events.ReplayOpenedCallback; import com.replaymod.replay.gui.overlay.GuiReplayOverlay; import de.johni0702.minecraft.gui.GuiRenderer; import de.johni0702.minecraft.gui.RenderInfo; @@ -17,6 +17,7 @@ import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.LayoutData; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.client.resources.I18n; @@ -26,39 +27,22 @@ import net.minecraft.client.resources.I18n; //$$ import org.lwjgl.input.Keyboard; //#endif -//#if MC>=10800 -//#if MC>=11300 -import net.minecraftforge.eventbus.api.SubscribeEvent; -//#else -//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -//#endif -//#else -//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; -//#endif - import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Map; -import static com.replaymod.core.versions.MCVer.*; - -public class HotkeyButtons implements Extra { +public class HotkeyButtons extends EventRegistrations implements Extra { private ReplayMod mod; @Override - public void register(ReplayMod mod) throws Exception { + public void register(ReplayMod mod) { this.mod = mod; - FML_BUS.register(this); - } - - @SubscribeEvent - public void postReplayOpen(ReplayOpenEvent.Post event) { - GuiReplayOverlay overlay = event.getReplayHandler().getOverlay(); - new Gui(mod, overlay); + register(); } + { on(ReplayOpenedCallback.EVENT, replayHandler -> new Gui(mod, replayHandler.getOverlay())); } public static final class Gui { private final GuiTexturedButton toggleButton; private final GridLayout panelLayout; diff --git a/src/main/java/com/replaymod/extras/OpenEyeExtra.java b/src/main/java/com/replaymod/extras/OpenEyeExtra.java index 02dd74f0..08e6c601 100644 --- a/src/main/java/com/replaymod/extras/OpenEyeExtra.java +++ b/src/main/java/com/replaymod/extras/OpenEyeExtra.java @@ -1,6 +1,7 @@ package com.replaymod.extras; import com.replaymod.core.ReplayMod; +import com.replaymod.core.versions.MCVer; import de.johni0702.minecraft.gui.container.AbstractGuiScreen; import de.johni0702.minecraft.gui.container.GuiContainer; import de.johni0702.minecraft.gui.container.GuiPanel; @@ -15,16 +16,6 @@ import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.utils.Colors; import org.apache.commons.io.FileUtils; -//#if MC>=11300 -import net.minecraftforge.fml.ModList; -//#else -//#if MC>=10800 -//$$ import net.minecraftforge.fml.common.Loader; -//#else -//$$ import cpw.mods.fml.common.Loader; -//#endif -//#endif - import javax.net.ssl.HttpsURLConnection; import java.io.File; import java.io.FileOutputStream; @@ -45,11 +36,7 @@ public class OpenEyeExtra implements Extra { public void register(ReplayMod mod) throws Exception { this.mod = mod; - //#if MC>=11300 - boolean isOpenEyeLoaded = ModList.get().isLoaded("openeye"); - //#else - //$$ boolean isOpenEyeLoaded = Loader.isModLoaded("OpenEye"); - //#endif + boolean isOpenEyeLoaded = MCVer.isModLoaded("OpenEye"); if (!isOpenEyeLoaded && mod.getSettingsRegistry().get(Setting.ASK_FOR_OPEN_EYE)) { new Thread(() -> { try { diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java b/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java index 49d7e2c8..8b71d507 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java @@ -2,7 +2,6 @@ package com.replaymod.extras.advancedscreenshots; import com.replaymod.core.ReplayMod; import com.replaymod.extras.Extra; -import net.minecraftforge.common.MinecraftForge; //#if MC<11300 //$$ import com.replaymod.core.versions.MCVer; @@ -10,6 +9,7 @@ import net.minecraftforge.common.MinecraftForge; //$$ import net.minecraft.client.Minecraft; //$$ import net.minecraft.client.gui.GuiControls; //$$ import net.minecraftforge.client.event.GuiScreenEvent; +//$$ import net.minecraftforge.common.MinecraftForge; //$$ import org.lwjgl.input.Keyboard; //#endif @@ -29,7 +29,9 @@ public class AdvancedScreenshots implements Extra { @Override public void register(ReplayMod mod) { this.mod = mod; - MinecraftForge.EVENT_BUS.register(this); + //#if MC<11300 + //$$ MinecraftForge.EVENT_BUS.register(this); + //#endif } //#if MC>=11300 diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java index 3e66ba33..86ac5ae5 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java @@ -65,7 +65,11 @@ public class ScreenshotRenderer implements RenderInfo { MainWindowAccessor acc = (MainWindowAccessor) (Object) mc.mainWindow; acc.setFramebufferWidth(displayWidthBefore); acc.setFramebufferHeight(displayHeightBefore); - mc.getFramebuffer().createBindFramebuffer(displayWidthBefore, displayHeightBefore); + mc.getFramebuffer().createBindFramebuffer(displayWidthBefore, displayHeightBefore + //#if MC>=11400 + //$$ , false + //#endif + ); //#else //$$ mc.resize(displayWidthBefore, displayHeightBefore); //#endif diff --git a/src/main/java/com/replaymod/extras/playeroverview/PlayerOverview.java b/src/main/java/com/replaymod/extras/playeroverview/PlayerOverview.java index 402340c4..a2f0c8cb 100644 --- a/src/main/java/com/replaymod/extras/playeroverview/PlayerOverview.java +++ b/src/main/java/com/replaymod/extras/playeroverview/PlayerOverview.java @@ -4,12 +4,19 @@ import com.google.common.base.Optional; import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.Utils; import com.replaymod.extras.Extra; +import com.replaymod.replay.ReplayHandler; import com.replaymod.replay.ReplayModReplay; import com.replaymod.replay.camera.CameraEntity; -import com.replaymod.replay.events.ReplayCloseEvent; -import com.replaymod.replay.events.ReplayOpenEvent; +import com.replaymod.replay.events.ReplayClosedCallback; +import com.replaymod.replay.events.ReplayOpenedCallback; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; + +//#if MC>=11400 +//$$ import com.replaymod.core.events.PreRenderHandCallback; +//$$ import java.util.stream.Collectors; +//#else import net.minecraftforge.client.event.RenderHandEvent; //#if MC>=11300 @@ -30,13 +37,14 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; //$$ import net.minecraftforge.client.event.RenderPlayerEvent; //$$ import java.util.stream.Collectors; //#endif +//#endif import java.io.IOException; import java.util.*; import static com.replaymod.core.versions.MCVer.*; -public class PlayerOverview implements Extra { +public class PlayerOverview extends EventRegistrations implements Extra { private ReplayModReplay module; private final Set hiddenPlayers = new HashSet<>(); @@ -50,6 +58,13 @@ public class PlayerOverview implements Extra { @Override public void run() { if (module.getReplayHandler() != null) { + //#if MC>=11400 + //$$ List players = mod.getMinecraft().world.getPlayers() + //$$ .stream() + //$$ .map(it -> (PlayerEntity) it) + //$$ .filter(it -> !(it instanceof CameraEntity)) + //$$ .collect(Collectors.toList()); + //#else @SuppressWarnings("unchecked") //#if MC>=10800 List players = mod.getMinecraft().world.getPlayers(EntityPlayer.class, new Predicate() { @@ -64,6 +79,7 @@ public class PlayerOverview implements Extra { //$$ .filter(it -> !(it instanceof CameraEntity)) // Exclude the camera entity //$$ .collect(Collectors.toList()); //#endif + //#endif if (!Utils.isCtrlDown()) { // Hide all players that have an UUID v2 (commonly used for NPCs) Iterator iter = players.iterator(); @@ -79,8 +95,12 @@ public class PlayerOverview implements Extra { } }); - FML_BUS.register(this); + ReplayOpenedCallback.EVENT.register(this::onReplayOpen); + ReplayClosedCallback.EVENT.register(this::onReplayClose); + //#if MC>=11400 + //#else FORGE_BUS.register(this); + //#endif } public boolean isHidden(UUID uuid) { @@ -95,9 +115,8 @@ public class PlayerOverview implements Extra { } } - @SubscribeEvent - public void onReplayOpen(ReplayOpenEvent.Pre event) throws IOException { - Optional> savedData = event.getReplayHandler().getReplayFile().getInvisiblePlayers(); + private void onReplayOpen(ReplayHandler replayHandler) throws IOException { + Optional> savedData = replayHandler.getReplayFile().getInvisiblePlayers(); if (savedData.isPresent()) { hiddenPlayers.addAll(savedData.get()); savingEnabled = true; @@ -106,18 +125,24 @@ public class PlayerOverview implements Extra { } } - @SubscribeEvent - public void onReplayClose(ReplayCloseEvent.Pre event) throws IOException { + private void onReplayClose(ReplayHandler replayHandler) { hiddenPlayers.clear(); } + //#if MC>=11400 + //$$ { on(PreRenderHandCallback.EVENT, this::shouldHideHand); } + //#else @SubscribeEvent public void oRenderHand(RenderHandEvent event) { - Entity view = getRenderViewEntity(module.getCore().getMinecraft()); - if (view != null && isHidden(view.getUniqueID())) { + if (shouldHideHand()) { event.setCanceled(true); } } + //#endif + private boolean shouldHideHand() { + Entity view = getRenderViewEntity(module.getCore().getMinecraft()); + return view != null && isHidden(view.getUniqueID()); + } // See MixinRender for why this is 1.7.10 only //#if MC<=10710 diff --git a/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java b/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java index 3122ab43..213ff388 100644 --- a/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java +++ b/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java @@ -218,11 +218,15 @@ public class GuiYoutubeUpload extends GuiScreen { try { Desktop.getDesktop().browse(new URL(url).toURI()); } catch(Throwable throwable) { + //#if MC>=11400 + //$$ SystemUtil.getOperatingSystem().open(url); + //#else //#if MC>=11300 Util.getOSType().openURI(url); //#else //$$ Sys.openURL(url); //#endif + //#endif } upload = null; progressBar.setLabel(I18n.format("replaymod.gui.ytuploadprogress.done", url)); diff --git a/src/main/java/com/replaymod/extras/youtube/YoutubeUpload.java b/src/main/java/com/replaymod/extras/youtube/YoutubeUpload.java index d171cd87..d05493ac 100644 --- a/src/main/java/com/replaymod/extras/youtube/YoutubeUpload.java +++ b/src/main/java/com/replaymod/extras/youtube/YoutubeUpload.java @@ -5,31 +5,34 @@ import com.replaymod.extras.Extra; import com.replaymod.render.gui.GuiRenderingDone; import de.johni0702.minecraft.gui.container.GuiScreen; import de.johni0702.minecraft.gui.element.GuiButton; -import net.minecraftforge.client.event.GuiScreenEvent; -import net.minecraftforge.common.MinecraftForge; +import de.johni0702.minecraft.gui.utils.EventRegistrations; -//#if MC>=10800 -//#if MC>=11300 +//#if MC>=11400 +//$$ import de.johni0702.minecraft.gui.versions.callbacks.OpenGuiScreenCallback; +//$$ import net.minecraft.client.gui.Screen; +//#else +import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; -//#else -//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -//#endif -//#else -//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif import static com.replaymod.core.versions.MCVer.*; -public class YoutubeUpload implements Extra { +public class YoutubeUpload extends EventRegistrations implements Extra { @Override - public void register(ReplayMod mod) throws Exception { - MinecraftForge.EVENT_BUS.register(this); + public void register(ReplayMod mod) { + register(); } + //#if MC>=11400 + //$$ { on(OpenGuiScreenCallback.EVENT, this::onGuiOpen); } + //$$ private void onGuiOpen(Screen vanillaGui) { + //#else @SubscribeEvent public void onGuiOpen(GuiScreenEvent.InitGuiEvent.Post event) { - if (GuiScreen.from(getGui(event)) instanceof GuiRenderingDone) { - GuiRenderingDone gui = (GuiRenderingDone) GuiScreen.from(getGui(event)); + net.minecraft.client.gui.GuiScreen vanillaGui = getGui(event); + //#endif + if (GuiScreen.from(vanillaGui) instanceof GuiRenderingDone) { + GuiRenderingDone gui = (GuiRenderingDone) GuiScreen.from(vanillaGui); // Check if there already is a youtube button if (gui.actionsPanel.getChildren().stream().anyMatch(it -> it instanceof YoutubeButton)) { return; // Button already added diff --git a/src/main/java/com/replaymod/online/ReplayModOnline.java b/src/main/java/com/replaymod/online/ReplayModOnline.java index d2856508..7a057ea0 100644 --- a/src/main/java/com/replaymod/online/ReplayModOnline.java +++ b/src/main/java/com/replaymod/online/ReplayModOnline.java @@ -10,7 +10,7 @@ import com.replaymod.online.gui.GuiReplayDownloading; import com.replaymod.online.gui.GuiSaveModifiedReplay; import com.replaymod.online.handler.GuiHandler; import com.replaymod.replay.ReplayModReplay; -import com.replaymod.replay.events.ReplayCloseEvent; +import com.replaymod.replay.events.ReplayClosedCallback; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ZipReplayFile; import com.replaymod.replaystudio.studio.ReplayStudio; @@ -18,15 +18,8 @@ import de.johni0702.minecraft.gui.container.GuiScreen; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -//#if MC>=11300 -import net.minecraftforge.eventbus.api.SubscribeEvent; -//#else +//#if MC<11300 //$$ import net.minecraftforge.common.config.Configuration; -//#if MC>=10800 -//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -//#else -//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; -//#endif //#endif import java.io.File; @@ -87,14 +80,24 @@ public class ReplayModOnline implements Module { } new GuiHandler(this).register(); - FML_BUS.register(this); + ReplayClosedCallback.EVENT.register(replayHandler -> onReplayClosed()); // Initial login prompt if (!core.getSettingsRegistry().get(Setting.SKIP_LOGIN_PROMPT)) { if (!isLoggedIn()) { - core.runLater(() -> { - GuiScreen parent = GuiScreen.wrap(getMinecraft().currentScreen); - new GuiLoginPrompt(apiClient, parent, parent, false).display(); + core.runLater(new Runnable() { + @Override + public void run() { + //#if MC>=11400 + //$$ if (getMinecraft().overlay != null) { + //$$ // delay until after resources have been loaded + //$$ core.runLater(this); + //$$ return; + //$$ } + //#endif + GuiScreen parent = GuiScreen.wrap(getMinecraft().currentScreen); + new GuiLoginPrompt(apiClient, parent, parent, false).display(); + } }); } } @@ -161,8 +164,7 @@ public class ReplayModOnline implements Module { } } - @SubscribeEvent - public void onReplayClosed(ReplayCloseEvent.Post event) { + private void onReplayClosed() { if (currentReplayOutputFile != null) { if (currentReplayOutputFile.exists()) { // Replay was modified, ask user for new name new GuiSaveModifiedReplay(currentReplayOutputFile).display(); diff --git a/src/main/java/com/replaymod/online/api/replay/holders/MinecraftVersion.java b/src/main/java/com/replaymod/online/api/replay/holders/MinecraftVersion.java index 2a3216eb..6b76e736 100644 --- a/src/main/java/com/replaymod/online/api/replay/holders/MinecraftVersion.java +++ b/src/main/java/com/replaymod/online/api/replay/holders/MinecraftVersion.java @@ -10,7 +10,8 @@ public enum MinecraftVersion { MC_1_12("Minecraft 1.12", "1.12"), MC_1_12_1("Minecraft 1.12.1", "1.12.1"), MC_1_12_2("Minecraft 1.12.2", "1.12.2"), - MC_1_13_2("Minecraft 1.13.2", "1.13.2"); + MC_1_13_2("Minecraft 1.13.2", "1.13.2"), + MC_1_14("Minecraft 1.14", "1.14"); private String niceName, apiName; diff --git a/src/main/java/com/replaymod/online/gui/GuiLoginPrompt.java b/src/main/java/com/replaymod/online/gui/GuiLoginPrompt.java index f85080d1..3ff971a2 100755 --- a/src/main/java/com/replaymod/online/gui/GuiLoginPrompt.java +++ b/src/main/java/com/replaymod/online/gui/GuiLoginPrompt.java @@ -1,5 +1,6 @@ package com.replaymod.online.gui; +import com.replaymod.core.ReplayMod; import com.replaymod.online.api.ApiClient; import de.johni0702.minecraft.gui.container.AbstractGuiScreen; import de.johni0702.minecraft.gui.container.GuiScreen; @@ -44,12 +45,7 @@ public class GuiLoginPrompt extends AbstractGuiScreen { switch (apiClient.login(username.getText(), password.getText())) { case SUCCESS: statusLabel.setText(""); - getMinecraft().addScheduledTask(new Runnable() { - @Override - public void run() { - successScreen.display(); - } - }); + ReplayMod.instance.runLater(successScreen::display); break; case INVALID_DATA: statusLabel.setI18nText("replaymod.gui.login.incorrect"); diff --git a/src/main/java/com/replaymod/online/gui/GuiRegister.java b/src/main/java/com/replaymod/online/gui/GuiRegister.java index 44473274..626b4140 100644 --- a/src/main/java/com/replaymod/online/gui/GuiRegister.java +++ b/src/main/java/com/replaymod/online/gui/GuiRegister.java @@ -1,6 +1,7 @@ package com.replaymod.online.gui; import com.mojang.authlib.exceptions.AuthenticationException; +import com.replaymod.core.ReplayMod; import com.replaymod.online.api.ApiClient; import com.replaymod.online.api.ApiException; import de.johni0702.minecraft.gui.container.AbstractGuiScreen; @@ -82,12 +83,7 @@ public class GuiRegister extends AbstractGuiScreen { String password = passwordInput.getText(); apiClient.register(username, mail, password); - getMinecraft().addScheduledTask(new Runnable() { - @Override - public void run() { - parent.getSuccessScreen().display(); - } - }); + ReplayMod.instance.runLater(parent.getSuccessScreen()::display); } catch (ApiException ae) { statusLabel.setText(ae.getLocalizedMessage()); } catch (AuthenticationException aue) { diff --git a/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java b/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java index 29999215..5b6e7f01 100644 --- a/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java +++ b/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java @@ -5,7 +5,6 @@ import com.google.common.base.Supplier; import com.google.common.primitives.Ints; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; -import com.mojang.realmsclient.gui.ChatFormatting; import com.replaymod.core.utils.Utils; import com.replaymod.online.ReplayModOnline; import com.replaymod.online.api.ApiClient; @@ -40,6 +39,7 @@ import de.johni0702.minecraft.gui.utils.Consumer; import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import org.apache.commons.lang3.StringUtils; +import net.minecraft.util.text.TextFormatting; import java.awt.image.BufferedImage; import java.io.IOException; @@ -417,9 +417,9 @@ public class GuiReplayCenter extends GuiScreen { this.downloaded = downloaded; ReplayMetaData metaData = fileInfo.getMetadata(); - name.setText(ChatFormatting.UNDERLINE + Utils.fileNameToReplayName(fileInfo.getName())); + name.setText(TextFormatting.UNDERLINE + Utils.fileNameToReplayName(fileInfo.getName())); author.setI18nText("replaymod.gui.center.author", - "" + ChatFormatting.GRAY + ChatFormatting.ITALIC, fileInfo.getOwner()); + "" + TextFormatting.GRAY + TextFormatting.ITALIC, fileInfo.getOwner()); if (StringUtils.isEmpty(metaData.getServerName())) { server.setI18nText("replaymod.gui.iphidden").setColor(Colors.DARK_RED); } else { @@ -443,7 +443,7 @@ public class GuiReplayCenter extends GuiScreen { favorites.setText("⭑" + fileInfo.getFavorites()); likes.setText("⬆" + fileInfo.getRatings().getPositive()); dislikes.setText("⬇" + fileInfo.getRatings().getNegative()); - category.setText(ChatFormatting.ITALIC + Optional.fromNullable(Category.fromId(fileInfo.getCategory())) + category.setText(TextFormatting.ITALIC + Optional.fromNullable(Category.fromId(fileInfo.getCategory())) .or(Category.MISCELLANEOUS).toNiceString()); addElements(null, durationPanel, downloadsPanel); diff --git a/src/main/java/com/replaymod/online/gui/GuiReplayDownloading.java b/src/main/java/com/replaymod/online/gui/GuiReplayDownloading.java index bd025b4a..15960c3b 100644 --- a/src/main/java/com/replaymod/online/gui/GuiReplayDownloading.java +++ b/src/main/java/com/replaymod/online/gui/GuiReplayDownloading.java @@ -1,6 +1,6 @@ package com.replaymod.online.gui; -import com.mojang.realmsclient.gui.ChatFormatting; +import com.replaymod.core.ReplayMod; import com.replaymod.online.ReplayModOnline; import com.replaymod.online.api.ApiClient; import com.replaymod.online.api.ApiException; @@ -10,6 +10,7 @@ import de.johni0702.minecraft.gui.element.GuiButton; import de.johni0702.minecraft.gui.element.GuiLabel; import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar; import de.johni0702.minecraft.gui.layout.CustomLayout; +import net.minecraft.util.text.TextFormatting; import java.io.File; import java.io.IOException; @@ -33,7 +34,7 @@ public class GuiReplayDownloading extends AbstractGuiScreen() { @Override protected void layout(GuiReplayDownloading container, int width, int height) { @@ -53,14 +54,11 @@ public class GuiReplayDownloading extends AbstractGuiScreen { + try { + mod.startReplay(replayId, null, null); + } catch (IOException e) { + e.printStackTrace(); } }); } diff --git a/src/main/java/com/replaymod/online/handler/GuiHandler.java b/src/main/java/com/replaymod/online/handler/GuiHandler.java index 165a8bbf..96eed5ae 100644 --- a/src/main/java/com/replaymod/online/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/online/handler/GuiHandler.java @@ -1,35 +1,33 @@ package com.replaymod.online.handler; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import com.replaymod.online.ReplayModOnline; import com.replaymod.online.gui.GuiLoginPrompt; import com.replaymod.online.gui.GuiReplayCenter; import com.replaymod.online.gui.GuiUploadReplay; import com.replaymod.replay.gui.screen.GuiReplayViewer; +import com.replaymod.replay.handler.GuiHandler.InjectedButton; import de.johni0702.minecraft.gui.container.AbstractGuiScreen; -import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.container.GuiScreen; -import de.johni0702.minecraft.gui.element.GuiElement; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiMainMenu; import net.minecraft.client.resources.I18n; -import net.minecraftforge.client.event.GuiScreenEvent; -//#if MC>=10800 -//#if MC>=11300 +//#if MC>=11400 +//$$ import de.johni0702.minecraft.gui.versions.callbacks.InitScreenCallback; +//$$ import net.minecraft.client.gui.Screen; +//$$ import net.minecraft.client.gui.widget.AbstractButtonWidget; +//$$ import java.util.List; +//#else +import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; -import java.util.ArrayList; -//#else -//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -//#endif -//#else -//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif import java.io.File; import static com.replaymod.core.versions.MCVer.*; -public class GuiHandler { +public class GuiHandler extends EventRegistrations { private static final int BUTTON_REPLAY_CENTER = 17890236; private final ReplayModOnline mod; @@ -38,42 +36,46 @@ public class GuiHandler { this.mod = mod; } - public void register() { - FML_BUS.register(this); - FORGE_BUS.register(this); - } - + //#if MC>=11400 + //$$ { on(InitScreenCallback.EVENT, this::injectIntoMainMenu); } + //$$ public void injectIntoMainMenu(Screen guiScreen, List buttonList) { + //#else @SubscribeEvent public void injectIntoMainMenu(GuiScreenEvent.InitGuiEvent event) { - if (!(getGui(event) instanceof GuiMainMenu)) { + final net.minecraft.client.gui.GuiScreen guiScreen = getGui(event); + //#endif + if (!(guiScreen instanceof GuiMainMenu)) { return; } - GuiButton button = new GuiButton( + GuiButton button = new InjectedButton( + guiScreen, BUTTON_REPLAY_CENTER, - getGui(event).width / 2 + 2, - getGui(event).height / 4 + 10 + 4 * 24, - I18n.format("replaymod.gui.replaycenter") - ) { - //#if MC>=11300 - @Override - public void onClick(double mouseX, double mouseY) { - onButton(new GuiScreenEvent.ActionPerformedEvent.Pre(getGui(event), this, new ArrayList<>())); - } - //#endif - }; - button.width = button.width / 2 - 2; - addButton(event, button); + guiScreen.width / 2 + 2, + guiScreen.height / 4 + 10 + 4 * 24, + 98, + 20, + I18n.format("replaymod.gui.replaycenter"), + this::onButton + ); + addButton(guiScreen, button); } + //#if MC>=11400 + //$$ { on(InitScreenCallback.EVENT, this::injectIntoReplayViewer); } + //$$ public void injectIntoReplayViewer(Screen vanillaGuiScreen, List buttonList) { + //#else @SubscribeEvent public void injectIntoReplayViewer(GuiScreenEvent.InitGuiEvent.Post event) { - AbstractGuiScreen guiScreen = GuiScreen.from(getGui(event)); + net.minecraft.client.gui.GuiScreen vanillaGuiScreen = getGui(event); + //#endif + AbstractGuiScreen guiScreen = GuiScreen.from(vanillaGuiScreen); if (!(guiScreen instanceof GuiReplayViewer)) { return; } final GuiReplayViewer replayViewer = (GuiReplayViewer) guiScreen; // Inject Upload button + if (!replayViewer.uploadButton.getChildren().isEmpty()) return; replayViewer.replaySpecificButtons.add(new de.johni0702.minecraft.gui.element.GuiButton(replayViewer.uploadButton).onClick(() -> { File replayFile = replayViewer.list.getSelected().file; GuiUploadReplay uploadGui = new GuiUploadReplay(replayViewer, mod, replayFile); @@ -85,17 +87,24 @@ public class GuiHandler { }).setSize(73, 20).setI18nLabel("replaymod.gui.upload").setDisabled()); } - @SubscribeEvent - public void onButton(GuiScreenEvent.ActionPerformedEvent.Pre event) { - if(!getButton(event).enabled) return; + //#if MC>=11300 + private void onButton(InjectedButton button) { + net.minecraft.client.gui.GuiScreen guiScreen = button.guiScreen; + //#else + //$$ @SubscribeEvent + //$$ public void onButton(GuiScreenEvent.ActionPerformedEvent.Pre event) { + //$$ net.minecraft.client.gui.GuiScreen guiScreen = getGui(event); + //$$ GuiButton button = getButton(event); + //#endif + if(!button.enabled) return; - if (getGui(event) instanceof GuiMainMenu) { - if (getButton(event).id == BUTTON_REPLAY_CENTER) { + if (guiScreen instanceof GuiMainMenu) { + if (button.id == BUTTON_REPLAY_CENTER) { GuiReplayCenter replayCenter = new GuiReplayCenter(mod); if (mod.isLoggedIn()) { replayCenter.display(); } else { - new GuiLoginPrompt(mod.getApiClient(), GuiScreen.wrap(getGui(event)), replayCenter, true).display(); + new GuiLoginPrompt(mod.getApiClient(), GuiScreen.wrap(guiScreen), replayCenter, true).display(); } } } diff --git a/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java b/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java index a4f47429..394dcb7a 100644 --- a/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java +++ b/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java @@ -13,19 +13,10 @@ import com.replaymod.replay.ReplayHandler; import com.replaymod.replaystudio.pathing.path.Keyframe; import com.replaymod.replaystudio.pathing.path.Path; import com.replaymod.replaystudio.pathing.path.Timeline; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import net.minecraft.client.Minecraft; import net.minecraft.util.Timer; -//#if MC>=10800 -//#if MC>=11300 -import net.minecraftforge.eventbus.api.SubscribeEvent; -//#else -//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -//#endif -//#else -//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; -//#endif - import javax.annotation.Nullable; import java.util.Iterator; @@ -34,7 +25,7 @@ import static com.replaymod.core.versions.MCVer.*; /** * Plays a timeline. */ -public abstract class AbstractTimelinePlayer { +public abstract class AbstractTimelinePlayer extends EventRegistrations { private final Minecraft mc = getMinecraft(); private final ReplayHandler replayHandler; private Timeline timeline; @@ -79,7 +70,7 @@ public abstract class AbstractTimelinePlayer { } replayHandler.getReplaySender().setSyncModeAndWait(); - FML_BUS.register(this); + register(); lastTime = 0; MinecraftAccessor mcA = (MinecraftAccessor) mc; @@ -106,14 +97,14 @@ public abstract class AbstractTimelinePlayer { return future != null && !future.isDone(); } - @SubscribeEvent - public void onTick(ReplayTimer.UpdatedEvent event) { + { on(ReplayTimer.UpdatedCallback.EVENT, this::onTick); } + public void onTick() { if (future.isDone()) { MinecraftAccessor mcA = (MinecraftAccessor) mc; mcA.setTimer(((ReplayTimer) mcA.getTimer()).getWrapped()); replayHandler.getReplaySender().setReplaySpeed(0); replayHandler.getReplaySender().setAsyncMode(true); - FML_BUS.unregister(this); + unregister(); return; } long time = getTimePassed(); diff --git a/src/main/java/com/replaymod/pathing/player/RealtimeTimelinePlayer.java b/src/main/java/com/replaymod/pathing/player/RealtimeTimelinePlayer.java index a4c92d44..d26ca2f0 100644 --- a/src/main/java/com/replaymod/pathing/player/RealtimeTimelinePlayer.java +++ b/src/main/java/com/replaymod/pathing/player/RealtimeTimelinePlayer.java @@ -4,10 +4,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.replaymod.replay.ReplayHandler; import com.replaymod.replaystudio.pathing.path.Timeline; -//#if MC<=10710 -//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; -//#endif - /** * Timeline player using the system time. */ @@ -36,15 +32,12 @@ public class RealtimeTimelinePlayer extends AbstractTimelinePlayer { } @Override - //#if MC<=10710 - //$$ @SubscribeEvent - //#endif - public void onTick(ReplayTimer.UpdatedEvent event) { + public void onTick() { if (secondFrame) { secondFrame = false; startTime = System.currentTimeMillis(); } - super.onTick(event); + super.onTick(); if (firstFrame) { firstFrame = false; secondFrame = true; diff --git a/src/main/java/com/replaymod/pathing/player/ReplayTimer.java b/src/main/java/com/replaymod/pathing/player/ReplayTimer.java index 597fe805..7e6bae5a 100644 --- a/src/main/java/com/replaymod/pathing/player/ReplayTimer.java +++ b/src/main/java/com/replaymod/pathing/player/ReplayTimer.java @@ -1,20 +1,10 @@ package com.replaymod.pathing.player; import com.replaymod.core.utils.WrappedTimer; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.util.Timer; -//#if MC>=10800 -//#if MC>=11300 -import net.minecraftforge.eventbus.api.Event; -//#else -//$$ import net.minecraftforge.fml.common.eventhandler.Event; -//#endif -//#else -//$$ import cpw.mods.fml.common.eventhandler.Event; -//#endif - -import static com.replaymod.core.versions.MCVer.*; - /** * Wrapper around the current timer that prevents the timer from advancing by itself. */ @@ -30,7 +20,12 @@ public class ReplayTimer extends WrappedTimer { } @Override + // This should be handled by Remap but it isn't (was handled before a9724e3). + //#if MC>=11400 + //$$ public void beginRenderTick( + //#else public void updateTimer( + //#endif //#if MC>=11300 long sysClock //#endif @@ -42,13 +37,20 @@ public class ReplayTimer extends WrappedTimer { //#endif ); // Update current state copy(state, this); // Restore our old state - FML_BUS.post(new UpdatedEvent()); + UpdatedCallback.EVENT.invoker().onUpdate(); } public Timer getWrapped() { return wrapped; } - public static class UpdatedEvent extends Event { + public interface UpdatedCallback { + Event EVENT = EventFactory.createArrayBacked(UpdatedCallback.class, + (listeners) -> () -> { + for (UpdatedCallback listener : listeners) { + listener.onUpdate(); + } + }); + void onUpdate(); } } diff --git a/src/main/java/com/replaymod/pathing/properties/SpectatorProperty.java b/src/main/java/com/replaymod/pathing/properties/SpectatorProperty.java index 5628b3fd..b2af033d 100644 --- a/src/main/java/com/replaymod/pathing/properties/SpectatorProperty.java +++ b/src/main/java/com/replaymod/pathing/properties/SpectatorProperty.java @@ -6,7 +6,6 @@ import com.replaymod.replay.ReplayHandler; import com.replaymod.replaystudio.pathing.property.AbstractProperty; import com.replaymod.replaystudio.pathing.property.PropertyPart; import com.replaymod.replaystudio.pathing.property.PropertyParts; -import lombok.NonNull; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.world.World; @@ -31,7 +30,7 @@ public class SpectatorProperty extends AbstractProperty { } @Override - public void applyToGame(Integer value, @NonNull Object replayHandler) { + public void applyToGame(Integer value, Object replayHandler) { ReplayHandler handler = ((ReplayHandler) replayHandler); World world = handler.getCameraEntity().getEntityWorld(); // Lookup entity by id, returns null if an entity with the id does not exists diff --git a/src/main/java/com/replaymod/recording/ReplayModRecording.java b/src/main/java/com/replaymod/recording/ReplayModRecording.java index 101cd68a..e7db0369 100644 --- a/src/main/java/com/replaymod/recording/ReplayModRecording.java +++ b/src/main/java/com/replaymod/recording/ReplayModRecording.java @@ -7,17 +7,25 @@ import com.replaymod.core.utils.Restrictions; import com.replaymod.core.versions.MCVer.Keyboard; import com.replaymod.recording.handler.ConnectionEventHandler; import com.replaymod.recording.handler.GuiHandler; +import com.replaymod.recording.mixin.NetworkManagerAccessor; import com.replaymod.recording.packet.PacketListener; import io.netty.channel.Channel; -import io.netty.channel.ChannelDuplexHandler; -import io.netty.channel.ChannelHandler; import io.netty.util.AttributeKey; import net.minecraft.network.NetworkManager; -import net.minecraftforge.fml.network.NetworkRegistry; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import static com.replaymod.core.versions.MCVer.*; +//#if MC>=11400 +//$$ import net.fabricmc.fabric.api.network.ClientSidePacketRegistry; +//#else +import net.minecraftforge.fml.network.NetworkRegistry; +//#endif + +//#if MC>=11300 +//#else +//$$ import io.netty.channel.ChannelDuplexHandler; +//$$ import io.netty.channel.ChannelHandler; +//#endif public class ReplayModRecording implements Module { @@ -55,22 +63,28 @@ public class ReplayModRecording implements Module { @Override public void initClient() { - FML_BUS.register(connectionEventHandler = new ConnectionEventHandler(LOGGER, core)); + connectionEventHandler = new ConnectionEventHandler(LOGGER, core); new GuiHandler(core).register(); + //#if MC>=11400 + //$$ ClientSidePacketRegistry.INSTANCE.register(Restrictions.PLUGIN_CHANNEL, (packetContext, packetByteBuf) -> {}); + //#else //#if MC>=11300 NetworkRegistry.newEventChannel(Restrictions.PLUGIN_CHANNEL, () -> "0", any -> true, any -> true); //#else //$$ NetworkRegistry.INSTANCE.newChannel(Restrictions.PLUGIN_CHANNEL, new RestrictionsChannelHandler()); //#endif + //#endif } - @ChannelHandler.Sharable - private static class RestrictionsChannelHandler extends ChannelDuplexHandler {} + //#if MC<11300 + //$$ @ChannelHandler.Sharable + //$$ private static class RestrictionsChannelHandler extends ChannelDuplexHandler {} + //#endif public void initiateRecording(NetworkManager networkManager) { - Channel channel = networkManager.channel(); + Channel channel = ((NetworkManagerAccessor) networkManager).getChannel(); if (channel.pipeline().get("ReplayModReplay_replaySender") != null) return; //#if MC>=11300 if (channel.hasAttr(ATTR_CHECKED)) return; diff --git a/src/main/java/com/replaymod/recording/gui/GuiRecordingControls.java b/src/main/java/com/replaymod/recording/gui/GuiRecordingControls.java index fa4495f9..ad5b900f 100644 --- a/src/main/java/com/replaymod/recording/gui/GuiRecordingControls.java +++ b/src/main/java/com/replaymod/recording/gui/GuiRecordingControls.java @@ -10,14 +10,20 @@ import de.johni0702.minecraft.gui.container.VanillaGuiScreen; import de.johni0702.minecraft.gui.element.GuiButton; import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import net.minecraft.client.gui.GuiIngameMenu; + +//#if MC>=11400 +//$$ import de.johni0702.minecraft.gui.versions.callbacks.InitScreenCallback; +//#else import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.SubscribeEvent; import static com.replaymod.core.versions.MCVer.getGui; +//#endif -public class GuiRecordingControls { +public class GuiRecordingControls extends EventRegistrations { private ReplayMod core; private PacketListener packetListener; private boolean paused; @@ -60,20 +66,20 @@ public class GuiRecordingControls { updateState(); } - public void register() { - MinecraftForge.EVENT_BUS.register(this); - } - - public void unregister() { - MinecraftForge.EVENT_BUS.unregister(this); - } - + //#if MC>=11400 + //$$ { on(InitScreenCallback.EVENT, (screen, buttons) -> { + //$$ if (screen instanceof PauseMenuScreen) { + //$$ show((PauseMenuScreen) screen); + //$$ } + //$$ }); } + //#else @SubscribeEvent public void onGuiInit(GuiScreenEvent.InitGuiEvent.Post event) { if (getGui(event) instanceof GuiIngameMenu) { show((GuiIngameMenu) getGui(event)); } } + //#endif private void updateState() { buttonPauseResume.setI18nLabel("replaymod.gui.recording." + (paused ? "resume" : "pause")); diff --git a/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java b/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java index 7eb6ae2b..75d63e08 100644 --- a/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java +++ b/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java @@ -1,24 +1,35 @@ package com.replaymod.recording.gui; import com.replaymod.core.SettingsRegistry; +import com.replaymod.core.versions.MCVer; import com.replaymod.recording.Setting; +import de.johni0702.minecraft.gui.GuiRenderer; +import de.johni0702.minecraft.gui.MinecraftGuiRenderer; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.gui.Gui; import net.minecraft.client.resources.I18n; + +//#if MC>=11400 +//$$ import de.johni0702.minecraft.gui.versions.callbacks.PostRenderHudCallback; +//#else import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.SubscribeEvent; +//#endif import static com.replaymod.core.ReplayMod.TEXTURE; import static com.replaymod.core.ReplayMod.TEXTURE_SIZE; import static com.replaymod.core.versions.MCVer.*; -import static net.minecraft.client.renderer.GlStateManager.*; + +//#if MC<11300 +//$$ import static net.minecraft.client.renderer.GlStateManager.*; +//#endif /** * Renders overlay during recording. */ -public class GuiRecordingOverlay { +public class GuiRecordingOverlay extends EventRegistrations { private final Minecraft mc; private final SettingsRegistry settingsRegistry; private final GuiRecordingControls guiControls; @@ -29,30 +40,26 @@ public class GuiRecordingOverlay { this.guiControls = guiControls; } - public void register() { - MinecraftForge.EVENT_BUS.register(this); - } - - public void unregister() { - MinecraftForge.EVENT_BUS.unregister(this); - } - /** * Render the recording icon and text in the top left corner of the screen. - * @param event Rendered post game overlay */ + //#if MC>=11400 + //$$ { on(PostRenderHudCallback.EVENT, partialTicks -> renderRecordingIndicator()); } + //$$ private void renderRecordingIndicator() { + //#else @SubscribeEvent public void renderRecordingIndicator(RenderGameOverlayEvent.Post event) { if (getType(event) != RenderGameOverlayEvent.ElementType.ALL) return; + //#endif if (guiControls.isStopped()) return; if (settingsRegistry.get(Setting.INDICATOR)) { FontRenderer fontRenderer = mc.fontRenderer; String text = guiControls.isPaused() ? I18n.format("replaymod.gui.paused") : I18n.format("replaymod.gui.recording"); fontRenderer.drawString(text.toUpperCase(), 30, 18 - (fontRenderer.FONT_HEIGHT / 2), 0xffffffff); bindTexture(TEXTURE); - resetColor(); enableAlpha(); - Gui.drawModalRectWithCustomSizedTexture(10, 10, 58, 20, 16, 16, TEXTURE_SIZE, TEXTURE_SIZE); + GuiRenderer renderer = new MinecraftGuiRenderer(MCVer.newScaledResolution(mc)); + renderer.drawTexturedRect(10, 10, 58, 20, 16, 16, 16, 16, TEXTURE_SIZE, TEXTURE_SIZE); } } } diff --git a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java index cd1cd4e9..34fe27cf 100755 --- a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java @@ -7,11 +7,13 @@ import com.replaymod.editor.gui.MarkerProcessor; import com.replaymod.recording.Setting; import com.replaymod.recording.gui.GuiRecordingControls; import com.replaymod.recording.gui.GuiRecordingOverlay; +import com.replaymod.recording.mixin.NetworkManagerAccessor; import com.replaymod.recording.packet.PacketListener; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ReplayMetaData; import com.replaymod.replaystudio.replay.ZipReplayFile; import com.replaymod.replaystudio.studio.ReplayStudio; +import io.netty.channel.Channel; import net.minecraft.client.Minecraft; import net.minecraft.network.NetworkManager; import org.apache.logging.log4j.Logger; @@ -108,7 +110,8 @@ public class ConnectionEventHandler { metaData.setDate(System.currentTimeMillis()); metaData.setMcVersion(ReplayMod.getMinecraftVersion()); packetListener = new PacketListener(core, currentFile.toPath(), replayFile, metaData); - networkManager.channel().pipeline().addBefore(packetHandlerKey, "replay_recorder", packetListener); + Channel channel = ((NetworkManagerAccessor) networkManager).getChannel(); + channel.pipeline().addBefore(packetHandlerKey, "replay_recorder", packetListener); recordingEventHandler = new RecordingEventHandler(packetListener); recordingEventHandler.register(); diff --git a/src/main/java/com/replaymod/recording/handler/GuiHandler.java b/src/main/java/com/replaymod/recording/handler/GuiHandler.java index 154a6455..c923a996 100644 --- a/src/main/java/com/replaymod/recording/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/recording/handler/GuiHandler.java @@ -7,15 +7,22 @@ import de.johni0702.minecraft.gui.container.GuiScreen; import de.johni0702.minecraft.gui.container.VanillaGuiScreen; import de.johni0702.minecraft.gui.element.GuiCheckbox; import de.johni0702.minecraft.gui.layout.CustomLayout; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import net.minecraft.client.gui.GuiMultiplayer; import net.minecraft.client.gui.GuiWorldSelection; + +//#if MC>=11400 +//$$ import de.johni0702.minecraft.gui.versions.callbacks.InitScreenCallback; +//$$ import net.minecraft.client.gui.Screen; +//#else import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.SubscribeEvent; import static com.replaymod.core.versions.MCVer.getGui; +//#endif -public class GuiHandler { +public class GuiHandler extends EventRegistrations { private final ReplayMod mod; @@ -23,14 +30,16 @@ public class GuiHandler { this.mod = mod; } - public void register() { - MinecraftForge.EVENT_BUS.register(this); - } - + //#if MC>=11400 + //$$ { on(InitScreenCallback.EVENT, (screen, buttons) -> onGuiInit(screen)); } + //$$ private void onGuiInit(Screen gui) { + //#else @SubscribeEvent public void onGuiInit(GuiScreenEvent.InitGuiEvent.Post event) { - if (getGui(event) instanceof GuiWorldSelection || getGui(event) instanceof GuiMultiplayer) { - boolean sp = getGui(event) instanceof GuiWorldSelection; + net.minecraft.client.gui.GuiScreen gui = getGui(event); + //#endif + if (gui instanceof GuiWorldSelection || gui instanceof GuiMultiplayer) { + boolean sp = gui instanceof GuiWorldSelection; SettingsRegistry settingsRegistry = mod.getSettingsRegistry(); Setting setting = sp ? Setting.RECORD_SINGLEPLAYER : Setting.RECORD_SERVER; @@ -42,7 +51,7 @@ public class GuiHandler { settingsRegistry.save(); }); - VanillaGuiScreen.setup(getGui(event)).setLayout(new CustomLayout() { + VanillaGuiScreen.setup(gui).setLayout(new CustomLayout() { @Override protected void layout(GuiScreen container, int width, int height) { //size(recordingCheckbox, 200, 20); diff --git a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java index bf3a571a..24a58e42 100755 --- a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java @@ -3,17 +3,25 @@ package com.replaymod.recording.handler; import com.replaymod.recording.mixin.EntityLivingBaseAccessor; import com.replaymod.recording.mixin.IntegratedServerAccessor; import com.replaymod.recording.packet.PacketListener; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.network.Packet; import net.minecraft.network.play.server.*; import net.minecraft.server.integrated.IntegratedServer; // FIXME not (yet?) 1.13 import net.minecraftforge.event.entity.minecart.MinecartInteractEvent; + +//#if MC>=11400 +//$$ import com.replaymod.core.events.PreRenderCallback; +//$$ import de.johni0702.minecraft.gui.versions.callbacks.PreTickCallback; +//#else import net.minecraftforge.event.entity.player.PlayerSleepInBedEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.PlayerEvent.ItemPickupEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; +//#endif //#if MC>=10904 //#if MC>=11300 @@ -37,7 +45,7 @@ import java.util.Objects; import static com.replaymod.core.versions.MCVer.*; -public class RecordingEventHandler { +public class RecordingEventHandler extends EventRegistrations { private final Minecraft mc = getMinecraft(); private final PacketListener packetListener; @@ -57,15 +65,15 @@ public class RecordingEventHandler { this.packetListener = packetListener; } + @Override public void register() { - FML_BUS.register(this); - FORGE_BUS.register(this); + super.register(); ((RecordingEventSender) mc.renderGlobal).setRecordingEventHandler(this); } + @Override public void unregister() { - FML_BUS.unregister(this); - FORGE_BUS.unregister(this); + super.unregister(); RecordingEventSender recordingEventSender = ((RecordingEventSender) mc.renderGlobal); if (recordingEventSender.getRecordingEventHandler() == this) { recordingEventSender.setRecordingEventHandler(null); @@ -116,17 +124,24 @@ public class RecordingEventHandler { } //#endif + //#if MC>=11400 + //$$ { on(PreTickCallback.EVENT, this::onPlayerTick); } + //$$ private void onPlayerTick() { + //$$ if (mc.player == null) return; + //#else @SubscribeEvent public void onPlayerTick(TickEvent.PlayerTickEvent e) { + if(e.player != mc.player) return; + //#endif + EntityPlayerSP player = mc.player; try { - if(e.player != mc.player) return; boolean force = false; if(lastX == null || lastY == null || lastZ == null) { force = true; - lastX = e.player.posX; - lastY = e.player.posY; - lastZ = e.player.posZ; + lastX = player.posX; + lastY = player.posY; + lastZ = player.posZ; } ticksSinceLastCorrection++; @@ -135,44 +150,44 @@ public class RecordingEventHandler { force = true; } - double dx = e.player.posX - lastX; - double dy = e.player.posY - lastY; - double dz = e.player.posZ - lastZ; + double dx = player.posX - lastX; + double dy = player.posY - lastY; + double dz = player.posZ - lastZ; - lastX = e.player.posX; - lastY = e.player.posY; - lastZ = e.player.posZ; + lastX = player.posX; + lastY = player.posY; + lastZ = player.posZ; Packet packet; if (force || Math.abs(dx) > 8.0 || Math.abs(dy) > 8.0 || Math.abs(dz) > 8.0) { //#if MC>=10800 - packet = new SPacketEntityTeleport(e.player); + packet = new SPacketEntityTeleport(player); //#else //$$ // In 1.7.10 the client player entity has its posY at eye height //$$ // but for all other entities it's at their feet (as it should be). //$$ // So, to correctly position the player, we teleport them to their feet (i.a. directly after spawn). //$$ // Note: this leaves the lastY value offset by the eye height but because it's only used for relative //$$ // movement, that doesn't matter. - //$$ S18PacketEntityTeleport teleportPacket = new S18PacketEntityTeleport(e.player); - //$$ teleportPacket.field_149457_c = MathHelper.floor_double(e.player.boundingBox.minY * 32); + //$$ S18PacketEntityTeleport teleportPacket = new S18PacketEntityTeleport(player); + //$$ teleportPacket.field_149457_c = MathHelper.floor_double(player.boundingBox.minY * 32); //$$ packet = teleportPacket; //#endif } else { - byte newYaw = (byte) ((int) (e.player.rotationYaw * 256.0F / 360.0F)); - byte newPitch = (byte) ((int) (e.player.rotationPitch * 256.0F / 360.0F)); + byte newYaw = (byte) ((int) (player.rotationYaw * 256.0F / 360.0F)); + byte newPitch = (byte) ((int) (player.rotationPitch * 256.0F / 360.0F)); //#if MC>=10904 packet = new SPacketEntity.Move( - e.player.getEntityId(), + player.getEntityId(), (short) Math.round(dx * 4096), (short) Math.round(dy * 4096), (short) Math.round(dz * 4096), - newYaw, newPitch, e.player.onGround + newYaw, newPitch, player.onGround ); //#else - //$$ packet = new S14PacketEntity.S17PacketEntityLookMove(e.player.getEntityId(), + //$$ packet = new S14PacketEntity.S17PacketEntityLookMove(player.getEntityId(), //$$ (byte) Math.round(dx * 32), (byte) Math.round(dy * 32), (byte) Math.round(dz * 32), //$$ newYaw, newPitch //#if MC>=10800 - //$$ , e.player.onGround + //$$ , player.onGround //#endif //$$ ); //#endif @@ -181,22 +196,28 @@ public class RecordingEventHandler { packetListener.save(packet); //HEAD POS - int rotationYawHead = ((int)(e.player.rotationYawHead * 256.0F / 360.0F)); + int rotationYawHead = ((int)(player.rotationYawHead * 256.0F / 360.0F)); if(!Objects.equals(rotationYawHead, rotationYawHeadBefore)) { - packetListener.save(new SPacketEntityHeadLook(e.player, (byte) rotationYawHead)); + packetListener.save(new SPacketEntityHeadLook(player, (byte) rotationYawHead)); rotationYawHeadBefore = rotationYawHead; } - packetListener.save(new SPacketEntityVelocity(e.player.getEntityId(), e.player.motionX, e.player.motionY, e.player.motionZ)); + packetListener.save(new SPacketEntityVelocity(player.getEntityId(), + //#if MC>=11400 + //$$ player.getVelocity() + //#else + player.motionX, player.motionY, player.motionZ + //#endif + )); //Animation Packets //Swing Animation - if (e.player.isSwingInProgress && e.player.swingProgressInt == -1) { + if (player.isSwingInProgress && player.swingProgressInt == -1) { packetListener.save(new SPacketAnimation( - e.player, + player, //#if MC>=10904 - e.player.swingingHand == EnumHand.MAIN_HAND ? 0 : 3 + player.swingingHand == EnumHand.MAIN_HAND ? 0 : 3 //#else //$$ 0 //#endif @@ -206,7 +227,7 @@ public class RecordingEventHandler { /* //Potion Effect Handling List found = new ArrayList(); - for(PotionEffect pe : (Collection)e.player.getActivePotionEffects()) { + for(PotionEffect pe : (Collection)player.getActivePotionEffects()) { found.add(pe.getPotionID()); if(lastEffects.contains(found)) continue; S1DPacketEntityEffect pee = new S1DPacketEntityEffect(entityID, pe); @@ -225,95 +246,95 @@ public class RecordingEventHandler { //Inventory Handling //#if MC>=10904 - if (playerItems[0] != mc.player.getHeldItem(EnumHand.MAIN_HAND)) { - playerItems[0] = mc.player.getHeldItem(EnumHand.MAIN_HAND); - packetListener.save(new SPacketEntityEquipment(e.player.getEntityId(), EntityEquipmentSlot.MAINHAND, playerItems[0])); + if (playerItems[0] != player.getHeldItem(EnumHand.MAIN_HAND)) { + playerItems[0] = player.getHeldItem(EnumHand.MAIN_HAND); + packetListener.save(new SPacketEntityEquipment(player.getEntityId(), EntityEquipmentSlot.MAINHAND, playerItems[0])); } - if (playerItems[1] != mc.player.getHeldItem(EnumHand.OFF_HAND)) { - playerItems[1] = mc.player.getHeldItem(EnumHand.OFF_HAND); - packetListener.save(new SPacketEntityEquipment(e.player.getEntityId(), EntityEquipmentSlot.OFFHAND, playerItems[1])); + if (playerItems[1] != player.getHeldItem(EnumHand.OFF_HAND)) { + playerItems[1] = player.getHeldItem(EnumHand.OFF_HAND); + packetListener.save(new SPacketEntityEquipment(player.getEntityId(), EntityEquipmentSlot.OFFHAND, playerItems[1])); } - if (playerItems[2] != mc.player.getItemStackFromSlot(EntityEquipmentSlot.FEET)) { - playerItems[2] = mc.player.getItemStackFromSlot(EntityEquipmentSlot.FEET); - packetListener.save(new SPacketEntityEquipment(e.player.getEntityId(), EntityEquipmentSlot.FEET, playerItems[2])); + if (playerItems[2] != player.getItemStackFromSlot(EntityEquipmentSlot.FEET)) { + playerItems[2] = player.getItemStackFromSlot(EntityEquipmentSlot.FEET); + packetListener.save(new SPacketEntityEquipment(player.getEntityId(), EntityEquipmentSlot.FEET, playerItems[2])); } - if (playerItems[3] != mc.player.getItemStackFromSlot(EntityEquipmentSlot.LEGS)) { - playerItems[3] = mc.player.getItemStackFromSlot(EntityEquipmentSlot.LEGS); - packetListener.save(new SPacketEntityEquipment(e.player.getEntityId(), EntityEquipmentSlot.LEGS, playerItems[3])); + if (playerItems[3] != player.getItemStackFromSlot(EntityEquipmentSlot.LEGS)) { + playerItems[3] = player.getItemStackFromSlot(EntityEquipmentSlot.LEGS); + packetListener.save(new SPacketEntityEquipment(player.getEntityId(), EntityEquipmentSlot.LEGS, playerItems[3])); } - if (playerItems[4] != mc.player.getItemStackFromSlot(EntityEquipmentSlot.CHEST)) { - playerItems[4] = mc.player.getItemStackFromSlot(EntityEquipmentSlot.CHEST); - packetListener.save(new SPacketEntityEquipment(e.player.getEntityId(), EntityEquipmentSlot.CHEST, playerItems[4])); + if (playerItems[4] != player.getItemStackFromSlot(EntityEquipmentSlot.CHEST)) { + playerItems[4] = player.getItemStackFromSlot(EntityEquipmentSlot.CHEST); + packetListener.save(new SPacketEntityEquipment(player.getEntityId(), EntityEquipmentSlot.CHEST, playerItems[4])); } - if (playerItems[5] != mc.player.getItemStackFromSlot(EntityEquipmentSlot.HEAD)) { - playerItems[5] = mc.player.getItemStackFromSlot(EntityEquipmentSlot.HEAD); - packetListener.save(new SPacketEntityEquipment(e.player.getEntityId(), EntityEquipmentSlot.HEAD, playerItems[5])); + if (playerItems[5] != player.getItemStackFromSlot(EntityEquipmentSlot.HEAD)) { + playerItems[5] = player.getItemStackFromSlot(EntityEquipmentSlot.HEAD); + packetListener.save(new SPacketEntityEquipment(player.getEntityId(), EntityEquipmentSlot.HEAD, playerItems[5])); } //#else //$$ if(playerItems[0] != mc.thePlayer.getHeldItem()) { //$$ playerItems[0] = mc.thePlayer.getHeldItem(); - //$$ S04PacketEntityEquipment pee = new S04PacketEntityEquipment(e.player.getEntityId(), 0, playerItems[0]); + //$$ S04PacketEntityEquipment pee = new S04PacketEntityEquipment(player.getEntityId(), 0, playerItems[0]); //$$ packetListener.save(pee); //$$ } //$$ //$$ if(playerItems[1] != mc.thePlayer.inventory.armorInventory[0]) { //$$ playerItems[1] = mc.thePlayer.inventory.armorInventory[0]; - //$$ S04PacketEntityEquipment pee = new S04PacketEntityEquipment(e.player.getEntityId(), 1, playerItems[1]); + //$$ S04PacketEntityEquipment pee = new S04PacketEntityEquipment(player.getEntityId(), 1, playerItems[1]); //$$ packetListener.save(pee); //$$ } //$$ //$$ if(playerItems[2] != mc.thePlayer.inventory.armorInventory[1]) { //$$ playerItems[2] = mc.thePlayer.inventory.armorInventory[1]; - //$$ S04PacketEntityEquipment pee = new S04PacketEntityEquipment(e.player.getEntityId(), 2, playerItems[2]); + //$$ S04PacketEntityEquipment pee = new S04PacketEntityEquipment(player.getEntityId(), 2, playerItems[2]); //$$ packetListener.save(pee); //$$ } //$$ //$$ if(playerItems[3] != mc.thePlayer.inventory.armorInventory[2]) { //$$ playerItems[3] = mc.thePlayer.inventory.armorInventory[2]; - //$$ S04PacketEntityEquipment pee = new S04PacketEntityEquipment(e.player.getEntityId(), 3, playerItems[3]); + //$$ S04PacketEntityEquipment pee = new S04PacketEntityEquipment(player.getEntityId(), 3, playerItems[3]); //$$ packetListener.save(pee); //$$ } //$$ //$$ if(playerItems[4] != mc.thePlayer.inventory.armorInventory[3]) { //$$ playerItems[4] = mc.thePlayer.inventory.armorInventory[3]; - //$$ S04PacketEntityEquipment pee = new S04PacketEntityEquipment(e.player.getEntityId(), 4, playerItems[4]); + //$$ S04PacketEntityEquipment pee = new S04PacketEntityEquipment(player.getEntityId(), 4, playerItems[4]); //$$ packetListener.save(pee); //$$ } //#endif //Leaving Ride - if((!mc.player.isPassenger() && lastRiding != -1) || - (mc.player.isPassenger() && lastRiding != getRidingEntity(mc.player).getEntityId())) { - if(!mc.player.isPassenger()) { + if((!player.isPassenger() && lastRiding != -1) || + (player.isPassenger() && lastRiding != getRiddenEntity(player).getEntityId())) { + if(!player.isPassenger()) { lastRiding = -1; } else { - lastRiding = getRidingEntity(mc.player).getEntityId(); + lastRiding = getRiddenEntity(player).getEntityId(); } packetListener.save(new SPacketEntityAttach( //#if MC<10904 //$$ 0, //#endif - e.player, getRidingEntity(e.player) + player, getRiddenEntity(player) )); } //Sleeping - if(!mc.player.isPlayerSleeping() && wasSleeping) { - packetListener.save(new SPacketAnimation(e.player, 2)); + if(!player.isPlayerSleeping() && wasSleeping) { + packetListener.save(new SPacketAnimation(player, 2)); wasSleeping = false; } //#if MC>=10904 // Active hand (e.g. eating, drinking, blocking) - if (mc.player.isHandActive() ^ wasHandActive || mc.player.getActiveHand() != lastActiveHand) { - wasHandActive = mc.player.isHandActive(); - lastActiveHand = mc.player.getActiveHand(); + if (player.isHandActive() ^ wasHandActive || player.getActiveHand() != lastActiveHand) { + wasHandActive = player.isHandActive(); + lastActiveHand = player.getActiveHand(); EntityDataManager dataManager = new EntityDataManager(null); int state = (wasHandActive ? 1 : 0) | (lastActiveHand == EnumHand.OFF_HAND ? 2 : 0); //#if MC>=11300 @@ -321,7 +342,7 @@ public class RecordingEventHandler { //#else //$$ dataManager.register(EntityLiving.HAND_STATES, (byte) state); //#endif - packetListener.save(new SPacketEntityMetadata(mc.player.getEntityId(), dataManager, true)); + packetListener.save(new SPacketEntityMetadata(player.getEntityId(), dataManager, true)); } //#endif @@ -330,6 +351,9 @@ public class RecordingEventHandler { } } + //#if MC>=11400 + //$$ // FIXME fabric + //#else @SubscribeEvent public void onPickupItem(ItemPickupEvent event) { try { @@ -357,7 +381,11 @@ public class RecordingEventHandler { e.printStackTrace(); } } + //#endif + //#if MC>=11400 + //$$ // FIXME fabric + //#else @SubscribeEvent public void onSleep(PlayerSleepInBedEvent event) { try { @@ -387,6 +415,7 @@ public class RecordingEventHandler { e.printStackTrace(); } } + //#endif /* FIXME event not (yet?) on 1.13 @SubscribeEvent @@ -432,9 +461,14 @@ public class RecordingEventHandler { } } + //#if MC>=11400 + //$$ { on(PreRenderCallback.EVENT, this::checkForGamePaused); } + //$$ private void checkForGamePaused() { + //#else @SubscribeEvent public void checkForGamePaused(TickEvent.RenderTickEvent event) { if (event.phase != TickEvent.Phase.START) return; + //#endif if (mc.isIntegratedServerRunning()) { IntegratedServer server = mc.getIntegratedServer(); if (server != null && ((IntegratedServerAccessor) server).isGamePaused()) { diff --git a/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java b/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java index 226f21ce..e1a6aed4 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java @@ -1,7 +1,6 @@ //#if MC>=11300 package com.replaymod.recording.mixin; -import com.google.common.util.concurrent.ListenableFuture; import com.replaymod.recording.packet.ResourcePackRecorder; import de.johni0702.minecraft.gui.utils.Consumer; import net.minecraft.client.resources.DownloadingPackFinder; @@ -12,6 +11,12 @@ import org.spongepowered.asm.mixin.injection.Redirect; import java.io.File; +//#if MC>=11400 +//$$ import java.util.concurrent.CompletableFuture; +//#else +import com.google.common.util.concurrent.ListenableFuture; +//#endif + @Mixin(DownloadingPackFinder.class) public abstract class MixinDownloadingPackFinder implements ResourcePackRecorder.IDownloadingPackFinder { private Consumer requestCallback; @@ -22,10 +27,22 @@ public abstract class MixinDownloadingPackFinder implements ResourcePackRecorder } @Shadow - public abstract ListenableFuture func_195741_a(File file); + public abstract + //#if MC>=11400 + //$$ CompletableFuture + //#else + ListenableFuture + //#endif + func_195741_a(File file); + //#if MC>=11400 + //$$ @Redirect(method = "download", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resource/ClientResourcePackCreator;loadServerPack(Ljava/io/File;)Ljava/util/concurrent/CompletableFuture;")) + //$$ private CompletableFuture + //#else @Redirect(method = "downloadResourcePack", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/DownloadingPackFinder;func_195741_a(Ljava/io/File;)Lcom/google/common/util/concurrent/ListenableFuture;")) - private ListenableFuture recordDownloadedPack(DownloadingPackFinder downloadingPackFinder, File file) { + private ListenableFuture + //#endif + recordDownloadedPack(DownloadingPackFinder downloadingPackFinder, File file) { if (requestCallback != null) { requestCallback.consume(file); requestCallback = null; diff --git a/src/main/java/com/replaymod/recording/mixin/MixinKeyboardListener.java b/src/main/java/com/replaymod/recording/mixin/MixinKeyboardListener.java deleted file mode 100644 index aafa81bd..00000000 --- a/src/main/java/com/replaymod/recording/mixin/MixinKeyboardListener.java +++ /dev/null @@ -1,47 +0,0 @@ -//#if MC>=11300 -package com.replaymod.recording.mixin; - -import com.replaymod.core.mixin.KeyBindingAccessor; -import com.replaymod.core.versions.MCVer; -import com.replaymod.extras.advancedscreenshots.AdvancedScreenshots; -import com.replaymod.replay.ReplayModReplay; -import net.minecraft.client.KeyboardListener; -import net.minecraft.client.settings.KeyBinding; -import net.minecraft.client.shader.Framebuffer; -import net.minecraft.util.ScreenShotHelper; -import net.minecraft.util.text.ITextComponent; -import net.minecraftforge.fml.hooks.BasicEventHooks; -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.Redirect; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.io.File; -import java.util.function.Consumer; - -@Mixin(KeyboardListener.class) -public abstract class MixinKeyboardListener { - @Inject(method = "onKeyEvent", at = @At("HEAD"), cancellable = true) - private void dispatchKeyEvent(long windowPointer, int key, int scanCode, int action, int modifiers, CallbackInfo ci) { - // FIXME this is a hack and will hopefully be done properly by forge before its release (MinecraftForge#5481) - // (and therefore before RM's release), this code will have to be removed then and handling code updated - KeyBinding keyBindChat = MCVer.getMinecraft().gameSettings.keyBindChat; - if (action == 1 && keyBindChat.matchesKey(key, scanCode)) { - KeyBindingAccessor acc = (KeyBindingAccessor) keyBindChat; - acc.setPressTime(acc.getPressTime() + 1); - BasicEventHooks.fireKeyInput(); - ci.cancel(); - } - } - - @Redirect(method = "onKeyEvent", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/ScreenShotHelper;saveScreenshot(Ljava/io/File;IILnet/minecraft/client/shader/Framebuffer;Ljava/util/function/Consumer;)V")) - private void takeScreenshot(File p_148260_0_, int p_148260_1_, int p_148260_2_, Framebuffer p_148260_3_, Consumer p_148260_4_) { - if (ReplayModReplay.instance.getReplayHandler() != null) { - AdvancedScreenshots.take(); - } else { - ScreenShotHelper.saveScreenshot(p_148260_0_, p_148260_1_, p_148260_2_, p_148260_3_, p_148260_4_); - } - } -} -//#endif diff --git a/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java b/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java index 4dba3a3f..19161397 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java @@ -13,7 +13,6 @@ import org.spongepowered.asm.mixin.injection.callback.LocalCapture; @Mixin(MouseHelper.class) public abstract class MixinMouseHelper { - //#ifdef DEV_ENV @Shadow private boolean mouseGrabbed; @@ -21,17 +20,25 @@ public abstract class MixinMouseHelper { private void noGrab(CallbackInfo ci) { // Used to be provided by Forge for 1.12.2 and below if (Boolean.valueOf(System.getProperty("fml.noGrab", "false"))) { - mouseGrabbed = true; + this.mouseGrabbed = true; ci.cancel(); } } - //#endif @Inject(method = "scrollCallback", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/entity/EntityPlayerSP;isSpectator()Z"), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true) - private void handleReplayModScroll(long _p0, double _p1, double _p2, CallbackInfo ci, double _l1, double yOffsetAccumulated) { + private void handleReplayModScroll( + long _p0, double _p1, double _p2, + CallbackInfo ci, + double _l1, + //#if MC>=11400 + //$$ float yOffsetAccumulated + //#else + double yOffsetAccumulated + //#endif + ) { if (ReplayModReplay.instance.getReplayHandler() != null) { InputReplayTimer.handleScroll((int) yOffsetAccumulated); ci.cancel(); diff --git a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerLoginClient.java b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerLoginClient.java index 0b713c8f..5494b93d 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerLoginClient.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerLoginClient.java @@ -12,7 +12,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; //#if MC>=11300 import com.replaymod.core.versions.MCVer; import com.replaymod.recording.handler.RecordingEventHandler.RecordingEventSender; +import net.minecraft.network.Packet; import net.minecraft.network.login.server.SPacketCustomPayloadLogin; +import net.minecraft.network.login.server.SPacketLoginSuccess; //#else //$$ import net.minecraftforge.fml.common.network.FMLNetworkEvent; //#endif @@ -29,12 +31,21 @@ public abstract class MixinNetHandlerLoginClient { //#if MC>=11300 @Inject(method = "func_209521_a", at=@At("HEAD")) - private void replayModRecording_initiateRecording(SPacketCustomPayloadLogin packet, CallbackInfo ci) { + private void earlyInitiateRecording(SPacketCustomPayloadLogin packet, CallbackInfo ci) { + initiateRecording(packet); + } + + @Inject(method = "handleLoginSuccess", at=@At("HEAD")) + private void lateInitiateRecording(SPacketLoginSuccess packet, CallbackInfo ci) { + initiateRecording(packet); + } + + private void initiateRecording(Packet packet) { RecordingEventSender eventSender = (RecordingEventSender) MCVer.getMinecraft().renderGlobal; if (eventSender.getRecordingEventHandler() != null) { return; // already recording } - ReplayModRecording.instance.initiateRecording(networkManager); + ReplayModRecording.instance.initiateRecording(this.networkManager); if (eventSender.getRecordingEventHandler() != null) { eventSender.getRecordingEventHandler().onPacket(packet); } @@ -48,17 +59,18 @@ public abstract class MixinNetHandlerLoginClient { //$$ @Inject(method = "handleLoginSuccess", at=@At("HEAD")) //$$ public void replayModRecording_initiateRecording(CallbackInfo cb) { //#if MC>=10800 - //$$ ReplayModRecording.instance.initiateRecording(networkManager); + //$$ ReplayModRecording.instance.initiateRecording(this.networkManager); //#else - //$$ ReplayModRecording.instance.initiateRecording(field_147393_d); + //$$ ReplayModRecording.instance.initiateRecording(this.field_147393_d); //#endif //$$ } //#endif - //#if MC>=11200 + // Race condition in Forge's networking (not sure if still present in 1.13) + //#if MC>=11200 && MC<11400 @Inject(method = "handleLoginSuccess", at=@At("RETURN")) public void replayModRecording_raceConditionWorkAround(CallbackInfo cb) { - networkManager.channel().config().setAutoRead(true); + ((NetworkManagerAccessor) this.networkManager).getChannel().config().setAutoRead(true); } //#endif } diff --git a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java index a58c4bae..dc453f0c 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java @@ -58,7 +58,7 @@ public abstract class MixinNetHandlerPlayClient { if (data.getProfile() == null || data.getProfile().getId() == null) continue; // Only add spawn packet for our own player and only if he isn't known yet if (data.getProfile().getId().equals(gameController.player.getGameProfile().getId()) - && !playerInfoMap.containsKey(data.getProfile().getId())) { + && !this.playerInfoMap.containsKey(data.getProfile().getId())) { handler.onPlayerJoin(); } } diff --git a/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java b/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java index 70d0b27e..0333c4d2 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java @@ -10,7 +10,6 @@ import net.minecraft.util.SoundCategory; import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; -import net.minecraft.world.storage.ISaveHandler; import net.minecraft.world.storage.WorldInfo; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -18,12 +17,20 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=11400 +//$$ import net.minecraft.world.chunk.ChunkManager; +//$$ import net.minecraft.world.dimension.Dimension; +//$$ import net.minecraft.world.dimension.DimensionType; +//$$ import java.util.function.BiFunction; +//#else +import net.minecraft.world.storage.ISaveHandler; //#if MC>=11300 import net.minecraft.world.dimension.Dimension; import net.minecraft.world.storage.WorldSavedDataStorage; //#else //$$ import net.minecraft.world.WorldProvider; //#endif +//#endif @Mixin(WorldClient.class) @@ -31,6 +38,11 @@ public abstract class MixinWorldClient extends World implements RecordingEventHa @Shadow private Minecraft mc; + //#if MC>=11400 + //$$ protected MixinWorldClient(LevelProperties levelProperties_1, DimensionType dimensionType_1, BiFunction biFunction_1, Profiler profiler_1, boolean boolean_1) { + //$$ super(levelProperties_1, dimensionType_1, biFunction_1, profiler_1, boolean_1); + //$$ } + //#else protected MixinWorldClient(ISaveHandler saveHandlerIn, //#if MC>=11300 WorldSavedDataStorage mapStorage, @@ -48,6 +60,7 @@ public abstract class MixinWorldClient extends World implements RecordingEventHa //#endif info, providerIn, profilerIn, client); } + //#endif private RecordingEventHandler replayModRecording_getRecordingEventHandler() { return ((RecordingEventHandler.RecordingEventSender) mc.renderGlobal).getRecordingEventHandler(); @@ -57,8 +70,13 @@ public abstract class MixinWorldClient extends World implements RecordingEventHa // but are instead played directly by the client. The server only sends these sounds to // other clients so we have to record them manually. // E.g. Block place sounds + //#if MC>=11400 + //$$ @Inject(method = "playSound(Lnet/minecraft/entity/player/PlayerEntity;DDDLnet/minecraft/sound/SoundEvent;Lnet/minecraft/sound/SoundCategory;FF)V", + //$$ at = @At("HEAD")) + //#else @Inject(method = "playSound(Lnet/minecraft/entity/player/EntityPlayer;DDDLnet/minecraft/util/SoundEvent;Lnet/minecraft/util/SoundCategory;FF)V", at = @At("HEAD")) + //#endif public void replayModRecording_recordClientSound(EntityPlayer player, double x, double y, double z, SoundEvent sound, SoundCategory category, float volume, float pitch, CallbackInfo ci) { if (player == mc.player) { @@ -70,9 +88,14 @@ public abstract class MixinWorldClient extends World implements RecordingEventHa } // Same goes for level events (also called effects). E.g. door open, block break, etc. + //#if MC>=11400 + //$$ @Inject(method = "playLevelEvent", at = @At("HEAD")) + //$$ private void playLevelEvent (PlayerEntity player, int type, BlockPos pos, int data, CallbackInfo ci) { + //#else // These are handled in the World class, so we override the method in WorldClient and add our special handling. @Override - public void playEvent(EntityPlayer player, int type, BlockPos pos, int data) { + public void playEvent (EntityPlayer player, int type, BlockPos pos, int data) { + //#endif if (player == mc.player) { // We caused this event, the server won't send it to us RecordingEventHandler handler = replayModRecording_getRecordingEventHandler(); @@ -80,7 +103,9 @@ public abstract class MixinWorldClient extends World implements RecordingEventHa handler.onClientEffect(type, pos, data); } } + //#if MC<11400 super.playEvent(player, type, pos, data); + //#endif } } //#endif diff --git a/src/main/java/com/replaymod/recording/mixin/NetworkManagerAccessor.java b/src/main/java/com/replaymod/recording/mixin/NetworkManagerAccessor.java new file mode 100644 index 00000000..9c47cfaf --- /dev/null +++ b/src/main/java/com/replaymod/recording/mixin/NetworkManagerAccessor.java @@ -0,0 +1,12 @@ +package com.replaymod.recording.mixin; + +import io.netty.channel.Channel; +import net.minecraft.network.NetworkManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(NetworkManager.class) +public interface NetworkManagerAccessor { + @Accessor + Channel getChannel(); +} diff --git a/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java b/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java index 4c491cc1..a3743b5f 100644 --- a/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java +++ b/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java @@ -5,13 +5,18 @@ import com.google.common.io.Files; import com.replaymod.replaystudio.replay.ReplayFile; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiYesNo; -import net.minecraft.client.gui.GuiYesNoCallback; import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.multiplayer.ServerList; -import net.minecraft.client.resources.I18n; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +//#if MC>=11400 +//$$ import net.minecraft.text.TranslatableTextComponent; +//#else +import net.minecraft.client.gui.GuiYesNoCallback; +import net.minecraft.client.resources.I18n; +//#endif + //#if MC>=11300 import de.johni0702.minecraft.gui.utils.Consumer; import net.minecraft.client.resources.DownloadingPackFinder; @@ -30,7 +35,11 @@ import net.minecraft.network.play.server.SPacketResourcePackSend; //#if MC>=10800 import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; +//#if MC>=11400 +//$$ import java.util.concurrent.CompletableFuture; +//#else import com.google.common.util.concurrent.ListenableFuture; +//#endif import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.network.NetworkManager; //#if MC<11300 @@ -123,17 +132,11 @@ public class ResourcePackRecorder { if (levelDir.isFile()) { netManager.sendPacket(makeStatusPacket(hash, Action.ACCEPTED)); - Futures.addCallback(setServerResourcePack(levelDir), new FutureCallback() { - @Override - public void onSuccess(Object result) { - recordResourcePack(levelDir, requestId); - netManager.sendPacket(makeStatusPacket(hash, Action.SUCCESSFULLY_LOADED)); - } - - @Override - public void onFailure(@Nonnull Throwable throwable) { - netManager.sendPacket(makeStatusPacket(hash, Action.FAILED_DOWNLOAD)); - } + addCallback(setServerResourcePack(levelDir), result -> { + recordResourcePack(levelDir, requestId); + netManager.sendPacket(makeStatusPacket(hash, Action.SUCCESSFULLY_LOADED)); + }, throwable -> { + netManager.sendPacket(makeStatusPacket(hash, Action.FAILED_DOWNLOAD)); }); } else { netManager.sendPacket(makeStatusPacket(hash, Action.FAILED_DOWNLOAD)); @@ -147,6 +150,9 @@ public class ResourcePackRecorder { netManager.sendPacket(makeStatusPacket(hash, Action.DECLINED)); } else { // Lambdas MUST NOT be used with methods that need re-obfuscation in FG prior to 2.2 (will result in AbstractMethodError) + //#if MC>=11400 + //$$ mc.execute(() -> mc.openScreen(new YesNoScreen(result -> { + //#else //noinspection Convert2Lambda mc.addScheduledTask(() -> mc.displayGuiScreen(new GuiYesNo(new GuiYesNoCallback() { @Override @@ -155,6 +161,7 @@ public class ResourcePackRecorder { //#else //$$ public void confirmClicked(boolean result, int id) { //#endif + //#endif if (serverData != null) { serverData.setResourceMode(result ? ServerData.ServerResourceMode.ENABLED : ServerData.ServerResourceMode.DISABLED); } @@ -168,7 +175,11 @@ public class ResourcePackRecorder { ServerList.saveSingleServer(serverData); mc.displayGuiScreen(null); } + //#if MC>=11400 + //$$ , new TranslatableTextComponent("multiplayer.texturePrompt.line1"), new TranslatableTextComponent("multiplayer.texturePrompt.line2")))); + //#else }, I18n.format("multiplayer.texturePrompt.line1"), I18n.format("multiplayer.texturePrompt.line2"), 0))); + //#endif } } @@ -176,21 +187,19 @@ public class ResourcePackRecorder { } private void downloadResourcePackFuture(int requestId, String url, final String hash) { - Futures.addCallback(downloadResourcePack(requestId, url, hash), new FutureCallback() { - @Override - public void onSuccess(Object result) { - mc.getConnection().sendPacket(makeStatusPacket(hash, Action.SUCCESSFULLY_LOADED)); - } - - @Override - public void onFailure(@Nonnull Throwable throwable) { - mc.getConnection().sendPacket(makeStatusPacket(hash, Action.FAILED_DOWNLOAD)); - } - }); + addCallback(downloadResourcePack(requestId, url, hash), + result -> mc.getConnection().sendPacket(makeStatusPacket(hash, Action.SUCCESSFULLY_LOADED)), + throwable -> mc.getConnection().sendPacket(makeStatusPacket(hash, Action.FAILED_DOWNLOAD))); } //#if MC>=11300 - private ListenableFuture downloadResourcePack(final int requestId, String url, String hash) { + private + //#if MC>=11400 + //$$ CompletableFuture + //#else + ListenableFuture + //#endif + downloadResourcePack(final int requestId, String url, String hash) { DownloadingPackFinder packFinder = mc.getPackFinder(); ((IDownloadingPackFinder) packFinder).setRequestCallback(file -> recordResourcePack(file, requestId)); return packFinder.downloadResourcePack(url, hash); diff --git a/src/main/java/com/replaymod/render/ReplayModRender.java b/src/main/java/com/replaymod/render/ReplayModRender.java index b6c74938..49baf1e8 100644 --- a/src/main/java/com/replaymod/render/ReplayModRender.java +++ b/src/main/java/com/replaymod/render/ReplayModRender.java @@ -3,10 +3,9 @@ package com.replaymod.render; import com.replaymod.core.Module; import com.replaymod.core.ReplayMod; import com.replaymod.render.utils.RenderJob; -import com.replaymod.replay.events.ReplayCloseEvent; +import com.replaymod.replay.events.ReplayClosedCallback; import net.minecraft.crash.CrashReport; import net.minecraft.crash.ReportedException; -import net.minecraftforge.eventbus.api.SubscribeEvent; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -41,12 +40,7 @@ public class ReplayModRender implements Module { @Override public void initClient() { - FML_BUS.register(this); - } - - @SubscribeEvent - public void onReplayClose(ReplayCloseEvent.Post event) { - renderQueue.clear(); + ReplayClosedCallback.EVENT.register(replayHandler -> renderQueue.clear()); } public File getVideoFolder() { diff --git a/src/main/java/com/replaymod/render/blend/BlendMeshBuilder.java b/src/main/java/com/replaymod/render/blend/BlendMeshBuilder.java index 7b53874d..0e5dda04 100644 --- a/src/main/java/com/replaymod/render/blend/BlendMeshBuilder.java +++ b/src/main/java/com/replaymod/render/blend/BlendMeshBuilder.java @@ -53,7 +53,7 @@ public class BlendMeshBuilder this.wellBehaved = wellBehaved; } - public void setOffset(Vector3f offset) { + public void setReverseOffset(Vector3f offset) { this.offset = offset; } diff --git a/src/main/java/com/replaymod/render/blend/Util.java b/src/main/java/com/replaymod/render/blend/Util.java index 4f10c55c..55cc6b75 100644 --- a/src/main/java/com/replaymod/render/blend/Util.java +++ b/src/main/java/com/replaymod/render/blend/Util.java @@ -4,6 +4,7 @@ import de.johni0702.minecraft.gui.utils.lwjgl.vector.Matrix3f; import de.johni0702.minecraft.gui.utils.lwjgl.vector.Matrix4f; import de.johni0702.minecraft.gui.utils.lwjgl.vector.Quaternion; import de.johni0702.minecraft.gui.utils.lwjgl.vector.Vector3f; +import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.GLAllocation; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; @@ -16,6 +17,10 @@ import org.lwjgl.opengl.GL11; import java.io.IOException; import java.nio.FloatBuffer; +//#if MC>=11400 +//$$ import net.minecraft.util.math.Vec3d; +//#endif + public class Util { public static BlenderFactory factory() { return BlendState.getState().getFactory(); @@ -131,6 +136,22 @@ public class Util { q.w = -q.w; } + //#if MC>=10800 + public static Vector3f getCameraPos() { + Minecraft mc = Minecraft.getInstance(); + //#if MC>=11400 + //$$ Vec3d pos = mc.getEntityRenderManager().camera.getPos(); + //$$ return new Vector3f((float) pos.x, (float) pos.y, (float) pos.z); + //#else + return new Vector3f( + (float) -mc.getRenderManager().viewerPosX, + (float) -mc.getRenderManager().viewerPosY, + (float) -mc.getRenderManager().viewerPosZ + ); + //#endif + } + //#endif + public static Vector3f rotate(Quaternion rot, Vector3f vec, Vector3f dest) { if (dest == null) dest = new Vector3f(); Quaternion vecQ = new Quaternion(vec.x, vec.y, vec.z, 0); diff --git a/src/main/java/com/replaymod/render/blend/exporters/EntityExporter.java b/src/main/java/com/replaymod/render/blend/exporters/EntityExporter.java index 4948bba9..586359e2 100644 --- a/src/main/java/com/replaymod/render/blend/exporters/EntityExporter.java +++ b/src/main/java/com/replaymod/render/blend/exporters/EntityExporter.java @@ -11,10 +11,10 @@ import net.minecraft.client.Minecraft; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import java.io.IOException; import java.util.IdentityHashMap; import java.util.Map; +import static com.replaymod.render.blend.Util.getCameraPos; import static com.replaymod.render.blend.Util.getGlModelViewMatrix; public class EntityExporter implements Exporter { @@ -42,11 +42,7 @@ public class EntityExporter implements Exporter { // Entities are rendered relative to the viewer location. // We however want our Entities object to not move when the viewer does, // so we position it at 0/0/0 and instead have the entities themselves move more - Matrix4f.translate(new Vector3f( - (float) -mc.getRenderManager().viewerPosX, - (float) -mc.getRenderManager().viewerPosY, - (float) -mc.getRenderManager().viewerPosZ - ), modelView, modelView); + Matrix4f.translate(getCameraPos(), modelView, modelView); renderState.push(entitiesObject, modelView); } diff --git a/src/main/java/com/replaymod/render/blend/exporters/ItemExporter.java b/src/main/java/com/replaymod/render/blend/exporters/ItemExporter.java index 91a08b5f..60637cf6 100644 --- a/src/main/java/com/replaymod/render/blend/exporters/ItemExporter.java +++ b/src/main/java/com/replaymod/render/blend/exporters/ItemExporter.java @@ -8,9 +8,14 @@ import com.replaymod.render.blend.data.DObject; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumFacing; -import net.minecraftforge.client.model.pipeline.LightUtil; import org.lwjgl.opengl.GL11; +//#if MC>=11400 +//$$ import net.minecraft.util.math.Vec3i; +//#else +import net.minecraftforge.client.model.pipeline.LightUtil; +//#endif + //#if MC>=11300 import com.replaymod.render.blend.mixin.ItemRendererAccessor; import net.minecraft.client.renderer.model.BakedQuad; @@ -122,7 +127,14 @@ public class ItemExporter implements Exporter { //#endif //#endif : 0xffffffff; + //#if MC>=11400 + //$$ buffer.putVertexData(quad.getVertexData()); + //$$ buffer.setQuadColor(color); + //$$ Vec3i vec3i_1 = quad.getFace().getVector(); + //$$ buffer.postNormal(vec3i_1.getX(), vec3i_1.getY(), vec3i_1.getZ()); + //#else LightUtil.renderQuadColor(buffer, quad, color); + //#endif } } diff --git a/src/main/java/com/replaymod/render/blend/exporters/ParticlesExporter.java b/src/main/java/com/replaymod/render/blend/exporters/ParticlesExporter.java index 70033122..3f9dd1c0 100644 --- a/src/main/java/com/replaymod/render/blend/exporters/ParticlesExporter.java +++ b/src/main/java/com/replaymod/render/blend/exporters/ParticlesExporter.java @@ -12,6 +12,10 @@ import de.johni0702.minecraft.gui.utils.lwjgl.vector.Matrix4f; import de.johni0702.minecraft.gui.utils.lwjgl.vector.Vector3f; import net.minecraft.client.Minecraft; +//#if MC>=11400 +//$$ import net.minecraft.util.math.Vec3d; +//#endif + //#if MC>=10904 import net.minecraft.client.particle.Particle; //#else @@ -27,6 +31,7 @@ import java.io.IOException; import java.util.IdentityHashMap; import java.util.Map; +import static com.replaymod.render.blend.Util.getCameraPos; import static com.replaymod.render.blend.Util.getGlModelViewMatrix; public class ParticlesExporter implements Exporter { @@ -74,11 +79,7 @@ public class ParticlesExporter implements Exporter { // Particles are rendered relative to the viewer location. // We however want our Particles object to not move when the viewer does, // so we position it at 0/0/0 and instead have the particles themselves move more - Matrix4f.translate(new Vector3f( - (float) -mc.getRenderManager().viewerPosX, - (float) -mc.getRenderManager().viewerPosY, - (float) -mc.getRenderManager().viewerPosZ - ), modelView, modelView); + Matrix4f.translate(getCameraPos(), modelView, modelView); renderState.push(lit ? litParticlesObject : particlesObject, modelView); } @@ -158,7 +159,7 @@ public class ParticlesExporter implements Exporter { //#endif DMesh mesh = new DMesh(); BlendMeshBuilder builder = new BlendMeshBuilder(mesh); - builder.setOffset(offset); + builder.setReverseOffset(offset); builder.setWellBehaved(true); //#if MC>=10809 builder.begin(7, DefaultVertexFormats.PARTICLE_POSITION_TEX_COLOR_LMAP); @@ -166,7 +167,13 @@ public class ParticlesExporter implements Exporter { //$$ builder.startDrawingQuads(); //#endif //#if MC>=10809 - particle.renderParticle(builder, MCVer.getMinecraft().getRenderViewEntity(), 0, 1, 1, 0, 0, 0); + particle.renderParticle(builder, + //#if MC>=11400 + //$$ MCVer.getMinecraft().gameRenderer.getCamera(), + //#else + MCVer.getMinecraft().getRenderViewEntity(), + //#endif + 0, 1, 1, 0, 0, 0); //#else //$$ particle.func_180434_a(builder, Minecraft.getMinecraft().getRenderViewEntity(), 0, 1, 1, 0, 0, 0); //#endif diff --git a/src/main/java/com/replaymod/render/blend/exporters/TileEntityExporter.java b/src/main/java/com/replaymod/render/blend/exporters/TileEntityExporter.java index d80cdf9f..ee3a1b83 100644 --- a/src/main/java/com/replaymod/render/blend/exporters/TileEntityExporter.java +++ b/src/main/java/com/replaymod/render/blend/exporters/TileEntityExporter.java @@ -51,9 +51,15 @@ public class TileEntityExporter implements Exporter { // We however want our TileEntities object to not move when the viewer does, // so we position it at 0/0/0 and instead have the tile entities themselves move more Matrix4f.translate(new Vector3f( + //#if MC>=11400 + //$$ (float) -mc.getEntityRenderManager().camera.getPos().x, + //$$ (float) -mc.getEntityRenderManager().camera.getPos().y, + //$$ (float) -mc.getEntityRenderManager().camera.getPos().z + //#else (float) -mc.getRenderManager().viewerPosX, (float) -mc.getRenderManager().viewerPosY, (float) -mc.getRenderManager().viewerPosZ + //#endif ), modelView, modelView); renderState.push(tileEntitiesObject, modelView); } diff --git a/src/main/java/com/replaymod/render/blend/mixin/ContainerLocalRenderInformationAccessor.java b/src/main/java/com/replaymod/render/blend/mixin/ContainerLocalRenderInformationAccessor.java index 7ee19793..38f28282 100644 --- a/src/main/java/com/replaymod/render/blend/mixin/ContainerLocalRenderInformationAccessor.java +++ b/src/main/java/com/replaymod/render/blend/mixin/ContainerLocalRenderInformationAccessor.java @@ -5,13 +5,21 @@ import net.minecraft.client.renderer.chunk.RenderChunk; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; +//#if MC>=11400 +//$$ @Mixin(targets = "net/minecraft/client/render/WorldRenderer$ChunkInfo") +//#else //#if MC>=11300 @Mixin(targets = "net/minecraft/client/renderer/WorldRenderer$ContainerLocalRenderInformation") //#else //$$ @Mixin(targets = "net/minecraft/client/renderer/RenderGlobal$ContainerLocalRenderInformation") //#endif +//#endif public interface ContainerLocalRenderInformationAccessor { + //#if MC>=11400 + //$$ @Accessor("renderer") + //#else @Accessor + //#endif RenderChunk getRenderChunk(); } //#endif diff --git a/src/main/java/com/replaymod/render/blend/mixin/MixinEffectRenderer.java b/src/main/java/com/replaymod/render/blend/mixin/MixinEffectRenderer.java index 2a78b54c..41dcb82b 100644 --- a/src/main/java/com/replaymod/render/blend/mixin/MixinEffectRenderer.java +++ b/src/main/java/com/replaymod/render/blend/mixin/MixinEffectRenderer.java @@ -3,12 +3,17 @@ package com.replaymod.render.blend.mixin; import com.replaymod.render.blend.BlendState; import com.replaymod.render.blend.exporters.ParticlesExporter; -import net.minecraft.entity.Entity; 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; +//#if MC>=11400 +//$$ import net.minecraft.client.render.Camera; +//#else +import net.minecraft.entity.Entity; +//#endif + //#if MC>=10904 import net.minecraft.client.particle.ParticleManager; //#else @@ -23,7 +28,15 @@ import net.minecraft.client.particle.ParticleManager; public abstract class MixinEffectRenderer { @Inject(method = "renderParticles", at = @At("HEAD")) - public void preRender(Entity view, float renderPartialTicks, CallbackInfo ci) { + private void preRender( + //#if MC>=11400 + //$$ Camera view, + //#else + Entity view, + //#endif + float renderPartialTicks, + CallbackInfo ci + ) { BlendState blendState = BlendState.getState(); if (blendState != null) { blendState.get(ParticlesExporter.class).preParticlesRender(false); @@ -31,13 +44,22 @@ public abstract class MixinEffectRenderer { } @Inject(method = "renderParticles", at = @At("RETURN")) - public void postRender(Entity view, float renderPartialTicks, CallbackInfo ci) { + private void postRender( + //#if MC>=11400 + //$$ Camera view, + //#else + Entity view, + //#endif + float renderPartialTicks, + CallbackInfo ci + ) { BlendState blendState = BlendState.getState(); if (blendState != null) { blendState.get(ParticlesExporter.class).postParticlesRender(); } } + //#if MC<11400 @Inject(method = "renderLitParticles", at = @At("HEAD")) public void preLitRender(Entity view, float renderPartialTicks, CallbackInfo ci) { BlendState blendState = BlendState.getState(); @@ -53,5 +75,6 @@ public abstract class MixinEffectRenderer { blendState.get(ParticlesExporter.class).postParticlesRender(); } } + //#endif } //#endif diff --git a/src/main/java/com/replaymod/render/blend/mixin/MixinRenderGlobal.java b/src/main/java/com/replaymod/render/blend/mixin/MixinRenderGlobal.java index bd1384b2..925ee531 100644 --- a/src/main/java/com/replaymod/render/blend/mixin/MixinRenderGlobal.java +++ b/src/main/java/com/replaymod/render/blend/mixin/MixinRenderGlobal.java @@ -5,12 +5,17 @@ import com.replaymod.render.blend.BlendState; import com.replaymod.render.blend.exporters.EntityExporter; import com.replaymod.render.blend.exporters.TileEntityExporter; import net.minecraft.client.renderer.culling.ICamera; -import net.minecraft.entity.Entity; 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; +//#if MC>=11400 +//$$ import net.minecraft.client.render.Camera; +//#else +import net.minecraft.entity.Entity; +//#endif + //#if MC>=11300 import net.minecraft.client.renderer.WorldRenderer; //#else @@ -33,7 +38,13 @@ public abstract class MixinRenderGlobal { //#else //$$ target = "Lnet/minecraft/client/renderer/entity/RenderManager;renderEntitySimple(Lnet/minecraft/entity/Entity;F)Z")) //#endif - public void preEntityRender(Entity view, ICamera camera, float renderPartialTicks, CallbackInfo ci) { + private void preEntityRender( + //#if MC>=11400 + //$$ Camera view, + //#else + Entity view, + //#endif + ICamera camera, float renderPartialTicks, CallbackInfo ci) { BlendState blendState = BlendState.getState(); if (blendState != null) { blendState.get(EntityExporter.class).preEntitiesRender(); @@ -48,7 +59,13 @@ public abstract class MixinRenderGlobal { //$$ target = "Lnet/minecraft/client/renderer/entity/RenderManager;renderEntitySimple(Lnet/minecraft/entity/Entity;F)Z", //#endif shift = At.Shift.AFTER)) - public void postEntityRender(Entity view, ICamera camera, float renderPartialTicks, CallbackInfo ci) { + private void postEntityRender( + //#if MC>=11400 + //$$ Camera view, + //#else + Entity view, + //#endif + ICamera camera, float renderPartialTicks, CallbackInfo ci) { BlendState blendState = BlendState.getState(); if (blendState != null) { blendState.get(EntityExporter.class).postEntitiesRender(); @@ -63,7 +80,13 @@ public abstract class MixinRenderGlobal { //$$ target = "Lnet/minecraft/client/renderer/tileentity/TileEntityRendererDispatcher;renderTileEntity(Lnet/minecraft/tileentity/TileEntity;FI)V" //#endif )) - public void preTileEntityRender(Entity view, ICamera camera, float renderPartialTicks, CallbackInfo ci) { + private void preTileEntityRender( + //#if MC>=11400 + //$$ Camera view, + //#else + Entity view, + //#endif + ICamera camera, float renderPartialTicks, CallbackInfo ci) { BlendState blendState = BlendState.getState(); if (blendState != null) { blendState.get(TileEntityExporter.class).preTileEntitiesRender(); @@ -79,7 +102,13 @@ public abstract class MixinRenderGlobal { //#endif shift = At.Shift.AFTER )) - public void postTileEntityRender(Entity view, ICamera camera, float renderPartialTicks, CallbackInfo ci) { + private void postTileEntityRender( + //#if MC>=11400 + //$$ Camera view, + //#else + Entity view, + //#endif + ICamera camera, float renderPartialTicks, CallbackInfo ci) { BlendState blendState = BlendState.getState(); if (blendState != null) { blendState.get(TileEntityExporter.class).postTileEntitiesRender(); diff --git a/src/main/java/com/replaymod/render/blend/mixin/MixinRenderItem.java b/src/main/java/com/replaymod/render/blend/mixin/MixinRenderItem.java index 016695b3..494348d9 100644 --- a/src/main/java/com/replaymod/render/blend/mixin/MixinRenderItem.java +++ b/src/main/java/com/replaymod/render/blend/mixin/MixinRenderItem.java @@ -28,6 +28,9 @@ import net.minecraft.client.renderer.model.IBakedModel; //$$ @Mixin(RenderItem.class) //#endif public abstract class MixinRenderItem { + //#if MC>=11400 + //$$ @Inject(method = "renderItemModel", at = @At("HEAD")) + //#else //#if MC>=11300 @Inject(method = "renderModel(Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/item/ItemStack;)V", at = @At("HEAD")) @@ -40,6 +43,7 @@ public abstract class MixinRenderItem { //$$ at = @At("HEAD")) //#endif //#endif + //#endif private void onRenderModel(IBakedModel model, ItemStack stack, CallbackInfo ci) { BlendState blendState = BlendState.getState(); if (blendState != null) { diff --git a/src/main/java/com/replaymod/render/blend/mixin/MixinRenderManager.java b/src/main/java/com/replaymod/render/blend/mixin/MixinRenderManager.java index 17931095..ec5ff5ba 100644 --- a/src/main/java/com/replaymod/render/blend/mixin/MixinRenderManager.java +++ b/src/main/java/com/replaymod/render/blend/mixin/MixinRenderManager.java @@ -19,11 +19,15 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public abstract class MixinRenderManager { @Inject( + //#if MC>=11400 + //$$ method = "render(Lnet/minecraft/entity/Entity;DDDFFZ)V", + //#else //#if MC>=11300 method = "renderEntity", //#else //$$ method = "doRenderEntity", //#endif + //#endif at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/Render;doRender(Lnet/minecraft/entity/Entity;DDDFF)V")) public void preRender(Entity entity, double x, double y, double z, float yaw, float renderPartialTicks, boolean box, @@ -39,11 +43,15 @@ public abstract class MixinRenderManager { } @Inject( + //#if MC>=11400 + //$$ method = "render(Lnet/minecraft/entity/Entity;DDDFFZ)V", + //#else //#if MC>=11300 method = "renderEntity", //#else //$$ method = "doRenderEntity", //#endif + //#endif at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/Render;doRender(Lnet/minecraft/entity/Entity;DDDFF)V", shift = At.Shift.AFTER)) diff --git a/src/main/java/com/replaymod/render/blend/mixin/MixinTileEntityRendererDispatcher.java b/src/main/java/com/replaymod/render/blend/mixin/MixinTileEntityRendererDispatcher.java index b0688fa0..f88593d8 100644 --- a/src/main/java/com/replaymod/render/blend/mixin/MixinTileEntityRendererDispatcher.java +++ b/src/main/java/com/replaymod/render/blend/mixin/MixinTileEntityRendererDispatcher.java @@ -14,8 +14,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public abstract class MixinTileEntityRendererDispatcher { //#if MC>=11300 + //#if MC>=11400 + //$$ @Inject(method = "renderEntity(Lnet/minecraft/block/entity/BlockEntity;DDDFIZ)V", + //$$ at = @At("HEAD")) + //#else @Inject(method = "render(Lnet/minecraft/tileentity/TileEntity;DDDFIZ)V", at = @At("HEAD")) + //#endif public void preRender(TileEntity tileEntity, double x, double y, double z, float renderPartialTicks, int destroyStage, boolean hasNoBlock, CallbackInfo ci) { float alpha = 1; //#else @@ -37,8 +42,13 @@ public abstract class MixinTileEntityRendererDispatcher { } //#if MC>=11300 + //#if MC>=11400 + //$$ @Inject(method = "renderEntity(Lnet/minecraft/block/entity/BlockEntity;DDDFIZ)V", + //$$ at = @At("RETURN")) + //#else @Inject(method = "render(Lnet/minecraft/tileentity/TileEntity;DDDFIZ)V", at = @At("RETURN")) + //#endif public void postRender(TileEntity tileEntity, double x, double y, double z, float renderPartialTicks, int destroyStage, boolean hasNoBlock, CallbackInfo ci) { //#else //#if MC>=11200 diff --git a/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java index 5f92bf7f..5f51c221 100644 --- a/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java @@ -1,6 +1,6 @@ package com.replaymod.render.capturer; -import com.replaymod.core.utils.EventRegistrations; +import de.johni0702.minecraft.gui.utils.EventRegistrations; import com.replaymod.render.RenderSettings; import com.replaymod.render.frame.CubicOpenGlFrame; import com.replaymod.render.frame.ODSOpenGlFrame; @@ -148,7 +148,11 @@ public class ODSFrameCapturer implements FrameCapturer { pushMatrix(); frameBuffer().bindFramebuffer(true); - clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT + //#if MC>=11400 + //$$ , false + //#endif + ); enableTexture2D(); directionVariable.set(captureData.ordinal()); diff --git a/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java index a2a9b968..a570d829 100644 --- a/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java @@ -89,7 +89,11 @@ public abstract class OpenGlFrameCapturer=11400 + //$$ , false + //#endif + ); enableTexture2D(); worldRenderer.renderWorld(partialTicks, captureData); @@ -114,7 +118,11 @@ public abstract class OpenGlFrameCapturer=11300 Framebuffer fb = mc.getFramebuffer(); if (fb.framebufferWidth != width || fb.framebufferHeight != height) { - fb.createFramebuffer(width, height); + fb.createFramebuffer(width, height + //#if MC>=11400 + //$$ , false + //#endif + ); } //noinspection ConstantConditions MainWindowAccessor mainWindow = (MainWindowAccessor) (Object) mc.mainWindow; diff --git a/src/main/java/com/replaymod/render/events/ReplayRenderCallback.java b/src/main/java/com/replaymod/render/events/ReplayRenderCallback.java new file mode 100644 index 00000000..6230d365 --- /dev/null +++ b/src/main/java/com/replaymod/render/events/ReplayRenderCallback.java @@ -0,0 +1,29 @@ +package com.replaymod.render.events; + +import com.replaymod.render.rendering.VideoRenderer; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +public interface ReplayRenderCallback { + interface Pre { + Event
 EVENT = EventFactory.createArrayBacked(Pre.class,
+                (listeners) -> (renderer) -> {
+                    for (Pre listener : listeners) {
+                        listener.beforeRendering(renderer);
+                    }
+                });
+
+        void beforeRendering(VideoRenderer renderer);
+    }
+
+    interface Post {
+        Event EVENT = EventFactory.createArrayBacked(Post.class,
+                (listeners) -> (renderer) -> {
+                    for (Post listener : listeners) {
+                        listener.afterRendering(renderer);
+                    }
+                });
+
+        void afterRendering(VideoRenderer renderer);
+    }
+}
diff --git a/src/main/java/com/replaymod/render/events/ReplayRenderEvent.java b/src/main/java/com/replaymod/render/events/ReplayRenderEvent.java
deleted file mode 100644
index 4bb94b80..00000000
--- a/src/main/java/com/replaymod/render/events/ReplayRenderEvent.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.replaymod.render.events;
-
-import com.replaymod.render.rendering.VideoRenderer;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-//#if MC>=10800
-//#if MC>=11300
-import net.minecraftforge.eventbus.api.Event;
-//#else
-//$$ import net.minecraftforge.fml.common.eventhandler.Event;
-//#endif
-//#else
-//$$ import cpw.mods.fml.common.eventhandler.Event;
-//#endif
-
-@RequiredArgsConstructor
-public abstract class ReplayRenderEvent extends Event {
-    @Getter
-    private final VideoRenderer videoRenderer;
-
-    public static class Pre extends ReplayRenderEvent {
-        public Pre(VideoRenderer videoRenderer) {
-            super(videoRenderer);
-        }
-    }
-
-    public static class Post extends ReplayRenderEvent {
-        public Post(VideoRenderer videoRenderer) {
-            super(videoRenderer);
-        }
-    }
-}
diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java
index ef3cf4e3..f559746c 100644
--- a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java
+++ b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java
@@ -37,6 +37,10 @@ import java.util.List;
 
 import static com.replaymod.render.ReplayModRender.LOGGER;
 
+//#if MC>=11400
+//$$ import net.minecraft.text.TranslatableTextComponent;
+//#endif
+
 public class GuiRenderQueue extends AbstractGuiPopup {
     private final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.renderqueue.title").setColor(Colors.BLACK);
     private final GuiVerticalList list = new GuiVerticalList().setDrawShadow(true).setDrawSlider(true);
@@ -210,8 +214,16 @@ public class GuiRenderQueue extends AbstractGuiPopup {
                 videoRenderer.renderVideo();
             } catch (VideoWriter.NoFFmpegException e) {
                 LOGGER.error("Rendering video:", e);
-                GuiErrorScreen errorScreen = new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"),
-                        I18n.format("replaymod.gui.rendering.error.message"));
+                GuiErrorScreen errorScreen = new GuiErrorScreen(
+                        //#if MC>=11400
+                        //$$ () -> {},
+                        //$$ new TranslatableTextComponent("replaymod.gui.rendering.error.title"),
+                        //$$ new TranslatableTextComponent("replaymod.gui.rendering.error.message")
+                        //#else
+                        I18n.format("replaymod.gui.rendering.error.title"),
+                        I18n.format("replaymod.gui.rendering.error.message")
+                        //#endif
+                );
                 getMinecraft().displayGuiScreen(errorScreen);
                 return;
             } catch (VideoWriter.FFmpegStartupException e) {
diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java
index 15d23d9c..98e38136 100644
--- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java
+++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java
@@ -51,6 +51,10 @@ import java.util.Map;
 import static com.replaymod.core.utils.Utils.error;
 import static com.replaymod.render.ReplayModRender.LOGGER;
 
+//#if MC>=11400
+//$$ import net.minecraft.text.TranslatableTextComponent;
+//#endif
+
 public class GuiRenderSettings extends GuiScreen implements Closeable {
     public final GuiPanel contentPanel = new GuiPanel(this).setBackgroundColor(Colors.DARK_TRANSPARENT);
     public final GuiVerticalList settingsList = new GuiVerticalList(contentPanel).setDrawSlider(true);
@@ -233,8 +237,16 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
                 videoRenderer.renderVideo();
             } catch (VideoWriter.NoFFmpegException e) {
                 LOGGER.error("Rendering video:", e);
-                GuiErrorScreen errorScreen = new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"),
-                        I18n.format("replaymod.gui.rendering.error.message"));
+                GuiErrorScreen errorScreen = new GuiErrorScreen(
+                        //#if MC>=11400
+                        //$$ () -> {},
+                        //$$ new TranslatableTextComponent("replaymod.gui.rendering.error.title"),
+                        //$$ new TranslatableTextComponent("replaymod.gui.rendering.error.message")
+                        //#else
+                        I18n.format("replaymod.gui.rendering.error.title"),
+                        I18n.format("replaymod.gui.rendering.error.message")
+                        //#endif
+                );
                 getMinecraft().displayGuiScreen(errorScreen);
             } catch (VideoWriter.FFmpegStartupException e) {
                 GuiExportFailed.tryToRecover(e, newSettings -> {
diff --git a/src/main/java/com/replaymod/render/hooks/ChunkLoadingRenderGlobal.java b/src/main/java/com/replaymod/render/hooks/ChunkLoadingRenderGlobal.java
index 72d65326..a0d921d5 100644
--- a/src/main/java/com/replaymod/render/hooks/ChunkLoadingRenderGlobal.java
+++ b/src/main/java/com/replaymod/render/hooks/ChunkLoadingRenderGlobal.java
@@ -75,7 +75,14 @@ public class ChunkLoadingRenderGlobal {
         workerJailingQueue = new JailingQueue<>(queueChunkUpdates);
         renderDispatcherAcc.setQueueChunkUpdates(workerJailingQueue);
         //#if MC>=10904
-        ChunkRenderTask element = new ChunkRenderTask(null, null, 0);
+        ChunkRenderTask element = new ChunkRenderTask(
+                null,
+                null,
+                0
+                //#if MC>=11400
+                //$$ , null
+                //#endif
+        );
         //#else
         //$$ ChunkCompileTaskGenerator element = new ChunkCompileTaskGenerator(null, null);
         //#endif
diff --git a/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java
index fe6ef9fa..a1830fd5 100644
--- a/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java
+++ b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java
@@ -8,6 +8,7 @@ import com.replaymod.render.capturer.WorldRenderer;
 import lombok.Getter;
 import net.minecraft.client.Minecraft;
 
+//#if MC<11400
 //#if MC>=10800
 //#if MC>=11300
 import net.minecraftforge.fml.hooks.BasicEventHooks;
@@ -19,6 +20,7 @@ import net.minecraftforge.fml.hooks.BasicEventHooks;
 //$$ import com.replaymod.core.versions.MCVer.GlStateManager;
 //$$ import cpw.mods.fml.common.FMLCommonHandler;
 //#endif
+//#endif
 
 import java.io.IOException;
 
@@ -49,11 +51,13 @@ public class EntityRendererHandler implements WorldRenderer {
     }
 
     public void renderWorld(float partialTicks, long finishTimeNano) {
+        //#if MC<11400
         //#if MC>=11300
         BasicEventHooks.onRenderTickStart(partialTicks);
         //#else
         //$$ FMLCommonHandler.instance().onRenderTickStart(partialTicks);
         //#endif
+        //#endif
 
         //#if MC>=11300
         mc.entityRenderer.renderWorld(partialTicks, finishTimeNano);
@@ -71,11 +75,13 @@ public class EntityRendererHandler implements WorldRenderer {
         //#endif
         //#endif
 
+        //#if MC<11400
         //#if MC>=11300
         BasicEventHooks.onRenderTickEnd(partialTicks);
         //#else
         //$$ FMLCommonHandler.instance().onRenderTickEnd(partialTicks);
         //#endif
+        //#endif
     }
 
     @Override
diff --git a/src/main/java/com/replaymod/render/mixin/MixinCamera.java b/src/main/java/com/replaymod/render/mixin/MixinCamera.java
new file mode 100644
index 00000000..b13bc31b
--- /dev/null
+++ b/src/main/java/com/replaymod/render/mixin/MixinCamera.java
@@ -0,0 +1,168 @@
+package com.replaymod.render.mixin;
+
+import com.replaymod.render.RenderSettings;
+import com.replaymod.render.capturer.CubicOpenGlFrameCapturer;
+import com.replaymod.render.hooks.EntityRendererHandler;
+import com.replaymod.replay.camera.CameraEntity;
+import net.minecraft.entity.Entity;
+import org.lwjgl.opengl.GL11;
+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 static com.replaymod.core.versions.MCVer.*;
+
+//#if MC>=11400
+//$$ import net.minecraft.client.render.Camera;
+//$$ import net.minecraft.world.BlockView;
+//#else
+import net.minecraft.client.renderer.GameRenderer;
+//#endif
+
+//#if MC>=11400
+//$$ @Mixin(value = Camera.class)
+//#else
+@Mixin(value = GameRenderer.class)
+//#endif
+public abstract class MixinCamera {
+    private EntityRendererHandler getHandler() {
+        return ((EntityRendererHandler.IEntityRenderer) getMinecraft().entityRenderer).replayModRender_getHandler();
+    }
+
+    private float orgYaw;
+    private float orgPitch;
+    private float orgPrevYaw;
+    private float orgPrevPitch;
+    private float orgRoll;
+
+    //#if MC>=11400
+    //$$ @Inject(method = "update", at = @At("HEAD"))
+    //#else
+    @Inject(method = "setupCameraTransform", at = @At("HEAD"))
+    //#endif
+    private void replayModRender_beforeSetupCameraTransform(
+            //#if MC>=11400
+            //$$ BlockView blockView,
+            //$$ Entity entity,
+            //$$ boolean thirdPerson,
+            //$$ boolean inverseView,
+            //#endif
+            float partialTicks,
+            //#if MC<11300
+            //$$ int renderPass,
+            //#endif
+            CallbackInfo ci
+    ) {
+        if (getHandler() != null) {
+            //#if MC<11400
+            Entity entity = getRenderViewEntity(getMinecraft());
+            //#endif
+            orgYaw = entity.rotationYaw;
+            orgPitch = entity.rotationPitch;
+            orgPrevYaw = entity.prevRotationYaw;
+            orgPrevPitch = entity.prevRotationPitch;
+            orgRoll = entity instanceof CameraEntity ? ((CameraEntity) entity).roll : 0;
+        }
+    //#if MC<11400
+    }
+
+    @Inject(method = "orientCamera", at = @At("HEAD"))
+    private void replayModRender_resetRotationIfNeeded(float partialTicks, CallbackInfo ci) {
+    //#endif
+        if (getHandler() != null) {
+            //#if MC<11400
+            Entity entity = getRenderViewEntity(getMinecraft());
+            //#endif
+            RenderSettings settings = getHandler().getSettings();
+            if (settings.isStabilizeYaw()) {
+                entity.prevRotationYaw = entity.rotationYaw = 0;
+            }
+            if (settings.isStabilizePitch()) {
+                entity.prevRotationPitch = entity.rotationPitch = 0;
+            }
+            if (settings.isStabilizeRoll() && entity instanceof CameraEntity) {
+                ((CameraEntity) entity).roll = 0;
+            }
+        }
+    }
+
+    //#if MC>=11400
+    //$$ @Inject(method = "update", at = @At("RETURN"))
+    //#else
+    @Inject(method = "setupCameraTransform", at = @At("RETURN"))
+    //#endif
+    private void replayModRender_afterSetupCameraTransform(
+            //#if MC>=11400
+            //$$ BlockView blockView,
+            //$$ Entity entity,
+            //$$ boolean thirdPerson,
+            //$$ boolean inverseView,
+            //#endif
+            float partialTicks,
+            //#if MC<11300
+            //$$ int renderPass,
+            //#endif
+            CallbackInfo ci
+    ) {
+        if (getHandler() != null) {
+            //#if MC<11400
+            Entity entity = getRenderViewEntity(getMinecraft());
+            //#endif
+            entity.rotationYaw = orgYaw;
+            entity.rotationPitch = orgPitch;
+            entity.prevRotationYaw = orgPrevYaw;
+            entity.prevRotationPitch = orgPrevPitch;
+            if (entity instanceof CameraEntity) {
+                ((CameraEntity) entity).roll = orgRoll;
+            }
+        }
+    }
+
+    //#if MC>=11400
+    //$$ @Inject(method = "update", at = @At("HEAD"))
+    //#else
+    @Inject(method = "orientCamera", at = @At("HEAD"))
+    //#endif
+    private void replayModRender_setupCubicFrameRotation(
+            //#if MC>=11400
+            //$$ BlockView blockView,
+            //$$ Entity entity,
+            //$$ boolean thirdPerson,
+            //$$ boolean inverseView,
+            //#endif
+            float partialTicks,
+            CallbackInfo ci
+    ) {
+        if (getHandler() != null && getHandler().data instanceof CubicOpenGlFrameCapturer.Data) {
+            switch ((CubicOpenGlFrameCapturer.Data) getHandler().data) {
+                case FRONT:
+                    GL11.glRotatef(0, 0.0F, 1.0F, 0.0F);
+                    break;
+                case RIGHT:
+                    GL11.glRotatef(90, 0.0F, 1.0F, 0.0F);
+                    break;
+                case BACK:
+                    GL11.glRotatef(180, 0.0F, 1.0F, 0.0F);
+                    break;
+                case LEFT:
+                    GL11.glRotatef(-90, 0.0F, 1.0F, 0.0F);
+                    break;
+                case BOTTOM:
+                    GL11.glRotatef(90, 1.0F, 0.0F, 0.0F);
+                    break;
+                case TOP:
+                    GL11.glRotatef(-90, 1.0F, 0.0F, 0.0F);
+                    break;
+            }
+        }
+        if (getHandler() != null && getHandler().omnidirectional) {
+            // Minecraft goes back a little so we have to revert that
+            //#if MC>=11300
+            GL11.glTranslatef(0.0F, 0.0F, -0.05F);
+            //#else
+            //$$ GL11.glTranslatef(0.0F, 0.0F, 0.1F);
+            //#endif
+        }
+    }
+}
diff --git a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java
index 65c04f07..9df1656e 100644
--- a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java
+++ b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java
@@ -1,14 +1,10 @@
 package com.replaymod.render.mixin;
 
 import com.replaymod.core.versions.MCVer;
-import com.replaymod.render.RenderSettings;
-import com.replaymod.render.capturer.CubicOpenGlFrameCapturer;
 import com.replaymod.render.capturer.StereoscopicOpenGlFrameCapturer;
 import com.replaymod.render.hooks.EntityRendererHandler;
-import com.replaymod.replay.camera.CameraEntity;
 import de.johni0702.minecraft.gui.utils.lwjgl.ReadableColor;
 import net.minecraft.client.Minecraft;
-import net.minecraft.entity.Entity;
 import net.minecraft.entity.player.EntityPlayer;
 import org.lwjgl.opengl.GL11;
 import org.spongepowered.asm.mixin.Mixin;
@@ -18,15 +14,22 @@ import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.Redirect;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
+//#if MC>=11400
+//$$ import net.minecraft.client.render.Camera;
+//$$ import net.minecraft.util.hit.HitResult;
+//#endif
+
 //#if MC>=11300
 import net.minecraft.client.GameSettings;
 import net.minecraft.client.renderer.GameRenderer;
 import net.minecraft.client.renderer.Matrix4f;
 import net.minecraft.client.renderer.WorldRenderer;
 //#else
+//$$ import com.replaymod.replay.camera.CameraEntity;
 //$$ import net.minecraft.client.renderer.EntityRenderer;
 //$$ import net.minecraft.client.renderer.RenderGlobal;
 //$$ import net.minecraft.client.settings.GameSettings;
+//$$ import net.minecraft.entity.Entity;
 //$$ import org.lwjgl.util.glu.Project;
 //#endif
 
@@ -88,69 +91,11 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti
     //$$ }
     //#endif
 
-    @Inject(method = "orientCamera", at = @At("HEAD"))
-    private void replayModRender_resetRotationIfNeeded(float partialTicks, CallbackInfo ci) {
-        if (replayModRender_handler != null) {
-            Entity entity = getRenderViewEntity(MCVer.getMinecraft());
-            RenderSettings settings = replayModRender_handler.getSettings();
-            if (settings.isStabilizeYaw()) {
-                entity.prevRotationYaw = entity.rotationYaw = 0;
-            }
-            if (settings.isStabilizePitch()) {
-                entity.prevRotationPitch = entity.rotationPitch = 0;
-            }
-            if (settings.isStabilizeRoll() && entity instanceof CameraEntity) {
-                ((CameraEntity) entity).roll = 0;
-            }
-        }
-    }
-
-    private float orgYaw;
-    private float orgPitch;
-    private float orgPrevYaw;
-    private float orgPrevPitch;
-    private float orgRoll;
-
-    @Inject(method = "setupCameraTransform", at = @At("HEAD"))
-    private void replayModRender_beforeSetupCameraTransform(
-            float partialTicks,
-            //#if MC<11300
-            //$$ int renderPass,
-            //#endif
-            CallbackInfo ci
-    ) {
-        if (replayModRender_handler != null) {
-            Entity entity = getRenderViewEntity(MCVer.getMinecraft());
-            orgYaw = entity.rotationYaw;
-            orgPitch = entity.rotationPitch;
-            orgPrevYaw = entity.prevRotationYaw;
-            orgPrevPitch = entity.prevRotationPitch;
-            orgRoll = entity instanceof CameraEntity ? ((CameraEntity) entity).roll : 0;
-        }
-    }
-
-    @Inject(method = "setupCameraTransform", at = @At("RETURN"))
-    private void replayModRender_afterSetupCameraTransform(
-            float partialTicks,
-            //#if MC<11300
-            //$$ int renderPass,
-            //#endif
-            CallbackInfo ci
-    ) {
-        if (replayModRender_handler != null) {
-            Entity entity = getRenderViewEntity(MCVer.getMinecraft());
-            entity.rotationYaw = orgYaw;
-            entity.rotationPitch = orgPitch;
-            entity.prevRotationYaw = orgPrevYaw;
-            entity.prevRotationPitch = orgPrevPitch;
-            if (entity instanceof CameraEntity) {
-                ((CameraEntity) entity).roll = orgRoll;
-            }
-        }
-    }
-
     @Inject(method = "renderHand", at = @At("HEAD"), cancellable = true)
     private void replayModRender_renderSpectatorHand(
+            //#if MC>=11400
+            //$$ Camera camera,
+            //#endif
             float partialTicks,
             //#if MC<11300
             //$$ int renderPass,
@@ -183,6 +128,14 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti
     //$$ public abstract void renderHand(float partialTicks, int renderPass);
     //#endif
 
+    //#if MC>=11400
+    //$$ @Redirect(method = "renderCenter", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;drawHighlightedBlockOutline(Lnet/minecraft/client/render/Camera;Lnet/minecraft/util/hit/HitResult;I)V"))
+    //$$ private void replayModRender_drawSelectionBox(WorldRenderer instance, Camera camera, HitResult hitResult, int something) {
+    //$$     if (replayModRender_handler == null) {
+    //$$         instance.drawHighlightedBlockOutline(camera, hitResult, something);
+    //$$     }
+    //$$ }
+    //#else
     //#if MC>=11300
     @Redirect(method = "updateCameraAndRender(FJ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/WorldRenderer;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/math/RayTraceResult;IF)V"))
     private void replayModRender_drawSelectionBox(WorldRenderer instance, EntityPlayer player, RayTraceResult rtr, int alwaysZero, float partialTicks) {
@@ -203,11 +156,16 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti
             instance.drawSelectionBox(player, rtr, alwaysZero, partialTicks);
         }
     }
+    //#endif
 
     private int orgRenderDistanceChunks;
 
     //#if MC>=11300
+    //#if MC>=11400
+    //$$ @Inject(method = "renderCenter", at = @At(value = "JUMP", ordinal = 0))
+    //#else
     @Inject(method = "updateCameraAndRender(FJ)V", at = @At(value = "JUMP", ordinal = 0))
+    //#endif
     //#else
     //#if MC>=10800
     //$$ @Inject(method = "renderWorldPass", at = @At(value = "JUMP", ordinal = 0))
@@ -225,7 +183,11 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti
     }
 
     //#if MC>=11300
+    //#if MC>=11400
+    //$$ @Redirect(method = "renderCenter", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;renderSky(F)V"))
+    //#else
     @Redirect(method = "updateCameraAndRender(FJ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/WorldRenderer;renderSky(F)V"))
+    //#endif
     private void replayModRender_renderSky(WorldRenderer instance, float partialTicks) {
     //#else
     //#if MC>=10800
@@ -240,7 +202,11 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti
             MCVer.getMinecraft().gameSettings.renderDistanceChunks = orgRenderDistanceChunks;
             ReadableColor color = replayModRender_handler.getSettings().getChromaKeyingColor();
             GlStateManager.clearColor(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, 1);
-            GlStateManager.clear(GL11.GL_COLOR_BUFFER_BIT);
+            GlStateManager.clear(GL11.GL_COLOR_BUFFER_BIT
+                    //#if MC>=11400
+                    //$$ , false
+                    //#endif
+            );
         } else {
             //#if MC>=11300
             instance.renderSky(partialTicks);
@@ -310,7 +276,11 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti
         return replayModRender_perspective((float) fovY, aspect, zNear, zFar);
     }
 
+    //#if MC>=11400
+    //$$ @Redirect(method = "renderCenter", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/Matrix4f;method_4929(DFFF)Lnet/minecraft/client/util/math/Matrix4f;"))
+    //#else
     @Redirect(method = "updateCameraAndRender(FJ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Matrix4f;perspective(DFFF)Lnet/minecraft/client/renderer/Matrix4f;"))
+    //#endif
     private Matrix4f replayModRender_perspective$1(double fovY, float aspect, float zNear, float zFar) {
         return replayModRender_perspective((float) fovY, aspect, zNear, zFar);
     }
@@ -357,38 +327,4 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti
     //$$     Project.gluPerspective(fovY, aspect, zNear, zFar);
     //$$ }
     //#endif
-
-    @Inject(method = "orientCamera", at = @At("HEAD"))
-    private void replayModRender_setupCubicFrameRotation(float partialTicks, CallbackInfo ci) {
-        if (replayModRender_getHandler() != null && replayModRender_getHandler().data instanceof CubicOpenGlFrameCapturer.Data) {
-            switch ((CubicOpenGlFrameCapturer.Data) replayModRender_getHandler().data) {
-                case FRONT:
-                    GL11.glRotatef(0, 0.0F, 1.0F, 0.0F);
-                    break;
-                case RIGHT:
-                    GL11.glRotatef(90, 0.0F, 1.0F, 0.0F);
-                    break;
-                case BACK:
-                    GL11.glRotatef(180, 0.0F, 1.0F, 0.0F);
-                    break;
-                case LEFT:
-                    GL11.glRotatef(-90, 0.0F, 1.0F, 0.0F);
-                    break;
-                case BOTTOM:
-                    GL11.glRotatef(90, 1.0F, 0.0F, 0.0F);
-                    break;
-                case TOP:
-                    GL11.glRotatef(-90, 1.0F, 0.0F, 0.0F);
-                    break;
-            }
-        }
-        if (replayModRender_getHandler() != null && replayModRender_getHandler().omnidirectional) {
-            // Minecraft goes back a little so we have to revert that
-            //#if MC>=11300
-            GL11.glTranslatef(0.0F, 0.0F, -0.05F);
-            //#else
-            //$$ GL11.glTranslatef(0.0F, 0.0F, 0.1F);
-            //#endif
-        }
-    }
 }
diff --git a/src/main/java/com/replaymod/render/mixin/MixinFogRenderer.java b/src/main/java/com/replaymod/render/mixin/MixinFogRenderer.java
index 5f51e5a5..e9647b09 100644
--- a/src/main/java/com/replaymod/render/mixin/MixinFogRenderer.java
+++ b/src/main/java/com/replaymod/render/mixin/MixinFogRenderer.java
@@ -9,10 +9,23 @@ import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
+//#if MC>=11400
+//$$ import net.minecraft.client.render.Camera;
+//#endif
+
 @Mixin(FogRenderer.class)
 public abstract class MixinFogRenderer {
     @Inject(method = "setupFog", at = @At("HEAD"), cancellable = true)
-    private void replayModRender_onSetupFog(int fogDistanceFlag, float partialTicks, CallbackInfo ci) {
+    private void replayModRender_onSetupFog(
+            //#if MC>=11400
+            //$$ Camera camera,
+            //$$ int something,
+            //#else
+            int fogDistanceFlag,
+            float partialTicks,
+            //#endif
+            CallbackInfo ci
+    ) {
         EntityRendererHandler handler =
                 ((EntityRendererHandler.IEntityRenderer) MCVer.getMinecraft().entityRenderer).replayModRender_getHandler();
         if (handler == null) return;
diff --git a/src/main/java/com/replaymod/render/mixin/MixinGlStateManager.java b/src/main/java/com/replaymod/render/mixin/MixinGlStateManager.java
index f145bb2d..989c84b1 100644
--- a/src/main/java/com/replaymod/render/mixin/MixinGlStateManager.java
+++ b/src/main/java/com/replaymod/render/mixin/MixinGlStateManager.java
@@ -5,19 +5,14 @@ import com.replaymod.render.hooks.FogStateCallback;
 import com.replaymod.render.hooks.Texture2DStateCallback;
 import net.minecraft.client.renderer.GlStateManager;
 import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.gen.Accessor;
+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;
 
 @Mixin(GlStateManager.class)
-public class MixinGlStateManager {
-    //#if MC>=11300
-    @Accessor
-    //#else
-    //$$ @Accessor("activeTextureUnit")
-    //#endif
-    private static int getActiveTexture() { return 0; }
+public abstract class MixinGlStateManager {
+    @Shadow private static int activeTexture;
 
     @Inject(method = "enableFog", at = @At("HEAD"))
     private static void enableFog(CallbackInfo ci) {
@@ -31,12 +26,12 @@ public class MixinGlStateManager {
 
     @Inject(method = "enableTexture2D", at = @At("HEAD"))
     private static void enableTexture2D(CallbackInfo ci) {
-        Texture2DStateCallback.EVENT.invoker().texture2DStateChanged(getActiveTexture(), true);
+        Texture2DStateCallback.EVENT.invoker().texture2DStateChanged(MixinGlStateManager.activeTexture, true);
     }
 
     @Inject(method = "disableTexture2D", at = @At("HEAD"))
     private static void disableTexture2D(CallbackInfo ci) {
-        Texture2DStateCallback.EVENT.invoker().texture2DStateChanged(getActiveTexture(), false);
+        Texture2DStateCallback.EVENT.invoker().texture2DStateChanged(MixinGlStateManager.activeTexture, false);
     }
 }
 //#endif
diff --git a/src/main/java/com/replaymod/render/mixin/MixinParticleManager.java b/src/main/java/com/replaymod/render/mixin/MixinParticleManager.java
index b0cf8990..cb8354a6 100644
--- a/src/main/java/com/replaymod/render/mixin/MixinParticleManager.java
+++ b/src/main/java/com/replaymod/render/mixin/MixinParticleManager.java
@@ -8,22 +8,28 @@ import com.replaymod.render.blend.mixin.ParticleAccessor;
 import com.replaymod.render.hooks.EntityRendererHandler;
 import net.minecraft.client.particle.Particle;
 import net.minecraft.client.particle.ParticleManager;
-import net.minecraft.entity.Entity;
+import net.minecraft.client.renderer.BufferBuilder;
+import net.minecraft.util.math.Vec3d;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Redirect;
 
-//#if MC>=11200
-import net.minecraft.client.renderer.BufferBuilder;
+//#if MC>=11400
+//$$ import net.minecraft.client.render.Camera;
 //#else
-//$$ import net.minecraft.client.renderer.VertexBuffer;
+import net.minecraft.entity.Entity;
 //#endif
 
 @Mixin(ParticleManager.class)
 public abstract class MixinParticleManager {
     //#if MC>=11200
+    //#if MC>=11400
+    //$$ @Redirect(method = "renderParticles", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/Particle;buildGeometry(Lnet/minecraft/client/render/BufferBuilder;Lnet/minecraft/client/render/Camera;FFFFFF)V"))
+    //$$ private void renderNormalParticle(Particle particle, BufferBuilder vertexBuffer, Camera view, float partialTicks,
+    //#else
     @Redirect(method = "renderParticles", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/Particle;renderParticle(Lnet/minecraft/client/renderer/BufferBuilder;Lnet/minecraft/entity/Entity;FFFFFF)V"))
     private void renderNormalParticle(Particle particle, BufferBuilder vertexBuffer, Entity view, float partialTicks,
+    //#endif
     //#else
     //$$ @Redirect(method = "renderParticles", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/Particle;renderParticle(Lnet/minecraft/client/renderer/VertexBuffer;Lnet/minecraft/entity/Entity;FFFFFF)V"))
     //$$ private void renderNormalParticle(Particle particle, VertexBuffer vertexBuffer, Entity view, float partialTicks,
@@ -32,6 +38,8 @@ public abstract class MixinParticleManager {
         renderParticle(particle, vertexBuffer, view, partialTicks, rotX, rotXZ, rotZ, rotYZ, rotXY);
     }
 
+    // Seems to be gone by 1.14
+    //#if MC<11400
     //#if MC>=11200
     @Redirect(method = "renderLitParticles", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/Particle;renderParticle(Lnet/minecraft/client/renderer/BufferBuilder;Lnet/minecraft/entity/Entity;FFFFFF)V"))
     private void renderLitParticle(Particle particle, BufferBuilder vertexBuffer, Entity view, float partialTicks,
@@ -42,22 +50,28 @@ public abstract class MixinParticleManager {
                                  float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
         renderParticle(particle, vertexBuffer, view, partialTicks, rotX, rotXZ, rotZ, rotYZ, rotXY);
     }
-
-    //#if MC>=11200
-    private void renderParticle(Particle particle, BufferBuilder vertexBuffer, Entity view, float partialTicks,
-    //#else
-    //$$ private void renderParticle(Particle particle, VertexBuffer vertexBuffer, Entity view, float partialTicks,
     //#endif
-                                 float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
+
+    private void renderParticle(Particle particle,
+                                BufferBuilder vertexBuffer,
+                                //#if MC>=11400
+                                //$$ Camera view,
+                                //#else
+                                Entity view,
+                                //#endif
+                                float partialTicks,
+                                float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
         EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) MCVer.getMinecraft().entityRenderer).replayModRender_getHandler();
         if (handler != null && handler.omnidirectional) {
             // Align all particles towards the camera
-            ParticleAccessor acc = (ParticleAccessor) particle;
-            double dx = acc.getPrevPosX() + (acc.getPosX() - acc.getPrevPosX()) * partialTicks - view.posX;
-            double dy = acc.getPrevPosY() + (acc.getPosY() - acc.getPrevPosY()) * partialTicks - view.posY;
-            double dz = acc.getPrevPosZ() + (acc.getPosZ() - acc.getPrevPosZ()) * partialTicks - view.posZ;
-            double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));
-            double yaw = -Math.atan2(dx, dz);
+            //#if MC>=11400
+            //$$ Vec3d pos = view.getPos();
+            //#else
+            Vec3d pos = new Vec3d(view.posX, view.posY, view.posZ);
+            //#endif
+            Vec3d d = MCVer.getPosition(particle, partialTicks).subtract(pos);
+            double pitch = -Math.atan2(d.y, Math.sqrt(d.x * d.x + d.z * d.z));
+            double yaw = -Math.atan2(d.x, d.z);
 
             rotX = (float) Math.cos(yaw);
             rotZ = (float) Math.sin(yaw);
diff --git a/src/main/java/com/replaymod/render/mixin/MixinRender.java b/src/main/java/com/replaymod/render/mixin/MixinRender.java
index 5f1bfd71..1186e1ff 100644
--- a/src/main/java/com/replaymod/render/mixin/MixinRender.java
+++ b/src/main/java/com/replaymod/render/mixin/MixinRender.java
@@ -11,7 +11,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
 @Mixin(Render.class)
 public abstract class MixinRender {
+    //#if MC>=11400
+    //$$ @Inject(method = "renderLabel(Lnet/minecraft/entity/Entity;Ljava/lang/String;DDDI)V", at = @At("HEAD"), cancellable = true)
+    //#else
     @Inject(method = "renderLivingLabel", at = @At("HEAD"), cancellable = true)
+    //#endif
     private void replayModRender_areAllNamesHidden(Entity entityIn, String str, double x, double y, double z, int maxDistance, CallbackInfo ci) {
         EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) MCVer.getMinecraft().entityRenderer).replayModRender_getHandler();
         if (handler != null && !handler.getSettings().isRenderNameTags()) {
diff --git a/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java b/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java
index 86374b7e..6a6cb680 100644
--- a/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java
+++ b/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java
@@ -11,6 +11,10 @@ import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
+//#if MC>=11400
+//$$ import net.minecraft.client.render.Camera;
+//#endif
+
 //#if MC>=11300
 import net.minecraft.client.renderer.WorldRenderer;
 //#else
@@ -40,12 +44,16 @@ public abstract class MixinRenderGlobal {
 
     @Shadow
     public abstract void setupTerrain(
+            //#if MC>=11400
+            //$$ Camera viewEntity,
+            //#else
             Entity viewEntity,
             //#if MC>=11300
             float partialTicks,
             //#else
             //$$ double partialTicks,
             //#endif
+            //#endif
             ICamera camera,
             int frameCount,
             boolean playerSpectator
@@ -53,12 +61,16 @@ public abstract class MixinRenderGlobal {
 
     @Inject(method = "setupTerrain", at = @At("HEAD"), cancellable = true)
     private void replayModRender_setupTerrain(
+            //#if MC>=11400
+            //$$ Camera viewEntity,
+            //#else
             Entity viewEntity,
             //#if MC>=11300
             float partialTicks,
             //#else
             //$$ double partialTicks,
             //#endif
+            //#endif
             ICamera camera,
             int frameCount,
             boolean playerSpectator,
@@ -68,11 +80,19 @@ public abstract class MixinRenderGlobal {
             replayModRender_passThroughSetupTerrain = true;
 
             do {
-                setupTerrain(viewEntity, partialTicks, camera, replayModRender_hook.nextFrameId(), playerSpectator);
+                setupTerrain(
+                        viewEntity,
+                        //#if MC<11400
+                        partialTicks,
+                        //#endif
+                        camera,
+                        replayModRender_hook.nextFrameId(),
+                        playerSpectator
+                );
                 replayModRender_hook.updateChunks();
-            } while (displayListEntitiesDirty);
+            } while (this.displayListEntitiesDirty);
 
-            displayListEntitiesDirty = true;
+            this.displayListEntitiesDirty = true;
 
             replayModRender_passThroughSetupTerrain = false;
             ci.cancel();
@@ -96,17 +116,20 @@ public abstract class MixinRenderGlobal {
         }
     }
 
+    // Prior to 1.9.4, MC always uses the same ChunkRenderDispatcher instance
+    //#if MC>=10904
     @Inject(method = "setWorldAndLoadRenderers", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher;stopWorkerThreads()V"))
     private void stopWorkerThreadsAndChunkLoadingRenderGlobal(CallbackInfo ci) {
         if (replayModRender_hook != null) {
             replayModRender_hook.updateRenderDispatcher(null);
         }
     }
+    //#endif
 
     @Inject(method = "loadRenderers", at = @At(value = "RETURN"))
     private void setupChunkLoadingRenderGlobal(CallbackInfo ci) {
         if (replayModRender_hook != null) {
-            replayModRender_hook.updateRenderDispatcher(renderDispatcher);
+            replayModRender_hook.updateRenderDispatcher(this.renderDispatcher);
         }
     }
 }
diff --git a/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java b/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java
index 389c7d88..84e84a60 100644
--- a/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java
+++ b/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java
@@ -23,11 +23,15 @@ public abstract class MixinRenderManager {
     @Shadow
     private float playerViewY;
 
+    //#if MC>=11400
+    //$$ @Inject(method = "render(Lnet/minecraft/entity/Entity;DDDFFZ)V", at = @At("HEAD"))
+    //#else
     //#if MC>=11300
     @Inject(method = "renderEntity", at = @At("HEAD"))
     //#else
     //$$ @Inject(method = "doRenderEntity", at = @At("HEAD"))
     //#endif
+    //#endif
     //#if MC>=10904
     private void replayModRender_reorientForCubicRendering(Entity entity, double dx, double dy, double dz, float iDoNotKnow, float partialTicks, boolean iDoNotCare, CallbackInfo ci) {
     //#else
@@ -37,8 +41,8 @@ public abstract class MixinRenderManager {
         if (handler != null && handler.omnidirectional) {
             double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));
             double yaw = -Math.atan2(dx, dz);
-            playerViewX = (float) Math.toDegrees(pitch);
-            playerViewY = (float) Math.toDegrees(yaw);
+            this.playerViewX = (float) Math.toDegrees(pitch);
+            this.playerViewY = (float) Math.toDegrees(yaw);
         }
     }
 }
diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java
index ec200c9c..44afb5e0 100644
--- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java
+++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java
@@ -12,7 +12,7 @@ import com.replaymod.render.ReplayModRender;
 import com.replaymod.render.VideoWriter;
 import com.replaymod.render.blend.BlendState;
 import com.replaymod.render.capturer.RenderInfo;
-import com.replaymod.render.events.ReplayRenderEvent;
+import com.replaymod.render.events.ReplayRenderCallback;
 import com.replaymod.render.frame.RGBFrame;
 import com.replaymod.render.gui.GuiRenderingDone;
 import com.replaymod.render.gui.GuiVideoRenderer;
@@ -39,6 +39,7 @@ import org.lwjgl.glfw.GLFW;
 //$$ import net.minecraft.client.gui.ScaledResolution;
 //$$ import org.lwjgl.input.Mouse;
 //$$ import org.lwjgl.opengl.Display;
+//$$ import static com.replaymod.core.versions.MCVer.newScaledResolution;
 //#endif
 
 //#if MC>=10800
@@ -58,7 +59,6 @@ import java.util.concurrent.Future;
 import java.util.concurrent.FutureTask;
 
 import static com.google.common.collect.Iterables.getLast;
-import static com.replaymod.core.versions.MCVer.*;
 import static com.replaymod.render.ReplayModRender.LOGGER;
 import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
 import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
@@ -124,7 +124,7 @@ public class VideoRenderer implements RenderInfo {
      * @return {@code true} if rendering was successful, {@code false} if the user aborted rendering (or the window was closed)
      */
     public boolean renderVideo() throws Throwable {
-        FML_BUS.post(new ReplayRenderEvent.Pre(this));
+        ReplayRenderCallback.Pre.EVENT.invoker().beforeRendering(this);
 
         setup();
 
@@ -177,7 +177,7 @@ public class VideoRenderer implements RenderInfo {
 
         finish();
 
-        FML_BUS.post(new ReplayRenderEvent.Post(this));
+        ReplayRenderCallback.Post.EVENT.invoker().afterRendering(this);
 
         if (failureCause != null) {
             throw failureCause;
@@ -319,7 +319,11 @@ public class VideoRenderer implements RenderInfo {
         //#endif
 
         // Set up our own framebuffer to render the GUI to
-        guiFramebuffer = new Framebuffer(displayWidth, displayHeight, true);
+        guiFramebuffer = new Framebuffer(displayWidth, displayHeight, true
+                //#if MC>=11400
+                //$$ , false
+                //#endif
+        );
     }
 
     private void finish() {
@@ -327,7 +331,7 @@ public class VideoRenderer implements RenderInfo {
             timelinePlayerFuture.cancel(false);
         }
         // Tear down of the timeline player might only happen the next tick after it was cancelled
-        timelinePlayer.onTick(new ReplayTimer.UpdatedEvent());
+        timelinePlayer.onTick();
 
         if (!OpenGlHelper.isFramebufferEnabled()) {
             //#if MC>=11300
@@ -374,7 +378,11 @@ public class VideoRenderer implements RenderInfo {
 
         // Finally, resize the Minecraft framebuffer to the actual width/height of the window
         //#if MC>=11300
-        mc.getFramebuffer().createFramebuffer(displayWidth, displayHeight);
+        mc.getFramebuffer().createFramebuffer(displayWidth, displayHeight
+                //#if MC>=11400
+                //$$ , false
+                //#endif
+        );
         //noinspection ConstantConditions
         MainWindowAccessor acc = (MainWindowAccessor) (Object) mc.mainWindow;
         acc.setFramebufferWidth(displayWidth);
@@ -385,6 +393,9 @@ public class VideoRenderer implements RenderInfo {
     }
 
     private void tick() {
+        //#if MC>=11400
+        //$$ ((MCVer.MinecraftMethodAccessor) mc).replayModExecuteTaskQueue();
+        //#else
         Queue> scheduledTasks = ((MinecraftAccessor) mc).getScheduledTasks();
         //noinspection SynchronizationOnLocalVariableOrMethodParameter
         synchronized (scheduledTasks) {
@@ -392,6 +403,7 @@ public class VideoRenderer implements RenderInfo {
                 scheduledTasks.poll().run();
             }
         }
+        //#endif
 
         mc.currentScreen = gui.toMinecraft();
         //#if MC>=10800 && MC<11300
@@ -411,19 +423,31 @@ public class VideoRenderer implements RenderInfo {
             if (!settings.isHighPerformance() && displaySizeChanged()) {
                 updateDisplaySize();
                 //#if MC>=11300
-                guiFramebuffer.createBindFramebuffer(mc.mainWindow.getFramebufferWidth(), mc.mainWindow.getFramebufferHeight());
+                guiFramebuffer.createBindFramebuffer(mc.mainWindow.getFramebufferWidth(), mc.mainWindow.getFramebufferHeight()
+                        //#if MC>=11400
+                        //$$ , false
+                        //#endif
+                );
                 //#else
                 //$$ guiFramebuffer.createBindFramebuffer(mc.displayWidth, mc.displayHeight);
                 //#endif
             }
 
             pushMatrix();
-            clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+            clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
+                    //#if MC>=11400
+                    //$$ , false
+                    //#endif
+            );
             enableTexture2D();
             guiFramebuffer.bindFramebuffer(true);
 
             //#if MC>=11300
-            mc.mainWindow.setupOverlayRendering();
+            mc.mainWindow.setupOverlayRendering(
+                    //#if MC>=11400
+                    //$$ false
+                    //#endif
+            );
             //#else
             //$$ mc.entityRenderer.setupOverlayRendering();
             //#endif
diff --git a/src/main/java/com/replaymod/render/utils/PixelBufferObject.java b/src/main/java/com/replaymod/render/utils/PixelBufferObject.java
index 7d400846..abe83ce8 100644
--- a/src/main/java/com/replaymod/render/utils/PixelBufferObject.java
+++ b/src/main/java/com/replaymod/render/utils/PixelBufferObject.java
@@ -1,7 +1,7 @@
 package com.replaymod.render.utils;
 
 import com.google.common.base.Objects;
-import com.replaymod.core.versions.MCVer;
+import com.replaymod.core.ReplayMod;
 import lombok.RequiredArgsConstructor;
 import org.apache.logging.log4j.LogManager;
 
@@ -224,7 +224,7 @@ public class PixelBufferObject {
         super.finalize();
         if (handle != -1) {
             LogManager.getLogger().warn("PBO garbage collected before deleted!");
-            MCVer.getMinecraft().addScheduledTask(this::delete);
+            ReplayMod.instance.runLater(this::delete);
         }
     }
 }
diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java
index 4510d4f9..5d5a9276 100755
--- a/src/main/java/com/replaymod/replay/FullReplaySender.java
+++ b/src/main/java/com/replaymod/replay/FullReplaySender.java
@@ -12,6 +12,7 @@ import com.replaymod.replaystudio.replay.ReplayFile;
 import com.replaymod.replaystudio.studio.ReplayStudio;
 import com.replaymod.replaystudio.studio.protocol.StudioCodec;
 import com.replaymod.replaystudio.studio.protocol.StudioSession;
+import de.johni0702.minecraft.gui.utils.EventRegistrations;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelDuplexHandler;
@@ -22,30 +23,44 @@ import net.minecraft.client.Minecraft;
 import net.minecraft.client.entity.EntityOtherPlayerMP;
 import net.minecraft.client.gui.GuiDownloadTerrain;
 import net.minecraft.client.gui.GuiErrorScreen;
-import net.minecraft.client.resources.I18n;
-import net.minecraft.entity.Entity;
+import net.minecraft.client.multiplayer.WorldClient;
 import net.minecraft.entity.player.EntityPlayer;
-import net.minecraft.network.*;
+import net.minecraft.network.EnumConnectionState;
+import net.minecraft.network.NetworkManager;
+import net.minecraft.network.Packet;
+import net.minecraft.network.PacketBuffer;
 import net.minecraft.network.login.server.SPacketLoginSuccess;
 import net.minecraft.network.play.server.*;
-import net.minecraft.util.Timer;
-import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.text.ITextComponent;
-import net.minecraft.world.EnumDifficulty;
 import net.minecraft.world.World;
-import net.minecraft.world.WorldType;
-import net.minecraft.world.chunk.Chunk;
 import net.minecraft.world.chunk.IChunkProvider;
-import net.minecraftforge.common.MinecraftForge;
-import net.minecraftforge.eventbus.api.SubscribeEvent;
-import net.minecraftforge.fml.common.gameevent.TickEvent;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 
+//#if MC>=11400
+//$$ import de.johni0702.minecraft.gui.versions.callbacks.PreTickCallback;
+//$$ import net.minecraft.entity.EntityType;
+//$$ import net.minecraft.text.TranslatableTextComponent;
+//#else
+import net.minecraft.client.resources.I18n;
+import net.minecraft.entity.Entity;
+import net.minecraft.util.math.MathHelper;
+import net.minecraft.world.EnumDifficulty;
+import net.minecraft.world.WorldType;
+import net.minecraft.world.chunk.Chunk;
+import net.minecraftforge.eventbus.api.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+//#endif
+
 //#if MC>=11300
 import net.minecraft.util.ResourceLocation;
+//#if MC<11400
 import net.minecraft.world.dimension.DimensionType;
 //#endif
+//#endif
 
 //#if MC>=11200
 import com.replaymod.core.utils.WrappedTimer;
@@ -56,14 +71,14 @@ import net.minecraft.world.GameType;
 //$$ import net.minecraft.world.WorldSettings.GameType;
 //#endif
 
-//#if MC<10800
+//#if MC>=10800
+import net.minecraft.network.EnumPacketDirection;
+//#else
 //$$ import org.apache.commons.io.Charsets;
 //#endif
 
 import java.io.*;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -80,6 +95,9 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
      * These packets are ignored completely during replay.
      */
     private static final List BAD_PACKETS = Arrays.asList(
+            //#if MC>=11400
+            //$$ OpenWrittenBookS2CPacket.class,
+            //#endif
             //#if MC>=11200
             SPacketRecipeBook.class,
             SPacketAdvancementInfo.class,
@@ -215,6 +233,8 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
      */
     private final File tempResourcePackFolder = Files.createTempDir();
 
+    private final EventHandler events = new EventHandler();
+
     /**
      * Create a new replay sender.
      * @param file The replay file
@@ -227,7 +247,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
         this.asyncMode = asyncMode;
         this.replayLength = file.getMetaData().getDuration();
 
-        MinecraftForge.EVENT_BUS.register(this);
+        events.register();
 
         if (asyncMode) {
             new Thread(asyncSender, "replaymod-async-sender").start();
@@ -293,7 +313,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
      */
     public void terminateReplay() {
         terminate = true;
-        MinecraftForge.EVENT_BUS.unregister(this);
+        events.unregister();
         try {
             channelInactive(ctx);
             ctx.channel().pipeline().close();
@@ -303,24 +323,31 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
         }
     }
 
-    @SubscribeEvent
-    public void onWorldTick(TickEvent.ClientTickEvent event) {
-        // Unfortunately the WorldTickEvent doesn't seem to be emitted on the CLIENT side
-        if (event.phase != TickEvent.Phase.START) return;
+    class EventHandler extends EventRegistrations {
+        //#if MC>=11400
+        //$$ { on(PreTickCallback.EVENT, this::onWorldTick); }
+        //$$ private void onWorldTick() {
+        //#else
+        @SubscribeEvent
+        public void onWorldTick(TickEvent.ClientTickEvent event) {
+            // Unfortunately the WorldTickEvent doesn't seem to be emitted on the CLIENT side
+            if (event.phase != TickEvent.Phase.START) return;
+        //#endif
 
-        // Spawning a player into an empty chunk (which we might do with the recording player)
-        // prevents it from being moved by teleport packets (it essentially gets stuck) because
-        // Entity#addedToChunk is not set and it is therefore not updated every tick.
-        // To counteract this, we need to manually update it's position if it hasn't been added
-        // to any chunk yet.
-        if (mc.world != null) {
-            for (EntityPlayer playerEntity : playerEntities(mc.world)) {
-                if (!playerEntity.addedToChunk && playerEntity instanceof EntityOtherPlayerMP) {
+            // Spawning a player into an empty chunk (which we might do with the recording player)
+            // prevents it from being moved by teleport packets (it essentially gets stuck) because
+            // Entity#addedToChunk is not set and it is therefore not updated every tick.
+            // To counteract this, we need to manually update it's position if it hasn't been added
+            // to any chunk yet.
+            if (mc.world != null) {
+                for (EntityPlayer playerEntity : playerEntities(mc.world)) {
+                    if (!playerEntity.addedToChunk && playerEntity instanceof EntityOtherPlayerMP) {
                     //#if MC>=11300
                     playerEntity.livingTick();
                     //#else
                     //$$ playerEntity.onLivingUpdate();
                     //#endif
+                    }
                 }
             }
         }
@@ -360,9 +387,14 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
                                 || p instanceof SPacketSpawnPainting
                                 || p instanceof SPacketSpawnExperienceOrb
                                 || p instanceof SPacketDestroyEntities) {
-                            World world = mc.world;
-                            for (int i = 0; i < world.loadedEntityList.size(); ++i) {
-                                Entity entity = loadedEntityList(world).get(i);
+                            WorldClient world = mc.world;
+                            //#if MC>=11400
+                            //$$ // Note: Not sure if it's still required but there's this really handy method anyway
+                            //$$ world.finishRemovingEntities();
+                            //#else
+                            Iterator iter = loadedEntityList(world).iterator();
+                            while (iter.hasNext()) {
+                                Entity entity = iter.next();
                                 if (entity.removed) {
                                     int chunkX = entity.chunkCoordX;
                                     int chunkY = entity.chunkCoordZ;
@@ -379,11 +411,12 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
                                         world.getChunk(chunkX, chunkY).removeEntity(entity);
                                     }
 
-                                    world.loadedEntityList.remove(i--);
+                                    iter.remove();
                                     world.onEntityRemoved(entity);
                                 }
 
                             }
+                            //#endif
                         }
                     }
                 }
@@ -433,19 +466,22 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
                     // Failed to parse options, make sure that under no circumstances further packets are parsed
                     terminateReplay();
                     // Then end replay and show error GUI
-                    mc.addScheduledTask(new Runnable() {
-                        @Override
-                        public void run() {
-                            try {
-                                replayHandler.endReplay();
-                            } catch (IOException e) {
-                                e.printStackTrace();
-                            }
-                            mc.displayGuiScreen(new GuiErrorScreen(
-                                    I18n.format("replaymod.error.unknownrestriction1"),
-                                    I18n.format("replaymod.error.unknownrestriction2", unknown)
-                            ));
+                    ReplayMod.instance.runLater(() -> {
+                        try {
+                            replayHandler.endReplay();
+                        } catch (IOException e) {
+                            e.printStackTrace();
                         }
+                        mc.displayGuiScreen(new GuiErrorScreen(
+                                //#if MC>=11400
+                                //$$ () -> {},
+                                //$$ new TranslatableTextComponent("replaymod.error.unknownrestriction1"),
+                                //$$ new TranslatableTextComponent("replaymod.error.unknownrestriction2", unknown)
+                                //#else
+                                I18n.format("replaymod.error.unknownrestriction1"),
+                                I18n.format("replaymod.error.unknownrestriction2", unknown)
+                                //#endif
+                        ));
                     });
                 }
             }
@@ -469,6 +505,8 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
             //#else
             //$$ String channelName = packet.getChannelName();
             //#endif
+            // On 1.14+ there's a dedicated OpenWrittenBookS2CPacket now
+            //#if MC<11400
             //#if MC>=11300
             if (SPacketCustomPayload.BOOK_OPEN.equals(channelName)) {
             //#else
@@ -476,6 +514,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
             //#endif
                 return null;
             }
+            //#endif
         //#if MC>=10800
         }
 
@@ -510,6 +549,18 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
             allowMovement = true;
             actualID = entId;
             entId = -1789435; // Camera entity id should be negative which is an invalid id and can't be used by servers
+            //#if MC>=11400
+            //$$ p = new GameJoinS2CPacket(
+            //$$         entId,
+            //$$         GameMode.SPECTATOR,
+            //$$         false,
+            //$$         packet.getDimension(),
+            //$$         0, // max players (has no getter -> never actually used)
+            //$$         packet.getGeneratorType(),
+            //$$         packet.getChunkLoadDistance(),
+            //$$         packet.hasReducedDebugInfo()
+            //$$ );
+            //#else
             //#if MC>=10800
             //#if MC>=11300
             DimensionType dimension = packet.func_212642_e();
@@ -540,10 +591,14 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
             //$$ p = new S01PacketJoinGame(entId, GameType.ADVENTURE, false, dimension,
             //$$         difficulty, maxPlayers, worldType);
             //#endif
+            //#endif
         }
 
         if(p instanceof SPacketRespawn) {
             SPacketRespawn respawn = (SPacketRespawn) p;
+            //#if MC>=11400
+            //$$ p = new PlayerRespawnS2CPacket(respawn.getDimension(), respawn.getGeneratorType(), GameMode.SPECTATOR);
+            //#else
             //#if MC>=10809
             p = new SPacketRespawn(respawn.func_212643_b(),
                     respawn.getDifficulty(), respawn.getWorldType(), GameType.SPECTATOR);
@@ -556,6 +611,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
                     //$$ GameType.ADVENTURE);
                     //#endif
             //#endif
+            //#endif
 
             allowMovement = true;
         }
@@ -605,7 +661,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
                 @Override
                 @SuppressWarnings("unchecked")
                 public void run() {
-                    if (mc.world == null || !mc.isCallingFromMinecraftThread()) {
+                    if (mc.world == null || !isOnMainThread()) {
                         ReplayMod.instance.runLater(this);
                         return;
                     }
@@ -878,10 +934,14 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
 
             if(p instanceof SPacketSpawnObject) {
                 SPacketSpawnObject pso = (SPacketSpawnObject)p;
+                //#if MC>=11400
+                //$$ if (pso.getEntityTypeId() == EntityType.FIREWORK_ROCKET) return null;
+                //#else
                 int type = pso.getType();
                 if(type == 76) { // Firework rocket
                     return null;
                 }
+                //#endif
             }
         }
         return p;
@@ -994,6 +1054,9 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
             World world = mc.world;
             IChunkProvider chunkProvider = world.getChunkProvider();
             // Get the chunk that will be unloaded
+            //#if MC>=11400
+            //$$ // FIXME fabric
+            //#else
             //#if MC>=11300
             Chunk chunk = chunkProvider.provideChunk(x, z, false, false);
             //#else
@@ -1062,6 +1125,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
                     }
                 }
             }
+            //#endif
         }
         return p; // During synchronous playback everything is sent normally
     }
diff --git a/src/main/java/com/replaymod/replay/InputReplayTimer.java b/src/main/java/com/replaymod/replay/InputReplayTimer.java
index 5db75541..67a041d1 100644
--- a/src/main/java/com/replaymod/replay/InputReplayTimer.java
+++ b/src/main/java/com/replaymod/replay/InputReplayTimer.java
@@ -43,7 +43,12 @@ public class InputReplayTimer extends WrappedTimer {
 
     @Override
     //#if MC>=11300
+    //#if MC>=11400
+    //$$ // FIXME this should be handled by the preprocessor but there seems to be a bug
+    //$$ public void beginRenderTick(long sysClock) {
+    //#else
     public void updateTimer(long sysClock) {
+    //#endif
         super.updateTimer(sysClock);
     //#else
     //$$ public void updateTimer() {
diff --git a/src/main/java/com/replaymod/replay/NoGuiScreenshot.java b/src/main/java/com/replaymod/replay/NoGuiScreenshot.java
index 1407c3c2..4a4ed1c4 100644
--- a/src/main/java/com/replaymod/replay/NoGuiScreenshot.java
+++ b/src/main/java/com/replaymod/replay/NoGuiScreenshot.java
@@ -2,9 +2,8 @@ package com.replaymod.replay;
 
 import com.google.common.io.Files;
 import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListenableFutureTask;
 import com.google.common.util.concurrent.SettableFuture;
-import com.replaymod.core.mixin.MinecraftAccessor;
+import com.replaymod.core.ReplayMod;
 import com.replaymod.core.versions.MCVer;
 import lombok.AccessLevel;
 import lombok.Getter;
@@ -18,8 +17,6 @@ import javax.imageio.ImageIO;
 import java.awt.*;
 import java.awt.image.BufferedImage;
 import java.io.File;
-import java.util.Queue;
-import java.util.concurrent.FutureTask;
 
 @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
 @Getter
@@ -49,7 +46,12 @@ public class NoGuiScreenshot {
 
                     // Render frame without GUI
                     GlStateManager.pushMatrix();
-                    GlStateManager.clear(16640);
+                    GlStateManager.clear(
+                            16640
+                            //#if MC>=11400
+                            //$$ , true
+                            //#endif
+                    );
                     mc.getFramebuffer().bindFramebuffer(true);
                     GlStateManager.enableTexture2D();
 
@@ -84,7 +86,7 @@ public class NoGuiScreenshot {
                 try {
                     //#if MC>=11300
                     ScreenShotHelper.saveScreenshot(tmpFolder, "tmp", frameWidth, frameHeight, mc.getFramebuffer(), (msg) ->
-                            mc.addScheduledTask(() -> mc.ingameGUI.getChatGUI().printChatMessage(msg)));
+                            ReplayMod.instance.runLater(() -> mc.ingameGUI.getChatGUI().printChatMessage(msg)));
                     //#else
                     //$$ ScreenShotHelper.saveScreenshot(tmpFolder, "tmp", frameWidth, frameHeight, mc.getFramebuffer());
                     //#endif
@@ -121,11 +123,7 @@ public class NoGuiScreenshot {
         // Make sure we are not somewhere in the middle of the rendering process but always at the beginning
         // of the game loop. We cannot use the addScheduledTask method as it'll run the task if called
         // from the minecraft thread which is exactly what we want to avoid.
-        Queue> scheduledTasks = ((MinecraftAccessor) mc).getScheduledTasks();
-        //noinspection SynchronizationOnLocalVariableOrMethodParameter
-        synchronized (scheduledTasks) {
-            scheduledTasks.add(ListenableFutureTask.create(runnable, null));
-        }
+        ReplayMod.instance.runLater(runnable);
         return future;
     }
 }
diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java
index 7fee9b1e..33bcad41 100644
--- a/src/main/java/com/replaymod/replay/QuickReplaySender.java
+++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java
@@ -7,7 +7,6 @@ import com.github.steveice10.mc.protocol.data.game.chunk.BlockStorage;
 import com.github.steveice10.mc.protocol.data.game.chunk.Column;
 import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
 import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
-import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
 import com.github.steveice10.mc.protocol.data.game.world.WorldType;
 import com.github.steveice10.mc.protocol.data.game.world.block.BlockChangeRecord;
 import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
@@ -52,6 +51,7 @@ import com.replaymod.replaystudio.replay.ReplayFile;
 import com.replaymod.replaystudio.studio.ReplayStudio;
 import com.replaymod.replaystudio.util.Location;
 import com.replaymod.replaystudio.util.PacketUtils;
+import de.johni0702.minecraft.gui.utils.EventRegistrations;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufOutputStream;
 import io.netty.buffer.Unpooled;
@@ -63,9 +63,17 @@ import net.minecraft.network.EnumConnectionState;
 import net.minecraft.network.EnumPacketDirection;
 import net.minecraft.network.Packet;
 import net.minecraft.network.PacketBuffer;
+
+//#if MC>=11400
+//$$ import de.johni0702.minecraft.gui.versions.callbacks.PreTickCallback;
+//#else
+import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
 import net.minecraftforge.eventbus.api.SubscribeEvent;
 import net.minecraftforge.fml.common.gameevent.TickEvent;
 
+import static com.replaymod.core.versions.MCVer.FML_BUS;
+//#endif
+
 import javax.annotation.Nullable;
 import java.io.IOException;
 import java.io.InputStream;
@@ -90,7 +98,6 @@ import java.util.zip.Inflater;
 import com.replaymod.core.utils.WrappedTimer;
 //#endif
 
-import static com.replaymod.core.versions.MCVer.FML_BUS;
 import static com.replaymod.core.versions.MCVer.getMinecraft;
 import static com.replaymod.replay.ReplayModReplay.LOGGER;
 
@@ -110,6 +117,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe
 
     private final ReplayModReplay mod;
     private final ReplayFile replayFile;
+    private final EventHandler eventHandler = new EventHandler();
     private ChannelHandlerContext ctx;
 
     private int currentTimeStamp;
@@ -140,11 +148,11 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe
     }
 
     public void register() {
-        FML_BUS.register(this);
+        eventHandler.register();
     }
 
     public void unregister() {
-        FML_BUS.unregister(this);
+        eventHandler.unregister();
     }
 
     @Override
@@ -205,7 +213,14 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe
     public void restart() {
         activeThings.clear();
         currentTimeStamp = 0;
-        ctx.fireChannelRead(toMC(new ServerRespawnPacket(0, Difficulty.NORMAL, GameMode.SPECTATOR, WorldType.DEFAULT)));
+        ctx.fireChannelRead(toMC(new ServerRespawnPacket(
+                0,
+                //#if MC<11400
+                Difficulty.NORMAL,
+                //#endif
+                GameMode.SPECTATOR,
+                WorldType.DEFAULT
+        )));
         ctx.fireChannelRead(toMC(new ServerPlayerPositionRotationPacket(0, 0, 0, 0, 0, 0)));
     }
 
@@ -262,16 +277,23 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe
         sendPacketsTill(value);
     }
 
-    @SubscribeEvent
-    public void onTick(TickEvent.ClientTickEvent event) {
-        if (event.phase != TickEvent.Phase.START) return;
-        if (!asyncMode) return;
+    private class EventHandler extends EventRegistrations {
+        //#if MC>=11400
+        //$$ { on(PreTickCallback.EVENT, this::onTick); }
+        //$$ private void onTick() {
+        //#else
+        @SubscribeEvent
+        public void onTick(TickEvent.ClientTickEvent event) {
+            if (event.phase != TickEvent.Phase.START) return;
+        //#endif
+            if (!asyncMode) return;
 
-        long now = System.currentTimeMillis();
-        long realTimePassed = now - lastAsyncUpdateTime;
-        lastAsyncUpdateTime = now;
-        int replayTimePassed = (int) (realTimePassed * replaySpeed);
-        sendPacketsTill(currentTimeStamp + replayTimePassed);
+            long now = System.currentTimeMillis();
+            long realTimePassed = now - lastAsyncUpdateTime;
+            lastAsyncUpdateTime = now;
+            int replayTimePassed = (int) (realTimePassed * replaySpeed);
+            sendPacketsTill(currentTimeStamp + replayTimePassed);
+        }
     }
 
     private boolean tryLoadFromCache(Consumer progress) throws IOException {
diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java
index d9d1e1a2..2e20b638 100755
--- a/src/main/java/com/replaymod/replay/ReplayHandler.java
+++ b/src/main/java/com/replaymod/replay/ReplayHandler.java
@@ -4,6 +4,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.replaymod.core.ReplayMod;
 import com.replaymod.core.mixin.MinecraftAccessor;
 import com.replaymod.core.mixin.TimerAccessor;
 import com.replaymod.core.utils.Restrictions;
@@ -11,8 +12,9 @@ import com.replaymod.core.utils.Utils;
 import com.replaymod.core.utils.WrappedTimer;
 import com.replaymod.replay.camera.CameraEntity;
 import com.replaymod.replay.camera.SpectatorCameraController;
-import com.replaymod.replay.events.ReplayCloseEvent;
-import com.replaymod.replay.events.ReplayOpenEvent;
+import com.replaymod.replay.events.ReplayClosedCallback;
+import com.replaymod.replay.events.ReplayClosingCallback;
+import com.replaymod.replay.events.ReplayOpenedCallback;
 import com.replaymod.replay.gui.overlay.GuiReplayOverlay;
 import com.replaymod.replay.mixin.EntityLivingBaseAccessor;
 import com.replaymod.replaystudio.data.Marker;
@@ -52,6 +54,8 @@ import net.minecraft.entity.EntityLivingBase;
 
 //#if MC>=10800
 import net.minecraft.network.EnumPacketDirection;
+//#if MC>=11400
+//#else
 //#if MC>=11300
 import net.minecraftforge.fml.network.NetworkHooks;
 //#else
@@ -60,6 +64,7 @@ import net.minecraftforge.fml.network.NetworkHooks;
 //$$ import net.minecraftforge.fml.client.FMLClientHandler;
 //$$ import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher;
 //#endif
+//#endif
 //#else
 //$$ import cpw.mods.fml.common.Loader;
 //$$ import cpw.mods.fml.common.network.internal.FMLNetworkHandler;
@@ -127,13 +132,11 @@ public class ReplayHandler {
     private UUID spectating;
 
     public ReplayHandler(ReplayFile replayFile, boolean asyncMode) throws IOException {
-        Preconditions.checkState(mc.isCallingFromMinecraftThread(), "Must be called from Minecraft thread.");
+        Preconditions.checkState(isOnMainThread(), "Must be called from Minecraft thread.");
         this.replayFile = replayFile;
 
         replayDuration = replayFile.getMetaData().getDuration();
 
-        FML_BUS.post(new ReplayOpenEvent.Pre(this));
-
         markers = replayFile.getMarkers().or(Collections.emptySet());
 
         fullReplaySender = new FullReplaySender(this, replayFile, false);
@@ -146,7 +149,7 @@ public class ReplayHandler {
         overlay = new GuiReplayOverlay(this);
         overlay.setVisible(true);
 
-        FML_BUS.post(new ReplayOpenEvent.Post(this));
+        ReplayOpenedCallback.EVENT.invoker().replayOpened(this);
 
         fullReplaySender.setAsyncMode(asyncMode);
     }
@@ -155,7 +158,7 @@ public class ReplayHandler {
         channel.close();
 
         // Force re-creation of camera entity by unloading the previous world
-        mc.addScheduledTask(() -> {
+        ReplayMod.instance.runLater(() -> {
             //#if MC>=11300
             mc.mouseHelper.ungrabMouse();
             //#else
@@ -170,13 +173,15 @@ public class ReplayHandler {
     }
 
     public void endReplay() throws IOException {
-        Preconditions.checkState(mc.isCallingFromMinecraftThread(), "Must be called from Minecraft thread.");
+        Preconditions.checkState(isOnMainThread(), "Must be called from Minecraft thread.");
 
-        FML_BUS.post(new ReplayCloseEvent.Pre(this));
+        ReplayClosingCallback.EVENT.invoker().replayClosing(this);
 
         fullReplaySender.terminateReplay();
         //#if MC>=10904
-        quickReplaySender.unregister();
+        if (quickMode) {
+            quickReplaySender.unregister();
+        }
         //#endif
 
         replayFile.save();
@@ -209,7 +214,7 @@ public class ReplayHandler {
 
         mc.displayGuiScreen(null);
 
-        FML_BUS.post(new ReplayCloseEvent.Post(this));
+        ReplayClosedCallback.EVENT.invoker().replayClosed(this);
     }
 
     private void setup() {
@@ -235,7 +240,9 @@ public class ReplayHandler {
         channel.pipeline().fireChannelActive();
 
         networkManager.setNetHandler(new NetHandlerLoginClient(networkManager, mc, null, it -> {}));
+        //#if MC<11400
         NetworkHooks.registerClientLoginChannel(networkManager);
+        //#endif
         // FIXME make this work (with vanilla and mods) on all other versions again, now that login phase is included
         //       probably have to change some of the forge handshake calls
         //#else
@@ -572,7 +579,11 @@ public class ReplayHandler {
                 replaySender.jumpToTime(targetTime);
             } else { // We either have to restart the replay or send a significant amount of packets
                 // Render our please-wait-screen
-                GuiScreen guiScreen = new GuiScreen() {
+                GuiScreen guiScreen = new GuiScreen(
+                        //#if MC>=11400
+                        //$$ null
+                        //#endif
+                ) {
                     @Override
                     //#if MC>=11300
                     public void render(int mouseX, int mouseY, float partialTicks) {
@@ -590,14 +601,22 @@ public class ReplayHandler {
 
                 // Perform the rendering using OpenGL
                 pushMatrix();
-                clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+                clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
+                        //#if MC>=11400
+                        //$$ , true
+                        //#endif
+                );
                 enableTexture2D();
                 mc.getFramebuffer().bindFramebuffer(true);
+                //#if MC>=11400
+                //$$ mc.window.method_4493(true);
+                //#else
                 //#if MC>=11300
                 mc.mainWindow.setupOverlayRendering();
                 //#else
                 //$$ mc.entityRenderer.setupOverlayRendering();
                 //#endif
+                //#endif
 
                 //#if MC>=11300
                 MainWindow
diff --git a/src/main/java/com/replaymod/replay/camera/CameraEntity.java b/src/main/java/com/replaymod/replay/camera/CameraEntity.java
index 4323011b..3ea4de8b 100755
--- a/src/main/java/com/replaymod/replay/camera/CameraEntity.java
+++ b/src/main/java/com/replaymod/replay/camera/CameraEntity.java
@@ -1,12 +1,12 @@
 package com.replaymod.replay.camera;
 
 import com.replaymod.core.ReplayMod;
-import com.replaymod.core.events.SettingsChangedEvent;
+import com.replaymod.core.SettingsRegistry;
+import com.replaymod.core.events.SettingsChangedCallback;
+import de.johni0702.minecraft.gui.utils.EventRegistrations;
 import com.replaymod.core.utils.Utils;
-import com.replaymod.core.versions.MCVer;
 import com.replaymod.replay.ReplayModReplay;
 import com.replaymod.replay.Setting;
-import com.replaymod.replay.events.ReplayChatMessageEvent;
 import com.replaymod.replay.mixin.FirstPersonRendererAccessor;
 import com.replaymod.replaystudio.util.Location;
 import lombok.Getter;
@@ -21,6 +21,22 @@ import net.minecraft.entity.item.EntityItemFrame;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.util.math.AxisAlignedBB;
+
+//#if MC>=11400
+//$$ import com.replaymod.core.events.KeyBindingEventCallback;
+//$$ import com.replaymod.core.events.PreRenderCallback;
+//$$ import com.replaymod.core.events.PreRenderHandCallback;
+//$$ import com.replaymod.replay.events.RenderSpectatorCrosshairCallback;
+//$$ import de.johni0702.minecraft.gui.versions.callbacks.PreTickCallback;
+//$$ import net.fabricmc.fabric.api.util.TriState;
+//$$ import net.minecraft.client.world.ClientWorld;
+//$$ import net.minecraft.fluid.Fluid;
+//$$ import net.minecraft.tag.Tag;
+//$$ import net.minecraft.util.hit.BlockHitResult;
+//$$ import net.minecraft.util.hit.HitResult;
+//#else
+import com.replaymod.core.versions.MCVer;
+import com.replaymod.replay.events.ReplayChatMessageEvent;
 import net.minecraft.util.math.RayTraceResult;
 import net.minecraft.util.text.ITextComponent;
 import net.minecraft.world.World;
@@ -38,6 +54,7 @@ import net.minecraft.util.math.RayTraceFluidMode;
 //$$ import net.minecraft.block.material.Material;
 //$$ import net.minecraft.entity.EntityLivingBase;
 //#endif
+//#endif
 
 //#if MC>=10904
 import net.minecraft.inventory.EntityEquipmentSlot;
@@ -104,7 +121,11 @@ public class CameraEntity
     private final EventHandler eventHandler = new EventHandler();
 
     //#if MC>=11300
+    //#if MC>=11400
+    //$$ public CameraEntity(MinecraftClient mcIn, ClientWorld worldIn, ClientPlayNetworkHandler netHandlerPlayClient, StatHandler statisticsManager, ClientRecipeBook recipeBookClient) {
+    //#else
     public CameraEntity(Minecraft mcIn, World worldIn, NetHandlerPlayClient netHandlerPlayClient, StatisticsManager statisticsManager, RecipeBookClient recipeBookClient) {
+    //#endif
         super(mcIn, worldIn, netHandlerPlayClient, statisticsManager, recipeBookClient);
     //#else
     //#if MC>=11200
@@ -125,8 +146,7 @@ public class CameraEntity
     //#endif
     //#endif
     //#endif
-        FORGE_BUS.register(eventHandler);
-        FML_BUS.register(eventHandler);
+        eventHandler.register();
         if (ReplayModReplay.instance.getReplayHandler().getSpectatedUUID() == null) {
             cameraController = ReplayModReplay.instance.createCameraController(this);
         } else {
@@ -141,7 +161,7 @@ public class CameraEntity
      * @param z Delta in Z direction
      */
     public void moveCamera(double x, double y, double z) {
-        setCameraPosition(posX + x, posY + y, posZ + z);
+        setCameraPosition(this.posX + x, this.posY + y, this.posZ + z);
     }
 
     /**
@@ -189,23 +209,27 @@ public class CameraEntity
         //#else
         //$$ float yOffset = 1.62f; // Magic value (eye height) from EntityRenderer#orientCamera
         //#endif
-        prevPosX = to.prevPosX;
-        prevPosY = to.prevPosY + yOffset;
-        prevPosZ = to.prevPosZ;
-        prevRotationYaw = to.prevRotationYaw;
-        prevRotationPitch = to.prevRotationPitch;
-        posX = to.posX;
-        posY = to.posY + yOffset;
-        posZ = to.posZ;
-        rotationYaw = to.rotationYaw;
-        rotationPitch = to.rotationPitch;
-        lastTickPosX = to.lastTickPosX;
-        lastTickPosY = to.lastTickPosY + yOffset;
-        lastTickPosZ = to.lastTickPosZ;
+        this.prevPosX = to.prevPosX;
+        this.prevPosY = to.prevPosY + yOffset;
+        this.prevPosZ = to.prevPosZ;
+        this.prevRotationYaw = to.prevRotationYaw;
+        this.prevRotationPitch = to.prevRotationPitch;
+        this.posX = to.posX;
+        this.posY = to.posY + yOffset;
+        this.posZ = to.posZ;
+        this.rotationYaw = to.rotationYaw;
+        this.rotationPitch = to.rotationPitch;
+        this.lastTickPosX = to.lastTickPosX;
+        this.lastTickPosY = to.lastTickPosY + yOffset;
+        this.lastTickPosZ = to.lastTickPosZ;
         updateBoundingBox();
     }
 
     private void updateBoundingBox() {
+        //#if MC>=11400
+        //$$ float width = getWidth();
+        //$$ float height = getHeight();
+        //#endif
         //#if MC>=10800
         //#if MC>=11300
         setBoundingBox(new AxisAlignedBB(
@@ -215,8 +239,8 @@ public class CameraEntity
         //#else
         //$$ this.boundingBox.setBB(AxisAlignedBB.getBoundingBox(
         //#endif
-                posX - width / 2, posY, posZ - width / 2,
-                posX + width / 2, posY + height, posZ + width / 2));
+                this.posX - width / 2, this.posY, this.posZ - width / 2,
+                this.posX + width / 2, this.posY + height, this.posZ + width / 2));
     }
 
     @Override
@@ -226,9 +250,9 @@ public class CameraEntity
     //$$ public void onUpdate() {
     //#endif
         //#if MC>=10800
-        Entity view = getRenderViewEntity(mc);
+        Entity view = getRenderViewEntity(this.mc);
         //#else
-        //$$ EntityLivingBase view = getRenderViewEntity(mc);
+        //$$ EntityLivingBase view = getRenderViewEntity(this.mc);
         //#endif
         if (view != null) {
             // Make sure we're always spectating the right entity
@@ -236,18 +260,18 @@ public class CameraEntity
             // entity is recreated and we have to spectate a new entity
             UUID spectating = ReplayModReplay.instance.getReplayHandler().getSpectatedUUID();
             if (spectating != null && (view.getUniqueID() != spectating
-                    || view.world != world)
-                    || world.getEntityByID(view.getEntityId()) != view) {
+                    || view.world != this.world)
+                    || this.world.getEntityByID(view.getEntityId()) != view) {
                 if (spectating == null) {
                     // Entity (non-player) died, stop spectating
                     ReplayModReplay.instance.getReplayHandler().spectateEntity(this);
                     return;
                 }
-                view = world.getPlayerEntityByUUID(spectating);
+                view = this.world.getPlayerEntityByUUID(spectating);
                 if (view != null) {
-                    setRenderViewEntity(mc, view);
+                    setRenderViewEntity(this.mc, view);
                 } else {
-                    setRenderViewEntity(mc, this);
+                    setRenderViewEntity(this.mc, this);
                     return;
                 }
             }
@@ -262,15 +286,15 @@ public class CameraEntity
     @Override
     public void preparePlayerToSpawn() {
         // Make sure our world is up-to-date in case of world changes
-        if (mc.world != null) {
-            world = mc.world;
+        if (this.mc.world != null) {
+            this.world = this.mc.world;
         }
         super.preparePlayerToSpawn();
     }
 
     @Override
     public void setRotation(float yaw, float pitch) {
-        if (getRenderViewEntity(mc) == this) {
+        if (getRenderViewEntity(this.mc) == this) {
             // Only update camera rotation when the camera is the view
             super.setRotation(yaw, pitch);
         }
@@ -288,6 +312,12 @@ public class CameraEntity
     //$$ }
     //#endif
 
+    //#if MC>=11400
+    //$$ @Override
+    //$$ public boolean isInFluid(Tag fluid, boolean loadedChunksOnly) {
+    //$$     return falseUnlessSpectating(entity -> entity.isInFluid(fluid, loadedChunksOnly));
+    //$$ }
+    //#else
     //#if MC>=10800
     @Override
     public boolean isInLava() {
@@ -304,6 +334,7 @@ public class CameraEntity
     public boolean isInWater() {
         return falseUnlessSpectating(Entity::isInWater); // Make sure no water overlay is rendered
     }
+    //#endif
 
     @Override
     public boolean isBurning() {
@@ -311,7 +342,7 @@ public class CameraEntity
     }
 
     private boolean falseUnlessSpectating(Function property) {
-        Entity view = getRenderViewEntity(mc);
+        Entity view = getRenderViewEntity(this.mc);
         if (view != null && view != this) {
             return property.apply(view);
         }
@@ -342,6 +373,12 @@ public class CameraEntity
     }
     //#endif
 
+    //#if MC>=11400
+    //$$ @Override
+    //$$ public boolean shouldRenderFrom(double double_1, double double_2, double double_3) {
+    //$$     return false; // never render the camera otherwise it'd be visible e.g. in 3rd-person or with shaders
+    //$$ }
+    //#else
     @Override
     public boolean shouldRenderInPass(int pass) {
         // Never render the camera
@@ -349,10 +386,11 @@ public class CameraEntity
         // cause any unwanted shadows when rendering with shaders.
         return false;
     }
+    //#endif
 
     @Override
     public boolean isInvisible() {
-        Entity view = getRenderViewEntity(mc);
+        Entity view = getRenderViewEntity(this.mc);
         if (view != this) {
             return view.isInvisible();
         }
@@ -361,7 +399,7 @@ public class CameraEntity
 
     @Override
     public ResourceLocation getLocationSkin() {
-        Entity view = getRenderViewEntity(mc);
+        Entity view = getRenderViewEntity(this.mc);
         if (view != this && view instanceof EntityPlayer) {
             return Utils.getResourceLocationForPlayerUUID(view.getUniqueID());
         }
@@ -371,7 +409,7 @@ public class CameraEntity
     //#if MC>=10800
     @Override
     public String getSkinType() {
-        Entity view = mc.getRenderViewEntity();
+        Entity view = this.mc.getRenderViewEntity();
         if (view != this && view instanceof AbstractClientPlayer) {
             return ((AbstractClientPlayer) view).getSkinType();
         }
@@ -381,7 +419,7 @@ public class CameraEntity
 
     @Override
     public float getSwingProgress(float renderPartialTicks) {
-        Entity view = getRenderViewEntity(mc);
+        Entity view = getRenderViewEntity(this.mc);
         if (view != this && view instanceof EntityPlayer) {
             return ((EntityPlayer) view).getSwingProgress(renderPartialTicks);
         }
@@ -391,7 +429,7 @@ public class CameraEntity
     //#if MC>=10904
     @Override
     public float getCooldownPeriod() {
-        Entity view = mc.getRenderViewEntity();
+        Entity view = this.mc.getRenderViewEntity();
         if (view != this && view instanceof EntityPlayer) {
             return ((EntityPlayer) view).getCooldownPeriod();
         }
@@ -400,7 +438,7 @@ public class CameraEntity
 
     @Override
     public float getCooledAttackStrength(float adjustTicks) {
-        Entity view = mc.getRenderViewEntity();
+        Entity view = this.mc.getRenderViewEntity();
         if (view != this && view instanceof EntityPlayer) {
             return ((EntityPlayer) view).getCooledAttackStrength(adjustTicks);
         }
@@ -410,7 +448,7 @@ public class CameraEntity
 
     @Override
     public EnumHand getActiveHand() {
-        Entity view = mc.getRenderViewEntity();
+        Entity view = this.mc.getRenderViewEntity();
         if (view != this && view instanceof EntityPlayer) {
             return ((EntityPlayer) view).getActiveHand();
         }
@@ -419,13 +457,27 @@ public class CameraEntity
 
     @Override
     public boolean isHandActive() {
-        Entity view = mc.getRenderViewEntity();
+        Entity view = this.mc.getRenderViewEntity();
         if (view != this && view instanceof EntityPlayer) {
             return ((EntityPlayer) view).isHandActive();
         }
         return super.isHandActive();
     }
 
+    //#if MC>=11400
+    //$$ @Override
+    //$$ public HitResult rayTrace(double maxDistance, float tickDelta, boolean fluids) {
+    //$$     HitResult result = super.rayTrace(maxDistance, tickDelta, fluids);
+    //$$
+    //$$     // Make sure we can never look at blocks (-> no outline)
+    //$$     if (result instanceof BlockHitResult) {
+    //$$         BlockHitResult blockResult = (BlockHitResult) result;
+    //$$         result = BlockHitResult.createMissed(result.getPos(), blockResult.getSide(), blockResult.getBlockPos());
+    //$$     }
+    //$$
+    //$$     return result;
+    //$$ }
+    //#else
     //#if MC>=11300
     @Override
     public RayTraceResult rayTrace(double blockReachDistance, float partialTicks, RayTraceFluidMode p_174822_4_) {
@@ -451,6 +503,7 @@ public class CameraEntity
     //$$     return pos;
     //$$ }
     //#endif
+    //#endif
     //#else
     //$$ @Override
     //$$ public MovingObjectPosition rayTrace(double p_174822_1_, float p_174822_3_) {
@@ -481,8 +534,7 @@ public class CameraEntity
     //$$ public void setDead() {
     //$$     super.setDead();
     //#endif
-        FORGE_BUS.unregister(eventHandler);
-        FML_BUS.unregister(eventHandler);
+        eventHandler.unregister();
     }
 
     private void update() {
@@ -491,18 +543,7 @@ public class CameraEntity
         cameraController.update(timePassed / 50f);
         lastControllerUpdate = now;
 
-        if (mc.gameSettings.keyBindAttack.isPressed() || mc.gameSettings.keyBindUseItem.isPressed()) {
-            if (canSpectate(mc.pointedEntity)) {
-                ReplayModReplay.instance.getReplayHandler().spectateEntity(
-                        //#if MC<=10710
-                        //$$ (EntityLivingBase)
-                        //#endif
-                        mc.pointedEntity);
-                // Make sure we don't exit right away
-                //noinspection StatementWithEmptyBody
-                while (mc.gameSettings.keyBindSneak.isPressed());
-            }
-        }
+        handleInputEvents();
 
         Map keyBindings = ReplayMod.instance.getKeyBindingRegistry().getKeyBindings();
         if (keyBindings.get("replaymod.input.rollclockwise").isKeyDown()) {
@@ -513,11 +554,26 @@ public class CameraEntity
         }
     }
 
+    private void handleInputEvents() {
+        if (this.mc.gameSettings.keyBindAttack.isPressed() || this.mc.gameSettings.keyBindUseItem.isPressed()) {
+            if (canSpectate(this.mc.pointedEntity)) {
+                ReplayModReplay.instance.getReplayHandler().spectateEntity(
+                        //#if MC<=10710
+                        //$$ (EntityLivingBase)
+                        //#endif
+                        this.mc.pointedEntity);
+                // Make sure we don't exit right away
+                //noinspection StatementWithEmptyBody
+                while (this.mc.gameSettings.keyBindSneak.isPressed());
+            }
+        }
+    }
+
     private void updateArmYawAndPitch() {
-        prevRenderArmYaw = renderArmYaw;
-        prevRenderArmPitch = renderArmPitch;
-        renderArmPitch = renderArmPitch +  (rotationPitch - renderArmPitch) * 0.5f;
-        renderArmYaw = renderArmYaw +  (rotationYaw - renderArmYaw) * 0.5f;
+        this.prevRenderArmYaw = this.renderArmYaw;
+        this.prevRenderArmPitch = this.renderArmPitch;
+        this.renderArmPitch = this.renderArmPitch +  (this.rotationPitch - this.renderArmPitch) * 0.5f;
+        this.renderArmYaw = this.renderArmYaw +  (this.rotationYaw - this.renderArmYaw) * 0.5f;
     }
 
     public boolean canSpectate(Entity e) {
@@ -529,6 +585,7 @@ public class CameraEntity
                 //#endif
     }
 
+    //#if MC<11400
     //#if MC>=11102
     @Override
     public void sendMessage(ITextComponent message) {
@@ -542,30 +599,50 @@ public class CameraEntity
     //$$     super.addChatMessage(message);
     //$$ }
     //#endif
+    //#endif
 
     //#if MC>=10800
     private
     //#else
     //$$ public // All event handlers need to be public in 1.7.10
     //#endif
-    class EventHandler {
+    class EventHandler extends EventRegistrations {
+        private final Minecraft mc = getMinecraft();
+
         private EventHandler() {}
 
+        //#if MC>=11400
+        //$$ { on(PreTickCallback.EVENT, this::onPreClientTick); }
+        //$$ private void onPreClientTick() {
+        //#else
         @SubscribeEvent
         public void onPreClientTick(TickEvent.ClientTickEvent event) {
-            if (event.phase == TickEvent.Phase.START) {
-                update();
-                updateArmYawAndPitch();
-            }
+            if (event.phase != TickEvent.Phase.START) return;
+        //#endif
+            updateArmYawAndPitch();
         }
 
+        //#if MC>=11400
+        //$$ { on(PreRenderCallback.EVENT, this::onRenderUpdate); }
+        //$$ private void onRenderUpdate() {
+        //#else
         @SubscribeEvent
         public void onRenderUpdate(TickEvent.RenderTickEvent event) {
-            if (event.phase == TickEvent.Phase.START) {
-                update();
-            }
+            if (event.phase != TickEvent.Phase.START) return;
+        //#endif
+            update();
         }
 
+        //#if MC>=11400
+        //$$ { on(KeyBindingEventCallback.EVENT, CameraEntity.this::handleInputEvents); }
+        //#endif
+
+        //#if MC>=11400
+        //$$ { on(RenderSpectatorCrosshairCallback.EVENT, this::shouldRenderSpectatorCrosshair); }
+        //$$ private TriState shouldRenderSpectatorCrosshair() {
+        //$$     return TriState.of(canSpectate(mc.targetedEntity));
+        //$$ }
+        //#else
         @SubscribeEvent
         public void preCrosshairRender(RenderGameOverlayEvent.Pre event) {
             // The crosshair should only render if targeted entity can actually be spectated
@@ -577,10 +654,11 @@ public class CameraEntity
                 event.setCanceled(true);
             }
         }
+        //#endif
 
-        @SubscribeEvent
-        public void onSettingsChanged(SettingsChangedEvent event) {
-            if (event.getKey() == Setting.CAMERA) {
+        { on(SettingsChangedCallback.EVENT, this::onSettingsChanged); }
+        private void onSettingsChanged(SettingsRegistry registry, SettingsRegistry.SettingKey key) {
+            if (key == Setting.CAMERA) {
                 if (ReplayModReplay.instance.getReplayHandler().getSpectatedUUID() == null) {
                     cameraController = ReplayModReplay.instance.createCameraController(CameraEntity.this);
                 } else {
@@ -589,6 +667,18 @@ public class CameraEntity
             }
         }
 
+        //#if MC>=11400
+        //$$ { on(PreRenderHandCallback.EVENT, this::onRenderHand); }
+        //$$ private boolean onRenderHand() {
+        //$$     // Unless we are spectating another player, don't render our hand
+        //$$     if (getRenderViewEntity(mc) == CameraEntity.this || !(getRenderViewEntity(mc) instanceof PlayerEntity)) {
+        //$$         return true; // cancel hand rendering
+        //$$     } else {
+        //$$         onRenderHandMonitor();
+        //$$         return false;
+        //$$     }
+        //$$ }
+        //#else
         @SubscribeEvent
         public void onRenderHand(RenderHandEvent event) {
             // Unless we are spectating another player, don't render our hand
@@ -596,9 +686,14 @@ public class CameraEntity
                 event.setCanceled(true);
             }
         }
+        //#endif
 
+        //#if MC>=11400
+        //$$ private void onRenderHandMonitor() {
+        //#else
         @SubscribeEvent(priority = EventPriority.LOWEST)
         public void onRenderHandMonitor(RenderHandEvent event) {
+        //#endif
             Entity view = getRenderViewEntity(mc);
             if (view instanceof EntityPlayer) {
                 EntityPlayer player = (EntityPlayer) view;
@@ -628,6 +723,9 @@ public class CameraEntity
             }
         }
 
+        //#if MC>=11400
+        //$$ // Moved to MixinCamera
+        //#else
         //#if MC>=10800
         @SubscribeEvent
         public void onEntityViewRenderEvent(EntityViewRenderEvent.CameraSetup event) {
@@ -640,9 +738,13 @@ public class CameraEntity
             }
         }
         //#endif
+        //#endif
 
         private boolean heldItemTooltipsWasTrue;
 
+        //#if MC>=11400
+        //$$ // FIXME fabric
+        //#else
         @SubscribeEvent
         public void preRenderGameOverlay(RenderGameOverlayEvent.Pre event) {
             switch (MCVer.getType(event)) {
@@ -684,5 +786,6 @@ public class CameraEntity
             if (MCVer.getType(event) != RenderGameOverlayEvent.ElementType.ALL) return;
             mc.gameSettings.heldItemTooltips = heldItemTooltipsWasTrue;
         }
+        //#endif
     }
 }
diff --git a/src/main/java/com/replaymod/replay/camera/ClassicCameraController.java b/src/main/java/com/replaymod/replay/camera/ClassicCameraController.java
index bc9cfbd9..80736230 100644
--- a/src/main/java/com/replaymod/replay/camera/ClassicCameraController.java
+++ b/src/main/java/com/replaymod/replay/camera/ClassicCameraController.java
@@ -1,9 +1,8 @@
 package com.replaymod.replay.camera;
 
+import de.johni0702.minecraft.gui.utils.lwjgl.vector.Vector3f;
 import net.minecraft.client.settings.KeyBinding;
 
-import javax.vecmath.Vector3f;
-
 import static com.replaymod.core.versions.MCVer.*;
 
 // TODO: Marius is responsible for this. Please, someone clean it up.
@@ -129,8 +128,7 @@ public class ClassicCameraController implements CameraController {
             return;
         }
 
-        Vector3f movement = new Vector3f(direction);
-        movement.normalize();
+        Vector3f movement = direction.normalise(null);
         double factor = motion * (frac / 1000D);
 
         camera.moveCamera(movement.x * factor, movement.y * factor, movement.z * factor);
@@ -162,8 +160,8 @@ public class ClassicCameraController implements CameraController {
         Vector3f dbf = direction;
 
         if(dirBefore != null) {
-            dirBefore.normalize();
-            dirBefore.add(direction);
+            dirBefore.normalise(dirBefore);
+            Vector3f.add(direction, direction, dirBefore);
             direction = dirBefore;
         }
 
diff --git a/src/main/java/com/replaymod/replay/camera/SpectatorCameraController.java b/src/main/java/com/replaymod/replay/camera/SpectatorCameraController.java
index 398cdf52..4d92a33f 100644
--- a/src/main/java/com/replaymod/replay/camera/SpectatorCameraController.java
+++ b/src/main/java/com/replaymod/replay/camera/SpectatorCameraController.java
@@ -8,8 +8,11 @@ import net.minecraft.client.settings.KeyBinding;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.player.EntityPlayer;
 
+//#if MC>=11400
+//$$ import net.minecraft.entity.EquipmentSlot;
+//#endif
+
 //#if MC>=11300
-// FIXME
 //#else
 //$$ import org.lwjgl.input.Mouse;
 //#endif
@@ -39,7 +42,7 @@ public class SpectatorCameraController implements CameraController {
 
         // Prevent mouse movement
         //#if MC>=11300
-        // FIXME
+        // No longer needed
         //#else
         //$$ Mouse.updateCursor();
         //#endif
@@ -53,7 +56,11 @@ public class SpectatorCameraController implements CameraController {
             // If it's a player, also 'steal' its inventory so the rendering code knows what item to render
             if (view instanceof EntityPlayer) {
                 EntityPlayer viewPlayer = (EntityPlayer) view;
+                //#if MC>=11400
+                //$$ camera.setEquippedStack(EquipmentSlot.HEAD, viewPlayer.getEquippedStack(EquipmentSlot.HEAD));
+                //#else
                 camera.inventory = viewPlayer.inventory;
+                //#endif
                 //#if MC>=10904
                 EntityPlayerAccessor cameraA = (EntityPlayerAccessor) camera;
                 EntityPlayerAccessor viewPlayerA = (EntityPlayerAccessor) camera;
diff --git a/src/main/java/com/replaymod/replay/camera/VanillaCameraController.java b/src/main/java/com/replaymod/replay/camera/VanillaCameraController.java
index f3bf0e0f..d4c0610f 100644
--- a/src/main/java/com/replaymod/replay/camera/VanillaCameraController.java
+++ b/src/main/java/com/replaymod/replay/camera/VanillaCameraController.java
@@ -1,5 +1,6 @@
 package com.replaymod.replay.camera;
 
+import de.johni0702.minecraft.gui.utils.lwjgl.vector.Vector3f;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.settings.KeyBinding;
 
@@ -9,8 +10,6 @@ import net.minecraft.client.GameSettings;
 //$$ import net.minecraft.client.settings.GameSettings;
 //#endif
 
-import javax.vecmath.Vector3f;
-
 /**
  * Camera controller performing vanilla creative-like camera movements.
  */
@@ -46,11 +45,11 @@ public class VanillaCameraController implements CameraController {
         Vector3f direction = new Vector3f(0, 0, 0);
         for (int i = 0; i < 6; i++) { // First, get movement direction depending on keys pressed
             if (bindings[i].isKeyDown()) {
-                direction.add(DIRECTIONS[i]);
+                Vector3f.add(direction, DIRECTIONS[i], direction);
             }
         }
         if (direction.length() == 0) return;
-        direction.normalize(); // Normalize, so we don't move quicker if we hold down multiple keys
+        direction.normalise(direction); // Normalize, so we don't move quicker if we hold down multiple keys
         double yawRadians = Math.toRadians(camera.rotationYaw);
         float yawSin = (float) Math.sin(yawRadians), yawCos = (float) Math.cos(yawRadians);
         // Rotate by yaw
diff --git a/src/main/java/com/replaymod/replay/events/RenderSpectatorCrosshairCallback.java b/src/main/java/com/replaymod/replay/events/RenderSpectatorCrosshairCallback.java
new file mode 100644
index 00000000..1630e180
--- /dev/null
+++ b/src/main/java/com/replaymod/replay/events/RenderSpectatorCrosshairCallback.java
@@ -0,0 +1,24 @@
+//#if MC>=11300
+package com.replaymod.replay.events;
+
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+import net.fabricmc.fabric.api.util.TriState;
+
+public interface RenderSpectatorCrosshairCallback {
+    Event EVENT = EventFactory.createArrayBacked(
+            RenderSpectatorCrosshairCallback.class,
+            (listeners) -> () -> {
+                for (RenderSpectatorCrosshairCallback listener : listeners) {
+                    TriState state = listener.shouldRenderSpectatorCrosshair();
+                    if (state != TriState.DEFAULT) {
+                        return state;
+                    }
+                }
+                return TriState.DEFAULT;
+            }
+    );
+
+    TriState shouldRenderSpectatorCrosshair();
+}
+//#endif
diff --git a/src/main/java/com/replaymod/replay/events/ReplayChatMessageEvent.java b/src/main/java/com/replaymod/replay/events/ReplayChatMessageEvent.java
index 4d534e4d..93b0c17b 100644
--- a/src/main/java/com/replaymod/replay/events/ReplayChatMessageEvent.java
+++ b/src/main/java/com/replaymod/replay/events/ReplayChatMessageEvent.java
@@ -1,3 +1,4 @@
+//#if MC<11400
 package com.replaymod.replay.events;
 
 import com.replaymod.replay.camera.CameraEntity;
@@ -12,3 +13,4 @@ public class ReplayChatMessageEvent extends Event {
     @Getter
     private final CameraEntity cameraEntity;
 }
+//#endif
diff --git a/src/main/java/com/replaymod/replay/events/ReplayCloseEvent.java b/src/main/java/com/replaymod/replay/events/ReplayCloseEvent.java
deleted file mode 100644
index 87bca719..00000000
--- a/src/main/java/com/replaymod/replay/events/ReplayCloseEvent.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.replaymod.replay.events;
-
-import com.replaymod.replay.ReplayHandler;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import net.minecraftforge.eventbus.api.Event;
-
-@RequiredArgsConstructor
-public abstract class ReplayCloseEvent extends Event {
-    @Getter
-    private final ReplayHandler replayHandler;
-
-    public static class Pre extends ReplayCloseEvent {
-        public Pre(ReplayHandler replayHandler) {
-            super(replayHandler);
-        }
-    }
-
-    public static class Post extends ReplayCloseEvent {
-        public Post(ReplayHandler replayHandler) {
-            super(replayHandler);
-        }
-    }
-}
diff --git a/src/main/java/com/replaymod/replay/events/ReplayClosedCallback.java b/src/main/java/com/replaymod/replay/events/ReplayClosedCallback.java
new file mode 100644
index 00000000..b0f2cf45
--- /dev/null
+++ b/src/main/java/com/replaymod/replay/events/ReplayClosedCallback.java
@@ -0,0 +1,16 @@
+package com.replaymod.replay.events;
+
+import com.replaymod.replay.ReplayHandler;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+
+public interface ReplayClosedCallback {
+    Event EVENT = EventFactory.createArrayBacked(ReplayClosedCallback.class,
+            (listeners) -> (replayHandler) -> {
+                for (ReplayClosedCallback listener : listeners) {
+                    listener.replayClosed(replayHandler);
+                }
+            });
+
+    void replayClosed(ReplayHandler replayHandler);
+}
diff --git a/src/main/java/com/replaymod/replay/events/ReplayClosingCallback.java b/src/main/java/com/replaymod/replay/events/ReplayClosingCallback.java
new file mode 100644
index 00000000..14c46e90
--- /dev/null
+++ b/src/main/java/com/replaymod/replay/events/ReplayClosingCallback.java
@@ -0,0 +1,18 @@
+package com.replaymod.replay.events;
+
+import com.replaymod.replay.ReplayHandler;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+
+import java.io.IOException;
+
+public interface ReplayClosingCallback {
+    Event EVENT = EventFactory.createArrayBacked(ReplayClosingCallback.class,
+            (listeners) -> (replayHandler) -> {
+                for (ReplayClosingCallback listener : listeners) {
+                    listener.replayClosing(replayHandler);
+                }
+            });
+
+    void replayClosing(ReplayHandler replayHandler) throws IOException;
+}
diff --git a/src/main/java/com/replaymod/replay/events/ReplayDispatchKeypressesEvent.java b/src/main/java/com/replaymod/replay/events/ReplayDispatchKeypressesEvent.java
index e3fe281e..8aeb1bb3 100644
--- a/src/main/java/com/replaymod/replay/events/ReplayDispatchKeypressesEvent.java
+++ b/src/main/java/com/replaymod/replay/events/ReplayDispatchKeypressesEvent.java
@@ -1,10 +1,12 @@
-package com.replaymod.replay.events;
-
-import net.minecraftforge.eventbus.api.Cancelable;
-import net.minecraftforge.eventbus.api.Event;
-
-public abstract class ReplayDispatchKeypressesEvent extends Event {
-
-    @Cancelable
-    public static class Pre extends ReplayDispatchKeypressesEvent {}
-}
+//#if MC<11300
+//$$ package com.replaymod.replay.events;
+//$$
+//$$ import net.minecraftforge.fml.common.eventhandler.Cancelable;
+//$$ import net.minecraftforge.fml.common.eventhandler.Event;
+//$$
+//$$ public abstract class ReplayDispatchKeypressesEvent extends Event {
+//$$
+//$$     @Cancelable
+//$$     public static class Pre extends ReplayDispatchKeypressesEvent {}
+//$$ }
+//#endif
diff --git a/src/main/java/com/replaymod/replay/events/ReplayOpenEvent.java b/src/main/java/com/replaymod/replay/events/ReplayOpenEvent.java
deleted file mode 100644
index 6151ae6b..00000000
--- a/src/main/java/com/replaymod/replay/events/ReplayOpenEvent.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.replaymod.replay.events;
-
-import com.replaymod.replay.ReplayHandler;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import net.minecraftforge.eventbus.api.Event;
-
-@RequiredArgsConstructor
-public abstract class ReplayOpenEvent extends Event {
-    @Getter
-    private final ReplayHandler replayHandler;
-
-    public static class Pre extends ReplayOpenEvent {
-        public Pre(ReplayHandler replayHandler) {
-            super(replayHandler);
-        }
-    }
-
-    public static class Post extends ReplayOpenEvent {
-        public Post(ReplayHandler replayHandler) {
-            super(replayHandler);
-        }
-    }
-}
diff --git a/src/main/java/com/replaymod/replay/events/ReplayOpenedCallback.java b/src/main/java/com/replaymod/replay/events/ReplayOpenedCallback.java
new file mode 100644
index 00000000..3dc7685e
--- /dev/null
+++ b/src/main/java/com/replaymod/replay/events/ReplayOpenedCallback.java
@@ -0,0 +1,18 @@
+package com.replaymod.replay.events;
+
+import com.replaymod.replay.ReplayHandler;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+
+import java.io.IOException;
+
+public interface ReplayOpenedCallback {
+    Event EVENT = EventFactory.createArrayBacked(ReplayOpenedCallback.class,
+            (listeners) -> (replayHandler) -> {
+                for (ReplayOpenedCallback listener : listeners) {
+                    listener.replayOpened(replayHandler);
+                }
+            });
+
+    void replayOpened(ReplayHandler replayHandler) throws IOException;
+}
diff --git a/src/main/java/com/replaymod/replay/gui/overlay/GuiReplayOverlay.java b/src/main/java/com/replaymod/replay/gui/overlay/GuiReplayOverlay.java
index 2b030d95..ddd4d641 100644
--- a/src/main/java/com/replaymod/replay/gui/overlay/GuiReplayOverlay.java
+++ b/src/main/java/com/replaymod/replay/gui/overlay/GuiReplayOverlay.java
@@ -1,6 +1,7 @@
 package com.replaymod.replay.gui.overlay;
 
 import com.replaymod.core.ReplayMod;
+import com.replaymod.core.versions.MCVer.Keyboard;
 import com.replaymod.replay.ReplayHandler;
 import com.replaymod.replay.ReplaySender;
 import de.johni0702.minecraft.gui.GuiRenderer;
@@ -14,20 +15,22 @@ import de.johni0702.minecraft.gui.element.GuiTooltip;
 import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline;
 import de.johni0702.minecraft.gui.layout.CustomLayout;
 import de.johni0702.minecraft.gui.layout.HorizontalLayout;
+import de.johni0702.minecraft.gui.utils.EventRegistrations;
 import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
 import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint;
 import de.johni0702.minecraft.gui.utils.lwjgl.WritablePoint;
 import net.minecraft.client.GameSettings;
 import net.minecraft.client.resources.I18n;
-import net.minecraftforge.eventbus.api.SubscribeEvent;
-import net.minecraftforge.fml.common.gameevent.InputEvent;
 
-//#if MC<11300
-//$$ import org.lwjgl.input.Keyboard;
+//#if MC>=11300
+import com.replaymod.core.events.KeyBindingEventCallback;
+import com.replaymod.core.events.KeyEventCallback;
+//#else
+//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+//$$ import net.minecraftforge.fml.common.gameevent.InputEvent;
 //#endif
 
 import static com.replaymod.core.ReplayMod.TEXTURE_SIZE;
-import static com.replaymod.core.versions.MCVer.*;
 
 public class GuiReplayOverlay extends AbstractGuiOverlay {
 
@@ -57,6 +60,8 @@ public class GuiReplayOverlay extends AbstractGuiOverlay {
     public final GuiPanel statusIndicatorPanel = new GuiPanel(this).setSize(100, 20)
             .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(5));
 
+    private final EventHandler eventHandler = new EventHandler();
+
     public GuiReplayOverlay(final ReplayHandler replayHandler) {
         timeline = new GuiMarkerTimeline(replayHandler){
             @Override
@@ -141,32 +146,14 @@ public class GuiReplayOverlay extends AbstractGuiOverlay {
     public void setVisible(boolean visible) {
         if (isVisible() != visible) {
             if (visible) {
-                FML_BUS.register(this);
+                eventHandler.register();
             } else {
-                FML_BUS.unregister(this);
+                eventHandler.unregister();
             }
         }
         super.setVisible(visible);
     }
 
-    @SubscribeEvent
-    public void onKeyInput(InputEvent.KeyInputEvent event) {
-        GameSettings gameSettings = getMinecraft().gameSettings;
-        while (gameSettings.keyBindChat.isPressed() || gameSettings.keyBindCommand.isPressed()) {
-            if (!isMouseVisible()) {
-                setMouseVisible(true);
-            }
-        }
-        /* FIXME
-        if (Keyboard.getEventKeyState()) {
-            // Handle the F1 key binding while the overlay is opened as a gui screen
-            if (isMouseVisible() && Keyboard.getEventKey() == Keyboard.KEY_F1) {
-                gameSettings.hideGUI = !gameSettings.hideGUI;
-            }
-        }
-        */
-    }
-
     @Override
     public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
         // Do not render overlay when user pressed F1 and we are not currently in some popup
@@ -182,4 +169,38 @@ public class GuiReplayOverlay extends AbstractGuiOverlay {
     protected GuiReplayOverlay getThis() {
         return this;
     }
+
+    private class EventHandler extends EventRegistrations {
+        //#if MC>=11300
+        { on(KeyBindingEventCallback.EVENT, this::onKeyBindingEvent); }
+        private void onKeyBindingEvent() {
+        //#else
+        //$$ @SubscribeEvent
+        //$$ public void onKeyBindingEvent(InputEvent.KeyInputEvent event) {
+        //#endif
+            GameSettings gameSettings = getMinecraft().gameSettings;
+            while (gameSettings.keyBindChat.isPressed() || gameSettings.keyBindCommand.isPressed()) {
+                if (!isMouseVisible()) {
+                    setMouseVisible(true);
+                }
+            }
+        }
+
+        //#if MC>=11300
+        { on(KeyEventCallback.EVENT, (int key, int scanCode, int action, int modifiers) -> onKeyInput(key, action)); }
+        private void onKeyInput(int key, int action) {
+            if (action != 0) return;
+        //#else
+        //$$ @SubscribeEvent
+        //$$ public void onKeyInput(InputEvent.KeyInputEvent event) {
+        //$$     if (!Keyboard.getEventKeyState()) return;
+        //$$     int key = Keyboard.getEventKey();
+        //#endif
+            GameSettings gameSettings = getMinecraft().gameSettings;
+            // Handle the F1 key binding while the overlay is opened as a gui screen
+            if (isMouseVisible() && key == Keyboard.KEY_F1) {
+                gameSettings.hideGUI = !gameSettings.hideGUI;
+            }
+        }
+    }
 }
diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java
index 786a69ce..8607c141 100755
--- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java
+++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java
@@ -5,7 +5,7 @@ import com.google.common.base.Supplier;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.SettableFuture;
-import com.mojang.realmsclient.gui.ChatFormatting;
+import net.minecraft.util.text.TextFormatting;
 import com.replaymod.core.ReplayMod;
 import com.replaymod.core.SettingsRegistry;
 import com.replaymod.core.gui.GuiReplaySettings;
@@ -37,7 +37,6 @@ import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
 import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint;
 import lombok.Getter;
 import net.minecraft.client.gui.GuiErrorScreen;
-import net.minecraft.client.resources.I18n;
 import net.minecraft.crash.CrashReport;
 import net.minecraft.crash.ReportedException;
 import org.apache.commons.io.FileUtils;
@@ -57,6 +56,12 @@ import java.util.List;
 
 import static com.replaymod.replay.ReplayModReplay.LOGGER;
 
+//#if MC>=11400
+//$$ import net.minecraft.text.TranslatableTextComponent;
+//#else
+import net.minecraft.client.resources.I18n;
+//#endif
+
 public class GuiReplayViewer extends GuiScreen {
     private final ReplayModReplay mod;
 
@@ -137,8 +142,14 @@ public class GuiReplayViewer extends GuiScreen {
                             // We failed (might also be their OS)
                             e.printStackTrace();
                             getMinecraft().displayGuiScreen(new GuiErrorScreen(
+                                    //#if MC>=11400
+                                    //$$ () -> {},
+                                    //$$ new TranslatableTextComponent("replaymod.gui.viewer.delete.failed1"),
+                                    //$$ new TranslatableTextComponent("replaymod.gui.viewer.delete.failed2")
+                                    //#else
                                     I18n.format("replaymod.gui.viewer.delete.failed1"),
                                     I18n.format("replaymod.gui.viewer.delete.failed2")
+                                    //#endif
                             ));
                             return;
                         }
@@ -159,7 +170,7 @@ public class GuiReplayViewer extends GuiScreen {
             String name = list.getSelected().name.getText();
             GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this,
                     new GuiLabel().setI18nText("replaymod.gui.viewer.delete.linea").setColor(Colors.BLACK),
-                    new GuiLabel().setI18nText("replaymod.gui.viewer.delete.lineb", name + ChatFormatting.RESET).setColor(Colors.BLACK)
+                    new GuiLabel().setI18nText("replaymod.gui.viewer.delete.lineb", name + TextFormatting.RESET).setColor(Colors.BLACK)
             ).setYesI18nLabel("replaymod.gui.delete").setNoI18nLabel("replaymod.gui.cancel");
             Futures.addCallback(popup.getFuture(), new FutureCallback() {
                 @Override
@@ -393,7 +404,7 @@ public class GuiReplayViewer extends GuiScreen {
         public GuiReplayEntry(File file, ReplayMetaData metaData, BufferedImage thumbImage) {
             this.file = file;
 
-            name.setText(ChatFormatting.UNDERLINE + Utils.fileNameToReplayName(file.getName()));
+            name.setText(TextFormatting.UNDERLINE + Utils.fileNameToReplayName(file.getName()));
             if (StringUtils.isEmpty(metaData.getServerName())
                     || !ReplayMod.instance.getSettingsRegistry().get(Setting.SHOW_SERVER_IPS)) {
                 server.setI18nText("replaymod.gui.iphidden").setColor(Colors.DARK_RED);
diff --git a/src/main/java/com/replaymod/replay/handler/GuiHandler.java b/src/main/java/com/replaymod/replay/handler/GuiHandler.java
index bbd4c57c..a0f568fc 100644
--- a/src/main/java/com/replaymod/replay/handler/GuiHandler.java
+++ b/src/main/java/com/replaymod/replay/handler/GuiHandler.java
@@ -1,52 +1,51 @@
 package com.replaymod.replay.handler;
 
-import com.replaymod.core.versions.MCVer;
+import de.johni0702.minecraft.gui.utils.EventRegistrations;
 import com.replaymod.replay.ReplayModReplay;
 import com.replaymod.replay.gui.screen.GuiReplayViewer;
-import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.GuiButton;
 import net.minecraft.client.gui.GuiIngameMenu;
 import net.minecraft.client.gui.GuiMainMenu;
 import net.minecraft.client.gui.GuiMultiplayer;
+import net.minecraft.client.gui.GuiScreen;
 import net.minecraft.client.resources.I18n;
+
+//#if MC>=11400
+//$$ import de.johni0702.minecraft.gui.versions.callbacks.InitScreenCallback;
+//$$ import net.minecraft.client.gui.widget.AbstractButtonWidget;
+//#else
 import net.minecraftforge.client.event.GuiScreenEvent;
 import net.minecraftforge.eventbus.api.SubscribeEvent;
+//#endif
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 
 import static com.replaymod.core.versions.MCVer.*;
 import static com.replaymod.replay.ReplayModReplay.LOGGER;
 
-public class GuiHandler {
-    //#if MC>=11300
-    private static final int BUTTON_OPTIONS = 0;
-    //#endif
-    private static final int BUTTON_EXIT_SERVER = 1;
-    private static final int BUTTON_ADVANCEMENTS = 5;
-    private static final int BUTTON_STATS = 6;
-    private static final int BUTTON_OPEN_TO_LAN = 7;
-
+public class GuiHandler extends EventRegistrations {
     private static final int BUTTON_REPLAY_VIEWER = 17890234;
     private static final int BUTTON_EXIT_REPLAY = 17890235;
 
-    private static final Minecraft mc = MCVer.getMinecraft();
-
     private final ReplayModReplay mod;
 
     public GuiHandler(ReplayModReplay mod) {
         this.mod = mod;
     }
 
-    public void register() {
-        FML_BUS.register(this);
-        FORGE_BUS.register(this);
-    }
-
+    //#if MC>=11400
+    //$$ { on(InitScreenCallback.EVENT, this::injectIntoIngameMenu); }
+    //$$ private void injectIntoIngameMenu(Screen guiScreen, List buttonList) {
+    //#else
     @SubscribeEvent
     public void injectIntoIngameMenu(GuiScreenEvent.InitGuiEvent.Post event) {
-        if (!(getGui(event) instanceof GuiIngameMenu)) {
+        GuiScreen guiScreen = getGui(event);
+        List buttonList = getButtonList(event);
+    //#endif
+        if (!(guiScreen instanceof GuiIngameMenu)) {
             return;
         }
 
@@ -54,42 +53,82 @@ public class GuiHandler {
             // Pause replay when menu is opened
             mod.getReplayHandler().getReplaySender().setReplaySpeed(0);
 
-            GuiButton achievements = null, stats = null, openToLan = null;
-            List buttonList = getButtonList(event);
+            //#if MC>=11400
+            //$$ final String BUTTON_OPTIONS = I18n.translate("menu.options");
+            //$$ final String BUTTON_EXIT_SERVER = I18n.translate("menu.disconnect");
+            //$$ final String BUTTON_ADVANCEMENTS = I18n.translate("gui.advancements");
+            //$$ final String BUTTON_STATS = I18n.translate("gui.stats");
+            //$$ final String BUTTON_OPEN_TO_LAN = I18n.translate("menu.shareToLan");
+            //#else
+            //#if MC>=11300
+            final int BUTTON_OPTIONS = 0;
+            //#endif
+            final int BUTTON_EXIT_SERVER = 1;
+            final int BUTTON_ADVANCEMENTS = 5;
+            final int BUTTON_STATS = 6;
+            final int BUTTON_OPEN_TO_LAN = 7;
+            //#endif
+
+
+            //#if MC<11300
+            //$$ GuiButton openToLan = null;
+            //#endif
+            //#if MC>=11400
+            //$$ AbstractButtonWidget achievements = null, stats = null;
+            //$$ for(AbstractButtonWidget b : new ArrayList<>(buttonList)) {
+            //#else
+            GuiButton achievements = null, stats = null;
             for(GuiButton b : new ArrayList<>(buttonList)) {
-                switch (b.id) {
+            //#endif
+                boolean remove = false;
+                //#if MC>=11400
+                //$$ String id = b.getMessage();
+                //#else
+                Integer id = b.id;
+                //#endif
+                if (id.equals(BUTTON_EXIT_SERVER)) {
                     // Replace "Exit Server" button with "Exit Replay" button
-                    case BUTTON_EXIT_SERVER:
-                        removeButton(event, b);
-                        addButton(event, new GuiButton(BUTTON_EXIT_REPLAY, b.x, b.y, b.width, b.height, I18n.format("replaymod.gui.exit")) {
-                            //#if MC>=11300
-                            @Override
-                            public void onClick(double mouseX, double mouseY) {
-                                onButton(new GuiScreenEvent.ActionPerformedEvent.Pre(getGui(event), this, new ArrayList<>()));
-                            }
-                            //#endif
-                        });
-                        break;
+                    remove = true;
+                    addButton(guiScreen, new InjectedButton(
+                            guiScreen,
+                            BUTTON_EXIT_REPLAY,
+                            b.x,
+                            b.y,
+                            width(b),
+                            height(b),
+                            I18n.format("replaymod.gui.exit"),
+                            this::onButton
+                    ));
+                } else if (id.equals(BUTTON_ADVANCEMENTS)) {
                     // Remove "Advancements", "Stats" and "Open to LAN" buttons
-                    case BUTTON_ADVANCEMENTS:
-                        removeButton(event, achievements = b);
-                        break;
-                    case BUTTON_STATS:
-                        removeButton(event, stats = b);
-                        break;
-                    case BUTTON_OPEN_TO_LAN:
-                        removeButton(event, openToLan = b);
-                        break;
-                    //#if MC>=11300
-                    case BUTTON_OPTIONS:
-                        b.width = 200;
-                        break;
+                    remove = true;
+                    achievements = b;
+                } else if (id.equals(BUTTON_STATS)) {
+                    remove = true;
+                    stats = b;
+                } else if (id.equals(BUTTON_OPEN_TO_LAN)) {
+                    remove = true;
+                    //#if MC<11300
+                    //$$ openToLan = b;
                     //#endif
+                //#if MC>=11300
+                } else if (id.equals(BUTTON_OPTIONS)) {
+                    //#if MC>=11400
+                    //$$ width(b, 204);
+                    //#else
+                    width(b, 200);
+                    //#endif
+                //#endif
+                }
+                if (remove) {
+                    // Moving the button far off-screen is easier to do cross-version than actually removing it
+                    b.x = -1000;
+                    b.y = -1000;
                 }
             }
             if (achievements != null && stats != null) {
                 moveAllButtonsDirectlyBelowUpwards(buttonList, achievements.y,
-                        achievements.x, stats.x + stats.width);
+                        achievements.x, stats.x + width(stats));
             }
             // In 1.13+ Forge, the Options button shares one row with the Open to LAN button
             //#if MC<11300
@@ -108,17 +147,31 @@ public class GuiHandler {
      * @param xStart Left x limit of the rectangle
      * @param xEnd Right x limit of the rectangle
      */
-    private void moveAllButtonsDirectlyBelowUpwards(List buttons, int belowY, int xStart, int xEnd) {
-        for (GuiButton button : buttons) {
-            if (button.y >= belowY && button.x <= xEnd && button.x + button.width >= xStart) {
-                button.y -= 24;
-            }
-        }
+    private void moveAllButtonsDirectlyBelowUpwards(
+            //#if MC>=11400
+            //$$ List buttons,
+            //#else
+            List buttons,
+            //#endif
+            int belowY,
+            int xStart,
+            int xEnd
+    ) {
+        buttons.stream()
+                .filter(button -> button.y >= belowY)
+                .filter(button -> button.x <= xEnd && button.x + width(button) >= xStart)
+                .forEach(button -> button.y -= 24);
     }
 
+    //#if MC>=11400
+    //$$ { on(InitScreenCallback.EVENT, this::ensureReplayStopped); }
+    //$$ private void ensureReplayStopped(Screen guiScreen, List buttonList) {
+    //#else
     @SubscribeEvent
     public void ensureReplayStopped(GuiScreenEvent.InitGuiEvent event) {
-        if (!(getGui(event) instanceof GuiMainMenu || getGui(event) instanceof GuiMultiplayer)) {
+        GuiScreen guiScreen = getGui(event);
+    //#endif
+        if (!(guiScreen instanceof GuiMainMenu || guiScreen instanceof GuiMultiplayer)) {
             return;
         }
 
@@ -137,38 +190,50 @@ public class GuiHandler {
         }
     }
 
-
+    //#if MC>=11400
+    //$$ { on(InitScreenCallback.EVENT, this::injectIntoMainMenu); }
+    //$$ private void injectIntoMainMenu(Screen guiScreen, List buttonList) {
+    //#else
     @SubscribeEvent
     public void injectIntoMainMenu(GuiScreenEvent.InitGuiEvent event) {
-        if (!(getGui(event) instanceof GuiMainMenu)) {
+        GuiScreen guiScreen = getGui(event);
+    //#endif
+        if (!(guiScreen instanceof GuiMainMenu)) {
             return;
         }
-        GuiButton button = new GuiButton(BUTTON_REPLAY_VIEWER, getGui(event).width / 2 - 100,
-                getGui(event).height / 4 + 10 + 4 * 24, I18n.format("replaymod.gui.replayviewer")) {
-            //#if MC>=11300
-            @Override
-            public void onClick(double mouseX, double mouseY) {
-                onButton(new GuiScreenEvent.ActionPerformedEvent.Pre(getGui(event), this, new ArrayList<>()));
-            }
-            //#endif
-        };
-        button.width = button.width / 2 - 2;
-        addButton(event, button);
+        GuiButton button = new InjectedButton(
+                guiScreen,
+                BUTTON_REPLAY_VIEWER,
+                guiScreen.width / 2 - 100,
+                guiScreen.height / 4 + 10 + 4 * 24,
+                98,
+                20,
+                I18n.format("replaymod.gui.replayviewer"),
+                this::onButton
+        );
+        addButton(guiScreen, button);
     }
 
-    @SubscribeEvent
-    public void onButton(GuiScreenEvent.ActionPerformedEvent.Pre event) {
-        if(!getButton(event).enabled) return;
+    //#if MC>=11300
+    private void onButton(InjectedButton button) {
+        GuiScreen guiScreen = button.guiScreen;
+    //#else
+    //$$ @SubscribeEvent
+    //$$ public void onButton(GuiScreenEvent.ActionPerformedEvent.Pre event) {
+    //$$     GuiScreen guiScreen = getGui(event);
+    //$$     GuiButton button = getButton(event);
+    //#endif
+        if(!button.enabled) return;
 
-        if (getGui(event) instanceof GuiMainMenu) {
-            if (getButton(event).id == BUTTON_REPLAY_VIEWER) {
+        if (guiScreen instanceof GuiMainMenu) {
+            if (button.id == BUTTON_REPLAY_VIEWER) {
                 new GuiReplayViewer(mod).display();
             }
         }
 
-        if (getGui(event) instanceof GuiIngameMenu && mod.getReplayHandler() != null) {
-            if (getButton(event).id == BUTTON_EXIT_REPLAY) {
-                getButton(event).enabled = false;
+        if (guiScreen instanceof GuiIngameMenu && mod.getReplayHandler() != null) {
+            if (button.id == BUTTON_EXIT_REPLAY) {
+                button.enabled = false;
                 try {
                     mod.getReplayHandler().endReplay();
                 } catch (IOException e) {
@@ -177,4 +242,45 @@ public class GuiHandler {
             }
         }
     }
+
+    public static class InjectedButton extends GuiButton {
+        public final GuiScreen guiScreen;
+        public final int id;
+        private Consumer onClick;
+        public InjectedButton(GuiScreen guiScreen, int buttonId, int x, int y, int width, int height, String buttonText,
+                              //#if MC>=11300
+                              Consumer onClick
+                              //#else
+                              //$$ Consumer onClick
+                              //#endif
+        ) {
+            super(
+                    //#if MC<11400
+                    buttonId,
+                    //#endif
+                    x,
+                    y,
+                    width,
+                    height,
+                    buttonText
+                    //#if MC>=11400
+                    //$$ , self -> onClick.accept((InjectedButton) self)
+                    //#endif
+            );
+            this.guiScreen = guiScreen;
+            this.id = buttonId;
+            //#if MC>=11300
+            this.onClick = onClick;
+            //#else
+            //$$ this.onClick = null;
+            //#endif
+        }
+
+        //#if MC>=11300 && MC<11400
+        @Override
+        public void onClick(double mouseX, double mouseY) {
+            onClick.accept(this);
+        }
+        //#endif
+    }
 }
diff --git a/src/main/java/com/replaymod/replay/mixin/MixinCamera.java b/src/main/java/com/replaymod/replay/mixin/MixinCamera.java
new file mode 100644
index 00000000..b4ff6096
--- /dev/null
+++ b/src/main/java/com/replaymod/replay/mixin/MixinCamera.java
@@ -0,0 +1,30 @@
+//#if MC>=11400
+//$$ package com.replaymod.replay.mixin;
+//$$
+//$$ import com.mojang.blaze3d.platform.GlStateManager;
+//$$ import com.replaymod.replay.camera.CameraEntity;
+//$$ import net.minecraft.client.render.Camera;
+//$$ import net.minecraft.entity.Entity;
+//$$ import net.minecraft.world.BlockView;
+//$$ 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;
+//$$
+//$$ @Mixin(Camera.class)
+//$$ public class MixinCamera {
+//$$     @Inject(
+//$$             method = "update",
+//$$             at = @At(
+//$$                     value = "INVOKE",
+//$$                     target = "Lcom/mojang/blaze3d/platform/GlStateManager;rotatef(FFFF)V",
+//$$                     ordinal = 0
+//$$             )
+//$$     )
+//$$     private void applyRoll(BlockView blockView, Entity view, boolean boolean_1, boolean boolean_2, float float_1, CallbackInfo ci) {
+//$$         if (view instanceof CameraEntity) {
+//$$             GlStateManager.rotatef(((CameraEntity) view).roll, 0.0F, 0.0F, 1.0F);
+//$$         }
+//$$     }
+//$$ }
+//#endif
diff --git a/src/main/java/com/replaymod/replay/mixin/MixinGuiSpectator.java b/src/main/java/com/replaymod/replay/mixin/MixinGuiSpectator.java
index 2c183d6a..14727046 100644
--- a/src/main/java/com/replaymod/replay/mixin/MixinGuiSpectator.java
+++ b/src/main/java/com/replaymod/replay/mixin/MixinGuiSpectator.java
@@ -17,7 +17,14 @@ public abstract class MixinGuiSpectator {
     //#else
     //$$ @Inject(method = "func_175260_a", at = @At("HEAD"), cancellable = true)
     //#endif
-    public void isInReplay(int i, CallbackInfo ci) {
+    public void isInReplay(
+            //#if MC>=11400
+            //$$ double i,
+            //#else
+            int i,
+            //#endif
+            CallbackInfo ci
+    ) {
         // Prevent spectator gui from opening while in a replay
         if (getMinecraft().player instanceof CameraEntity) {
             ci.cancel();
diff --git a/src/main/java/com/replaymod/replay/mixin/MixinInGameHud.java b/src/main/java/com/replaymod/replay/mixin/MixinInGameHud.java
new file mode 100644
index 00000000..41d9bd5a
--- /dev/null
+++ b/src/main/java/com/replaymod/replay/mixin/MixinInGameHud.java
@@ -0,0 +1,22 @@
+//#if MC>=11400
+//$$ package com.replaymod.replay.mixin;
+//$$
+//$$ import com.replaymod.replay.events.RenderSpectatorCrosshairCallback;
+//$$ import net.fabricmc.fabric.api.util.TriState;
+//$$ import net.minecraft.client.gui.hud.InGameHud;
+//$$ 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.CallbackInfoReturnable;
+//$$
+//$$ @Mixin(InGameHud.class)
+//$$ public class MixinInGameHud {
+//$$     @Inject(method = "shouldRenderSpectatorCrosshair", at = @At("HEAD"), cancellable = true)
+//$$     private void shouldRenderSpectatorCrosshair(CallbackInfoReturnable ci) {
+//$$         TriState state = RenderSpectatorCrosshairCallback.EVENT.invoker().shouldRenderSpectatorCrosshair();
+//$$         if (state != TriState.DEFAULT) {
+//$$             ci.setReturnValue(state.get());
+//$$         }
+//$$     }
+//$$ }
+//#endif
diff --git a/src/main/java/com/replaymod/replay/mixin/MixinKeyboardListener.java b/src/main/java/com/replaymod/replay/mixin/MixinKeyboardListener.java
new file mode 100644
index 00000000..ab92a831
--- /dev/null
+++ b/src/main/java/com/replaymod/replay/mixin/MixinKeyboardListener.java
@@ -0,0 +1,56 @@
+//#if MC>=11300
+package com.replaymod.replay.mixin;
+
+import com.replaymod.extras.advancedscreenshots.AdvancedScreenshots;
+import com.replaymod.replay.ReplayModReplay;
+import net.minecraft.client.KeyboardListener;
+import net.minecraft.client.shader.Framebuffer;
+import net.minecraft.util.ScreenShotHelper;
+import net.minecraft.util.text.ITextComponent;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+import java.io.File;
+import java.util.function.Consumer;
+
+@Mixin(KeyboardListener.class)
+public abstract class MixinKeyboardListener {
+    @Redirect(
+            method = "onKeyEvent",
+            at = @At(
+                    value = "INVOKE",
+                    //#if MC>=11400
+                    //$$ target = "Lnet/minecraft/client/util/ScreenshotUtils;method_1662(Ljava/io/File;Ljava/lang/String;IILnet/minecraft/client/gl/GlFramebuffer;Ljava/util/function/Consumer;)V"
+                    //#else
+                    target = "Lnet/minecraft/util/ScreenShotHelper;saveScreenshot(Ljava/io/File;IILnet/minecraft/client/shader/Framebuffer;Ljava/util/function/Consumer;)V"
+                    //#endif
+            )
+    )
+    private void takeScreenshot(
+            File p_148260_0_,
+            //#if MC>=11400
+            //$$ String something,
+            //#endif
+            int p_148260_1_,
+            int p_148260_2_,
+            Framebuffer p_148260_3_,
+            Consumer p_148260_4_
+    ) {
+        if (ReplayModReplay.instance.getReplayHandler() != null) {
+            AdvancedScreenshots.take();
+        } else {
+            ScreenShotHelper.saveScreenshot(
+                    p_148260_0_,
+                    //#if MC>=11400
+                    //$$ something,
+                    //#endif
+                    p_148260_1_,
+                    p_148260_2_,
+                    p_148260_3_,
+                    p_148260_4_
+            );
+        }
+    }
+}
+//#endif
diff --git a/src/main/java/com/replaymod/replay/mixin/MixinParticleManager.java b/src/main/java/com/replaymod/replay/mixin/MixinParticleManager.java
index 14210b94..80ebece8 100644
--- a/src/main/java/com/replaymod/replay/mixin/MixinParticleManager.java
+++ b/src/main/java/com/replaymod/replay/mixin/MixinParticleManager.java
@@ -32,9 +32,9 @@ public abstract class MixinParticleManager {
     @Inject(method = "clearEffects", at = @At("HEAD"))
     public void replayModReplay_clearParticleQueue(World world, CallbackInfo ci) {
         //#if MC>=11300
-        queue.clear();
+        this.queue.clear();
         //#else
-        //$$ queueEntityFX.clear();
+        //$$ this.queueEntityFX.clear();
         //#endif
     }
 }
diff --git a/src/main/java/com/replaymod/replay/mixin/MixinPlayerControllerMP.java b/src/main/java/com/replaymod/replay/mixin/MixinPlayerControllerMP.java
index efd9977a..a5719a0c 100644
--- a/src/main/java/com/replaymod/replay/mixin/MixinPlayerControllerMP.java
+++ b/src/main/java/com/replaymod/replay/mixin/MixinPlayerControllerMP.java
@@ -5,13 +5,18 @@ import com.replaymod.replay.camera.CameraEntity;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.multiplayer.PlayerControllerMP;
 import net.minecraft.client.network.NetHandlerPlayClient;
-import net.minecraft.world.World;
 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.CallbackInfoReturnable;
 
+//#if MC>=11400
+//$$ import net.minecraft.client.world.ClientWorld;
+//#else
+import net.minecraft.world.World;
+//#endif
+
 //#if MC>=11200
 //#if MC>=11300
 import net.minecraft.client.util.RecipeBookClient;
@@ -47,32 +52,41 @@ public abstract class MixinPlayerControllerMP {
 
     //#if MC>=11300
     @Inject(method = "createPlayer", at=@At("HEAD"), cancellable = true)
-    private void replayModReplay_createReplayCamera(World worldIn, StatisticsManager statisticsManager, RecipeBookClient recipeBookClient, CallbackInfoReturnable ci) {
+    private void replayModReplay_createReplayCamera(
+            //#if MC>=11400
+            //$$ ClientWorld worldIn,
+            //#else
+            World worldIn,
+            //#endif
+            StatisticsManager statisticsManager,
+            RecipeBookClient recipeBookClient,
+            CallbackInfoReturnable ci
+    ) {
         if (ReplayModReplay.instance.getReplayHandler() != null) {
-            ci.setReturnValue(new CameraEntity(mc, worldIn, connection, statisticsManager, recipeBookClient));
+            ci.setReturnValue(new CameraEntity(this.mc, worldIn, this.connection, statisticsManager, recipeBookClient));
     //#else
     //#if MC>=11200
     //$$ @Inject(method = "func_192830_a", at=@At("HEAD"), cancellable = true)
     //$$ private void replayModReplay_createReplayCamera(World worldIn, StatisticsManager statisticsManager, RecipeBook recipeBook, CallbackInfoReturnable ci) {
     //$$     if (ReplayModReplay.instance.getReplayHandler() != null) {
-    //$$         ci.setReturnValue(new CameraEntity(mc, worldIn, connection, statisticsManager, recipeBook));
+    //$$         ci.setReturnValue(new CameraEntity(this.mc, worldIn, this.connection, statisticsManager, recipeBook));
     //#else
     //#if MC>=10904
     //$$ @Inject(method = "createClientPlayer", at=@At("HEAD"), cancellable = true)
     //$$ private void replayModReplay_createReplayCamera(World worldIn, StatisticsManager statisticsManager, CallbackInfoReturnable ci) {
     //$$     if (ReplayModReplay.instance.getReplayHandler() != null) {
-    //$$         ci.setReturnValue(new CameraEntity(mc, worldIn, connection, statisticsManager));
+    //$$         ci.setReturnValue(new CameraEntity(this.mc, worldIn, this.connection, statisticsManager));
     //#else
     //#if MC>=10800
     //$$ @Inject(method = "func_178892_a", at=@At("HEAD"), cancellable = true)
     //$$ private void replayModReplay_createReplayCamera(World worldIn, StatFileWriter statFileWriter, CallbackInfoReturnable ci) {
     //$$     if (ReplayModReplay.instance.getReplayHandler() != null) {
-    //$$         ci.setReturnValue(new CameraEntity(mc, worldIn, netClientHandler, statFileWriter));
+    //$$         ci.setReturnValue(new CameraEntity(this.mc, worldIn, this.netClientHandler, statFileWriter));
     //#else
     //$$ @Inject(method = "createPlayer", at=@At("HEAD"), cancellable = true)
     //$$ private void replayModReplay_createReplayCamera(World worldIn, StatFileWriter statFileWriter, CallbackInfoReturnable ci) {
     //$$     if (ReplayModReplay.instance.getReplayHandler() != null) {
-    //$$         ci.setReturnValue(new CameraEntity(mc, worldIn, mc.getSession(), netClientHandler, statFileWriter));
+    //$$         ci.setReturnValue(new CameraEntity(this.mc, worldIn, this.mc.getSession(), this.netClientHandler, statFileWriter));
     //#endif
     //#endif
     //#endif
@@ -88,8 +102,8 @@ public abstract class MixinPlayerControllerMP {
     //$$ @Inject(method = "isSpectator", at=@At("HEAD"), cancellable = true)
     //#endif
     private void replayModReplay_isSpectator(CallbackInfoReturnable ci) {
-        if (mc.player instanceof CameraEntity) { // this check should in theory not be required
-            ci.setReturnValue(mc.player.isSpectator());
+        if (this.mc.player instanceof CameraEntity) { // this check should in theory not be required
+            ci.setReturnValue(this.mc.player.isSpectator());
         }
     }
     //#endif
diff --git a/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java b/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java
index ce425650..00de694d 100644
--- a/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java
+++ b/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java
@@ -16,7 +16,11 @@ import net.minecraft.util.Util;
 @Mixin(ItemRenderer.class)
 public class MixinRenderItem {
     //#if MC>=11300
+    //#if MC>=11400
+    //$$ @Redirect(method = "renderGlint", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/SystemUtil;getMeasuringTimeMs()J"))
+    //#else
     @Redirect(method = "renderEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;milliTime()J"))
+    //#endif
     private static long getEnchantmentTime() {
     //#else
     //$$ @Redirect(method = "renderEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J"))
diff --git a/src/main/java/com/replaymod/replay/mixin/MixinRenderLivingBase.java b/src/main/java/com/replaymod/replay/mixin/MixinRenderLivingBase.java
index be5f624c..58017b06 100644
--- a/src/main/java/com/replaymod/replay/mixin/MixinRenderLivingBase.java
+++ b/src/main/java/com/replaymod/replay/mixin/MixinRenderLivingBase.java
@@ -31,7 +31,17 @@ public abstract class MixinRenderLivingBase {
         }
     }
 
-    @Redirect(method = "renderModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/EntityLivingBase;isInvisibleToPlayer(Lnet/minecraft/entity/player/EntityPlayer;)Z"))
+    @Redirect(
+            method = "renderModel",
+            at = @At(
+                    value = "INVOKE",
+                    //#if MC>=11400
+                    //$$ target = "Lnet/minecraft/entity/LivingEntity;canSeePlayer(Lnet/minecraft/entity/player/PlayerEntity;)Z"
+                    //#else
+                    target = "Lnet/minecraft/entity/EntityLivingBase;isInvisibleToPlayer(Lnet/minecraft/entity/player/EntityPlayer;)Z"
+                    //#endif
+            )
+    )
     private boolean replayModReplay_shouldInvisibleNotBeRendered(EntityLivingBase entity, EntityPlayer thePlayer) {
         return thePlayer instanceof CameraEntity || entity.isInvisibleToPlayer(thePlayer);
     }
diff --git a/src/main/java/com/replaymod/replay/mixin/MixinRenderManager.java b/src/main/java/com/replaymod/replay/mixin/MixinRenderManager.java
index 69145572..795e36c4 100644
--- a/src/main/java/com/replaymod/replay/mixin/MixinRenderManager.java
+++ b/src/main/java/com/replaymod/replay/mixin/MixinRenderManager.java
@@ -1,4 +1,5 @@
-//#if MC>=10800
+// Appears to no longer be needed in 1.14+ (maybe not even 1.13?)
+//#if MC>=10800 && MC<11400
 package com.replaymod.replay.mixin;
 
 import net.minecraft.client.GameSettings;
diff --git a/src/main/java/com/replaymod/replay/mixin/MixinViewFrustum.java b/src/main/java/com/replaymod/replay/mixin/MixinViewFrustum.java
index 466dd1f0..54eb1f94 100644
--- a/src/main/java/com/replaymod/replay/mixin/MixinViewFrustum.java
+++ b/src/main/java/com/replaymod/replay/mixin/MixinViewFrustum.java
@@ -10,16 +10,31 @@ import org.spongepowered.asm.mixin.injection.Redirect;
 
 @Mixin(ViewFrustum.class)
 public abstract class MixinViewFrustum {
-    @Redirect(method = "updateChunkPositions", at=@At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/chunk/RenderChunk;setPosition(III)V"))
-    private void replayModReplay_updatePositionAndMarkForUpdate(RenderChunk renderChunk, int x, int y, int z) {
+    @Redirect(
+            method = "updateChunkPositions",
+            at = @At(
+                    value = "INVOKE",
+                    //#if MC>=10904
+                    target = "Lnet/minecraft/client/renderer/chunk/RenderChunk;setPosition(III)V"
+                    //#else
+                    //$$ target = "Lnet/minecraft/client/renderer/chunk/RenderChunk;setPosition(Lnet/minecraft/util/BlockPos;)V"
+                    //#endif
+            )
+    )
+    private void replayModReplay_updatePositionAndMarkForUpdate(
+            RenderChunk renderChunk,
+            //#if MC>=10904
+            int x, int y, int z
+            //#else
+            //$$ BlockPos pos
+            //#endif
+    ) {
+        //#if MC>=10904
         BlockPos pos = new BlockPos(x, y, z);
+        //#endif
         if (!pos.equals(renderChunk.getPosition())) {
             //#if MC>=10904
-            //#if MC>=11100
             renderChunk.setPosition(x, y, z);
-            //#else
-            //$$ renderChunk.setOrigin(x, y, z);
-            //#endif
             renderChunk.setNeedsUpdate(false);
             //#else
             //$$ renderChunk.setPosition(pos);
diff --git a/src/main/java/com/replaymod/replay/mixin/MixinWorldClient.java b/src/main/java/com/replaymod/replay/mixin/MixinWorldClient.java
index 643ac038..3e9743bb 100644
--- a/src/main/java/com/replaymod/replay/mixin/MixinWorldClient.java
+++ b/src/main/java/com/replaymod/replay/mixin/MixinWorldClient.java
@@ -10,6 +10,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 @Mixin(WorldClient.class)
 public abstract class MixinWorldClient {
 
+    // Looks like this has finally been fixed in 1.14 (or been moved somewhere else entirely, guess we'll find out)
+    //#if MC<11400
     /**
      * Fixes a bug in vanilla Minecraft that leaves entities remaining in the entityList even after respawn.
      * The entityList in WorldClient is a Set that assumes that the entity ID of its entities does not change.
@@ -28,4 +30,5 @@ public abstract class MixinWorldClient {
     public void replayModReplay_fix_addEntityToWorld(int entityId, Entity entity, CallbackInfo ci) {
         entity.setEntityId(entityId);
     }
+    //#endif
 }
diff --git a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java
index db777101..cf53c07f 100644
--- a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java
+++ b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java
@@ -3,11 +3,14 @@ package com.replaymod.simplepathing;
 import com.replaymod.core.KeyBindingRegistry;
 import com.replaymod.core.Module;
 import com.replaymod.core.ReplayMod;
-import com.replaymod.core.events.SettingsChangedEvent;
+import com.replaymod.core.events.SettingsChangedCallback;
+import de.johni0702.minecraft.gui.utils.EventRegistrations;
 import com.replaymod.core.versions.MCVer.Keyboard;
+import com.replaymod.replay.ReplayHandler;
 import com.replaymod.replay.ReplayModReplay;
-import com.replaymod.replay.events.ReplayCloseEvent;
-import com.replaymod.replay.events.ReplayOpenEvent;
+import com.replaymod.replay.events.ReplayClosedCallback;
+import com.replaymod.replay.events.ReplayClosingCallback;
+import com.replaymod.replay.events.ReplayOpenedCallback;
 import com.replaymod.replay.gui.overlay.GuiReplayOverlay;
 import com.replaymod.replaystudio.pathing.PathingRegistry;
 import com.replaymod.replaystudio.pathing.change.Change;
@@ -21,7 +24,6 @@ import com.replaymod.simplepathing.preview.PathPreview;
 import lombok.Getter;
 import net.minecraft.crash.CrashReport;
 import net.minecraft.crash.ReportedException;
-import net.minecraftforge.eventbus.api.SubscribeEvent;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -33,9 +35,7 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import static com.replaymod.core.versions.MCVer.FML_BUS;
-
-public class ReplayModSimplePathing implements Module {
+public class ReplayModSimplePathing extends EventRegistrations implements Module {
     { instance = this; }
     public static ReplayModSimplePathing instance;
 
@@ -50,12 +50,31 @@ public class ReplayModSimplePathing implements Module {
         this.core = core;
 
         core.getSettingsRegistry().register(Setting.class);
+
+        on(SettingsChangedCallback.EVENT, (registry, key) -> {
+            if (key == Setting.DEFAULT_INTERPOLATION) {
+                if (currentTimeline != null && guiPathing != null) {
+                    updateDefaultInterpolatorType();
+                }
+            }
+        });
+    }
+
+    @Override
+    public void register() {
+        super.register();
+        pathPreview.register();
+    }
+
+    @Override
+    public void unregister() {
+        super.unregister();
+        pathPreview.unregister();
     }
 
     @Override
     public void initClient() {
-        FML_BUS.register(this);
-        pathPreview.register();
+        register();
     }
 
     @Override
@@ -75,9 +94,9 @@ public class ReplayModSimplePathing implements Module {
         });
     }
 
-    @SubscribeEvent
-    public void postReplayOpen(ReplayOpenEvent.Post event) {
-        ReplayFile replayFile = event.getReplayHandler().getReplayFile();
+    { on(ReplayOpenedCallback.EVENT, this::onReplayOpened); }
+    private void onReplayOpened(ReplayHandler replayHandler) {
+        ReplayFile replayFile = replayHandler.getReplayFile();
         try {
             synchronized (replayFile) {
                 Timeline timeline = replayFile.getTimelines(new SPTimeline()).get("");
@@ -91,7 +110,7 @@ public class ReplayModSimplePathing implements Module {
             throw new ReportedException(CrashReport.makeCrashReport(e, "Reading timeline"));
         }
 
-        guiPathing = new GuiPathing(core, this, event.getReplayHandler());
+        guiPathing = new GuiPathing(core, this, replayHandler);
 
         saveService = Executors.newSingleThreadExecutor();
         new Runnable() {
@@ -105,8 +124,8 @@ public class ReplayModSimplePathing implements Module {
         }.run();
     }
 
-    @SubscribeEvent
-    public void preReplayClose(ReplayCloseEvent.Pre event) {
+    { on(ReplayClosingCallback.EVENT, replayHandler -> onReplayClosing()); }
+    private void onReplayClosing() {
         saveService.shutdown();
         try {
             saveService.awaitTermination(1, TimeUnit.MINUTES);
@@ -116,22 +135,13 @@ public class ReplayModSimplePathing implements Module {
         saveService = null;
     }
 
-    @SubscribeEvent
-    public void onReplayClose(ReplayCloseEvent.Post event) {
+    { on(ReplayClosedCallback.EVENT, replayHandler -> onReplayClosed()); }
+    private void onReplayClosed() {
         currentTimeline = null;
         guiPathing = null;
         selectedPath = null;
     }
 
-    @SubscribeEvent
-    public void onSettingsChanged(SettingsChangedEvent event) {
-        if (event.getKey() == Setting.DEFAULT_INTERPOLATION) {
-            if (currentTimeline != null && guiPathing != null) {
-                updateDefaultInterpolatorType();
-            }
-        }
-    }
-
     private GuiReplayOverlay getReplayOverlay() {
         return ReplayModReplay.instance.getReplayHandler().getOverlay();
     }
diff --git a/src/main/java/com/replaymod/simplepathing/preview/PathPreview.java b/src/main/java/com/replaymod/simplepathing/preview/PathPreview.java
index 578c5f9c..fb11f524 100644
--- a/src/main/java/com/replaymod/simplepathing/preview/PathPreview.java
+++ b/src/main/java/com/replaymod/simplepathing/preview/PathPreview.java
@@ -2,27 +2,16 @@ package com.replaymod.simplepathing.preview;
 
 import com.replaymod.core.KeyBindingRegistry;
 import com.replaymod.core.SettingsRegistry;
-import com.replaymod.core.events.SettingsChangedEvent;
+import com.replaymod.core.events.SettingsChangedCallback;
+import de.johni0702.minecraft.gui.utils.EventRegistrations;
+import com.replaymod.core.versions.MCVer.Keyboard;
 import com.replaymod.replay.ReplayHandler;
-import com.replaymod.replay.events.ReplayCloseEvent;
-import com.replaymod.replay.events.ReplayOpenEvent;
+import com.replaymod.replay.events.ReplayClosedCallback;
+import com.replaymod.replay.events.ReplayOpenedCallback;
 import com.replaymod.simplepathing.ReplayModSimplePathing;
 import com.replaymod.simplepathing.Setting;
 
-//#if MC>=11300
-import net.minecraftforge.eventbus.api.SubscribeEvent;
-//#else
-//$$ import org.lwjgl.input.Keyboard;
-//#if MC>=10800
-//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
-//#else
-//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent;
-//#endif
-//#endif
-
-import static com.replaymod.core.versions.MCVer.*;
-
-public class PathPreview {
+public class PathPreview extends EventRegistrations {
     private final ReplayModSimplePathing mod;
 
     private ReplayHandler replayHandler;
@@ -30,10 +19,22 @@ public class PathPreview {
 
     public PathPreview(ReplayModSimplePathing mod) {
         this.mod = mod;
-    }
 
-    public void register() {
-        FML_BUS.register(this);
+        on(SettingsChangedCallback.EVENT, (registry, key) -> {
+            if (key == Setting.PATH_PREVIEW) {
+                update();
+            }
+        });
+
+        on(ReplayOpenedCallback.EVENT, replayHandler -> {
+            this.replayHandler = replayHandler;
+            update();
+        });
+
+        on(ReplayClosedCallback.EVENT, replayHandler -> {
+            this.replayHandler = null;
+            update();
+        });
     }
 
     public void registerKeyBindings(KeyBindingRegistry registry) {
@@ -44,25 +45,6 @@ public class PathPreview {
         });
     }
 
-    @SubscribeEvent
-    public void onReplayOpen(ReplayOpenEvent.Post event) {
-        replayHandler = event.getReplayHandler();
-        update();
-    }
-
-    @SubscribeEvent
-    public void onReplayClose(ReplayCloseEvent.Pre event) {
-        replayHandler = null;
-        update();
-    }
-
-    @SubscribeEvent
-    public void onSettingsChanged(SettingsChangedEvent event) {
-        if (event.getKey() == Setting.PATH_PREVIEW) {
-            update();
-        }
-    }
-
     private void update() {
         if (mod.getCore().getSettingsRegistry().get(Setting.PATH_PREVIEW) && replayHandler != null) {
             if (renderer == null) {
diff --git a/src/main/java/com/replaymod/simplepathing/preview/PathPreviewRenderer.java b/src/main/java/com/replaymod/simplepathing/preview/PathPreviewRenderer.java
index 619f01a5..5fe142b3 100644
--- a/src/main/java/com/replaymod/simplepathing/preview/PathPreviewRenderer.java
+++ b/src/main/java/com/replaymod/simplepathing/preview/PathPreviewRenderer.java
@@ -15,25 +15,20 @@ import com.replaymod.replaystudio.util.Location;
 import com.replaymod.simplepathing.ReplayModSimplePathing;
 import com.replaymod.simplepathing.SPTimeline;
 import com.replaymod.simplepathing.gui.GuiPathing;
+import de.johni0702.minecraft.gui.utils.EventRegistrations;
 import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.GlStateManager;
 import net.minecraft.entity.Entity;
 import net.minecraft.util.ResourceLocation;
-import net.minecraftforge.client.event.RenderWorldLastEvent;
-import net.minecraftforge.common.MinecraftForge;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Triple;
 import org.lwjgl.opengl.GL11;
 
-//#if MC>=10800
-import net.minecraft.client.renderer.GlStateManager;
-//#if MC>=11300
+//#if MC>=11400
+//$$ import com.replaymod.core.events.PostRenderWorldCallback;
+//#else
+import net.minecraftforge.client.event.RenderWorldLastEvent;
 import net.minecraftforge.eventbus.api.SubscribeEvent;
-//#else
-//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
-//#endif
-//#else
-//$$ import com.replaymod.core.versions.MCVer.GlStateManager;
-//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent;
 //#endif
 
 import java.util.Comparator;
@@ -42,7 +37,7 @@ import java.util.Optional;
 import static com.replaymod.core.ReplayMod.TEXTURE;
 import static com.replaymod.core.versions.MCVer.*;
 
-public class PathPreviewRenderer {
+public class PathPreviewRenderer extends EventRegistrations {
     private static final ResourceLocation CAMERA_HEAD = new ResourceLocation("replaymod", "camera_head.png");
     private static final Minecraft mc = MCVer.getMinecraft();
 
@@ -58,16 +53,13 @@ public class PathPreviewRenderer {
         this.replayHandler = replayHandler;
     }
 
-    public void register() {
-        MinecraftForge.EVENT_BUS.register(this);
-    }
-
-    public void unregister() {
-        MinecraftForge.EVENT_BUS.unregister(this);
-    }
-
+    //#if MC>=11400
+    //$$ { on(PostRenderWorldCallback.EVENT, this::renderCameraPath); }
+    //$$ private void renderCameraPath() {
+    //#else
     @SubscribeEvent
     public void renderCameraPath(RenderWorldLastEvent event) {
+    //#endif
         if (!replayHandler.getReplaySender().isAsyncMode() || mc.gameSettings.hideGUI) return;
 
         Entity view = getRenderViewEntity(mc);
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
new file mode 100755
index 00000000..af9dde9c
--- /dev/null
+++ b/src/main/resources/fabric.mod.json
@@ -0,0 +1,39 @@
+{
+  "schemaVersion": 1,
+  "id": "replaymod",
+  "version": "${version}",
+
+  "name": "Replay Mod",
+  "description": "A Mod which allows you to record, replay and share your Minecraft experience.",
+  "authors": [
+    "CrushedPixel",
+    "johni0702"
+  ],
+  "contact": {
+    "homepage": "https://replaymod.com/",
+    "sources": "https://github.com/ReplayMod/ReplayMod"
+  },
+
+  "license": "GPL-3.0-or-later",
+  "icon": "replaymod_logo.png",
+
+  "environment": "client",
+  "entrypoints": {
+    "client": [
+      "com.replaymod.core.ReplayMod"
+    ]
+  },
+  "mixins": [
+    "mixins.core.replaymod.json",
+    "mixins.extras.playeroverview.replaymod.json",
+    "mixins.recording.replaymod.json",
+    "mixins.render.blend.replaymod.json",
+    "mixins.render.replaymod.json",
+    "mixins.replay.replaymod.json"
+  ],
+
+  "requires": {
+    "fabricloader": ">=0.4.0",
+    "fabric": ">=0.2.0"
+  }
+}
diff --git a/src/main/resources/mixins.core.replaymod.json b/src/main/resources/mixins.core.replaymod.json
index 9e061afc..4418b746 100644
--- a/src/main/resources/mixins.core.replaymod.json
+++ b/src/main/resources/mixins.core.replaymod.json
@@ -4,11 +4,22 @@
   "mixins": [],
   "server": [],
   "client": [
+    //#if MC>=11400
+    //$$ "AbstractButtonWidgetAccessor",
+    //$$ "MixinGameRenderer",
+    //$$ "MixinScreen",
+    //$$ "MixinModResourcePackUtil",
+    //$$ "MixinMouseListener",
+    //#endif
+    //#if MC>=11300
+    "MixinKeyboardListener",
+    "MixinMinecraft",
+    //#endif
     "GuiMainMenuAccessor",
+    "GuiScreenAccessor",
     "KeyBindingAccessor",
     "MinecraftAccessor",
-    "TimerAccessor",
-    "MixinMinecraft"
+    "TimerAccessor"
   ],
   "compatibilityLevel": "JAVA_8",
   "minVersion": "0.6.11",
diff --git a/src/main/resources/mixins.recording.replaymod.json b/src/main/resources/mixins.recording.replaymod.json
index bd86abd8..89fd886a 100644
--- a/src/main/resources/mixins.recording.replaymod.json
+++ b/src/main/resources/mixins.recording.replaymod.json
@@ -6,11 +6,11 @@
   "client": [
     "EntityLivingBaseAccessor",
     "IntegratedServerAccessor",
+    "NetworkManagerAccessor",
     "SPacketSpawnMobAccessor",
     "SPacketSpawnPlayerAccessor",
     //#if MC>=11300
     "MixinDownloadingPackFinder",
-    "MixinKeyboardListener",
     "MixinMouseHelper",
     //#endif
     //#if MC>=10904
diff --git a/src/main/resources/mixins.render.replaymod.json b/src/main/resources/mixins.render.replaymod.json
index 63ae6e64..635b6f35 100644
--- a/src/main/resources/mixins.render.replaymod.json
+++ b/src/main/resources/mixins.render.replaymod.json
@@ -17,6 +17,7 @@
     //$$ "MixinEffectRenderer",
     //$$ "MixinRendererLivingEntity",
     //#endif
+    "MixinCamera",
     "MixinEntityRenderer",
     //#if MC>=11300
     "MixinFogRenderer",
diff --git a/src/main/resources/mixins.replay.replaymod.json b/src/main/resources/mixins.replay.replaymod.json
index 0c2e9cb1..204f125e 100644
--- a/src/main/resources/mixins.replay.replaymod.json
+++ b/src/main/resources/mixins.replay.replaymod.json
@@ -4,6 +4,10 @@
   "mixins": [],
   "server": [],
   "client": [
+    //#if MC>=11400
+    //$$ "MixinCamera",
+    //$$ "MixinInGameHud",
+    //#endif
     "EntityLivingBaseAccessor",
     "EntityPlayerAccessor",
     "FirstPersonRendererAccessor",
@@ -12,8 +16,13 @@
     //#endif
     //#if MC>=10800
     "MixinGuiSpectator",
+    //#if MC>=11300
+    "MixinKeyboardListener",
+    //#endif
     "MixinRenderArrow",
+    //#if MC<11400
     "MixinRenderManager",
+    //#endif
     "MixinViewFrustum",
     //#endif
     "MixinPlayerControllerMP",
diff --git a/versions/1.11/mapping.txt b/versions/1.11/mapping.txt
new file mode 100644
index 00000000..1d3d4e24
--- /dev/null
+++ b/versions/1.11/mapping.txt
@@ -0,0 +1 @@
+net.minecraft.client.renderer.chunk.RenderChunk setPosition() setOrigin()
\ No newline at end of file
diff --git a/versions/1.12/mapping.txt b/versions/1.12/mapping.txt
index 3b8fdbb8..d409c7fb 100644
--- a/versions/1.12/mapping.txt
+++ b/versions/1.12/mapping.txt
@@ -1,4 +1,7 @@
 net.minecraft.client.Minecraft fontRenderer fontRendererObj
 net.minecraft.client.renderer.BufferBuilder net.minecraft.client.renderer.VertexBuffer
 net.minecraft.client.gui.GuiButton x xPosition
-net.minecraft.client.gui.GuiButton y yPosition
\ No newline at end of file
+net.minecraft.client.gui.GuiButton y yPosition
+net.minecraft.util.math.Vec3d x xCoord
+net.minecraft.util.math.Vec3d y yCoord
+net.minecraft.util.math.Vec3d z zCoord
\ No newline at end of file
diff --git a/versions/1.13.2/mapping.txt b/versions/1.13.2/mapping.txt
index 3ec3d4da..2fdc5f98 100644
--- a/versions/1.13.2/mapping.txt
+++ b/versions/1.13.2/mapping.txt
@@ -1,3 +1,4 @@
+net.minecraft.client.renderer.GlStateManager activeTexture activeTextureUnit
 net.minecraft.client.renderer.FirstPersonRenderer net.minecraft.client.renderer.ItemRenderer
 net.minecraft.client.renderer.chunk.ChunkRenderTask net.minecraft.client.renderer.chunk.ChunkCompileTaskGenerator
 net.minecraft.client.GameSettings net.minecraft.client.settings.GameSettings
@@ -22,4 +23,5 @@ net.minecraft.world.World getChunk() getChunkFromChunkCoords()
 net.minecraft.util.text.ITextComponent getString() getUnformattedText()
 net.minecraft.network.play.server.SPacketRespawn func_212643_b() getDimensionID()
 net.minecraft.client.renderer.GameRenderer net.minecraft.client.renderer.EntityRenderer
-net.minecraft.client.renderer.WorldRenderer net.minecraft.client.renderer.RenderGlobal
\ No newline at end of file
+net.minecraft.client.renderer.WorldRenderer net.minecraft.client.renderer.RenderGlobal
+net.minecraft.client.gui.GuiScreen buttons buttonList
\ No newline at end of file
diff --git a/versions/1.14/mapping.txt b/versions/1.14/mapping.txt
new file mode 100644
index 00000000..34936247
--- /dev/null
+++ b/versions/1.14/mapping.txt
@@ -0,0 +1,558 @@
+net.minecraft.client.particle.Particle x posX
+net.minecraft.client.particle.Particle y posY
+net.minecraft.client.particle.Particle z posZ
+net.minecraft.client.particle.Particle cameraX interpPosX
+net.minecraft.client.particle.Particle cameraY interpPosY
+net.minecraft.client.particle.Particle cameraZ interpPosZ
+net.minecraft.client.particle.Particle buildGeometry() renderParticle()
+net.minecraft.client.render.entity.PlayerModelPart net.minecraft.entity.player.EnumPlayerModelParts
+net.minecraft.client.render.entity.PlayerModelPart HEAD HAT
+net.minecraft.entity.effect.StatusEffects net.minecraft.init.MobEffects
+net.minecraft.util.math.Direction net.minecraft.util.EnumFacing
+net.minecraft.client.render.model.BakedQuad net.minecraft.client.renderer.model.BakedQuad
+net.minecraft.client.render.model.BakedQuad hasColor() hasTintIndex()
+net.minecraft.client.render.model.BakedQuad getColorIndex() getTintIndex()
+net.minecraft.client.gl.GlBuffer net.minecraft.client.renderer.vertex.VertexBuffer
+net.minecraft.client.gl.GlBuffer bind() bindBuffer()
+net.minecraft.client.gl.GlBuffer unbind() unbindBuffer()
+net.minecraft.block.BlockRenderLayer net.minecraft.util.BlockRenderLayer
+net.minecraft.client.render.block.entity.BlockEntityRenderDispatcher net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher
+net.minecraft.client.render.model.BakedModel net.minecraft.client.renderer.model.IBakedModel
+net.minecraft.client.render.chunk.ChunkRenderData net.minecraft.client.renderer.chunk.CompiledChunk
+net.minecraft.client.render.chunk.ChunkRenderData isBufferInitialized() isLayerStarted()
+net.minecraft.client.util.math.Matrix4f net.minecraft.client.renderer.Matrix4f
+net.minecraft.client.util.math.Matrix4f method_4929() perspective()
+net.minecraft.client.render.Frustum net.minecraft.client.renderer.culling.Frustum
+net.minecraft.client.render.Frustum intersects() isBoxInFrustum()
+net.minecraft.client.texture.NativeImage net.minecraft.client.renderer.texture.NativeImage
+net.minecraft.client.render.chunk.BlockLayeredBufferBuilder net.minecraft.client.renderer.RegionRenderCacheBuilder
+net.minecraft.client.render.chunk.ChunkRenderWorker net.minecraft.client.renderer.chunk.ChunkRenderWorker
+net.minecraft.client.render.chunk.ChunkRenderWorker runTask() processTask()
+net.minecraft.client.render.chunk.ChunkRenderTask net.minecraft.client.renderer.chunk.ChunkRenderTask
+net.minecraft.client.render.chunk.ChunkRenderTask cancel() finish()
+net.minecraft.client.render.chunk.ChunkRenderTask getStage() getStatus()
+net.minecraft.client.render.chunk.ChunkRenderTask getMode() getType()
+net.minecraft.client.render.chunk.ChunkRenderTask getChunkRenderer() getRenderChunk()
+net.minecraft.client.render.chunk.ChunkRenderTask getRenderData() getCompiledChunk()
+net.minecraft.client.render.chunk.ChunkRenderTask.Stage net.minecraft.client.renderer.chunk.ChunkRenderTask.Status
+net.minecraft.client.render.chunk.ChunkRenderTask.Mode net.minecraft.client.renderer.chunk.ChunkRenderTask.Type
+net.minecraft.client.render.chunk.ChunkBatcher net.minecraft.client.renderer.chunk.ChunkRenderDispatcher
+net.minecraft.client.render.chunk.ChunkBatcher workers listThreadedWorkers
+net.minecraft.client.render.chunk.ChunkBatcher pendingChunks queueChunkUpdates
+net.minecraft.client.render.chunk.ChunkBatcher method_3631() runChunkUploads()
+net.minecraft.client.render.chunk.ChunkBatcher method_3627() updateChunkNow()
+net.minecraft.client.render.chunk.ChunkBatcher method_3619() stopWorkerThreads()
+net.minecraft.client.particle.ParticleManager newParticles queue
+net.minecraft.client.particle.ParticleManager setWorld() clearEffects()
+net.minecraft.client.util.Session net.minecraft.util.Session
+net.minecraft.client.util.Session getAccessToken() getToken()
+net.minecraft.server.integrated.IntegratedServer field_5524 isGamePaused
+net.minecraft.client.util.ScreenshotUtils net.minecraft.util.ScreenShotHelper
+net.minecraft.client.util.ScreenshotUtils method_1662() saveScreenshot()
+net.minecraft.client.render.ChunkRenderDispatcher net.minecraft.client.renderer.ViewFrustum
+net.minecraft.client.render.ChunkRenderDispatcher updateCameraPosition() updateChunkPositions()
+net.minecraft.client.render.block.entity.EndPortalBlockEntityRenderer net.minecraft.client.renderer.tileentity.TileEntityEndPortalRenderer
+# Note: method actually part of super class but Remap doesn't support those for @Redirect
+net.minecraft.client.render.block.entity.EndPortalBlockEntityRenderer method_3591() render()
+net.minecraft.client.render.block.entity.BlockEntityRenderer net.minecraft.client.renderer.tileentity.TileEntityRenderer
+net.minecraft.client.render.block.entity.BlockEntityRenderer method_3591() render()
+net.minecraft.client.gui.hud.SpectatorHud net.minecraft.client.gui.GuiSpectator
+net.minecraft.client.gui.hud.SpectatorHud method_1976() onHotbarSelected()
+net.minecraft.entity.passive.AnimalEntity net.minecraft.entity.passive.EntityAnimal
+net.minecraft.client.render.item.ItemRenderer net.minecraft.client.renderer.ItemRenderer
+net.minecraft.client.render.item.ItemRenderer colorMap itemColors
+net.minecraft.client.render.item.ItemRenderer renderGlint() renderEffect()
+net.minecraft.client.render.item.ItemColorMap net.minecraft.client.renderer.color.ItemColors
+net.minecraft.client.render.item.ItemColorMap getRenderColor() getColor()
+net.minecraft.client.gui.Element net.minecraft.client.gui.IGuiEventListener
+net.minecraft.text.TextComponent net.minecraft.util.text.ITextComponent
+net.minecraft.text.TextComponent append() appendSibling()
+net.minecraft.text.Style net.minecraft.util.text.Style
+net.minecraft.text.StringTextComponent net.minecraft.util.text.TextComponentString
+net.minecraft.text.TranslatableTextComponent net.minecraft.util.text.TextComponentTranslation
+net.minecraft.text.TextFormat net.minecraft.util.text.TextFormatting
+net.minecraft.client.render.VisibleRegion net.minecraft.client.renderer.culling.ICamera
+net.minecraft.client.render.entity.LivingEntityRenderer net.minecraft.client.renderer.entity.RenderLivingBase
+net.minecraft.client.render.entity.LivingEntityRenderer method_4055() canRenderName()
+net.minecraft.client.render.entity.LivingEntityRenderer render() renderModel()
+net.minecraft.client.render.entity.LivingEntityRenderer scaleAndTranslate() prepareScale()
+net.minecraft.client.render.entity.LivingEntityRenderer method_4054() doRender()
+net.minecraft.client.render.entity.ArrowEntityRenderer net.minecraft.client.renderer.entity.RenderArrow
+net.minecraft.client.render.entity.EntityRenderer net.minecraft.client.renderer.entity.Render
+net.minecraft.client.render.entity.EntityRenderer isVisible() shouldRender()
+net.minecraft.client.render.entity.EntityRenderer render() doRender()
+net.minecraft.client.render.entity.EntityRenderer renderLabel() renderLivingLabel()
+net.minecraft.client.network.ClientPlayerInteractionManager net.minecraft.client.multiplayer.PlayerControllerMP
+net.minecraft.client.network.ClientPlayerInteractionManager client mc
+net.minecraft.client.network.ClientPlayerInteractionManager networkHandler connection
+net.minecraft.client.network.ClientPlayerInteractionManager isFlyingLocked() isSpectatorMode()
+com.mojang.blaze3d.platform.GLX net.minecraft.client.renderer.OpenGlHelper
+com.mojang.blaze3d.platform.GLX isUsingFBOs() isFramebufferEnabled()
+net.minecraft.entity.data.TrackedData net.minecraft.network.datasync.DataParameter
+net.minecraft.client.render.chunk.ChunkRenderer net.minecraft.client.renderer.chunk.RenderChunk
+net.minecraft.client.render.chunk.ChunkRenderer method_3653() setPosition()
+net.minecraft.client.render.chunk.ChunkRenderer getOrigin() getPosition()
+net.minecraft.client.render.chunk.ChunkRenderer getChunkRenderData() getCompiledChunk()
+net.minecraft.client.render.chunk.ChunkRenderer getGlBuffer() getVertexBufferByLayer()
+net.minecraft.client.render.chunk.ChunkRenderer scheduleRender() setNeedsUpdate()
+net.minecraft.client.render.chunk.ChunkRenderer method_3662() clearNeedsUpdate()
+net.minecraft.client.util.GlAllocationUtils net.minecraft.client.renderer.GLAllocation
+net.minecraft.client.util.GlAllocationUtils allocateFloatBuffer() createDirectFloatBuffer()
+net.minecraft.client.util.GlAllocationUtils allocateByteBuffer() createDirectByteBuffer()
+net.minecraft.nbt.CompoundTag net.minecraft.nbt.NBTTagCompound
+net.minecraft.block.entity.BlockEntity net.minecraft.tileentity.TileEntity
+net.minecraft.block.entity.BlockEntity toTag() write()
+net.minecraft.client.render.BackgroundRenderer net.minecraft.client.renderer.FogRenderer
+net.minecraft.client.render.BackgroundRenderer applyFog() setupFog()
+net.minecraft.resource.Resource net.minecraft.resources.IResource
+net.minecraft.client.render.FirstPersonRenderer net.minecraft.client.renderer.FirstPersonRenderer
+net.minecraft.client.render.FirstPersonRenderer mainHand itemStackMainHand
+net.minecraft.client.render.FirstPersonRenderer offHand itemStackOffHand
+net.minecraft.client.render.FirstPersonRenderer equipProgressMainHand equippedProgressMainHand
+net.minecraft.client.render.FirstPersonRenderer equipProgressOffHand equippedProgressOffHand
+net.minecraft.client.render.FirstPersonRenderer prevEquipProgressMainHand prevEquippedProgressMainHand
+net.minecraft.client.render.FirstPersonRenderer prevEquipProgressOffHand prevEquippedProgressOffHand
+net.minecraft.client.gl.GlFramebuffer net.minecraft.client.shader.Framebuffer
+net.minecraft.client.gl.GlFramebuffer viewWidth framebufferWidth
+net.minecraft.client.gl.GlFramebuffer viewHeight framebufferHeight
+net.minecraft.client.gl.GlFramebuffer beginWrite() bindFramebuffer()
+net.minecraft.client.gl.GlFramebuffer endWrite() unbindFramebuffer()
+net.minecraft.client.gl.GlFramebuffer draw() framebufferRender()
+net.minecraft.client.gl.GlFramebuffer resize() createBindFramebuffer()
+net.minecraft.client.gl.GlFramebuffer initFbo() createFramebuffer()
+net.minecraft.client.gui.menu.PauseMenuScreen net.minecraft.client.gui.GuiIngameMenu
+net.minecraft.client.gui.menu.MultiplayerScreen net.minecraft.client.gui.GuiMultiplayer
+net.minecraft.client.gui.menu.LevelSelectScreen net.minecraft.client.gui.GuiWorldSelection
+net.minecraft.entity.data.DataTracker net.minecraft.network.datasync.EntityDataManager
+net.minecraft.entity.data.DataTracker startTracking() register()
+net.minecraft.entity.data.DataTracker.Entry net.minecraft.network.datasync.EntityDataManager.DataEntry
+net.minecraft.entity.data.DataTracker.Entry getData() getKey()
+net.minecraft.entity.data.DataTracker.Entry get() getValue()
+# Note: GuiButton has been split into ButtonWidget and AbstractButtonWidget (now shared with e.g. checkboxes),
+#       most members are actually on the latter
+net.minecraft.client.gui.widget.ButtonWidget net.minecraft.client.gui.GuiButton
+net.minecraft.client.gui.widget.AbstractButtonWidget active net.minecraft.client.gui.GuiButton enabled
+net.minecraft.client.gui.MainMenuScreen net.minecraft.client.gui.GuiMainMenu
+net.minecraft.client.gui.MainMenuScreen realmsNotificationGui realmsNotification
+net.minecraft.realms.RealmsScreenProxy net.minecraft.client.gui.GuiScreenRealmsProxy
+net.minecraft.resource.ResourceNotFoundException net.minecraft.resources.ResourcePackFileNotFoundException
+net.minecraft.resource.ResourcePack net.minecraft.resources.IResourcePack
+net.minecraft.resource.ResourcePack findResources() getAllResourceLocations()
+net.minecraft.resource.ResourcePack getNamespaces() getResourceNamespaces()
+net.minecraft.resource.ResourceType net.minecraft.resources.ResourcePackType
+net.minecraft.resource.ResourceType ASSETS CLIENT_RESOURCES
+net.minecraft.network.NetworkSide net.minecraft.network.EnumPacketDirection
+net.minecraft.network.NetworkSide CLIENT CLIENTBOUND
+net.minecraft.network.NetworkState net.minecraft.network.EnumConnectionState
+net.minecraft.network.NetworkState getPacketHandler() getPacket()
+net.minecraft.network.Packet read() readPacketData()
+net.minecraft.network.Packet write() writePacketData()
+net.minecraft.world.GameMode net.minecraft.world.GameType
+net.minecraft.client.gui.menu.DownloadingTerrainScreen net.minecraft.client.gui.GuiDownloadTerrain
+net.minecraft.client.gui.menu.NoticeScreen net.minecraft.client.gui.GuiErrorScreen
+net.minecraft.client.network.OtherClientPlayerEntity net.minecraft.client.entity.EntityOtherPlayerMP
+net.minecraft.world.Difficulty net.minecraft.world.EnumDifficulty
+net.minecraft.world.chunk.ChunkProvider net.minecraft.world.chunk.IChunkProvider
+net.minecraft.sound.SoundCategory net.minecraft.util.SoundCategory
+net.minecraft.util.profiler.Profiler net.minecraft.profiler.Profiler
+net.minecraft.client.render.GameRenderer net.minecraft.client.renderer.GameRenderer
+net.minecraft.client.render.GameRenderer firstPersonRenderer itemRenderer
+net.minecraft.client.render.GameRenderer client mc
+net.minecraft.client.render.GameRenderer render() updateCameraAndRender()
+net.minecraft.client.render.GameRenderer applyCameraTransformations() setupCameraTransform()
+net.minecraft.client.render.GameRenderer renderAboveClouds() renderCloudsCheck()
+net.minecraft.client.render.WorldRenderer net.minecraft.client.renderer.WorldRenderer
+net.minecraft.client.render.WorldRenderer setBlockBreakingProgress() sendBlockBreakProgress()
+net.minecraft.client.render.WorldRenderer drawHighlightedBlockOutline() drawSelectionBox()
+net.minecraft.client.render.WorldRenderer chunkBatcher renderDispatcher
+net.minecraft.client.render.WorldRenderer chunkInfos renderInfos
+net.minecraft.client.render.WorldRenderer chunkRenderers chunksToUpdate
+net.minecraft.client.render.WorldRenderer terrainUpdateNecessary displayListEntitiesDirty
+net.minecraft.client.render.WorldRenderer field_4076 renderEntitiesStartupCounter
+net.minecraft.client.render.WorldRenderer setUpTerrain() setupTerrain()
+net.minecraft.client.render.WorldRenderer setWorld() setWorldAndLoadRenderers()
+net.minecraft.client.render.WorldRenderer reload() loadRenderers()
+net.minecraft.world.level.LevelProperties net.minecraft.world.storage.WorldInfo
+com.mojang.blaze3d.platform.GlStateManager popAttributes() popAttrib()
+com.mojang.blaze3d.platform.GlStateManager disableColorLogicOp() disableColorLogic()
+com.mojang.blaze3d.platform.GlStateManager disableTexture() disableTexture2D()
+com.mojang.blaze3d.platform.GlStateManager enableColorLogicOp() enableColorLogic()
+com.mojang.blaze3d.platform.GlStateManager enableTexture() enableTexture2D()
+com.mojang.blaze3d.platform.GlStateManager net.minecraft.client.renderer.GlStateManager
+com.mojang.blaze3d.platform.GlStateManager.CapabilityTracker net.minecraft.client.renderer.GlStateManager.BooleanState
+com.mojang.blaze3d.platform.GlStateManager.FogState net.minecraft.client.renderer.GlStateManager.FogState
+com.mojang.blaze3d.platform.GlStateManager.FogState capState fog
+com.mojang.blaze3d.platform.GlStateManager.Texture2DState net.minecraft.client.renderer.GlStateManager.Texture2DState
+com.mojang.blaze3d.platform.GlStateManager.Texture2DState capState texture2DState
+net.minecraft.SharedConstants isValidChar() isAllowedCharacter()
+net.minecraft.SharedConstants net.minecraft.util.SharedConstants
+net.minecraft.server.MinecraftServer getLevelName() getWorldName()
+net.minecraft.client.Keyboard onKey() onKeyEvent()
+net.minecraft.client.Keyboard pollDebugCrash() tick()
+net.minecraft.client.Keyboard getClipboard() getClipboardString()
+net.minecraft.client.Keyboard net.minecraft.client.KeyboardListener
+net.minecraft.client.Keyboard setClipboard() setClipboardString()
+net.minecraft.client.MinecraftClient crashed hasCrashed
+net.minecraft.client.MinecraftClient setCrashReport() crashed()
+net.minecraft.client.MinecraftClient crashReport crashReporter
+net.minecraft.client.MinecraftClient targetedEntity pointedEntity
+net.minecraft.client.MinecraftClient fontRenderer textRenderer
+net.minecraft.client.MinecraftClient gameRenderer entityRenderer
+net.minecraft.client.MinecraftClient options gameSettings
+net.minecraft.client.MinecraftClient getSoundManager() getSoundHandler()
+net.minecraft.client.MinecraftClient inGameHud ingameGUI
+net.minecraft.client.MinecraftClient keyboard keyboardListener
+net.minecraft.client.MinecraftClient mouse mouseHelper
+net.minecraft.client.MinecraftClient getResourcePackDownloader() getPackFinder()
+net.minecraft.client.MinecraftClient net.minecraft.client.Minecraft
+net.minecraft.client.MinecraftClient openScreen() displayGuiScreen()
+net.minecraft.client.MinecraftClient getServer() getIntegratedServer()
+net.minecraft.client.MinecraftClient getEntityRenderManager() getRenderManager()
+net.minecraft.client.MinecraftClient resourcePacketContainerManager resourcePackRepository
+net.minecraft.client.MinecraftClient runDirectory gameDir
+net.minecraft.client.MinecraftClient textRenderer fontRenderer
+net.minecraft.client.MinecraftClient window mainWindow
+net.minecraft.client.MinecraftClient worldRenderer renderGlobal
+net.minecraft.client.MinecraftClient getCameraEntity() getRenderViewEntity()
+net.minecraft.client.MinecraftClient setCameraEntity() setRenderViewEntity()
+net.minecraft.client.MinecraftClient renderTickCounter timer
+net.minecraft.client.MinecraftClient getNetworkHandler() getConnection()
+net.minecraft.client.MinecraftClient getCurrentServerEntry() getCurrentServerData()
+net.minecraft.client.MinecraftClient joinWorld() loadWorld()
+net.minecraft.client.MinecraftClient tick() runTick()
+net.minecraft.client.MinecraftClient render() runGameLoop()
+net.minecraft.client.MinecraftClient handleInputEvents() processKeyBinds()
+net.minecraft.client.MinecraftClient method_1520() getResourcePackList()
+net.minecraft.client.options.ServerList net.minecraft.client.multiplayer.ServerList
+net.minecraft.client.options.ServerList updateServerListEntry() saveSingleServer()
+net.minecraft.client.options.ServerEntry net.minecraft.client.multiplayer.ServerData
+net.minecraft.client.options.ServerEntry getResourcePack() getResourceMode()
+net.minecraft.client.options.ServerEntry setResourcePackState() setResourceMode()
+net.minecraft.client.options.ServerEntry address serverIP
+net.minecraft.client.options.ServerEntry.ResourcePackState net.minecraft.client.multiplayer.ServerData.ServerResourceMode
+net.minecraft.client.gui.menu.YesNoScreen net.minecraft.client.gui.GuiYesNo
+net.minecraft.client.Mouse getX() getMouseX()
+net.minecraft.client.Mouse getY() getMouseY()
+net.minecraft.client.Mouse net.minecraft.client.MouseHelper
+net.minecraft.client.Mouse isCursorLocked mouseGrabbed
+net.minecraft.client.Mouse lockCursor() grabMouse()
+net.minecraft.client.Mouse unlockCursor() ungrabMouse()
+net.minecraft.client.Mouse isCursorLocked() isMouseGrabbed()
+net.minecraft.client.Mouse onMouseScroll() scrollCallback()
+net.minecraft.client.audio.PositionedSoundInstance master() getMasterRecord()
+net.minecraft.client.audio.PositionedSoundInstance net.minecraft.client.audio.SimpleSound
+net.minecraft.client.font.TextRenderer draw() drawString()
+net.minecraft.client.font.TextRenderer drawWithShadow() drawStringWithShadow()
+net.minecraft.client.font.TextRenderer fontHeight FONT_HEIGHT
+net.minecraft.client.font.TextRenderer net.minecraft.client.gui.FontRenderer
+net.minecraft.client.font.TextRenderer trimToWidth() trimStringToWidth()
+net.minecraft.client.font.TextRenderer wrapStringToWidthAsList() listFormattedStringToWidth()
+net.minecraft.client.gui.DrawableHelper fill() drawRect()
+net.minecraft.client.gui.DrawableHelper net.minecraft.client.gui.Gui
+net.minecraft.client.gui.Screen hasControlDown() isCtrlKeyDown()
+net.minecraft.client.gui.Screen hasShiftDown() isShiftKeyDown()
+net.minecraft.client.gui.Screen init() setWorldAndResolution()
+net.minecraft.client.gui.Screen minecraft mc
+net.minecraft.client.gui.Screen net.minecraft.client.gui.GuiScreen
+net.minecraft.client.gui.Screen passEvents allowUserInput
+net.minecraft.client.gui.Screen removed() onGuiClosed()
+net.minecraft.client.gui.Screen renderBackground() drawDefaultBackground()
+net.minecraft.client.gui.Screen renderDirtBackground() drawBackground()
+net.minecraft.client.gui.hud.ChatHud addMessage() printChatMessage()
+net.minecraft.client.gui.hud.ChatHud clear() clearChatMessages()
+net.minecraft.client.gui.hud.ChatHud net.minecraft.client.gui.GuiNewChat
+net.minecraft.client.gui.hud.InGameHud getChatHud() getChatGUI()
+net.minecraft.client.gui.hud.InGameHud net.minecraft.client.gui.GuiIngame
+net.minecraft.client.options.GameOptions net.minecraft.client.GameSettings
+net.minecraft.client.options.GameOptions debugEnabled showDebugInfo
+net.minecraft.client.options.GameOptions viewDistance renderDistanceChunks
+net.minecraft.client.options.GameOptions hudHidden hideGUI
+net.minecraft.client.options.GameOptions gamma gammaSetting
+net.minecraft.client.options.GameOptions keysAll keyBindings
+net.minecraft.client.options.GameOptions keyChat keyBindChat
+net.minecraft.client.options.GameOptions keyCommand keyBindCommand
+net.minecraft.client.options.GameOptions keyAttack keyBindAttack
+net.minecraft.client.options.GameOptions keyUse keyBindUseItem
+net.minecraft.client.options.GameOptions keySneak keyBindSneak
+net.minecraft.client.options.GameOptions keyForward keyBindForward
+net.minecraft.client.options.GameOptions keyBack keyBindBack
+net.minecraft.client.options.GameOptions keyLeft keyBindLeft
+net.minecraft.client.options.GameOptions keyRight keyBindRight
+net.minecraft.client.options.GameOptions keyJump keyBindJump
+net.minecraft.client.options.GameOptions getSoundVolume() getSoundLevel()
+net.minecraft.client.options.GameOptions setSoundVolume() setSoundLevel()
+net.minecraft.client.options.KeyBinding net.minecraft.client.settings.KeyBinding
+net.minecraft.client.options.KeyBinding timesPressed pressTime
+net.minecraft.client.options.KeyBinding wasPressed() isPressed()
+net.minecraft.client.options.KeyBinding isPressed() isKeyDown()
+net.minecraft.client.options.KeyBinding getId() getKeyDescription()
+net.minecraft.client.options.KeyBinding getLocalizedName() func_197978_k()
+net.minecraft.client.options.KeyBinding onKeyPressed() onTick()
+net.minecraft.client.render.BufferBuilder net.minecraft.client.renderer.BufferBuilder
+net.minecraft.client.render.BufferBuilder next() endVertex()
+net.minecraft.client.render.BufferBuilder vertex() pos()
+net.minecraft.client.render.BufferBuilder texture() tex()
+net.minecraft.client.render.BufferBuilder setOffset() setTranslation()
+net.minecraft.client.render.BufferBuilder end() finishDrawing()
+net.minecraft.client.render.Tessellator getBufferBuilder() getBuffer()
+net.minecraft.client.render.Tessellator net.minecraft.client.renderer.Tessellator
+net.minecraft.client.render.VertexFormat net.minecraft.client.renderer.vertex.VertexFormat
+net.minecraft.client.render.VertexFormat getVertexSize() getSize()
+net.minecraft.client.render.VertexFormat getElementOffset() getOffset()
+net.minecraft.client.render.VertexFormatElement net.minecraft.client.renderer.vertex.VertexFormatElement
+net.minecraft.client.render.VertexFormatElement getType() getUsage()
+net.minecraft.client.render.VertexFormatElement getFormat() getType()
+net.minecraft.client.render.VertexFormatElement.Format net.minecraft.client.renderer.vertex.VertexFormatElement.EnumType
+net.minecraft.client.render.VertexFormatElement.Format UNSIGNED_BYTE UBYTE
+net.minecraft.client.render.VertexFormats net.minecraft.client.renderer.vertex.DefaultVertexFormats
+net.minecraft.client.render.VertexFormats POSITION_COLOR_UV_LMAP BLOCK
+net.minecraft.client.render.VertexFormats POSITION_COLOR_UV_NORMAL ITEM
+net.minecraft.client.render.VertexFormats POSITION_UV_COLOR_LMAP PARTICLE_POSITION_TEX_COLOR_LMAP
+net.minecraft.client.render.VertexFormats POSITION_UV POSITION_TEX
+net.minecraft.client.render.VertexFormats POSITION_UV_COLOR POSITION_TEX_COLOR
+net.minecraft.client.resource.language.I18n net.minecraft.client.resources.I18n
+net.minecraft.client.resource.language.I18n translate format
+net.minecraft.client.resource.language.I18n translate() format()
+net.minecraft.client.texture.AbstractTexture clearGlId() deleteGlTexture()
+net.minecraft.client.texture.AbstractTexture net.minecraft.client.renderer.texture.AbstractTexture
+net.minecraft.client.texture.NativeImageBackedTexture getImage() getTextureData()
+net.minecraft.client.texture.NativeImageBackedTexture net.minecraft.client.renderer.texture.DynamicTexture
+net.minecraft.client.texture.NativeImageBackedTexture upload() updateDynamicTexture()
+net.minecraft.client.texture.Texture getGlId() getGlTextureId()
+net.minecraft.client.texture.Texture net.minecraft.client.renderer.texture.ITextureObject
+net.minecraft.client.util.Window getScaleFactor() getGuiScaleFactor()
+net.minecraft.client.util.Window setFullscreen() update()
+net.minecraft.client.util.Window method_4493() setupOverlayRendering()
+net.minecraft.client.util.Window net.minecraft.client.MainWindow
+net.minecraft.resource.DirectoryResourcePack net.minecraft.resources.FolderPack
+net.minecraft.resource.AbstractFilenameResourcePack net.minecraft.resources.AbstractResourcePack
+net.minecraft.resource.AbstractFilenameResourcePack openFilename() getInputStream()
+net.minecraft.resource.AbstractFilenameResourcePack containsFilename() resourceExists()
+net.minecraft.resource.AbstractFilenameResourcePack base file
+net.minecraft.resource.ResourcePackContainer net.minecraft.resources.ResourcePackInfo
+net.minecraft.resource.ResourcePackContainer.Factory net.minecraft.resources.ResourcePackInfo.IFactory
+net.minecraft.resource.ResourcePackContainer.SortingDirection net.minecraft.resources.ResourcePackInfo.Priority
+net.minecraft.resource.ResourcePackContainer of() func_195793_a()
+net.minecraft.resource.ResourcePackContainerManager net.minecraft.resources.ResourcePackList
+net.minecraft.resource.ResourcePackContainerManager addCreator() addPackFinder()
+net.minecraft.resource.ResourcePackCreator net.minecraft.resources.IPackFinder
+net.minecraft.resource.ResourcePackCreator registerContainer() addPackInfosToMap()
+net.minecraft.sound.SoundEvent net.minecraft.util.SoundEvent
+net.minecraft.sound.SoundEvents net.minecraft.init.SoundEvents
+net.minecraft.util.Identifier net.minecraft.util.ResourceLocation
+net.minecraft.util.crash.CrashException net.minecraft.crash.ReportedException
+net.minecraft.util.crash.CrashReport asString() getCompleteReport()
+net.minecraft.util.crash.CrashReport writeToFile() saveToFile()
+net.minecraft.util.crash.CrashReport addElement() makeCategory()
+net.minecraft.util.crash.CrashReport create() makeCrashReport()
+net.minecraft.util.crash.CrashReport net.minecraft.crash.CrashReport
+net.minecraft.util.crash.CrashReportSection add() addDetail()
+net.minecraft.util.crash.CrashReportSection net.minecraft.crash.CrashReportCategory
+net.minecraft.util.PacketByteBuf net.minecraft.network.PacketBuffer
+net.minecraft.network.ClientConnection net.minecraft.network.NetworkManager
+net.minecraft.network.ClientConnection ATTR_KEY_PROTOCOL PROTOCOL_ATTRIBUTE_KEY
+net.minecraft.network.ClientConnection send() sendPacket()
+net.minecraft.network.ClientConnection setPacketListener() setNetHandler()
+net.minecraft.network.ClientConnection isLocal() isLocalChannel()
+net.minecraft.world.World getGeneratorType() getWorldType()
+net.minecraft.world.World getChunkManager() getChunkProvider()
+net.minecraft.world.World getEntityById() getEntityByID()
+net.minecraft.world.EntityView getPlayerByUuid() net.minecraft.world.World getPlayerEntityByUUID()
+net.minecraft.world.level.LevelGeneratorType net.minecraft.world.WorldType
+net.minecraft.client.render.entity.EntityRenderDispatcher net.minecraft.client.renderer.entity.RenderManager
+net.minecraft.client.render.entity.EntityRenderDispatcher cameraYaw playerViewY
+net.minecraft.client.render.entity.EntityRenderDispatcher cameraPitch playerViewX
+net.minecraft.client.render.entity.EntityRenderDispatcher configure() cacheActiveRenderInfo()
+net.minecraft.client.render.entity.EntityRenderDispatcher render() renderEntityStatic()
+net.minecraft.util.SystemUtil net.minecraft.util.Util
+net.minecraft.util.SystemUtil getMeasuringTimeMs() milliTime()
+net.minecraft.util.SystemUtil getOperatingSystem() getOSType()
+net.minecraft.util.SystemUtil.OperatingSystem net.minecraft.util.Util.EnumOS
+net.minecraft.util.SystemUtil.OperatingSystem UNIX LINUX
+net.minecraft.util.SystemUtil.OperatingSystem MAC OSX
+net.minecraft.util.SystemUtil.OperatingSystem open() openFile()
+net.minecraft.client.util.InputUtil net.minecraft.client.util.InputMappings
+net.minecraft.client.util.InputUtil isKeyPressed() isKeyDown()
+net.minecraft.client.util.InputUtil$KeyCode net.minecraft.client.util.InputMappings$Input
+net.minecraft.client.resource.ClientResourcePackCreator net.minecraft.client.resources.DownloadingPackFinder
+net.minecraft.client.resource.ClientResourcePackCreator loadServerPack() func_195741_a()
+net.minecraft.client.resource.ClientResourcePackCreator download() downloadResourcePack()
+net.minecraft.client.network.ClientPlayerEntity net.minecraft.client.entity.EntityPlayerSP
+net.minecraft.client.network.ClientPlayerEntity client mc
+net.minecraft.client.network.ClientPlayerEntity renderYaw renderArmYaw
+net.minecraft.client.network.ClientPlayerEntity renderPitch renderArmPitch
+net.minecraft.client.network.ClientPlayerEntity lastRenderYaw prevRenderArmYaw
+net.minecraft.client.network.ClientPlayerEntity lastRenderPitch prevRenderArmPitch
+net.minecraft.client.network.AbstractClientPlayerEntity net.minecraft.client.entity.AbstractClientPlayer
+net.minecraft.client.network.AbstractClientPlayerEntity getSkinTexture() getLocationSkin()
+net.minecraft.client.network.AbstractClientPlayerEntity getModel() getSkinType()
+net.minecraft.client.network.ClientLoginNetworkHandler net.minecraft.client.network.NetHandlerLoginClient
+net.minecraft.client.network.ClientLoginNetworkHandler connection networkManager
+net.minecraft.client.network.ClientLoginNetworkHandler onQueryRequest() func_209521_a()
+net.minecraft.client.network.ClientLoginNetworkHandler onLoginSuccess() handleLoginSuccess()
+net.minecraft.client.network.ClientPlayNetworkHandler net.minecraft.client.network.NetHandlerPlayClient
+net.minecraft.client.network.ClientPlayNetworkHandler getClientConnection() getNetworkManager()
+net.minecraft.client.network.ClientPlayNetworkHandler playerListEntries playerInfoMap
+net.minecraft.client.network.ClientPlayNetworkHandler onPlayerList() handlePlayerListItem()
+net.minecraft.client.network.ClientPlayNetworkHandler onPlayerRespawn() handleRespawn()
+net.minecraft.client.network.ClientPlayNetworkHandler getPlayerListEntry() getPlayerInfo()
+net.minecraft.client.network.PlayerListEntry net.minecraft.client.network.NetworkPlayerInfo
+net.minecraft.client.network.PlayerListEntry hasSkinTexture() hasLocationSkin()
+net.minecraft.client.network.PlayerListEntry getSkinTexture() getLocationSkin()
+net.minecraft.client.util.DefaultSkinHelper net.minecraft.client.resources.DefaultPlayerSkin
+net.minecraft.client.util.DefaultSkinHelper getTexture() getDefaultSkin()
+net.minecraft.entity.decoration.ItemFrameEntity net.minecraft.entity.item.EntityItemFrame
+net.minecraft.entity.player.PlayerEntity net.minecraft.entity.player.EntityPlayer
+net.minecraft.entity.player.PlayerEntity field_7525 itemStackMainHand
+net.minecraft.entity.player.PlayerEntity isSleeping() isPlayerSleeping()
+net.minecraft.entity.player.PlayerEntity getEquippedStack() getItemStackFromSlot()
+net.minecraft.entity.player.PlayerEntity method_7279() getCooldownPeriod()
+net.minecraft.entity.player.PlayerEntity method_7261() getCooledAttackStrength()
+net.minecraft.entity.player.PlayerEntity isSkinOverlayVisible() isWearing()
+net.minecraft.entity.mob.MobEntity net.minecraft.entity.EntityLiving
+net.minecraft.entity.LivingEntity net.minecraft.entity.EntityLivingBase
+net.minecraft.entity.LivingEntity headYaw rotationYawHead
+net.minecraft.entity.LivingEntity prevHeadYaw prevRotationYawHead
+net.minecraft.entity.LivingEntity isHandSwinging isSwingInProgress
+net.minecraft.entity.LivingEntity handSwingTicks swingProgressInt
+net.minecraft.entity.LivingEntity preferredHand swingingHand
+net.minecraft.entity.LivingEntity itemUseTimeLeft activeItemStackUseCount
+net.minecraft.entity.LivingEntity field_6224 interpTargetX
+net.minecraft.entity.LivingEntity field_6245 interpTargetY
+net.minecraft.entity.LivingEntity field_6263 interpTargetZ
+net.minecraft.entity.LivingEntity field_6284 interpTargetYaw
+net.minecraft.entity.LivingEntity field_6221 interpTargetPitch
+net.minecraft.entity.LivingEntity getStackInHand() getHeldItem()
+net.minecraft.entity.LivingEntity getHandSwingProgress() getSwingProgress()
+net.minecraft.entity.LivingEntity isUsingItem() isHandActive()
+net.minecraft.entity.LivingEntity isSleeping() isPlayerSleeping()
+net.minecraft.entity.LivingEntity getStatusEffect() getActivePotionEffect()
+net.minecraft.entity.LivingEntity updateState() livingTick()
+net.minecraft.entity.Entity method_5823() preparePlayerToSpawn()
+net.minecraft.entity.Entity getVehicle() getRidingEntity()
+net.minecraft.entity.Entity isRiding() isPassenger()
+net.minecraft.entity.Entity getUuid() getUniqueID()
+net.minecraft.entity.Entity isInsideWall() isEntityInsideOpaqueBlock()
+net.minecraft.entity.Entity isOnFire() isBurning()
+net.minecraft.entity.Entity isPushable() canBePushed()
+net.minecraft.entity.Entity collides() canBeCollidedWith()
+net.minecraft.entity.Entity spawnSprintingParticles() createRunningParticles()
+net.minecraft.entity.Entity canSeePlayer() isInvisibleToPlayer()
+net.minecraft.entity.Entity getStandingEyeHeight() getEyeHeight()
+net.minecraft.entity.Entity field_6016 addedToChunk
+net.minecraft.entity.Entity chunkX chunkCoordX
+net.minecraft.entity.Entity chunkY chunkCoordY
+net.minecraft.entity.Entity chunkZ chunkCoordZ
+net.minecraft.entity.Entity x posX
+net.minecraft.entity.Entity y posY
+net.minecraft.entity.Entity z posZ
+net.minecraft.entity.Entity yaw rotationYaw
+net.minecraft.entity.Entity pitch rotationPitch
+net.minecraft.entity.Entity prevX prevPosX
+net.minecraft.entity.Entity prevY prevPosY
+net.minecraft.entity.Entity prevZ prevPosZ
+net.minecraft.entity.Entity prevYaw prevRotationYaw
+net.minecraft.entity.Entity prevPitch prevRotationPitch
+net.minecraft.entity.Entity prevRenderX lastTickPosX
+net.minecraft.entity.Entity prevRenderY lastTickPosY
+net.minecraft.entity.Entity prevRenderZ lastTickPosZ
+net.minecraft.world.chunk.WorldChunk net.minecraft.world.chunk.Chunk
+net.minecraft.world.chunk.WorldChunk getEntitySectionArray() getEntityLists()
+net.minecraft.util.math.BoundingBox net.minecraft.util.math.AxisAlignedBB
+net.minecraft.world.RayTraceContext net.minecraft.util.math.RayTraceResult
+net.minecraft.entity.EquipmentSlot net.minecraft.inventory.EntityEquipmentSlot
+net.minecraft.entity.EquipmentSlot HAND_MAIN MAINHAND
+net.minecraft.entity.EquipmentSlot HAND_OFF OFFHAND
+net.minecraft.stat.StatHandler net.minecraft.stats.StatisticsManager
+net.minecraft.client.recipe.book.ClientRecipeBook net.minecraft.client.util.RecipeBookClient
+net.minecraft.util.Hand net.minecraft.util.EnumHand
+net.minecraft.util.Hand MAIN MAIN_HAND
+net.minecraft.util.Hand OFF OFF_HAND
+net.minecraft.client.model.Box net.minecraft.client.renderer.entity.model.ModelBox
+net.minecraft.client.model.Cuboid net.minecraft.client.renderer.entity.model.ModelRenderer
+net.minecraft.client.model.Cuboid boxes cubeList
+net.minecraft.client.model.Cuboid name boxName
+net.minecraft.client.model.Cuboid method_2852() renderWithRotation()
+net.minecraft.client.texture.SpriteAtlasTexture net.minecraft.client.renderer.texture.TextureMap
+net.minecraft.client.texture.SpriteAtlasTexture BLOCK_ATLAS_TEX LOCATION_BLOCKS_TEXTURE
+net.minecraft.client.render.RenderTickCounter net.minecraft.util.Timer
+net.minecraft.client.render.RenderTickCounter ticksThisFrame elapsedTicks
+net.minecraft.client.render.RenderTickCounter tickDelta renderPartialTicks
+net.minecraft.client.render.RenderTickCounter lastFrameDuration elapsedPartialTicks
+net.minecraft.client.render.RenderTickCounter prevTimeMillis lastSyncSysClock
+net.minecraft.client.render.RenderTickCounter timeScale tickLength
+net.minecraft.client.render.RenderTickCounter beginRenderTick() updateTimer()
+net.minecraft.client.world.ClientWorld net.minecraft.client.multiplayer.WorldClient
+net.minecraft.client.world.ClientWorld client mc
+net.minecraft.client.world.ClientWorld disconnect() sendQuittingDisconnectingPacket()
+
+net.minecraft.client.network.packet.LoginQueryRequestS2CPacket net.minecraft.network.login.server.SPacketCustomPayloadLogin
+net.minecraft.client.network.packet.LoginSuccessS2CPacket net.minecraft.network.login.server.SPacketLoginSuccess
+net.minecraft.client.network.packet net.minecraft.network.play.server
+net.minecraft.client.network.packet.GameJoinS2CPacket net.minecraft.network.play.server.SPacketJoinGame
+net.minecraft.client.network.packet.GameJoinS2CPacket getEntityId() getPlayerId()
+net.minecraft.client.network.packet.GameJoinS2CPacket getDimension() func_212642_e()
+net.minecraft.client.network.packet.GameJoinS2CPacket getDifficulty() getDifficulty()
+net.minecraft.client.network.packet.ChatMessageS2CPacket net.minecraft.network.play.server.SPacketChat
+net.minecraft.client.network.packet.ParticleS2CPacket net.minecraft.network.play.server.SPacketParticles
+net.minecraft.client.network.packet.GameStateChangeS2CPacket net.minecraft.network.play.server.SPacketChangeGameState
+net.minecraft.client.network.packet.GameStateChangeS2CPacket getReason() getGameState()
+net.minecraft.client.network.packet.PlayerPositionLookS2CPacket net.minecraft.network.play.server.SPacketPlayerPosLook
+net.minecraft.client.network.packet.PlayerPositionLookS2CPacket.Flag net.minecraft.network.play.server.SPacketPlayerPosLook.EnumFlags
+net.minecraft.client.network.packet.PlayerListS2CPacket net.minecraft.network.play.server.SPacketPlayerListItem
+net.minecraft.client.network.packet.PlayerListS2CPacket.Action net.minecraft.network.play.server.SPacketPlayerListItem.Action
+net.minecraft.client.network.packet.PlayerListS2CPacket.Action ADD ADD_PLAYER
+net.minecraft.client.network.packet.PlayerListS2CPacket.Entry net.minecraft.network.play.server.SPacketPlayerListItem.AddPlayerData
+net.minecraft.client.network.packet.PlayerRespawnS2CPacket net.minecraft.network.play.server.SPacketRespawn
+net.minecraft.client.network.packet.PlayerSpawnS2CPacket net.minecraft.network.play.server.SPacketSpawnPlayer
+net.minecraft.client.network.packet.PlayerSpawnS2CPacket getPlayerUuid() getUniqueId()
+net.minecraft.client.network.packet.PlayerSpawnS2CPacket getTrackedValues() getDataManagerEntries()
+net.minecraft.client.network.packet.PlayerSpawnS2CPacket dataTracker watcher
+net.minecraft.client.network.packet.EntitySpawnS2CPacket net.minecraft.network.play.server.SPacketSpawnObject
+net.minecraft.client.network.packet.MobSpawnS2CPacket net.minecraft.network.play.server.SPacketSpawnMob
+net.minecraft.client.network.packet.MobSpawnS2CPacket getTrackedValues() getDataManagerEntries()
+net.minecraft.client.network.packet.MobSpawnS2CPacket dataTracker dataManager
+net.minecraft.client.network.packet.EntitySpawnGlobalS2CPacket net.minecraft.network.play.server.SPacketSpawnGlobalEntity
+net.minecraft.client.network.packet.PaintingSpawnS2CPacket net.minecraft.network.play.server.SPacketSpawnPainting
+net.minecraft.client.network.packet.ExperienceOrbSpawnS2CPacket net.minecraft.network.play.server.SPacketSpawnExperienceOrb
+net.minecraft.client.network.packet.EntitiesDestroyS2CPacket net.minecraft.network.play.server.SPacketDestroyEntities
+net.minecraft.client.network.packet.EntityPositionS2CPacket net.minecraft.network.play.server.SPacketEntityTeleport
+net.minecraft.client.network.packet.EntitySetHeadYawS2CPacket net.minecraft.network.play.server.SPacketEntityHeadLook
+net.minecraft.client.network.packet.EntityVelocityUpdateS2CPacket net.minecraft.network.play.server.SPacketEntityVelocity
+net.minecraft.client.network.packet.EntityAttachS2CPacket net.minecraft.network.play.server.SPacketEntityAttach
+net.minecraft.client.network.packet.EntityAnimationS2CPacket net.minecraft.network.play.server.SPacketAnimation
+net.minecraft.client.network.packet.ItemPickupAnimationS2CPacket net.minecraft.network.play.server.SPacketCollectItem
+net.minecraft.client.network.packet.ItemPickupAnimationS2CPacket getEntityId() getEntityID()
+net.minecraft.client.network.packet.BlockBreakingProgressS2CPacket net.minecraft.network.play.server.SPacketBlockBreakAnim
+net.minecraft.server.network.packet.ResourcePackStatusC2SPacket net.minecraft.network.play.client.CPacketResourcePackStatus
+net.minecraft.server.network.packet.ResourcePackStatusC2SPacket.Status net.minecraft.network.play.client.CPacketResourcePackStatus.Action
+net.minecraft.client.network.packet.ResourcePackSendS2CPacket net.minecraft.network.play.server.SPacketResourcePackSend
+net.minecraft.client.network.packet.ResourcePackSendS2CPacket getSHA1() getHash()
+net.minecraft.client.network.packet.CustomPayloadS2CPacket net.minecraft.network.play.server.SPacketCustomPayload
+net.minecraft.client.network.packet.CustomPayloadS2CPacket getChannel() getChannelName()
+net.minecraft.client.network.packet.CustomPayloadS2CPacket getData() getBufferData()
+net.minecraft.client.network.packet.DisconnectS2CPacket net.minecraft.network.play.server.SPacketDisconnect
+net.minecraft.client.network.packet.LoginSuccessS2CPacket net.minecraft.network.login.server.SPacketLoginSuccess
+net.minecraft.client.network.packet.SynchronizeRecipesS2CPacket net.minecraft.network.play.server.SPacketRecipeBook
+net.minecraft.client.network.packet.AdvancementUpdateS2CPacket net.minecraft.network.play.server.SPacketAdvancementInfo
+net.minecraft.client.network.packet.SelectAdvancementTabS2CPacket net.minecraft.network.play.server.SPacketSelectAdvancementsTab
+net.minecraft.client.network.packet.HealthUpdateS2CPacket net.minecraft.network.play.server.SPacketUpdateHealth
+net.minecraft.client.network.packet.GuiOpenS2CPacket net.minecraft.network.play.server.SPacketOpenWindow
+net.minecraft.client.network.packet.GuiCloseS2CPacket net.minecraft.network.play.server.SPacketCloseWindow
+net.minecraft.client.network.packet.GuiSlotUpdateS2CPacket net.minecraft.network.play.server.SPacketSetSlot
+net.minecraft.client.network.packet.GuiUpdateS2CPacket net.minecraft.network.play.server.SPacketWindowItems
+net.minecraft.client.network.packet.SignEditorOpenS2CPacket net.minecraft.network.play.server.SPacketSignEditorOpen
+net.minecraft.client.network.packet.StatisticsS2CPacket net.minecraft.network.play.server.SPacketStatistics
+net.minecraft.client.network.packet.ExperienceBarUpdateS2CPacket net.minecraft.network.play.server.SPacketSetExperience
+net.minecraft.client.network.packet.SetCameraEntityS2CPacket net.minecraft.network.play.server.SPacketCamera
+net.minecraft.client.network.packet.PlayerAbilitiesS2CPacket net.minecraft.network.play.server.SPacketPlayerAbilities
+net.minecraft.client.network.packet.TitleS2CPacket net.minecraft.network.play.server.SPacketTitle
+net.minecraft.client.network.packet.UnloadChunkS2CPacket net.minecraft.network.play.server.SPacketUnloadChunk
+net.minecraft.client.network.packet.PlaySoundS2CPacket net.minecraft.network.play.server.SPacketSoundEffect
+net.minecraft.client.network.packet.WorldEventS2CPacket net.minecraft.network.play.server.SPacketEffect
+net.minecraft.client.network.packet.EntityTrackerUpdateS2CPacket net.minecraft.network.play.server.SPacketEntityMetadata
+net.minecraft.client.network.packet.EntityEquipmentUpdateS2CPacket net.minecraft.network.play.server.SPacketEntityEquipment
+net.minecraft.client.network.packet.EntityS2CPacket net.minecraft.network.play.server.SPacketEntity
+net.minecraft.client.network.packet.EntityS2CPacket.Rotate net.minecraft.network.play.server.SPacketEntity.Look
+net.minecraft.client.network.packet.EntityS2CPacket.MoveRelative net.minecraft.network.play.server.SPacketEntity.RelMove
+net.minecraft.client.network.packet.EntityS2CPacket.RotateAndMoveRelative net.minecraft.network.play.server.SPacketEntity.Move
diff --git a/versions/1.9.4/mapping.txt b/versions/1.9.4/mapping.txt
index b5775dda..9f5dd9fe 100644
--- a/versions/1.9.4/mapping.txt
+++ b/versions/1.9.4/mapping.txt
@@ -1,3 +1,10 @@
+net.minecraft.init.MobEffects net.minecraft.potion.Potion
+net.minecraft.init.MobEffects INVISIBILITY invisibility
+net.minecraft.util.text.TextComponentString net.minecraft.util.ChatComponentText
+net.minecraft.util.text.TextComponentTranslation net.minecraft.util.ChatComponentTranslation
+net.minecraft.util.text.Style net.minecraft.util.ChatStyle
+net.minecraft.util.text.TextFormatting net.minecraft.util.EnumChatFormatting
+net.minecraft.util.text.ITextComponent net.minecraft.util.IChatComponent
 net.minecraft.network.datasync.EntityDataManager net.minecraft.entity.DataWatcher
 net.minecraft.client.renderer.VertexBuffer net.minecraft.client.renderer.WorldRenderer
 net.minecraft.client.renderer.Tessellator getBuffer() getWorldRenderer()
@@ -9,8 +16,6 @@ net.minecraft.client.multiplayer.ServerList saveSingleServer() func_147414_b()
 net.minecraft.client.network.NetHandlerPlayClient sendPacket() addToSendQueue()
 net.minecraft.util.math.BlockPos net.minecraft.util.BlockPos
 net.minecraft.client.gui.GuiWorldSelection net.minecraft.client.gui.GuiSelectWorld
-net.minecraft.util.text.TextComponentString net.minecraft.util.ChatComponentText
-net.minecraft.util.text.ITextComponent net.minecraft.util.IChatComponent
 net.minecraft.network.play.server.SPacketSpawnPlayer getUniqueId() getPlayer()
 net.minecraft.util.math.AxisAlignedBB net.minecraft.util.AxisAlignedBB
 net.minecraft.util.math.RayTraceResult net.minecraft.util.MovingObjectPosition
diff --git a/versions/common.gradle b/versions/common.gradle
index 4439c93d..077f4bf4 100644
--- a/versions/common.gradle
+++ b/versions/common.gradle
@@ -10,11 +10,19 @@ buildscript {
     project.ext.mcVersion = mcVersion
 
     repositories {
+        mavenLocal()
         jcenter()
         mavenCentral()
-        maven {
-            name = "forge"
-            url = "https://files.minecraftforge.net/maven"
+        if (mcVersion >= 11400) {
+            maven {
+                name = "fabric"
+                url = "https://maven.fabricmc.net/"
+            }
+        } else {
+            maven {
+                name = "forge"
+                url = "https://files.minecraftforge.net/maven"
+            }
         }
         maven {
             name = "sonatype"
@@ -27,22 +35,26 @@ buildscript {
         if (!gradle.gradleVersion.startsWith('3.')) {
             classpath 'com.github.jengelman.gradle.plugins:shadow:4.0.2' // Gradle 4+ only
         }
-        classpath 'org.ow2.asm:asm:6.0'
-        classpath('com.github.SpongePowered:MixinGradle:dcfaf61'){ // 0.6
-            // Because forgegradle requires 6.0 (not -debug-all) while mixingradle depends on 5.0
-            // and putting mixin right here will place it before forge in the class loader
-            exclude group: 'org.ow2.asm', module: 'asm-debug-all'
+        if (mcVersion >= 11400) {
+            classpath 'fabric-loom:fabric-loom.gradle.plugin:0.2.2-SNAPSHOT'
+        } else {
+            classpath 'org.ow2.asm:asm:6.0'
+            classpath('com.github.SpongePowered:MixinGradle:dcfaf61'){ // 0.6
+                // Because forgegradle requires 6.0 (not -debug-all) while mixingradle depends on 5.0
+                // and putting mixin right here will place it before forge in the class loader
+                exclude group: 'org.ow2.asm', module: 'asm-debug-all'
+            }
+            classpath 'net.minecraftforge.gradle:ForgeGradle:' + (
+                    mcVersion >= 11300 ? '3.+' :
+                    mcVersion >= 11200 ? '2.3-SNAPSHOT' :
+                    mcVersion >= 10904 ? '2.2-SNAPSHOT' :
+                    mcVersion >= 10809 ? '2.1-SNAPSHOT' :
+                    mcVersion >= 10800 ? '2.0-SNAPSHOT' :
+                    mcVersion >= 10710 ? '1.2-SNAPSHOT' :
+                                         'invalid'
+            )
         }
-        classpath 'net.minecraftforge.gradle:ForgeGradle:' + (
-                mcVersion >= 11300 ? '3.+' :
-                mcVersion >= 11200 ? '2.3-SNAPSHOT' :
-                mcVersion >= 10904 ? '2.2-SNAPSHOT' :
-                mcVersion >= 10809 ? '2.1-SNAPSHOT' :
-                mcVersion >= 10800 ? '2.0-SNAPSHOT' :
-                mcVersion >= 10710 ? '1.2-SNAPSHOT' :
-                                     'invalid'
-        )
-        classpath 'com.github.replaymod:preprocessor:30248d0'
+        classpath 'com.github.replaymod:preprocessor:9d3202c'
     }
 }
 
@@ -50,7 +62,8 @@ import com.replaymod.gradle.preprocess.PreprocessTask
 
 def mcVersionStr = "${(int)(mcVersion/10000)}.${(int)(mcVersion/100)%100}" + (mcVersion%100==0 ? '' : ".${mcVersion%100}")
 
-def FG3 = mcVersion >= 11300
+def FG3 = mcVersion >= 11300 && mcVersion < 11400
+def FABRIC = mcVersion >= 11400
 
 def jGuiVersion = project.name
 if (['1.10.2', '1.11', '1.11.2'].contains(jGuiVersion)) jGuiVersion = '1.9.4'
@@ -61,7 +74,9 @@ if (!gradle.gradleVersion.startsWith('3.')) {
 }
 
 if (mcVersion >= 10800) {
-    if (FG3) {
+    if (FABRIC) {
+        apply plugin: 'fabric-loom'
+    } else if (FG3) {
         apply plugin: 'net.minecraftforge.gradle'
         // FIXME use mixin plugin once updated
         compileJava {
@@ -85,59 +100,63 @@ apply plugin: 'com.replaymod.preprocess'
 sourceCompatibility = 1.8
 targetCompatibility = 1.8
 
-afterEvaluate {
-    version = mcVersionStr + '-' + rootProject.version
-}
+version = mcVersionStr + '-' + rootProject.version
 group= "com.replaymod"
 archivesBaseName = "replaymod"
 
-minecraft {
-    if (FG3) {
-        runs {
-            client {
-                workingDirectory rootProject.file('eclipse')
-                property 'forge.logging.console.level', 'info'
-                mods {
-                    replaymod {
-                        source sourceSets.main
+if (FABRIC) {
+    minecraft {
+        refmapName = 'mixins.replaymod.refmap.json'
+    }
+} else {
+    minecraft {
+        if (FG3) {
+            runs {
+                client {
+                    workingDirectory rootProject.file('eclipse')
+                    property 'forge.logging.console.level', 'info'
+                    mods {
+                        replaymod {
+                            source sourceSets.main
+                        }
                     }
                 }
             }
+        } else {
+            if (mcVersion >= 10800) {
+                coreMod = 'com.replaymod.core.LoadingPlugin'
+            }
+            runDir = "../../eclipse"
         }
-    } else {
-        if (mcVersion >= 10800) {
-            coreMod = 'com.replaymod.core.LoadingPlugin'
-        }
-        runDir = "../../eclipse"
-    }
 
-    if (!FG3) {
-        version = [
-                11202: '1.12.2-14.23.0.2486',
-                11201: '1.12.1-14.22.0.2444',
-                11200: '1.12-14.21.1.2387',
-                11102: '1.11.2-13.20.0.2216',
-                11100: '1.11-13.19.1.2188',
-                11002: '1.10.2-12.18.2.2099',
-                10904: '1.9.4-12.17.0.1976',
-                10809: '1.8.9-11.15.1.1722',
-                10800: '1.8-11.14.4.1563',
-                10710: '1.7.10-10.13.4.1558-1.7.10',
+        if (!FG3) {
+            version = [
+                    11202: '1.12.2-14.23.0.2486',
+                    11201: '1.12.1-14.22.0.2444',
+                    11200: '1.12-14.21.1.2387',
+                    11102: '1.11.2-13.20.0.2216',
+                    11100: '1.11-13.19.1.2188',
+                    11002: '1.10.2-12.18.2.2099',
+                    10904: '1.9.4-12.17.0.1976',
+                    10809: '1.8.9-11.15.1.1722',
+                    10800: '1.8-11.14.4.1563',
+                    10710: '1.7.10-10.13.4.1558-1.7.10',
+            ][mcVersion]
+        }
+        mappings = [
+                11302: "snapshot_20180921-1.13",
+                11202: "snapshot_20170615",
+                11201: "snapshot_20170615",
+                11200: "snapshot_20170615",
+                11102: "snapshot_20161220",
+                11100: "snapshot_20161111",
+                11002: "snapshot_20160518",
+                10904: "snapshot_20160518",
+                10809: "stable_22",
+                10800: "snapshot_nodoc_20141130",
+                10710: "stable_12",
         ][mcVersion]
     }
-    mappings = [
-            11302: "snapshot_20180921-1.13",
-            11202: "snapshot_20170615",
-            11201: "snapshot_20170615",
-            11200: "snapshot_20170615",
-            11102: "snapshot_20161220",
-            11100: "snapshot_20161111",
-            11002: "snapshot_20160518",
-            10904: "snapshot_20160518",
-            10809: "stable_22",
-            10800: "snapshot_nodoc_20141130",
-            10710: "stable_12",
-    ][mcVersion]
 }
 
 afterEvaluate {
@@ -153,6 +172,7 @@ afterEvaluate {
 }
 
 repositories {
+    mavenLocal()
     maven {
         name = "SpongePowered Repo"
         url = "http://repo.spongepowered.org/maven/"
@@ -190,24 +210,41 @@ def shadeExclusions = {
 }
 
 dependencies {
-    shadow('net.fabricmc.fabric-api:fabric-lib:0.1.0'){ // for Event, EventFactory, etc.
-        transitive = false
+    if (FABRIC) {
+        minecraft 'com.mojang:minecraft:' + [
+                11400: '1.14'
+        ][mcVersion]
+        mappings 'net.fabricmc:yarn:' + [
+                11400: '1.14+build.1'
+        ][mcVersion]
+        modCompile 'net.fabricmc:fabric-loader:' + [
+                11400: '0.4.2+build.132'
+        ][mcVersion]
+        modCompile 'net.fabricmc:fabric:' + [
+                11400: '0.2.7+build.127'
+        ][mcVersion]
+    } else {
+        shadow('net.fabricmc.fabric-api:fabric-lib:0.1.0') { // for Event, EventFactory, etc.
+            transitive = false
+        }
     }
-    
+
     if (FG3) {
         minecraft 'net.minecraftforge:forge:' + [
                 11302: '1.13.2-25.0.76',
         ][mcVersion]
     }
 
-    // compile 'com.github.SpongePowered:Mixin:404f5da' // 0.7.5-SNAPSHOT
-    // ^ mixin doesn't compile on jitpack, so we'll have to depend on the SNAPSHOT and build it manually for reprod
-    if (!gradle.gradleVersion.startsWith('3.')) {
-        annotationProcessor 'org.spongepowered:mixin:0.7.8-SNAPSHOT'
-        compileOnly 'org.spongepowered:mixin:0.7.8-SNAPSHOT'
-    }
-    shade('org.spongepowered:mixin:0.7.8-SNAPSHOT'){
-        transitive = false // deps should all be bundled with MC
+    if (!FABRIC) {
+        // compile 'com.github.SpongePowered:Mixin:404f5da' // 0.7.5-SNAPSHOT
+        // ^ mixin doesn't compile on jitpack, so we'll have to depend on the SNAPSHOT and build it manually for reprod
+        if (!gradle.gradleVersion.startsWith('3.')) {
+            annotationProcessor 'org.spongepowered:mixin:0.7.8-SNAPSHOT'
+            compileOnly 'org.spongepowered:mixin:0.7.8-SNAPSHOT'
+        }
+        shade('org.spongepowered:mixin:0.7.8-SNAPSHOT') {
+            transitive = false // deps should all be bundled with MC
+        }
     }
     shadow 'com.googlecode.mp4parser:isoparser:1.1.7'
     shadow 'org.apache.commons:commons-exec:1.3'
@@ -215,6 +252,10 @@ dependencies {
     shadow 'com.google.api-client:google-api-client-gson:1.20.0', shadeExclusions
     shadow 'com.google.api-client:google-api-client-java6:1.20.0', shadeExclusions
     shadow 'com.google.oauth-client:google-oauth-client-jetty:1.20.0'
+    
+    if (FABRIC) {
+        shadow 'org.apache.maven:maven-artifact:3.6.1'
+    }
 
     shadow 'org.aspectj:aspectjrt:1.8.2'
 
@@ -222,7 +263,7 @@ dependencies {
 
     def studioVersion = mcVersionStr
     if (studioVersion == '1.8.9') studioVersion = '1.8'
-    shadow "com.github.ReplayMod.ReplayStudio:$studioVersion:04a929c", shadeExclusions
+    shadow "com.github.ReplayMod.ReplayStudio:$studioVersion:6d1ffa9", shadeExclusions
 
     shadowReobf(project(":jGui:$jGuiVersion")){
         transitive = false // FG 1.2 puts all MC deps into the compile configuration and we don't want to shade those
@@ -266,7 +307,9 @@ if (project.name != 'core') {
     def originalAT = originalRes + '/META-INF/replaymod_at.cfg'
     def vars = [MC: mcVersion as int]
 
-    if (FG3) {
+    if (FABRIC) {
+        // Fabric doesn't have ATs, so we've stopped using them on 1.13+
+    } else if (FG3) {
         // FG3 reads the AT file right after project evaluation, so we need to convert it right now
         PreprocessTask.convertFile(PreprocessTask.CFG_KEYWORDS, vars, file(originalAT), file(preprocessedAT))
         minecraft.accessTransformers << file(preprocessedAT)
@@ -322,6 +365,7 @@ if (!gradle.gradleVersion.startsWith('3.')) { // Gradle 4 (never used for 1.7.10
     tasks.removeByName('shadowJar') // we want to base our shadowed jar on the reobfJar output, not the sourceSet output
     task shadowJar(type: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
         from { tasks.jar.archivePath }
+        afterEvaluate { dependsOn FABRIC ? tasks.remapJar : tasks.reobfJar }
         manifest.inheritFrom tasks.jar.manifest
 
         from project.configurations.shade
@@ -329,7 +373,6 @@ if (!gradle.gradleVersion.startsWith('3.')) { // Gradle 4 (never used for 1.7.10
         exclude 'META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'module-info.class'
 
         dependsOn tasks.configureRelocation
-        afterEvaluate { dependsOn tasks.reobfJar }
 
         // No need to shadow netty, MC provides it
         relocate 'com.github.steveice10.netty', 'io.netty'
@@ -397,12 +440,14 @@ jar {
 
     from { files(configurations.shadowReobf).collect { it.isDirectory() ? it : zipTree(it) } }
 
-    manifest {
-        attributes 'TweakClass': 'org.spongepowered.asm.launch.MixinTweaker',
-                'TweakOrder': '0',
-                'FMLCorePluginContainsFMLMod': 'true',
-                'FMLCorePlugin': 'com.replaymod.core.LoadingPlugin',
-                'FMLAT': 'replaymod_at.cfg'
+    if (!FABRIC) {
+        manifest {
+            attributes 'TweakClass': 'org.spongepowered.asm.launch.MixinTweaker',
+                    'TweakOrder': '0',
+                    'FMLCorePluginContainsFMLMod': 'true',
+                    'FMLCorePlugin': 'com.replaymod.core.LoadingPlugin',
+                    'FMLAT': 'replaymod_at.cfg'
+        }
     }
 }
 
@@ -412,10 +457,11 @@ processResources
             inputs.property 'version', { project.version }
             inputs.property 'mcversion', { mcVersionStr }
 
-            // replace stuff in mcmod.info (pre 1.13) and mods.toml (1.13+), nothing else
+            // replace stuff in mcmod.info (pre 1.13) and mods.toml (1.13+) and fabric.mod.json, nothing else
             from(sourceSets.main.resources.srcDirs) { spec ->
                 include 'mcmod.info'
                 include 'mods.toml'
+                include 'fabric.mod.json'
 
                 // replace version and mcversion
                 afterEvaluate {
@@ -423,16 +469,26 @@ processResources
                 }
             }
 
-            // copy everything else, thats not the mcmod.info (or mods.toml)
+            // strip comments from (strict) JSON files
+            from(sourceSets.main.resources.srcDirs) {
+                include 'pack.mcmeta'
+                filter { line -> line.trim().startsWith('//') ? '' : line}
+            }
+
+            // copy everything else, thats not the mcmod.info (or mods.toml or fabric.mod.json)
             from(sourceSets.main.resources.srcDirs) {
                 exclude 'mcmod.info'
                 exclude 'mods.toml'
+                exclude 'fabric.mod.json'
+                exclude 'pack.mcmeta'
             }
         }
 
 sourceSets {
-    main {
-        ext.refMap = "mixins.replaymod.refmap.json"
+    if (!FABRIC) {
+        main {
+            ext.refMap = "mixins.replaymod.refmap.json"
+        }
     }
     integrationTest {
         compileClasspath += main.runtimeClasspath + main.output
@@ -443,7 +499,9 @@ sourceSets {
     }
 }
 
-if (FG3) {
+if (FABRIC) {
+    // FIXME
+} else if (FG3) {
     task copySrg(dependsOn: 'createMcpToSrg') {
         doLast {
             def tsrg = file(project.tasks.createMcpToSrg.output).readLines()
@@ -472,7 +530,9 @@ if (FG3) {
     }
 }
 
-if (FG3) {
+if (FABRIC) {
+    // FIXME
+} else if (FG3) {
     // FIXME project.tasks.idea.dependsOn copySrg
     compileJava.dependsOn copySrg // FIXME can probably remove this one once mixingradle is updated
 } else {
@@ -573,11 +633,11 @@ def annotationWorkaround = {
     jar.delete()
     tmp.renameTo(jar)
 }
-if (mcVersion >= 10809 && !FG3) {
+if (mcVersion >= 10809 && !FG3 && !FABRIC) {
     tasks.deobfMcMCP.doLast annotationWorkaround
 }
 
-if (!FG3) { // FIXME
+if (!FG3 && !FABRIC) { // FIXME
 task runIntegrationTest(type: JavaExec, dependsOn: ["makeStart", "jar"]) {
     main = 'GradleStart'
     standardOutput = System.out