Skip to content

Commit

Permalink
Add Immersive Portals compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
Rubydesic committed Sep 28, 2024
1 parent 875c06a commit 8be7800
Show file tree
Hide file tree
Showing 21 changed files with 449 additions and 36 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions common/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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;
}
Expand All @@ -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")) {
Expand All @@ -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")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}

}
Original file line number Diff line number Diff line change
@@ -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<RenderChunk[]> 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 = "<init>", 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<RenderChunk> 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<RenderChunk[]> entry : vs$shipRenderChunks.long2ObjectEntrySet()) {
for (final ChunkRenderDispatcher.RenderChunk renderChunk : entry.getValue()) {
if (renderChunk != null) {
renderChunk.releaseBuffers();
}
}
}
vs$shipRenderChunks.clear();
}
}
Original file line number Diff line number Diff line change
@@ -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<RenderChunkInfo> resultHolder_, CallbackInfo ci) {

if (!(Minecraft.getInstance().levelRenderer instanceof final LevelRendererVanillaDuck renderer)) return;

renderer.vs$addShipVisibleChunks(frustum);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
These mixins add compatibility with the Immersive Portals mod
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Loading

0 comments on commit 8be7800

Please sign in to comment.