From 8be78004d5481b6f56e2a4773452747956d779f3 Mon Sep 17 00:00:00 2001 From: Rubydesic Date: Fri, 27 Sep 2024 21:28:15 -0400 Subject: [PATCH] Add Immersive Portals compatibility --- build.gradle | 1 + common/build.gradle | 4 + common/gradle.properties | 1 + .../ValkyrienCommonMixinConfigPlugin.java | 14 ++ .../client/world/MixinClientChunkCache.java | 14 +- .../MixinIpNewChunkTrackingGraph.java | 62 ++++++++ .../MixinMyBuiltChunkStorage.java | 139 ++++++++++++++++++ .../MixinVisibleSectionDiscovery.java | 36 +++++ .../mod_compat/immersive_portals/README.md | 1 + .../optifine_vanilla/MixinLevelRenderer.java | 5 +- .../MixinLevelRendererVanilla.java | 45 +++--- .../render/LevelRendererVanillaDuck.java | 9 ++ .../valkyrienskies/mod/compat/LoadedMods.kt | 3 + .../valkyrienskies-common.mixins.json | 13 +- fabric/build.gradle | 4 + fabric/gradle.properties | 1 + forge/build.gradle | 1 + .../mixin/ValkyrienSkiesForgeMixinPlugin.java | 63 ++++++++ .../mixinbooster/MixinIForgePlayer.java | 59 ++++++++ .../forge/mixin/compat/mixinbooster/README.md | 4 + .../valkyrienskies-forge.mixins.json | 6 +- 21 files changed, 449 insertions(+), 36 deletions(-) create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/MixinIpNewChunkTrackingGraph.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/MixinMyBuiltChunkStorage.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/MixinVisibleSectionDiscovery.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/README.md create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixinducks/client/render/LevelRendererVanillaDuck.java create mode 100644 forge/src/main/java/org/valkyrienskies/mod/forge/mixin/ValkyrienSkiesForgeMixinPlugin.java create mode 100644 forge/src/main/java/org/valkyrienskies/mod/forge/mixin/compat/mixinbooster/MixinIForgePlayer.java create mode 100644 forge/src/main/java/org/valkyrienskies/mod/forge/mixin/compat/mixinbooster/README.md diff --git a/build.gradle b/build.gradle index 26fcc2f63..39adb1e01 100644 --- a/build.gradle +++ b/build.gradle @@ -119,6 +119,7 @@ subprojects { maven { url = "https://maven.tterrag.com/" } // Registrate, Forge Create and Flywheel maven { url = "https://maven.cafeteria.dev/releases" } // Fake Player API maven { url = "https://maven.jamieswhiteshirt.com/libs-release" } // Reach Entity Attributes + maven { url = "https://jitpack.io"} } maven { name = "Valkyrien Skies Internal" diff --git a/common/build.gradle b/common/build.gradle index 7f8f55e72..e8e216ed8 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -47,6 +47,10 @@ dependencies { //Bluemap fabric 1.20.1 modCompileOnly("curse.maven:bluemap-406463:5555756") + + modCompileOnly("com.github.iPortalTeam.ImmersivePortalsMod:imm_ptl_core:${immptl_version}") + modCompileOnly("com.github.iPortalTeam.ImmersivePortalsMod:q_misc_util:${immptl_version}") + modCompileOnly("com.github.iPortalTeam.ImmersivePortalsMod:build:${immptl_version}") } architectury { diff --git a/common/gradle.properties b/common/gradle.properties index a61644dcc..7069609b8 100644 --- a/common/gradle.properties +++ b/common/gradle.properties @@ -7,3 +7,4 @@ cloth_config_version = 11.1.106 # https://modrinth.com/mod/sodium/versions sodium_version = mc1.20.1-0.5.8 +immptl_version=v3.3.9-mc1.20.1 diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/ValkyrienCommonMixinConfigPlugin.java b/common/src/main/java/org/valkyrienskies/mod/mixin/ValkyrienCommonMixinConfigPlugin.java index 5e7d3d55a..c102db6e3 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/ValkyrienCommonMixinConfigPlugin.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/ValkyrienCommonMixinConfigPlugin.java @@ -8,6 +8,7 @@ import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import org.spongepowered.asm.service.MixinService; +import org.valkyrienskies.mod.compat.LoadedMods; import org.valkyrienskies.mod.compat.VSRenderer; /** @@ -59,6 +60,17 @@ public String getRefMapperConfig() { @Override public boolean shouldApplyMixin(final String s, final String mixinClassName) { final VSRenderer renderer = getVSRenderer(); + + if (mixinClassName.contains("org.valkyrienskies.mod.mixin.mod_compat.immersive_portals")) { + return LoadedMods.getImmersivePortals(); // Only load this mixin if immersive portals is present + } + if ( + mixinClassName.equals("org.valkyrienskies.mod.mixin.client.world.MixinClientChunkCache") || + mixinClassName.equals("org.valkyrienskies.mod.mixin.mod_compat.vanilla_renderer.MixinViewAreaVanilla") + ) { + return !LoadedMods.getImmersivePortals(); // Only load this if immersive portals is NOT present + } + if (mixinClassName.contains("org.valkyrienskies.mod.mixin.mod_compat.sodium")) { return renderer == VSRenderer.SODIUM; } @@ -74,6 +86,7 @@ public boolean shouldApplyMixin(final String s, final String mixinClassName) { if (mixinClassName.contains("org.valkyrienskies.mod.mixin.feature.render_pathfinding")) { return PATH_FINDING_DEBUG; } + if (mixinClassName.contains("org.valkyrienskies.mod.mixin.mod_compat.create.client.trackOutlines")) { //interactive has its own track outline stuff so disable fixed version of VS2's track outline stuff if (classExists("org.valkyrienskies.create_interactive.mixin.client.MixinTrackBlockOutline")) { @@ -83,6 +96,7 @@ public boolean shouldApplyMixin(final String s, final String mixinClassName) { return false; } } + // Only load this mixin when ETF is installed if (mixinClassName.equals("org.valkyrienskies.mod.mixin.mod_compat.etf.MixinBlockEntity")) { if (!classExists("traben.entity_texture_features.utils.ETFEntity")) { diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/client/world/MixinClientChunkCache.java b/common/src/main/java/org/valkyrienskies/mod/mixin/client/world/MixinClientChunkCache.java index 28a764996..d76bd4d57 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/client/world/MixinClientChunkCache.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/client/world/MixinClientChunkCache.java @@ -79,13 +79,15 @@ private void preLoadChunkFromPacket(final int x, final int z, @Inject(method = "drop", at = @At("HEAD"), cancellable = true) public void preUnload(final int chunkX, final int chunkZ, final CallbackInfo ci) { - vs$shipChunks.remove(ChunkPos.asLong(chunkX, chunkZ)); - if (ValkyrienCommonMixinConfigPlugin.getVSRenderer() != VSRenderer.SODIUM) { - ((IVSViewAreaMethods) ((LevelRendererAccessor) ((ClientLevelAccessor) level).getLevelRenderer()).getViewArea()) - .unloadChunk(chunkX, chunkZ); + if (VSGameUtilsKt.isChunkInShipyard(level, chunkX, chunkZ)) { + vs$shipChunks.remove(ChunkPos.asLong(chunkX, chunkZ)); + if (ValkyrienCommonMixinConfigPlugin.getVSRenderer() != VSRenderer.SODIUM) { + ((IVSViewAreaMethods) ((LevelRendererAccessor) ((ClientLevelAccessor) level).getLevelRenderer()).getViewArea()) + .unloadChunk(chunkX, chunkZ); + } + SodiumCompat.onChunkRemoved(this.level, chunkX, chunkZ); + ci.cancel(); } - SodiumCompat.onChunkRemoved(this.level, chunkX, chunkZ); - ci.cancel(); } @Inject( diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/MixinIpNewChunkTrackingGraph.java b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/MixinIpNewChunkTrackingGraph.java new file mode 100644 index 000000000..d4855139f --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/MixinIpNewChunkTrackingGraph.java @@ -0,0 +1,62 @@ +package org.valkyrienskies.mod.mixin.mod_compat.immersive_portals; + +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.server.level.ServerLevel; +import org.joml.primitives.AABBd; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.valkyrienskies.core.api.ships.Ship; +import org.valkyrienskies.mod.common.VSGameUtilsKt; +import qouteall.imm_ptl.core.chunk_loading.ChunkLoader; +import qouteall.imm_ptl.core.chunk_loading.ChunkLoader.ChunkPosConsumer; +import qouteall.imm_ptl.core.chunk_loading.NewChunkTrackingGraph; + +/** + * This mixin ensures that ship chunks are sent to players + */ +@Mixin(NewChunkTrackingGraph.class) +public class MixinIpNewChunkTrackingGraph { + + @Redirect( + method = "updateForPlayer", + at = @At(value = "INVOKE", + target = "Lqouteall/imm_ptl/core/chunk_loading/ChunkLoader;foreachChunkPos(Lqouteall/imm_ptl/core/chunk_loading/ChunkLoader$ChunkPosConsumer;)V") + ) + private static void addShipChunks(final ChunkLoader instance, final ChunkPosConsumer func, @Local final ServerLevel world) { + // region original function + for (int dx = -instance.radius; dx <= instance.radius; dx++) { + for (int dz = -instance.radius; dz <= instance.radius; dz++) { + func.consume( + instance.center.dimension, + instance.center.x + dx, + instance.center.z + dz, + Math.max(Math.abs(dx), Math.abs(dz)) + ); + } + } + // endregion + + // region inject ships + final AABBd box = new AABBd( + (instance.center.x - instance.radius) << 4, + world.getMinBuildHeight(), + (instance.center.z - instance.radius) << 4, + (instance.center.x + instance.radius) << 4, + world.getMaxBuildHeight(), + (instance.center.z + instance.radius) << 4 + ); + for (final Ship ship : VSGameUtilsKt.getShipsIntersecting(world, box)) { + ship.getActiveChunksSet().forEach((x, z) -> { + func.consume( + instance.center.dimension, + x, + z, + 1 // todo: change this? + ); + }); + } + // endregion + } + +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/MixinMyBuiltChunkStorage.java b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/MixinMyBuiltChunkStorage.java new file mode 100644 index 000000000..a5b7d5d4e --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/MixinMyBuiltChunkStorage.java @@ -0,0 +1,139 @@ +package org.valkyrienskies.mod.mixin.mod_compat.immersive_portals; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.ViewArea; +import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher; +import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher.RenderChunk; +import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.mixinducks.client.render.IVSViewAreaMethods; +import qouteall.imm_ptl.core.render.MyBuiltChunkStorage; + +/** + * Reimplementation of {@link org.valkyrienskies.mod.mixin.mod_compat.vanilla_renderer.MixinViewAreaVanilla} for immersive portals + */ +@Mixin(MyBuiltChunkStorage.class) +public class MixinMyBuiltChunkStorage extends ViewArea implements IVSViewAreaMethods { + + // Maps chunk position to an array of BuiltChunk, indexed by the y value. + @Unique + private final Long2ObjectMap vs$shipRenderChunks = + new Long2ObjectOpenHashMap<>(); + // This creates render chunks + @Unique + private ChunkRenderDispatcher vs$chunkBuilder; + + public MixinMyBuiltChunkStorage(final ChunkRenderDispatcher chunkRenderDispatcher, final Level level, final int i, + final LevelRenderer levelRenderer) { + super(chunkRenderDispatcher, level, i, levelRenderer); + } + + /** + * This mixin stores the [chunkBuilder] object from the constructor. It is used to create new render chunks. + */ + @Inject(method = "", at = @At("TAIL")) + private void postInit(final ChunkRenderDispatcher chunkBuilder, final Level world, final int viewDistance, + final LevelRenderer worldRenderer, final CallbackInfo callbackInfo) { + + this.vs$chunkBuilder = chunkBuilder; + } + + /** + * This mixin creates render chunks for ship chunks. + */ + @Inject(method = "setDirty", at = @At("HEAD"), cancellable = true) + private void preScheduleRebuild(final int x, final int y, final int z, final boolean important, + final CallbackInfo callbackInfo) { + + final int yIndex = y - level.getMinSection(); + + if (yIndex < 0 || yIndex >= chunkGridSizeY) { + return; // Weird, but just ignore it + } + + if (VSGameUtilsKt.isChunkInShipyard(level, x, z)) { + final long chunkPosAsLong = ChunkPos.asLong(x, z); + final ChunkRenderDispatcher.RenderChunk[] renderChunksArray = + vs$shipRenderChunks.computeIfAbsent(chunkPosAsLong, + k -> new ChunkRenderDispatcher.RenderChunk[chunkGridSizeY]); + + if (renderChunksArray[yIndex] == null) { + final ChunkRenderDispatcher.RenderChunk builtChunk = + vs$chunkBuilder.new RenderChunk(0, x << 4, y << 4, z << 4); + renderChunksArray[yIndex] = builtChunk; + } + + renderChunksArray[yIndex].setDirty(important); + + callbackInfo.cancel(); + } + } + + /** + * This mixin allows {@link ViewArea} to return the render chunks for ships. + */ + @Inject(method = "getRenderChunkAt", at = @At("HEAD"), cancellable = true) + private void preGetRenderedChunk(final BlockPos pos, + final CallbackInfoReturnable callbackInfoReturnable) { + final int chunkX = Mth.floorDiv(pos.getX(), 16); + final int chunkY = Mth.floorDiv(pos.getY() - level.getMinBuildHeight(), 16); + final int chunkZ = Mth.floorDiv(pos.getZ(), 16); + + if (chunkY < 0 || chunkY >= chunkGridSizeY) { + return; // Weird, but ignore it + } + + if (VSGameUtilsKt.isChunkInShipyard(level, chunkX, chunkZ)) { + final long chunkPosAsLong = ChunkPos.asLong(chunkX, chunkZ); + final ChunkRenderDispatcher.RenderChunk[] renderChunksArray = vs$shipRenderChunks.get(chunkPosAsLong); + if (renderChunksArray == null) { + callbackInfoReturnable.setReturnValue(null); + return; + } + final ChunkRenderDispatcher.RenderChunk renderChunk = renderChunksArray[chunkY]; + callbackInfoReturnable.setReturnValue(renderChunk); + } + } + + @Override + public void unloadChunk(final int chunkX, final int chunkZ) { + if (VSGameUtilsKt.isChunkInShipyard(level, chunkX, chunkZ)) { + final ChunkRenderDispatcher.RenderChunk[] chunks = + vs$shipRenderChunks.remove(ChunkPos.asLong(chunkX, chunkZ)); + if (chunks != null) { + for (final ChunkRenderDispatcher.RenderChunk chunk : chunks) { + if (chunk != null) { + chunk.releaseBuffers(); + } + } + } + } + } + + /** + * Clear VS ship render chunks so that we don't leak memory + */ + @Inject(method = "releaseAllBuffers", at = @At("HEAD")) + private void postReleaseAllBuffers(final CallbackInfo ci) { + for (final Entry entry : vs$shipRenderChunks.long2ObjectEntrySet()) { + for (final ChunkRenderDispatcher.RenderChunk renderChunk : entry.getValue()) { + if (renderChunk != null) { + renderChunk.releaseBuffers(); + } + } + } + vs$shipRenderChunks.clear(); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/MixinVisibleSectionDiscovery.java b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/MixinVisibleSectionDiscovery.java new file mode 100644 index 000000000..93ab0cec1 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/MixinVisibleSectionDiscovery.java @@ -0,0 +1,36 @@ +package org.valkyrienskies.mod.mixin.mod_compat.immersive_portals; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.LevelRenderer.RenderChunkInfo; +import net.minecraft.client.renderer.culling.Frustum; +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 org.valkyrienskies.mod.mixinducks.client.render.LevelRendererVanillaDuck; +import qouteall.imm_ptl.core.render.MyBuiltChunkStorage; +import qouteall.imm_ptl.core.render.VisibleSectionDiscovery; + +/** + * Calls vs$addShipVisibleChunks, since immersive portals injects and cancels a callback preventing + * MixinLevelRendererVanilla from calling it at the right time. + */ +@Mixin(VisibleSectionDiscovery.class) +public class MixinVisibleSectionDiscovery { + + @Inject( + method = "discoverVisibleSections", + at = @At("RETURN") + ) + private static void onDiscoverVisibleSections(ClientLevel world, MyBuiltChunkStorage builtChunks_, Camera camera, + Frustum frustum, ObjectArrayList resultHolder_, CallbackInfo ci) { + + if (!(Minecraft.getInstance().levelRenderer instanceof final LevelRendererVanillaDuck renderer)) return; + + renderer.vs$addShipVisibleChunks(frustum); + + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/README.md b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/README.md new file mode 100644 index 000000000..fc6027ad3 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/immersive_portals/README.md @@ -0,0 +1 @@ +These mixins add compatibility with the Immersive Portals mod diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/optifine_vanilla/MixinLevelRenderer.java b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/optifine_vanilla/MixinLevelRenderer.java index 8e58f1908..0d258f636 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/optifine_vanilla/MixinLevelRenderer.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/optifine_vanilla/MixinLevelRenderer.java @@ -48,7 +48,10 @@ public abstract class MixinLevelRenderer { ) ) private void afterRefresh(final CallbackInfo ci) { - ((ClientChunkCacheDuck) this.level.getChunkSource()).vs$getShipChunks().forEach((pos, chunk) -> { + // This can happen when immersive portals is installed + if (!(this.level.getChunkSource() instanceof final ClientChunkCacheDuck chunks)) return; + + chunks.vs$getShipChunks().forEach((pos, chunk) -> { for (int y = level.getMinSection(); y < level.getMaxSection(); y++) { viewArea.setDirty(ChunkPos.getX(pos), y, ChunkPos.getZ(pos), false); } diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/vanilla_renderer/MixinLevelRendererVanilla.java b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/vanilla_renderer/MixinLevelRendererVanilla.java index d6da16c66..a2c9d07f2 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/vanilla_renderer/MixinLevelRendererVanilla.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/vanilla_renderer/MixinLevelRendererVanilla.java @@ -11,6 +11,7 @@ import it.unimi.dsi.fastutil.objects.ObjectList; import java.util.ListIterator; import java.util.WeakHashMap; +import net.minecraft.client.Camera; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.LevelRenderer; @@ -32,6 +33,7 @@ import org.joml.primitives.AABBd; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -47,16 +49,20 @@ import org.valkyrienskies.mod.mixin.ValkyrienCommonMixinConfigPlugin; import org.valkyrienskies.mod.mixin.accessors.client.render.ViewAreaAccessor; import org.valkyrienskies.mod.mixin.mod_compat.optifine.RenderChunkInfoAccessorOptifine; +import org.valkyrienskies.mod.mixinducks.client.render.LevelRendererVanillaDuck; -@Mixin(LevelRenderer.class) -public abstract class MixinLevelRendererVanilla { +@Mixin(value = LevelRenderer.class, priority = 999) +public abstract class MixinLevelRendererVanilla implements LevelRendererVanillaDuck { @Unique - private final WeakHashMap> shipRenderChunks = new WeakHashMap<>(); + private final WeakHashMap> shipRenderChunks = new WeakHashMap<>(); @Shadow private ClientLevel level; + @Shadow @Final + @Mutable private ObjectArrayList renderChunksInFrustum; + @Shadow private @Nullable ViewArea viewArea; @Shadow @@ -64,7 +70,7 @@ public abstract class MixinLevelRendererVanilla { private Minecraft minecraft; @Unique - private ObjectList renderChunksGeneratedByVanilla = new ObjectArrayList<>(); + private ObjectArrayList renderChunksGeneratedByVanilla = new ObjectArrayList<>(); /** * Fix the distance to render chunks, so that MC doesn't think ship chunks are too far away @@ -105,14 +111,16 @@ private boolean needsFrustumUpdate(final boolean needsFrustumUpdate) { * Add ship render chunks to [renderChunks] */ @Inject( - method = "applyFrustum", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/util/profiling/ProfilerFiller;pop()V" - ) + method = "setupRender", + at = @At("RETURN") ) - private void addShipVisibleChunks( - final Frustum frustum, final CallbackInfo ci) { + private void preSetupRender(final Camera camera, final Frustum frustum, final boolean bl, final boolean bl2, final CallbackInfo ci) { + // This mixin never gets called for IP dimensions, instead we'll call it manually + vs$addShipVisibleChunks(frustum); + } + + @Override + public void vs$addShipVisibleChunks(final Frustum frustum) { renderChunksGeneratedByVanilla = new ObjectArrayList<>(renderChunksInFrustum); final BlockPos.MutableBlockPos tempPos = new BlockPos.MutableBlockPos(); @@ -177,13 +185,16 @@ private void clearShipChunks(final CallbackInfo ci) { value = "INVOKE", target = "Lnet/minecraft/client/renderer/LevelRenderer;renderChunkLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/vertex/PoseStack;DDDLorg/joml/Matrix4f;)V" ), - method = "*" + method = "renderLevel" ) private void redirectRenderChunkLayer(final LevelRenderer receiver, final RenderType renderType, final PoseStack poseStack, final double camX, final double camY, final double camZ, final Matrix4f matrix4f, final Operation renderChunkLayer) { + final var originalRenderChunks = renderChunksInFrustum; + renderChunksInFrustum = renderChunksGeneratedByVanilla; renderChunkLayer.call(receiver, renderType, poseStack, camX, camY, camZ, matrix4f); + renderChunksInFrustum = originalRenderChunks; VSGameEvents.INSTANCE.getShipsStartRendering().emit(new VSGameEvents.ShipStartRenderEvent( receiver, renderType, poseStack, camX, camY, camZ, matrix4f @@ -208,16 +219,6 @@ private void redirectRenderChunkLayer(final LevelRenderer receiver, }); } - @Redirect( - at = @At( - value = "FIELD", - target = "Lnet/minecraft/client/renderer/LevelRenderer;renderChunksInFrustum:Lit/unimi/dsi/fastutil/objects/ObjectArrayList;" - ), - method = "renderChunkLayer" - ) - private ObjectArrayList redirectRenderChunksInFrustum(final LevelRenderer instance) { - return (ObjectArrayList) renderChunksGeneratedByVanilla; - } @Unique private void renderChunkLayer(final RenderType renderType, final PoseStack poseStack, final double d, diff --git a/common/src/main/java/org/valkyrienskies/mod/mixinducks/client/render/LevelRendererVanillaDuck.java b/common/src/main/java/org/valkyrienskies/mod/mixinducks/client/render/LevelRendererVanillaDuck.java new file mode 100644 index 000000000..564f520bb --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixinducks/client/render/LevelRendererVanillaDuck.java @@ -0,0 +1,9 @@ +package org.valkyrienskies.mod.mixinducks.client.render; + +import net.minecraft.client.renderer.culling.Frustum; + +public interface LevelRendererVanillaDuck { + + void vs$addShipVisibleChunks(final Frustum frustum); + +} diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/compat/LoadedMods.kt b/common/src/main/kotlin/org/valkyrienskies/mod/compat/LoadedMods.kt index 995539262..3a70fb9c6 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/compat/LoadedMods.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/compat/LoadedMods.kt @@ -11,6 +11,9 @@ object LoadedMods { @JvmStatic val weather2 by CompatInfo("weather2.Weather") + @JvmStatic + val immersivePortals by CompatInfo("qouteall.imm_ptl.core.IPModMain") + class CompatInfo(private val className: String) : ReadOnlyProperty { private var isLoaded: Boolean? = null diff --git a/common/src/main/resources/valkyrienskies-common.mixins.json b/common/src/main/resources/valkyrienskies-common.mixins.json index f633e45ff..4fda08e41 100644 --- a/common/src/main/resources/valkyrienskies-common.mixins.json +++ b/common/src/main/resources/valkyrienskies-common.mixins.json @@ -93,6 +93,7 @@ "mod_compat.create.pr.MixinSeatBlock", "mod_compat.create_big_cannons.MixinPitchOrientedContraptionEntity", "mod_compat.create_utilities.MixinVoidLinkBehaviour", + "mod_compat.immersive_portals.MixinIpNewChunkTrackingGraph", "mod_compat.reachentityattributes.MixinReachEntityAttributes", "server.MinecraftServerAccessor", "server.MixinMinecraftServer", @@ -155,6 +156,7 @@ "mod_compat.create.client.MixinCogwheelBlockItemHitOnShaft", "mod_compat.create.client.MixinContraptionRenderDispatcher", "mod_compat.create.client.MixinContraptionRenderInfo", + "mod_compat.create.client.MixinDeployTool", "mod_compat.create.client.MixinElevatorControlsHandler", "mod_compat.create.client.MixinFilteringRenderer", "mod_compat.create.client.MixinGhostBlockRenderer", @@ -163,19 +165,22 @@ "mod_compat.create.client.MixinMultiplePlacementHelpers", "mod_compat.create.client.MixinOutline", "mod_compat.create.client.MixinPlacementHelpers", + "mod_compat.create.client.MixinSchematicToolBase", + "mod_compat.create.client.MixinSchematicTransformation", "mod_compat.create.client.MixinSoundScapes", "mod_compat.create.client.MixinTileEntityRenderHelper", "mod_compat.create.client.MixinTrainRelocator", - "mod_compat.create.client.MixinDeployTool", - "mod_compat.create.client.MixinSchematicToolBase", - "mod_compat.create.client.MixinSchematicTransformation", "mod_compat.create.client.MixinValueBox", "mod_compat.create.client.trackOutlines.MixinBigOutlines", + "mod_compat.emf.MixinEMFAnimationEntityContext", + "mod_compat.etf.MixinBlockEntity", "mod_compat.flywheel.InstancingEngineAccessor", "mod_compat.flywheel.MixinBlockEntityInstanceManager", "mod_compat.flywheel.MixinInstanceManager", "mod_compat.flywheel.MixinInstanceWorld", "mod_compat.flywheel.MixinInstancingEngine", + "mod_compat.immersive_portals.MixinMyBuiltChunkStorage", + "mod_compat.immersive_portals.MixinVisibleSectionDiscovery", "mod_compat.optifine.RenderChunkInfoAccessorOptifine", "mod_compat.optifine_vanilla.MixinLevelRenderer", "mod_compat.sodium.MixinChunkTracker", @@ -185,8 +190,6 @@ "mod_compat.vanilla_renderer.MixinLevelRendererVanilla", "mod_compat.vanilla_renderer.MixinViewAreaVanilla", "mod_compat.vanilla_renderer.RenderChunkInfoAccessor", - "mod_compat.emf.MixinEMFAnimationEntityContext", - "mod_compat.etf.MixinBlockEntity", "realms.MixinRealmsConnect" ], "injectors": { diff --git a/fabric/build.gradle b/fabric/build.gradle index da7bdc668..ca1017bcb 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -96,6 +96,10 @@ dependencies { modCompileOnly("com.jamieswhiteshirt:reach-entity-attributes:${reach_entity_attributes_version}") modCompileOnly("dev.cafeteria:fake-player-api:${fake_player_api_version}") modCompileOnly("io.github.tropheusj:milk-lib:${milk_lib_version}") + + modCompileOnly("com.github.iPortalTeam.ImmersivePortalsMod:imm_ptl_core:${immptl_version}") + modCompileOnly("com.github.iPortalTeam.ImmersivePortalsMod:q_misc_util:${immptl_version}") + modCompileOnly("com.github.iPortalTeam.ImmersivePortalsMod:build:${immptl_version}") } // Copy the VS common access widener to the generated resources folder diff --git a/fabric/gradle.properties b/fabric/gradle.properties index a47ab01a3..9ad47341d 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -38,3 +38,4 @@ modmenu_version = 7.1.0 # https://linkie.shedaniel.me/dependencies?loader=fabric&version=1.19.2 cloth_config_version = 11.1.106 +immptl_version=v3.3.9-mc1.20.1 diff --git a/forge/build.gradle b/forge/build.gradle index bd1df6b35..b51c33772 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -53,6 +53,7 @@ configurations { dependencies { implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:0.3.5")) implementation(include("io.github.llamalad7:mixinextras-forge:0.3.5")) + annotationProcessor("net.fabricmc:sponge-mixin:0.12.5+mixin.0.8.5") // use fabric mixin so we can write interface injectors (conditionally loaded if mixinbooster is enabled) forge "net.minecraftforge:forge:${rootProject.forge_version}" diff --git a/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/ValkyrienSkiesForgeMixinPlugin.java b/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/ValkyrienSkiesForgeMixinPlugin.java new file mode 100644 index 000000000..0bd0e2bad --- /dev/null +++ b/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/ValkyrienSkiesForgeMixinPlugin.java @@ -0,0 +1,63 @@ +package org.valkyrienskies.mod.forge.mixin; + +import java.util.List; +import java.util.Set; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +public class ValkyrienSkiesForgeMixinPlugin implements IMixinConfigPlugin { + + private static boolean classExists(final String className) { + try { + Class.forName(className, false, ValkyrienSkiesForgeMixinPlugin.class.getClassLoader()); + return true; + } catch (final ClassNotFoundException ex) { + return false; + } + } + + @Override + public void onLoad(final String s) { + + } + + @Override + public String getRefMapperConfig() { + return ""; + } + + @Override + public boolean shouldApplyMixin(final String s, final String mixinClassName) { + final boolean isMixinBoosterLoaded = classExists("io.github.steelwoolmc.mixintransmog.MixinModlauncherRemapper"); + + if (mixinClassName.contains("org.valkyrienskies.mod.forge.mixin.compat.mixinbooster")) { + return isMixinBoosterLoaded; // Load only if mixinbooster is enabled + } + if (mixinClassName.equals("org.valkyrienskies.mod.forge.mixin.feature.forge_interact.MixinIForgePlayer")) { + return !isMixinBoosterLoaded; // Load only if mixinbooster is not enabled + } + + return true; + } + + @Override + public void acceptTargets(final Set set, final Set set1) { + + } + + @Override + public List getMixins() { + return List.of(); + } + + @Override + public void preApply(final String s, final ClassNode classNode, final String s1, final IMixinInfo iMixinInfo) { + + } + + @Override + public void postApply(final String s, final ClassNode classNode, final String s1, final IMixinInfo iMixinInfo) { + + } +} diff --git a/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/compat/mixinbooster/MixinIForgePlayer.java b/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/compat/mixinbooster/MixinIForgePlayer.java new file mode 100644 index 000000000..50d46f2c9 --- /dev/null +++ b/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/compat/mixinbooster/MixinIForgePlayer.java @@ -0,0 +1,59 @@ +package org.valkyrienskies.mod.forge.mixin.compat.mixinbooster; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import java.util.Optional; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.extensions.IForgePlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +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; +import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.config.VSGameConfig; + +/** + * A variant of {@link org.valkyrienskies.mod.forge.mixin.feature.forge_interact.MixinIForgePlayer} that uses + * injectors to be more compatible with other mods + */ +@Mixin(IForgePlayer.class) +@Pseudo +public interface MixinIForgePlayer { + + @Shadow + Player self(); + + /** + * Include ships in server-side distance check when player interacts with a block. + * + * @return + */ + @ModifyExpressionValue( + method = "canReach(Lnet/minecraft/core/BlockPos;D)Z", + at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/phys/Vec3;atCenterOf(Lnet/minecraft/core/Vec3i;)Lnet/minecraft/world/phys/Vec3;") + ) + default Vec3 replacePosition(final Vec3 original) { + return VSGameUtilsKt.toWorldCoordinates(self().level(), original); + } + + @Inject( + method = "isCloseEnough(Lnet/minecraft/world/entity/Entity;D)Z", + at = @At(value = "HEAD"), + cancellable = true, + remap = false + ) + default void preIsCloseEnough(final Entity entity, final double distance, final CallbackInfoReturnable cir) { + if (VSGameConfig.SERVER.getEnableInteractDistanceChecks() && + VSGameUtilsKt.isBlockInShipyard(entity.level(), entity.blockPosition())) { + final Vec3 eye = this.self().getEyePosition(); + final Vec3 targetCenter = entity.getPosition(1.0F).add(0.0, entity.getBbHeight() / 2.0F, 0.0); + final Optional hit = entity.getBoundingBox().clip(eye, targetCenter); + hit.ifPresent(vec3 -> cir.setReturnValue(VSGameUtilsKt.squaredDistanceBetweenInclShips(this.self().level(), + vec3.x, vec3.y, vec3.z, eye.x, eye.y, eye.z))); + } + } +} diff --git a/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/compat/mixinbooster/README.md b/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/compat/mixinbooster/README.md new file mode 100644 index 000000000..872df9c85 --- /dev/null +++ b/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/compat/mixinbooster/README.md @@ -0,0 +1,4 @@ +Contains variants of mixins that only work with fabric mixin +(i.e. mixinbooster installed). These mixins are conditionally enabled only if +mixinbooster is installed by the ValkyrienSkiesForgeMixinPlugin. Otherwise, +less compatible variants are used. diff --git a/forge/src/main/resources/valkyrienskies-forge.mixins.json b/forge/src/main/resources/valkyrienskies-forge.mixins.json index f98e1c0fe..129b12639 100644 --- a/forge/src/main/resources/valkyrienskies-forge.mixins.json +++ b/forge/src/main/resources/valkyrienskies-forge.mixins.json @@ -2,6 +2,7 @@ "required": true, "package": "org.valkyrienskies.mod.forge.mixin", "compatibilityLevel": "JAVA_17", + "plugin": "org.valkyrienskies.mod.forge.mixin.ValkyrienSkiesForgeMixinPlugin", "mixins": [ "compat.create.MixinBlockBreakingKineticTileEntity", "compat.create.MixinBlocks", @@ -9,15 +10,16 @@ "compat.create.MixinControlledContraptionEntity", "compat.immersivengineering.MixinBlockEntityInventory", "compat.integrateddynamics.MixinVoxelShapeComponents", - "compat.modular_routers.MixinRouterMenu", "compat.mekanism.MixinRadiationManager", + "compat.mixinbooster.MixinIForgePlayer", + "compat.modular_routers.MixinRouterMenu", "compat.tfc.MixinTFCChunkGenerator", "compat.thermalexpansion.MixinTileCoFH", "compat.tis3d.MixinInfraredPacketEntity", "compat.twilightforest.ChunkGeneratorTwilightMixin", "feature.forge_interact.MixinIForgePlayer", - "feature.water_in_ships_entity.MixinEntity", "feature.shipyard_entities.MixinPersistentEntitySectionManager", + "feature.water_in_ships_entity.MixinEntity", "world.level.block.FireMixin" ], "client": [