diff --git a/build.gradle b/build.gradle index c7085f95..378e8923 100644 --- a/build.gradle +++ b/build.gradle @@ -332,7 +332,7 @@ dependencies { shadow 'com.github.ReplayMod.JavaBlend:2.79.0:a0696f8' - shadow "com.github.ReplayMod:ReplayStudio:a67fb11", shadeExclusions + shadow "com.github.ReplayMod:ReplayStudio:c9de2f5", shadeExclusions implementation(jGui){ transitive = false // FG 1.2 puts all MC deps into the compile configuration and we don't want to shade those diff --git a/jGui b/jGui index 71d36174..31bcfabe 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 71d361746ef7d3b9b2bd9a8029296a895498ad39 +Subproject commit 31bcfabe67a5b2de490e0f5c53c1a59e636497be diff --git a/root.gradle.kts b/root.gradle.kts index de90b92f..870e71bc 100755 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -68,6 +68,12 @@ fun command(vararg cmd: Any): List { } fun generateVersionsJson(): Map { + val versionComparator = compareBy( + { (it.split(".").getOrNull(0) ?: "0").toInt() }, + { (it.split(".").getOrNull(1) ?: "0").toInt() }, + { (it.split(".").getOrNull(2) ?: "0").toInt() } + ) + // Find all tag-style releases by listing all tags val tagVersions = command("git", "for-each-ref", "--sort=taggerdate", "--format=%(refname:short)", "refs/tags") @@ -85,6 +91,9 @@ fun generateVersionsJson(): Map { .filter { it != "core" } // Internal project used to automatically remap from Forge 1.12.2 to Fabric 1.14.4 .filter { it != "1.14.4-forge" } + // We dropped 1.7.10 with the Gradle 7 update but still kept its source in case someone + // volunteers to update FG 1.2 to Gradle 7. + .filterNot { it == "1.7.10" && versionComparator.compare(version, "2.6.0") >= 0 } mcVersions.map { "$it-$version" } }.flatten() @@ -92,11 +101,7 @@ fun generateVersionsJson(): Map { val mcVersions = versions .map {it.substring(0, it.indexOf("-"))} .distinct() - .sortedWith(compareBy( - { (it.split(".").getOrNull(0) ?: "0").toInt() }, - { (it.split(".").getOrNull(1) ?: "0").toInt() }, - { (it.split(".").getOrNull(2) ?: "0").toInt() } - )) + .sortedWith(versionComparator) val promos = mutableMapOf() val root = mutableMapOf( @@ -126,6 +131,14 @@ fun generateVersionsJson(): Map { return root } +val writeVersionsJson by tasks.registering { + doLast { + val versionsRoot = generateVersionsJson() + val versionsJson = JsonOutput.prettyPrint(JsonOutput.toJson(versionsRoot)) + File("versions.json").writeText(versionsJson) + } +} + val doRelease by tasks.registering { doLast { // Parse version diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 8176cadc..c21ed0be 100644 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -325,6 +325,18 @@ public class ReplayMod implements Module, Scheduler { } catch (IOException e) { e.printStackTrace(); } + + // Cleanup leftover no_recover files + try (DirectoryStream paths = Files.newDirectoryStream(getReplayFolder())) { + for (Path path : paths) { + String name = path.getFileName().toString(); + if (name.endsWith(".no_recover")) { + Files.delete(path); + } + } + } catch (IOException e) { + e.printStackTrace(); + } }); } diff --git a/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java b/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java index 27f6834d..bb17e61d 100644 --- a/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java +++ b/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java @@ -18,12 +18,25 @@ import static com.replaymod.core.versions.MCVer.getMinecraft; public class GuiBackgroundProcesses extends EventRegistrations { private GuiPanel panel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(10)); + private boolean reentrant; { on(InitScreenCallback.EVENT, (screen, buttons) -> onGuiInit(screen)); } private void onGuiInit(net.minecraft.client.gui.screen.Screen guiScreen) { if (guiScreen != getMinecraft().currentScreen) return; // people tend to construct GuiScreens without opening them - VanillaGuiScreen vanillaGui = VanillaGuiScreen.wrap(guiScreen); + VanillaGuiScreen vanillaGui; + // TODO Workaround for #473 and #501 where another mod opens a new gui in response to the MCGuiScreen.init we + // call from VanillaGuiScreen.register. + // Ideally, we don't have an hidden inner MCGuiScreen and instead have a common parent class for + // AbstractGuiScreen, AbstractGuiOverlay and VanillaGuiScreen which deals with the common things. That's + // quite a bit of changes though, so I'll keep that for 2.7 and have this workaround until then. + if (reentrant) return; + try { + reentrant = true; + vanillaGui = VanillaGuiScreen.wrap(guiScreen); + } finally { + reentrant = false; + } vanillaGui.setLayout(new CustomLayout(vanillaGui.getLayout()) { @Override protected void layout(GuiScreen container, int width, int height) { diff --git a/src/main/java/com/replaymod/core/mixin/Mixin_HideDynamicResourcePacks.java b/src/main/java/com/replaymod/core/mixin/Mixin_HideDynamicResourcePacks.java deleted file mode 100644 index 853c9cd4..00000000 --- a/src/main/java/com/replaymod/core/mixin/Mixin_HideDynamicResourcePacks.java +++ /dev/null @@ -1,37 +0,0 @@ -//#if FABRIC>=1 -package com.replaymod.core.mixin; - -import net.minecraft.client.gui.screen.pack.PackListWidget; -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>=11600 -import net.minecraft.client.gui.screen.pack.PackScreen; -import net.minecraft.client.gui.screen.pack.ResourcePackOrganizer; -//#else -//$$ import net.minecraft.client.resource.ClientResourcePackProfile; -//#endif - -//#if MC>=11600 -@Mixin(PackScreen.class) -//#else -//$$ @Mixin(ResourcePackListWidget.class) -//#endif -public abstract class Mixin_HideDynamicResourcePacks { - //#if MC>=11600 - @Inject(method = "method_29672", at = @At("HEAD"), cancellable = true) - private void hideInternalPacks(PackListWidget packListWidget, ResourcePackOrganizer.Pack pack, CallbackInfo info) { - //#else - //$$ @Inject(method = "add", at = @At("HEAD"), cancellable = true) - //$$ private void hideInternalPacks(ResourcePackListWidget.ResourcePackEntry entry, CallbackInfo info) { - //$$ ClientResourcePackProfile pack = entry.getPack(); - //#endif - String name = pack.getDisplayName().asString(); - if (name.equals("replaymod_lang") || name.equals("replaymod_jgui")) { - info.cancel(); - } - } -} -//#endif diff --git a/src/main/java/com/replaymod/core/mixin/Mixin_InjectDynamicResourcePacks.java b/src/main/java/com/replaymod/core/mixin/Mixin_InjectDynamicResourcePacks.java new file mode 100644 index 00000000..6078c41c --- /dev/null +++ b/src/main/java/com/replaymod/core/mixin/Mixin_InjectDynamicResourcePacks.java @@ -0,0 +1,52 @@ +//#if FABRIC>=1 +package com.replaymod.core.mixin; + +import com.replaymod.core.ReplayMod; +import com.replaymod.core.versions.LangResourcePack; +import net.minecraft.resource.ResourcePack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.util.function.BiConsumer; +import java.util.stream.Collector; + + +//#if MC>=11600 +@Mixin(net.minecraft.resource.ResourcePackManager.class) +//#else +//$$ @Mixin(net.minecraft.client.MinecraftClient.class) +//#endif +public class Mixin_InjectDynamicResourcePacks { + @ModifyArg( + //#if MC>=11600 + method = "createResourcePacks", + //#elseif MC>=11500 + //$$ method = { "", "reloadResources" }, + //#else + //$$ method = { "init", "reloadResources" }, + //#endif + at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;collect(Ljava/util/stream/Collector;)Ljava/lang/Object;") + ) + private Collector injectReplayModPacks(Collector collector) { + collector = append(collector, new LangResourcePack()); + if (ReplayMod.jGuiResourcePack != null) { + collector = append(collector, ReplayMod.jGuiResourcePack); + } + return collector; + } + + private static Collector append(Collector collector, T value) { + BiConsumer accumulator = collector.accumulator(); + return Collector.of( + collector.supplier(), + accumulator, + collector.combiner(), + result -> { + accumulator.accept(result, value); + return collector.finisher().apply(result); + } + ); + } +} +//#endif diff --git a/src/main/java/com/replaymod/core/mixin/Mixin_RegisterDynamicResourcePacks.java b/src/main/java/com/replaymod/core/mixin/Mixin_RegisterDynamicResourcePacks.java deleted file mode 100644 index fde3a013..00000000 --- a/src/main/java/com/replaymod/core/mixin/Mixin_RegisterDynamicResourcePacks.java +++ /dev/null @@ -1,56 +0,0 @@ -//#if FABRIC>=1 -package com.replaymod.core.mixin; - -import com.replaymod.core.ReplayMod; -import com.replaymod.core.versions.LangResourcePack; -import net.minecraft.client.resource.ClientBuiltinResourcePackProvider; -import net.minecraft.resource.ResourcePackProfile; -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.function.Consumer; - -//#if MC>=11600 -import net.minecraft.resource.ResourcePackSource; -//#else -//$$ import java.util.Map; -//#endif - -@Mixin(ClientBuiltinResourcePackProvider.class) -public class Mixin_RegisterDynamicResourcePacks { - @Inject(method = "register", at = @At("RETURN")) - //#if MC>=11600 - private void registerReplayMod(Consumer consumer, ResourcePackProfile.Factory factory, CallbackInfo ci) { - //#else - //$$ private void registerReplayMod(Map map, ResourcePackProfile.Factory factory, CallbackInfo ci) { - //$$ Consumer consumer = (pack) -> map.put(pack.getName(), pack); - //#endif - - consumer.accept(ResourcePackProfile.of( - LangResourcePack.NAME, - true, - LangResourcePack::new, - factory, - ResourcePackProfile.InsertionPosition.BOTTOM - //#if MC>=11600 - , ResourcePackSource.PACK_SOURCE_BUILTIN - //#endif - )); - - if (ReplayMod.jGuiResourcePack != null) { - consumer.accept(ResourcePackProfile.of( - ReplayMod.JGUI_RESOURCE_PACK_NAME, - true, - () -> ReplayMod.jGuiResourcePack, - factory, - ResourcePackProfile.InsertionPosition.BOTTOM - //#if MC>=11600 - , ResourcePackSource.PACK_SOURCE_BUILTIN - //#endif - )); - } - } -} -//#endif diff --git a/src/main/java/com/replaymod/core/versions/Patterns.java b/src/main/java/com/replaymod/core/versions/Patterns.java index 2de3d7b5..f7dee207 100644 --- a/src/main/java/com/replaymod/core/versions/Patterns.java +++ b/src/main/java/com/replaymod/core/versions/Patterns.java @@ -9,6 +9,7 @@ import net.minecraft.client.world.ClientWorld; import net.minecraft.client.render.Tessellator; import net.minecraft.client.render.entity.EntityRenderDispatcher; import net.minecraft.client.sound.PositionedSoundInstance; +import net.minecraft.entity.player.PlayerInventory; import net.minecraft.util.crash.CrashReportSection; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; @@ -215,6 +216,15 @@ class Patterns { //#endif } + @Pattern + private static PlayerInventory getInventory(PlayerEntity entity) { + //#if MC>=11700 + //$$ return entity.getInventory(); + //#else + return entity.inventory; + //#endif + } + @Pattern private static Iterable loadedEntityList(ClientWorld world) { //#if MC>=11400 diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java index 0f38c79d..b27254ab 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java @@ -31,8 +31,6 @@ public class ScreenshotRenderer implements RenderInfo { Window window = mc.getWindow(); int widthBefore = window.getFramebufferWidth(); int heightBefore = window.getFramebufferHeight(); - boolean hideGUIBefore = mc.options.hudHidden; - mc.options.hudHidden = true; ForceChunkLoadingHook clrg = new ForceChunkLoadingHook(mc.worldRenderer); @@ -46,7 +44,6 @@ public class ScreenshotRenderer implements RenderInfo { clrg.uninstall(); - mc.options.hudHidden = hideGUIBefore; resizeMainWindow(mc, widthBefore, heightBefore); return true; } catch (OutOfMemoryError e) { diff --git a/src/main/java/com/replaymod/pathing/player/RealtimeTimelinePlayer.java b/src/main/java/com/replaymod/pathing/player/RealtimeTimelinePlayer.java index d26ca2f0..af53c5b8 100644 --- a/src/main/java/com/replaymod/pathing/player/RealtimeTimelinePlayer.java +++ b/src/main/java/com/replaymod/pathing/player/RealtimeTimelinePlayer.java @@ -3,6 +3,7 @@ package com.replaymod.pathing.player; import com.google.common.util.concurrent.ListenableFuture; import com.replaymod.replay.ReplayHandler; import com.replaymod.replaystudio.pathing.path.Timeline; +import net.minecraft.client.MinecraftClient; /** * Timeline player using the system time. @@ -21,6 +22,9 @@ public class RealtimeTimelinePlayer extends AbstractTimelinePlayer { */ private long startTime; + private boolean loadingResources; + private long timeBeforeResourceLoading; + public RealtimeTimelinePlayer(ReplayHandler replayHandler) { super(replayHandler); } @@ -28,6 +32,7 @@ public class RealtimeTimelinePlayer extends AbstractTimelinePlayer { @Override public ListenableFuture start(Timeline timeline) { firstFrame = true; + loadingResources = false; return super.start(timeline); } @@ -35,9 +40,25 @@ public class RealtimeTimelinePlayer extends AbstractTimelinePlayer { public void onTick() { if (secondFrame) { secondFrame = false; - startTime = System.currentTimeMillis(); + startTime = System.currentTimeMillis() - startOffset; } + + //#if MC>=11400 + if (MinecraftClient.getInstance().getOverlay() != null) { + if (!loadingResources) { + timeBeforeResourceLoading = getTimePassed(); + loadingResources = true; + } + super.onTick(); + return; + } else if (loadingResources && !firstFrame) { + startTime = System.currentTimeMillis() - timeBeforeResourceLoading; + loadingResources = false; + } + //#endif + super.onTick(); + if (firstFrame) { firstFrame = false; secondFrame = true; @@ -46,6 +67,8 @@ public class RealtimeTimelinePlayer extends AbstractTimelinePlayer { @Override public long getTimePassed() { - return startOffset + (firstFrame ? 0 : System.currentTimeMillis() - startTime); + if (firstFrame) return 0; + if (loadingResources) return timeBeforeResourceLoading; + return System.currentTimeMillis() - startTime; } } diff --git a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java index a04cbee6..2a7cee0f 100644 --- a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java @@ -166,8 +166,14 @@ public class RecordingEventHandler extends EventRegistrations { lastY = player.getY(); lastZ = player.getZ(); + //#if MC>=10904 + final double maxRelDist = 8.0; + //#else + //$$ final double maxRelDist = 4.0; + //#endif + Packet packet; - if (force || Math.abs(dx) > 8.0 || Math.abs(dy) > 8.0 || Math.abs(dz) > 8.0) { + if (force || Math.abs(dx) > maxRelDist || Math.abs(dy) > maxRelDist || Math.abs(dz) > maxRelDist) { //#if MC>=10800 packet = new EntityPositionS2CPacket(player); //#else diff --git a/src/main/java/com/replaymod/recording/packet/PacketListener.java b/src/main/java/com/replaymod/recording/packet/PacketListener.java index e7236e5b..1771395a 100644 --- a/src/main/java/com/replaymod/recording/packet/PacketListener.java +++ b/src/main/java/com/replaymod/recording/packet/PacketListener.java @@ -275,7 +275,8 @@ public class PacketListener extends ChannelInboundHandlerAdapter { // If we crash right here, on the next start we'll prompt the user for recovery // but we don't really want that, so drop a marker file to skip recovery for this replay. - Files.createFile(outputPath.resolveSibling(outputPath.getFileName() + ".no_recover")); + Path noRecoverMarker = outputPath.resolveSibling(outputPath.getFileName() + ".no_recover"); + Files.createFile(noRecoverMarker); // We still have the replay, so we just save it (at least for a few weeks) in case they change their mind String replayName = FilenameUtils.getBaseName(outputPath.getFileName().toString()); @@ -287,6 +288,9 @@ public class PacketListener extends ChannelInboundHandlerAdapter { Files.createDirectories(rawPath.getParent()); replayFile.saveTo(rawPath.toFile()); replayFile.close(); + + // Done, clean up the marker + Files.delete(noRecoverMarker); return; } diff --git a/src/main/java/com/replaymod/render/RenderSettings.java b/src/main/java/com/replaymod/render/RenderSettings.java index 28558f3c..e83d4947 100644 --- a/src/main/java/com/replaymod/render/RenderSettings.java +++ b/src/main/java/com/replaymod/render/RenderSettings.java @@ -4,6 +4,7 @@ import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; import com.replaymod.core.utils.FileTypeAdapter; import com.replaymod.core.versions.MCVer; +import de.johni0702.minecraft.gui.utils.lwjgl.Color; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableColor; import net.minecraft.client.resource.language.I18n; import net.minecraft.util.Util; @@ -153,7 +154,7 @@ public class RenderSettings { private final boolean stabilizeYaw; private final boolean stabilizePitch; private final boolean stabilizeRoll; - private final ReadableColor chromaKeyingColor; + private final Color chromaKeyingColor; private final int sphericalFovX; private final int sphericalFovY; private final boolean injectSphericalMetadata; @@ -173,6 +174,32 @@ public class RenderSettings { private final boolean highPerformance; + public RenderSettings() { + this( + RenderSettings.RenderMethod.DEFAULT, + RenderSettings.EncodingPreset.MP4_CUSTOM, + 1920, + 1080, + 60, + 20 << 20, + null, + true, + false, + false, + false, + null, + 360, + 180, + false, + false, + false, + RenderSettings.AntiAliasing.NONE, + "", + RenderSettings.EncodingPreset.MP4_CUSTOM.getValue(), + false + ); + } + public RenderSettings( RenderMethod renderMethod, EncodingPreset encodingPreset, @@ -207,7 +234,7 @@ public class RenderSettings { this.stabilizeYaw = stabilizeYaw; this.stabilizePitch = stabilizePitch; this.stabilizeRoll = stabilizeRoll; - this.chromaKeyingColor = chromaKeyingColor; + this.chromaKeyingColor = chromaKeyingColor == null ? null : new Color(chromaKeyingColor); this.sphericalFovX = sphericalFovX; this.sphericalFovY = sphericalFovY; this.injectSphericalMetadata = injectSphericalMetadata; diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index 0a1fbdcd..38d80a27 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -2,8 +2,6 @@ package com.replaymod.render.gui; import com.google.common.base.Preconditions; import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.InstanceCreator; import com.google.gson.JsonSyntaxException; import com.replaymod.core.ReplayMod; import com.replaymod.render.RenderSettings; @@ -31,7 +29,6 @@ import de.johni0702.minecraft.gui.utils.Consumer; import de.johni0702.minecraft.gui.utils.Utils; import de.johni0702.minecraft.gui.utils.lwjgl.Color; import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; -import de.johni0702.minecraft.gui.utils.lwjgl.ReadableColor; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.client.gui.screen.NoticeScreen; import net.minecraft.client.resource.language.I18n; @@ -331,16 +328,13 @@ public class GuiRenderSettings extends AbstractGuiPopup { } RenderSettings settings = null; try { - settings = new GsonBuilder() - .registerTypeAdapter(RenderSettings.class, (InstanceCreator) type -> getDefaultRenderSettings()) - .registerTypeAdapter(ReadableColor.class, new Gson().getAdapter(Color.class)) - .create().fromJson(json, RenderSettings.class); + settings = new Gson().fromJson(json, RenderSettings.class); } catch (JsonSyntaxException e) { LOGGER.error("Parsing render settings:", e); LOGGER.error("Raw JSON: {}", json); } if (settings == null) { - settings = getDefaultRenderSettings(); + settings = new RenderSettings(); } load(settings); } @@ -508,7 +502,7 @@ public class GuiRenderSettings extends AbstractGuiPopup { /* encodingPreset can be null from a previously supported and later removed preset */ boolean invalidEncodingPreset = encodingPreset == null || !encodingPreset.isSupported(); if (invalidEncodingPreset) { - encodingPreset = getDefaultRenderSettings().getEncodingPreset(); + encodingPreset = new RenderSettings().getEncodingPreset(); } encodingPresetDropdown.setSelected(encodingPreset); videoWidth.setValue(settings.getTargetVideoWidth()); @@ -628,11 +622,6 @@ public class GuiRenderSettings extends AbstractGuiPopup { return ReplayModRender.instance.getRenderSettingsPath(); } - private RenderSettings getDefaultRenderSettings() { - return new RenderSettings(RenderSettings.RenderMethod.DEFAULT, RenderSettings.EncodingPreset.MP4_CUSTOM, 1920, 1080, 60, 20 << 20, null, - true, false, false, false, null, 360, 180, false, false, false, RenderSettings.AntiAliasing.NONE, "", RenderSettings.EncodingPreset.MP4_CUSTOM.getValue(), false); - } - @Override public void open() { super.open(); diff --git a/src/main/java/com/replaymod/replay/camera/CameraEntity.java b/src/main/java/com/replaymod/replay/camera/CameraEntity.java index 973e4fbc..ba1b59e3 100644 --- a/src/main/java/com/replaymod/replay/camera/CameraEntity.java +++ b/src/main/java/com/replaymod/replay/camera/CameraEntity.java @@ -10,6 +10,7 @@ import com.replaymod.core.events.SettingsChangedCallback; import com.replaymod.replay.ReplayHandler; import com.replaymod.replay.events.RenderHotbarCallback; import com.replaymod.replay.events.RenderSpectatorCrosshairCallback; +import com.replaymod.replay.mixin.EntityPlayerAccessor; import de.johni0702.minecraft.gui.utils.EventRegistrations; import de.johni0702.minecraft.gui.versions.callbacks.PreTickCallback; import com.replaymod.core.utils.Utils; @@ -21,9 +22,11 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.decoration.ItemFrameEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; import net.minecraft.item.ItemStack; import net.minecraft.stat.StatHandler; import net.minecraft.util.Identifier; @@ -246,9 +249,37 @@ public class CameraEntity this.lastRenderX = to.lastRenderX; this.lastRenderY = to.lastRenderY + yOffset; this.lastRenderZ = to.lastRenderZ; + if (to instanceof LivingEntity) { + LivingEntity toLiving = (LivingEntity) to; + this.headYaw = toLiving.headYaw; + this.prevHeadYaw = toLiving.prevHeadYaw; + } else { + this.headYaw = to.yaw; + this.prevHeadYaw = to.prevYaw; + } updateBoundingBox(); } + //#if MC>=11400 + @Override + public float getYaw(float tickDelta) { + Entity view = this.client.getCameraEntity(); + if (view != null && view != this) { + return this.prevHeadYaw + (this.headYaw - this.prevHeadYaw) * tickDelta; + } + return super.getYaw(tickDelta); + } + + @Override + public float getPitch(float tickDelta) { + Entity view = this.client.getCameraEntity(); + if (view != null && view != this) { + return this.prevPitch + (this.pitch - this.prevPitch) * tickDelta; + } + return super.getPitch(tickDelta); + } + //#endif + private void updateBoundingBox() { //#if MC>=11400 float width = getWidth(); @@ -618,6 +649,43 @@ public class CameraEntity //#if MC>=10800 this.noClip = this.isSpectator(); //#endif + + syncInventory(); + } + + private final PlayerInventory originalInventory = this.inventory; + + // If we are spectating a player, "steal" its inventory so the rendering code knows what item(s) to render + // and if we aren't, then reset ours. + private void syncInventory() { + Entity view = this.client.getCameraEntity(); + PlayerEntity viewPlayer = view != this && view instanceof PlayerEntity ? (PlayerEntity) view : null; + EntityPlayerAccessor cameraA = (EntityPlayerAccessor) this; + EntityPlayerAccessor viewPlayerA = (EntityPlayerAccessor) viewPlayer; + + //#if MC>=11100 + ItemStack empty = ItemStack.EMPTY; + //#else + //$$ ItemStack empty = null; + //#endif + + // TODO switch to replacing the entire inventory for 1.14+ as well, should be easier and faster + //#if MC>=11400 + this.equipStack(EquipmentSlot.HEAD, viewPlayer != null ? viewPlayer.getEquippedStack(EquipmentSlot.HEAD) : empty); + this.equipStack(EquipmentSlot.MAINHAND, viewPlayer != null ? viewPlayer.getEquippedStack(EquipmentSlot.MAINHAND) : empty); + this.equipStack(EquipmentSlot.OFFHAND, viewPlayer != null ? viewPlayer.getEquippedStack(EquipmentSlot.OFFHAND) : empty); + //#else + //$$ this.inventory = viewPlayer != null ? viewPlayer.inventory : originalInventory; + //#endif + + //#if MC>=10904 + cameraA.setItemStackMainHand(viewPlayerA != null ? viewPlayerA.getItemStackMainHand() : empty); + this.preferredHand = viewPlayer != null ? viewPlayer.preferredHand : Hand.MAIN_HAND; + cameraA.setActiveItemStackUseCount(viewPlayerA != null ? viewPlayerA.getActiveItemStackUseCount() : 0); + //#else + //$$ cameraA.setItemInUse(viewPlayerA != null ? viewPlayerA.getItemInUse() : empty); + //$$ cameraA.setItemInUseCount(viewPlayerA != null ? viewPlayerA.getItemInUseCount() : 0); + //#endif } private void handleInputEvents() { @@ -639,7 +707,7 @@ public class CameraEntity this.lastRenderYaw = this.renderYaw; this.lastRenderPitch = this.renderPitch; this.renderPitch = this.renderPitch + (this.pitch - this.renderPitch) * 0.5f; - this.renderYaw = this.renderYaw + (this.yaw - this.renderYaw) * 0.5f; + this.renderYaw = this.renderYaw + (this.headYaw - this.renderYaw) * 0.5f; } public boolean canSpectate(Entity e) { diff --git a/src/main/java/com/replaymod/replay/camera/SpectatorCameraController.java b/src/main/java/com/replaymod/replay/camera/SpectatorCameraController.java index 30a15018..3422a5de 100644 --- a/src/main/java/com/replaymod/replay/camera/SpectatorCameraController.java +++ b/src/main/java/com/replaymod/replay/camera/SpectatorCameraController.java @@ -1,15 +1,9 @@ package com.replaymod.replay.camera; import com.replaymod.replay.ReplayModReplay; -import com.replaymod.replay.mixin.EntityPlayerAccessor; import net.minecraft.client.MinecraftClient; import net.minecraft.client.options.KeyBinding; import net.minecraft.entity.Entity; -import net.minecraft.entity.player.PlayerEntity; - -//#if MC>=11400 -import net.minecraft.entity.EquipmentSlot; -//#endif //#if MC>=11400 //#else @@ -55,27 +49,6 @@ public class SpectatorCameraController implements CameraController { Entity view = mc.getCameraEntity(); if (view != null && view != camera) { camera.setCameraPosRot(mc.getCameraEntity()); - // If it's a player, also 'steal' its inventory so the rendering code knows what item to render - if (view instanceof PlayerEntity) { - PlayerEntity viewPlayer = (PlayerEntity) view; - //#if MC>=11400 - camera.equipStack(EquipmentSlot.HEAD, viewPlayer.getEquippedStack(EquipmentSlot.HEAD)); - camera.equipStack(EquipmentSlot.MAINHAND, viewPlayer.getEquippedStack(EquipmentSlot.MAINHAND)); - camera.equipStack(EquipmentSlot.OFFHAND, viewPlayer.getEquippedStack(EquipmentSlot.OFFHAND)); - //#else - //$$ camera.inventory = viewPlayer.inventory; - //#endif - EntityPlayerAccessor cameraA = (EntityPlayerAccessor) camera; - EntityPlayerAccessor viewPlayerA = (EntityPlayerAccessor) viewPlayer; - //#if MC>=10904 - cameraA.setItemStackMainHand(viewPlayerA.getItemStackMainHand()); - camera.preferredHand = viewPlayer.preferredHand; - cameraA.setActiveItemStackUseCount(viewPlayerA.getActiveItemStackUseCount()); - //#else - //$$ cameraA.setItemInUse(viewPlayerA.getItemInUse()); - //$$ cameraA.setItemInUseCount(viewPlayerA.getItemInUseCount()); - //#endif - } } } diff --git a/src/main/java/com/replaymod/replay/mixin/MixinKeyboardListener.java b/src/main/java/com/replaymod/replay/mixin/MixinKeyboardListener.java index 12caef5a..b868eb64 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinKeyboardListener.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinKeyboardListener.java @@ -27,7 +27,9 @@ public abstract class MixinKeyboardListener { //#endif at = @At( value = "INVOKE", - //#if MC>=11400 + //#if MC>=11701 + //$$ target = "Lnet/minecraft/client/util/ScreenshotRecorder;saveScreenshot(Ljava/io/File;Lnet/minecraft/client/gl/Framebuffer;Ljava/util/function/Consumer;)V" + //#elseif MC>=11400 target = "Lnet/minecraft/client/util/ScreenshotUtils;saveScreenshot(Ljava/io/File;IILnet/minecraft/client/gl/Framebuffer;Ljava/util/function/Consumer;)V" //#else //#if MC>=11400 diff --git a/src/main/java/com/replaymod/replay/mixin/Mixin_FixNPCSkinCaching.java b/src/main/java/com/replaymod/replay/mixin/Mixin_FixNPCSkinCaching.java index 925c2b0e..11d31423 100644 --- a/src/main/java/com/replaymod/replay/mixin/Mixin_FixNPCSkinCaching.java +++ b/src/main/java/com/replaymod/replay/mixin/Mixin_FixNPCSkinCaching.java @@ -2,6 +2,7 @@ package com.replaymod.replay.mixin; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.network.OtherClientPlayerEntity; import net.minecraft.client.network.PlayerListEntry; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -24,8 +25,19 @@ public abstract class Mixin_FixNPCSkinCaching { // intended ones. To fix that, we make this caching-glitch which servers have come to rely on an actual feature // by just fetching the cache in the constructor which arguably is what MC should have done to begin with, // especially because the spawn packet handling code already requires the entry to be present). - if (MinecraftClient.getInstance().getNetworkHandler() != null) { // will be null if this is the client player + + // To reduce the chance of incompatibility with custom player entities, we only do this for the vanilla MP one. + //noinspection ConstantConditions + if (!(((Object) this) instanceof OtherClientPlayerEntity)) return; + + // To get the player list entry, we need to be connected (we usually are, but better be safe than sorry) + if (MinecraftClient.getInstance().getNetworkHandler() == null) return; + + // And we catch any exceptions, so if there is still something, it's hopefully not fatal + try { this.getPlayerListEntry(); + } catch (Exception e) { + e.printStackTrace(); } } } diff --git a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java index fd60d3d8..aaa839e8 100644 --- a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java +++ b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java @@ -150,9 +150,9 @@ public class ReplayModSimplePathing extends EventRegistrations implements Module synchronized (replayFile) { Timeline timeline = replayFile.getTimelines(new SPTimeline()).get(""); if (timeline != null) { - setCurrentTimeline(new SPTimeline(timeline)); + setCurrentTimeline(new SPTimeline(timeline), false); } else { - setCurrentTimeline(new SPTimeline()); + setCurrentTimeline(new SPTimeline(), false); } } } catch (IOException e) { @@ -225,8 +225,16 @@ public class ReplayModSimplePathing extends EventRegistrations implements Module } public void setCurrentTimeline(SPTimeline newTimeline) { + setCurrentTimeline(newTimeline, true); + } + + private void setCurrentTimeline(SPTimeline newTimeline, boolean save) { selectedPath = null; currentTimeline = newTimeline; + if (!save) { + lastTimeline = newTimeline; + lastChange = newTimeline.getTimeline().peekUndoStack(); + } updateDefaultInterpolatorType(); } @@ -254,18 +262,21 @@ public class ReplayModSimplePathing extends EventRegistrations implements Module private final AtomicInteger lastSaveId = new AtomicInteger(); private ExecutorService saveService; + private SPTimeline lastTimeline; private Change lastChange; private void maybeSaveTimeline(ReplayFile replayFile) { SPTimeline spTimeline = currentTimeline; if (spTimeline == null || saveService == null) { + lastTimeline = null; lastChange = null; return; } Change latestChange = spTimeline.getTimeline().peekUndoStack(); - if (latestChange == null || latestChange == lastChange) { + if (spTimeline == lastTimeline && latestChange == lastChange) { return; } + lastTimeline = spTimeline; lastChange = latestChange; // Clone the timeline for async saving diff --git a/src/main/java/com/replaymod/simplepathing/gui/GuiKeyframeTimeline.java b/src/main/java/com/replaymod/simplepathing/gui/GuiKeyframeTimeline.java index 3576ffa0..da17ba14 100644 --- a/src/main/java/com/replaymod/simplepathing/gui/GuiKeyframeTimeline.java +++ b/src/main/java/com/replaymod/simplepathing/gui/GuiKeyframeTimeline.java @@ -34,7 +34,7 @@ import java.util.Optional; import static com.replaymod.core.versions.MCVer.emitLine; import static de.johni0702.minecraft.gui.versions.MCVer.popScissorState; import static de.johni0702.minecraft.gui.versions.MCVer.pushScissorState; -import static de.johni0702.minecraft.gui.versions.MCVer.setScissorState; +import static de.johni0702.minecraft.gui.versions.MCVer.setScissorDisabled; //#if MC>=11700 //$$ import com.mojang.blaze3d.systems.RenderSystem; @@ -176,7 +176,7 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline preparePathsForPlayback(false)) { @Override @@ -378,7 +379,21 @@ public class GuiPathing { startLoadingEntityTracker(); } + private void abortPathPlayback() { + if (!player.isActive()) { + return; + } + + ListenableFuture future = player.getFuture(); + if (!future.isDone() && !future.isCancelled()) { + future.cancel(false); + } + // Tear down of the player might only happen the next tick after it was cancelled + player.onTick(); + } + public void keyframeRepoButtonPressed() { + abortPathPlayback(); try { GuiKeyframeRepository gui = new GuiKeyframeRepository( mod.getCurrentTimeline(), replayHandler.getReplayFile(), mod.getCurrentTimeline().getTimeline()); diff --git a/src/main/resources/mixins.core.replaymod.json b/src/main/resources/mixins.core.replaymod.json index c0b0a787..d1232a20 100644 --- a/src/main/resources/mixins.core.replaymod.json +++ b/src/main/resources/mixins.core.replaymod.json @@ -8,8 +8,7 @@ //#if MC>=11400 "AbstractButtonWidgetAccessor", "MixinGameRenderer", - "Mixin_HideDynamicResourcePacks", - "Mixin_RegisterDynamicResourcePacks", + "Mixin_InjectDynamicResourcePacks", //#endif //#if MC>=11400 "MixinMouse", diff --git a/version.txt b/version.txt index e70b4523..6a6a3d8e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.6.0 +2.6.1 diff --git a/versions/1.11.2/mapping.txt b/versions/1.11.2/mapping.txt index e69de29b..7f741972 100644 --- a/versions/1.11.2/mapping.txt +++ b/versions/1.11.2/mapping.txt @@ -0,0 +1 @@ +net.minecraft.item.ItemStack EMPTY field_190927_a diff --git a/versions/1.14.4-forge/mapping.txt b/versions/1.14.4-forge/mapping.txt index d57a46f3..f858f3e5 100644 --- a/versions/1.14.4-forge/mapping.txt +++ b/versions/1.14.4-forge/mapping.txt @@ -1,3 +1,4 @@ +net.minecraft.entity.player.PlayerInventory net.minecraft.entity.player.InventoryPlayer net.minecraft.potion.EffectInstance net.minecraft.potion.PotionEffect net.minecraft.client.gui.screen.AddServerScreen net.minecraft.client.gui.GuiScreenAddServer net.minecraft.resources.AbstractResourcePack net.minecraft.client.resources.AbstractResourcePack