diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java index 9e0d1fd36..f48cc65d1 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java @@ -679,7 +679,7 @@ public class ClientApi // When immersive portals and sodium are combined the fade renders on top of the portal, so turn it off when a portal is on-screen. IImmersivePortalsAccessor immersivePortals = ModAccessorInjector.INSTANCE.get(IImmersivePortalsAccessor.class); ISodiumAccessor sodium = ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class); - if (sodium != null && immersivePortals != null && immersivePortals.hasPortalOnScreen()) { + if (sodium != null && immersivePortals != null && immersivePortals.wasPortalRecentlyVisible()) { return false; } return true; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderParams.java b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderParams.java index 123e6e2c2..041e59283 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderParams.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderParams.java @@ -87,11 +87,12 @@ public class RenderParams extends DhApiRenderParam this.dhClientWorld = SharedApi.tryGetDhClientWorld(); if (this.dhClientWorld != null) { - this.dhClientLevel = (IDhClientLevel) this.dhClientWorld.getLevel(clientLevelWrapper); + this.dhClientLevel = this.dhClientWorld.getOrLoadClientLevel(clientLevelWrapper); if (this.dhClientLevel != null) { this.renderBufferHandler = this.dhClientLevel.getRenderBufferHandler(); this.genericRenderer = this.dhClientLevel.getGenericRenderer(); + clientLevelWrapper.markRendered(); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/IImmersivePortalsAccessor.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/IImmersivePortalsAccessor.java index b9420a157..a030c6fd4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/IImmersivePortalsAccessor.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/IImmersivePortalsAccessor.java @@ -24,6 +24,6 @@ public interface IImmersivePortalsAccessor extends IModAccessor boolean isRenderingPortal(); - boolean hasPortalOnScreen(); + boolean wasPortalRecentlyVisible(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ImmersivePortalsAbstractAccessor.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ImmersivePortalsAbstractAccessor.java new file mode 100644 index 000000000..18c53cccd --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ImmersivePortalsAbstractAccessor.java @@ -0,0 +1,234 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor; + +import com.seibel.distanthorizons.api.DhApi; +import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; +import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.util.List; +import java.util.function.Supplier; + +public abstract class ImmersivePortalsAbstractAccessor implements IImmersivePortalsAccessor +{ + + private static Class portal; + private static MethodHandle isRendering; + private static Method shouldSkipRenderingPortal; + private static MethodHandle getGlobalPortals; + + private static long lastPortalTime = -1; + private static boolean portalVisible = false; + + + public ImmersivePortalsAbstractAccessor() { + DhApi.events.bind(DhApiBeforeRenderEvent.class, BeforeRender.INSTANCE); + } + + private static Class getPortalRenderingClass() { + try { + return Class.forName("qouteall.imm_ptl.core.render.context_management.PortalRendering"); + } catch (ClassNotFoundException first) { + try { + return Class.forName("com.qouteall.immersive_portals.render.context_management.PortalRendering"); // 1.16 + } catch (ClassNotFoundException second) { + RuntimeException err = new RuntimeException(first); + err.addSuppressed(second); + throw err; + } + } + } + + private static Class getPortalRendererClass() { + try { + return Class.forName("qouteall.imm_ptl.core.render.renderer.PortalRenderer"); // 1.21+ + } catch (ClassNotFoundException first) { + try { + return Class.forName("qouteall.imm_ptl.core.render.PortalRenderer"); + } catch (ClassNotFoundException second) { + try { + return Class.forName("com.qouteall.immersive_portals.render.PortalRenderer"); // 1.16 + } catch (ClassNotFoundException third) { + RuntimeException err = new RuntimeException(first); + err.addSuppressed(second); + err.addSuppressed(third); + throw err; + } + } + } + } + + private static Class getPortalClass() { + try { + portal = Class.forName("qouteall.imm_ptl.core.portal.Portal"); + } catch (ClassNotFoundException first) { + try { + portal = Class.forName("com.qouteall.immersive_portals.portal.Portal"); // 1.16 + } catch (ClassNotFoundException second) { + RuntimeException err = new RuntimeException(first); + err.addSuppressed(second); + throw err; + } + } + return portal; + } + + private static Class getGlobalPortalStorageClass() { + try { + return Class.forName("qouteall.imm_ptl.core.portal.global_portals.GlobalPortalStorage"); + } catch (ClassNotFoundException first) { + try { + return Class.forName("com.qouteall.immersive_portals.McHelper"); // 1.16 + } catch (ClassNotFoundException second) { + RuntimeException err = new RuntimeException(first); + err.addSuppressed(second); + throw err; + } + } + } + + private static boolean shouldSkipRenderingPortal(Object portal, Supplier frustumSupplier) { + try { + if (shouldSkipRenderingPortal == null) { + shouldSkipRenderingPortal = getPortalRendererClass().getDeclaredMethod( + "shouldSkipRenderingPortal", getPortalClass(), Supplier.class + ); + shouldSkipRenderingPortal.setAccessible(true); + } + return (boolean) shouldSkipRenderingPortal.invoke(null, portal, frustumSupplier); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + protected abstract Object getClientLevel(); + + protected abstract Class getLevelClass(); + + protected abstract Iterable getEntitiesForRendering(); + + protected abstract Supplier getFrustumSupplier(); + + private static boolean isPortal(Object object) { + return getPortalClass().isInstance(object); + } + + private List getGlobalPortals(Object level) { + try { + if (getGlobalPortals == null) { + getGlobalPortals = MethodHandles.lookup().findStatic( + getGlobalPortalStorageClass(), + "getGlobalPortals", MethodType.methodType(List.class).appendParameterTypes( + getLevelClass() + ) + ); + } + return (List) getGlobalPortals.invoke(level); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean isRenderingPortal() + { + try { + if (isRendering == null) { + isRendering = MethodHandles.lookup().findStatic( + getPortalRenderingClass(), + "isRendering", MethodType.methodType(Boolean.TYPE) + ); + } + return (boolean) isRendering.invoke(); + } + catch (Throwable e) + { + throw new RuntimeException(e); + } + } + + /** + * Essentially reimplements PortalRenderer::getPortalsToRender because it did not exist in 1.16. + */ + private boolean isPortalVisibleRightNow() { + Supplier frustumSupplier = getFrustumSupplier(); + for (Object portal : getGlobalPortals(getClientLevel())) { + if (!shouldSkipRenderingPortal(portal, frustumSupplier)) { + return true; + } + } + for (Object entity : getEntitiesForRendering()) { + if (isPortal(entity) && !shouldSkipRenderingPortal(entity, frustumSupplier)) { + return true; + } + } + return false; + } + + @Override + public boolean wasPortalRecentlyVisible() { + // I did consider setting portalVisible to true whenever PortalRendering::isRendering was true instead, + // but that would still render fading immediately after startup before entering the portal at least once. + // This is more robust, but slightly worse for performance. Still, people can just turn fading off if they have issues. + boolean isPortalVisible = isPortalVisibleRightNow(); + if (isPortalVisible) { + lastPortalTime = System.currentTimeMillis(); + portalVisible = true; + } else if (portalVisible) { + if (System.currentTimeMillis() - lastPortalTime > 1000) { + portalVisible = false; + } + } + // Simply checking portal visibility right now is not sufficient, that will still render the fading on top of the portal. + // Instead, we check if a portal was rendered during the last second or so. + return portalVisible; + } + + @Override + public String getModName() + { + return "ImmersivePortalsMod"; + } + + + public static class BeforeRender extends DhApiBeforeRenderEvent + { + + public static BeforeRender INSTANCE = new BeforeRender(); + + private BeforeRender() {} + + @Override + public void beforeRender(DhApiCancelableEventParam event) + { + if (ModAccessorInjector.INSTANCE.get(IImmersivePortalsAccessor.class).isRenderingPortal()) { + event.cancelEvent(); + } + } + + } + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ImmersivePortalsAccessor.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ImmersivePortalsAccessor.java deleted file mode 100644 index 86fea687a..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ImmersivePortalsAccessor.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * This file is part of the Distant Horizons mod - * licensed under the GNU LGPL v3 License. - * - * Copyright (C) 2020 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor; - -import com.seibel.distanthorizons.api.DhApi; -import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent; -import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam; -import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; -import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; - -public class ImmersivePortalsAccessor implements IImmersivePortalsAccessor -{ - - private static MethodHandle isRendering; - - private static long lastPortalTime = -1; - private static boolean portalVisible = false; - - public ImmersivePortalsAccessor() { - DhApi.events.bind(DhApiBeforeRenderEvent.class, BeforeRender.INSTANCE); - } - - @Override - public boolean isRenderingPortal() - { - try { - if (isRendering == null) { - isRendering = MethodHandles.lookup().findStatic( - #if MC_VER > MC_1_16_5 - Class.forName("qouteall.imm_ptl.core.render.context_management.PortalRendering"), - #else - Class.forName("com.qouteall.immersive_portals.render.context_management.PortalRendering"), - #endif - "isRendering", MethodType.methodType(Boolean.TYPE) - ); - } - boolean result = (boolean) isRendering.invoke(); - if (result) { - portalVisible = true; - lastPortalTime = System.currentTimeMillis(); - } else if (portalVisible) { - if (System.currentTimeMillis() - lastPortalTime > 1000) { - portalVisible = false; - } - } - return result; - } - catch (Throwable e) - { - throw new RuntimeException(e); - } - } - - @Override - public boolean hasPortalOnScreen() { - return portalVisible; - } - - @Override - public String getModName() - { - return "ImmersivePortalsMod"; - } - - - public static class BeforeRender extends DhApiBeforeRenderEvent - { - - public static BeforeRender INSTANCE = new BeforeRender(); - - private BeforeRender() {} - - @Override - public void beforeRender(DhApiCancelableEventParam event) - { - if (ModAccessorInjector.INSTANCE.get(IImmersivePortalsAccessor.class).isRenderingPortal()) { - event.cancelEvent(); - } - } - - } - -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/IClientLevelWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/IClientLevelWrapper.java index e0bc88e0b..4b42fabee 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/IClientLevelWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/IClientLevelWrapper.java @@ -29,6 +29,8 @@ import java.awt.*; public interface IClientLevelWrapper extends ILevelWrapper { + void markRendered(); + @Nullable IServerLevelWrapper tryGetServerSideWrapper();