Compare commits

...

35 Commits

Author SHA1 Message Date
James Seibel 9897570e6c Fully move getShade into the clientLevelWrapper 2026-05-31 07:43:05 -05:00
Acuadragon100 653b86c51a Move getShade into ClientLevelWrapper. 2026-05-31 11:15:34 +02:00
James Seibel 24d2fa90f4 fix world gen stopping when moving between dimensions 2026-05-30 17:53:50 -05:00
Acuadragon100 4f6d78189b Also use the original camera position when Immersive Portals is loaded. 2026-05-16 13:24:52 +02:00
Acuadragon100 1f7cf793c6 Merge remote-tracking branch 'root/main' into fix-portals 2026-05-15 16:42:23 +02:00
Acuadragon100 1908a0ccbf Fix null pointer exception when server level was unloaded before the client level. 2026-05-15 15:15:01 +02:00
James Seibel fd3a8f7ddf Add MC Version locking to the config 2026-05-15 07:44:00 -05:00
Acuadragon100 592b050937 Merge remote-tracking branch 'root/main' into fix-portals
# Conflicts:
#	core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java
#	core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java
2026-05-14 22:34:38 +02:00
Acuadragon100 9fa9b430f6 Only tick levels the player is in on the client. 2026-05-14 12:08:38 +02:00
Acuadragon100 c720a36f83 Always return the player's level and position even when a portal is being rendered. 2026-05-13 21:05:58 +02:00
James Seibel e3f586da56 temp comment out PooledDataSourceCheckoutTest 2026-05-12 21:56:18 -05:00
Acuadragon100 552ad226ea Avoid updating camera speed average when rendering a portal. 2026-05-12 21:21:18 +02:00
James Seibel 775984f651 add TODO 2026-05-11 22:01:00 -05:00
James Seibel b674f49600 up version number 3.0.3 -> 3.0.4 2026-05-04 07:41:32 -05:00
Acuadragon100 fd704bf8e6 Prevent multiple DhClientLevels of the same level from existing at once. 2026-05-03 21:07:03 +02:00
Acuadragon100 cc2febcb5c Fix current level unloading on the client. 2026-05-03 21:06:58 +02:00
Acuadragon100 809990f766 Make sure stale wrappers are not stored in the client level map. 2026-05-03 21:06:43 +02:00
James Seibel 5cb30ed7ce disable immersive portals cam speed calculation 2026-05-02 09:56:28 -05:00
James Seibel 5f54ad0650 up net protocol version 13 -> 14 2026-05-02 09:55:57 -05:00
James Seibel 9fc4d840fc immersive portal quad tree player pos fix 2026-05-02 09:55:45 -05:00
James Seibel 52b0acc452 Immersive Portal Accessor refactoring 2026-05-02 09:55:00 -05:00
James Seibel 4e647395e8 minor format updating 2026-05-02 09:53:02 -05:00
Acuadragon100 25ac1de59b Check if same level before trying to decode the data. 2026-04-26 13:25:00 +02:00
Acuadragon100 949124f8dc Fix not unloading client levels on servers. 2026-04-26 13:24:55 +02:00
Acuadragon100 c363b7fe4b Check fade rendering even without Sodium, because it can still happen without Sodium in some cases. 2026-04-26 13:24:53 +02:00
Acuadragon100 dcb049d4c2 Disable fading detection on 1.21.6+ for now.
Might be worth revisiting once a proper fork of Immersive Portals appear for newer versions.
2026-04-26 13:24:51 +02:00
Acuadragon100 ea51b9135d Import cleanup 2026-04-26 13:24:48 +02:00
Acuadragon100 da31547cfc Allow client to load all dimensions. 2026-04-26 13:24:46 +02:00
Acuadragon100 00f9fd8e53 Allow updating other dimensions. 2026-04-26 13:24:44 +02:00
Acuadragon100 7149baf0f6 Fix server loading. 2026-04-26 13:24:42 +02:00
Acuadragon100 ef3e7763dc Redo loading 2026-04-26 13:24:39 +02:00
Acuadragon100 f5ac5c56b4 Looks like shouldSkipRenderingPortal is sometimes not static. 2026-04-26 13:24:37 +02:00
Acuadragon100 77f10bed48 Probably want to detect rubidium and embeddium as well. 2026-04-26 13:24:35 +02:00
Acuadragon100 7fe0c9b0e8 Tweaked portal loading and fix portals not being detected to disable fading until entered at least once. 2026-04-26 13:24:32 +02:00
Acuadragon100 3d13ba7645 Disable fade rendering when immersive portals and sodium are active at once. 2026-04-26 13:24:13 +02:00
32 changed files with 779 additions and 230 deletions
@@ -31,7 +31,7 @@ public final class ModInfo
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial"; public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial";
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */ /** Incremented every time any packets are added, changed or removed, with a few exceptions. */
public static final int PROTOCOL_VERSION = 13; public static final int PROTOCOL_VERSION = 14;
/** /**
* The full plugin channel name (RESOURCE_NAMESPACE:WRAPPER_PACKET_PATH) * The full plugin channel name (RESOURCE_NAMESPACE:WRAPPER_PACKET_PATH)
@@ -43,7 +43,7 @@ public final class ModInfo
public static final String NAME = "DistantHorizons"; public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */ /** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons"; public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "3.0.3-b"; public static final String VERSION = "3.0.4-b-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */ /** Returns true if the current build is an unstable developer build, false otherwise. */
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
@@ -41,6 +41,7 @@ import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IImmersivePortalsAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor; import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhMetaRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhMetaRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer;
@@ -53,7 +54,6 @@ import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering; import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.world.AbstractDhWorld; import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld; import com.seibel.distanthorizons.core.world.DhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
@@ -68,6 +68,7 @@ import org.lwjgl.glfw.GLFW;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
@@ -87,6 +88,11 @@ public class ClientApi
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
// Need to classload this field later because otherwise it will be null even when Immersive Portals is present.
public static class Late {
private static final IImmersivePortalsAccessor IMMERSIVE_PORTALS = ModAccessorInjector.INSTANCE.get(IImmersivePortalsAccessor.class);
}
/** this includes the is dev build message and low allocated memory warning */ /** this includes the is dev build message and low allocated memory warning */
private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000; private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000;
@@ -119,7 +125,7 @@ public class ClientApi
public boolean rendererDisabledBecauseOfExceptions = false; public boolean rendererDisabledBecauseOfExceptions = false;
private final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi(this::clientLevelLoadEvent, this::clientLevelUnloadEvent); private final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi();
/** Delay loading the first level to give the server some time to respond with level to actually load */ /** Delay loading the first level to give the server some time to respond with level to actually load */
private Timer firstLevelLoadTimer; private Timer firstLevelLoadTimer;
@@ -127,8 +133,8 @@ public class ClientApi
/** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */ /** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */
public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>(); public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>();
/** Holds any chunks that were loaded before the {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} was fired. */ /** Holds any chunks that were found before the client levels are loaded. */
public final HashMap<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new HashMap<>(); public final Map<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new ConcurrentHashMap<>();
/** publicly available so {@link F3Screen} can display the error */ /** publicly available so {@link F3Screen} can display the error */
@Nullable @Nullable
@@ -144,9 +150,13 @@ public class ClientApi
* tracked should also be to keep the ratio roughly the same. * tracked should also be to keep the ratio roughly the same.
* @see ClientApi#MIN_MS_BETWEEN_SPEED_CHECKS * @see ClientApi#MIN_MS_BETWEEN_SPEED_CHECKS
*/ */
public RollingAverage cameraSpeedRollingAverage = new RollingAverage(40); private RollingAverage cameraSpeedRollingAverage = new RollingAverage(40);
private Vec3d lastCameraPosForSpeedCheck = new Vec3d(); private Vec3d lastCameraPosForSpeedCheck = new Vec3d();
private long msSinceLastSpeedCheck = 0L; private long msSinceLastSpeedCheck = 0L;
public double getAvgCameraSpeed()
{
return cameraSpeedRollingAverage.getAverage();
}
public static long firstRenderTimeMs = 0; public static long firstRenderTimeMs = 0;
@@ -176,7 +186,7 @@ public class ClientApi
/** /**
* May be fired slightly before or after the associated * May be fired slightly before or after the associated
* {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} event * level is loaded
* depending on how the host mod loader functions. <br><br> * depending on how the host mod loader functions. <br><br>
* *
* Synchronized shouldn't be necessary, but is present to match {@see onClientOnlyDisconnected} and prevent any unforeseen issues. * Synchronized shouldn't be necessary, but is present to match {@see onClientOnlyDisconnected} and prevent any unforeseen issues.
@@ -213,14 +223,6 @@ public class ClientApi
this.pluginChannelApi.onJoinServer(world.networkState.getSession()); this.pluginChannelApi.onJoinServer(world.networkState.getSession());
world.networkState.sendConfigMessage(); world.networkState.sendConfigMessage();
LOGGER.info("Loading [" + this.waitingClientLevels.size() + "] waiting client level wrappers.");
for (IClientLevelWrapper level : this.waitingClientLevels)
{
this.clientLevelLoadEvent(level);
}
this.waitingClientLevels.clear();
} }
} }
@@ -247,7 +249,6 @@ public class ClientApi
// remove any waiting items // remove any waiting items
this.waitingChunkByClientLevelAndPos.clear(); this.waitingChunkByClientLevelAndPos.clear();
this.waitingClientLevels.clear();
} }
//endregion //endregion
@@ -259,44 +260,12 @@ public class ClientApi
//==============// //==============//
//region level events //region level events
public void clientLevelUnloadEvent(IClientLevelWrapper level) /**
* used in conjunction with the server networking to
* handle level load requests.
*/
public boolean canLoadClientLevel(IClientLevelWrapper wrapper)
{ {
try
{
LOGGER.info("Unloading client level [" + level.getClass().getSimpleName() + "]-[" + level.getDhIdentifier() + "].");
if (level instanceof IServerKeyedClientLevel)
{
this.pluginChannelApi.onClientLevelUnload();
}
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
world.unloadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
}
else
{
this.waitingClientLevels.remove(level);
}
}
catch (Exception e)
{
// handle errors here to prevent blowing up a mixin or API up stream
LOGGER.error("Unexpected error in ClientApi.clientLevelUnloadEvent(), error: "+e.getMessage(), e);
}
}
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper levelWrapper)
{
// can happen if there was an issue during level load
if (levelWrapper == null)
{
return;
}
// wait a moment before loading the level to give the server a chance to handle the client's login request // wait a moment before loading the level to give the server a chance to handle the client's login request
if (MC_CLIENT.clientConnectedToDedicatedServer()) if (MC_CLIENT.clientConnectedToDedicatedServer())
{ {
@@ -306,48 +275,41 @@ public class ClientApi
this.firstLevelLoadTimer.schedule(new TimerTask() this.firstLevelLoadTimer.schedule(new TimerTask()
{ {
@Override @Override
public void run() { ClientApi.this.clientLevelLoadEvent(levelWrapper); } public void run() { canLoadClientLevel(wrapper); }
}, FIRST_LEVEL_LOAD_DELAY_IN_MS); }, FIRST_LEVEL_LOAD_DELAY_IN_MS);
return; return false;
} }
this.firstLevelLoadTimer.cancel(); this.firstLevelLoadTimer.cancel();
} }
if (!this.pluginChannelApi.allowLevelLoading(wrapper))
try
{ {
LOGGER.info("Loading client level [" + levelWrapper + "]-[" + levelWrapper.getDhIdentifier() + "]."); LOGGER.debug("Client levels in this connection are managed by the server, skipping auto-load of: ["+wrapper+"]");
AbstractDhWorld world = SharedApi.getAbstractDhWorld(); AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null) if (world == null)
{ {
if (!this.pluginChannelApi.allowLevelLoading(levelWrapper)) return false;
{
LOGGER.info("Levels in this connection are managed by the server, skipping auto-load.");
// Instead of attempting to load themselves, send the config and wait for a server provided level key.
((DhClientWorld) world).networkState.sendConfigMessage();
return;
}
world.getOrLoadLevel(levelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
this.loadWaitingChunksForLevel(levelWrapper);
}
else
{
this.waitingClientLevels.add(levelWrapper);
} }
// Instead of attempting to load themselves, send the config and wait for a server provided level key.
((DhClientWorld) world).networkState.sendLevelInitRequest(wrapper.getDimensionName());
return false;
} }
catch (Exception e)
{ return true;
// handle errors here to prevent blowing up a mixin or API up stream
LOGGER.error("Unexpected error in ClientApi.clientLevelLoadEvent(), error: "+e.getMessage(), e);
}
} }
private void loadWaitingChunksForLevel(IClientLevelWrapper level)
//endregion
//==============//
// level events //
//==============//
//region
public void loadWaitingChunksForLevel(IClientLevelWrapper level)
{ {
HashSet<Pair<IClientLevelWrapper, DhChunkPos>> keysToRemove = new HashSet<>(); HashSet<Pair<IClientLevelWrapper, DhChunkPos>> keysToRemove = new HashSet<>();
for (Pair<IClientLevelWrapper, DhChunkPos> levelChunkPair : this.waitingChunkByClientLevelAndPos.keySet()) for (Pair<IClientLevelWrapper, DhChunkPos> levelChunkPair : this.waitingChunkByClientLevelAndPos.keySet())
@@ -498,7 +460,7 @@ public class ClientApi
//region //region
long nowMs = System.currentTimeMillis(); long nowMs = System.currentTimeMillis();
if (this.msSinceLastSpeedCheck + MIN_MS_BETWEEN_SPEED_CHECKS < nowMs) if (this.msSinceLastSpeedCheck + MIN_MS_BETWEEN_SPEED_CHECKS < nowMs && (Late.IMMERSIVE_PORTALS == null || !Late.IMMERSIVE_PORTALS.isRenderingPortal()))
{ {
// calc time since last check // calc time since last check
double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0; double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0;
@@ -727,8 +689,7 @@ public class ClientApi
// or if LOD-only mode is enabled (fading is used to remove the MC render pass) // or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|| Config.Client.Advanced.Debugging.lodOnlyMode.get() || Config.Client.Advanced.Debugging.lodOnlyMode.get()
) )
// don't fade when Iris shaders are active, otherwise the rendering can get weird && shouldRenderFade())
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{ {
RenderParams renderParams = new RenderParams(EDhApiRenderPass.OPAQUE, RENDER_STATE); RenderParams renderParams = new RenderParams(EDhApiRenderPass.OPAQUE, RENDER_STATE);
fadeRenderer.render(renderParams); fadeRenderer.render(renderParams);
@@ -757,8 +718,7 @@ public class ClientApi
// or if LOD-only mode is enabled (fading is used to remove the MC render pass) // or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|| Config.Client.Advanced.Debugging.lodOnlyMode.get() || Config.Client.Advanced.Debugging.lodOnlyMode.get()
) )
// don't fade when Iris shaders are active, otherwise the rendering can get weird && shouldRenderFade();
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering();
if (renderFade) if (renderFade)
{ {
RenderParams renderParams = new RenderParams(EDhApiRenderPass.TRANSPARENT, RENDER_STATE); RenderParams renderParams = new RenderParams(EDhApiRenderPass.TRANSPARENT, RENDER_STATE);
@@ -767,6 +727,25 @@ public class ClientApi
} }
} }
private static boolean shouldRenderFade()
{
// don't fade when Iris shaders are active, otherwise the rendering can get weird
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{
return false;
}
// 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);
if (immersivePortals != null
&& immersivePortals.wasPortalRecentlyVisible())
{
return false;
}
return true;
}
//endregion //endregion
@@ -10,6 +10,7 @@ import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage; import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession; import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -30,9 +31,6 @@ public class ClientPluginChannelApi
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class); private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
private final Consumer<IServerKeyedClientLevel> levelLoadHandler;
private final Consumer<IClientLevelWrapper> levelUnloadHandler;
@Nullable @Nullable
public NetworkSession networkSession; public NetworkSession networkSession;
@@ -42,10 +40,8 @@ public class ClientPluginChannelApi
// constructor // // constructor //
//=============// //=============//
public ClientPluginChannelApi(Consumer<IServerKeyedClientLevel> levelLoadHandler, Consumer<IClientLevelWrapper> levelUnloadHandler) public ClientPluginChannelApi()
{ {
this.levelLoadHandler = levelLoadHandler;
this.levelUnloadHandler = levelUnloadHandler;
} }
@@ -94,24 +90,6 @@ public class ClientPluginChannelApi
{ {
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true); IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(); IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
if (existingKeyedClientLevel != null)
{
if (!existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
{
LOGGER.info("Unloading previous level with key: [" + existingKeyedClientLevel.getServerLevelKey() + "].");
this.levelUnloadHandler.accept(existingKeyedClientLevel);
}
else
{
LOGGER.info("Level key matches the previous level key, ignoring the message.");
}
}
else
{
LOGGER.info("Unloading non-keyed level: [" + clientLevel.getDhIdentifier() + "].");
this.levelUnloadHandler.accept(clientLevel);
}
if (existingKeyedClientLevel == null if (existingKeyedClientLevel == null
|| !existingKeyedClientLevel.getServerKey().equals(msg.serverKey) || !existingKeyedClientLevel.getServerKey().equals(msg.serverKey)
@@ -119,7 +97,11 @@ public class ClientPluginChannelApi
{ {
LOGGER.info("Loading level with key: [" + msg.levelKey + "]."); LOGGER.info("Loading level with key: [" + msg.levelKey + "].");
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.serverKey, msg.levelKey); IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.serverKey, msg.levelKey);
this.levelLoadHandler.accept(keyedLevel); AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
world.getOrLoadLevel(keyedLevel);
}
} }
}); });
} }
@@ -19,13 +19,10 @@
package com.seibel.distanthorizons.core.api.internal; package com.seibel.distanthorizons.core.api.internal;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry; import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.world.*; import com.seibel.distanthorizons.core.world.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
@@ -77,7 +74,6 @@ public class ServerApi
} }
//==============// //==============//
// level events // // level events //
//==============// //==============//
@@ -90,7 +86,6 @@ public class ServerApi
if (serverWorld != null) if (serverWorld != null)
{ {
serverWorld.getOrLoadLevel(levelWrapper); serverWorld.getOrLoadLevel(levelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
} }
} }
public void serverLevelUnloadEvent(IServerLevelWrapper level) public void serverLevelUnloadEvent(IServerLevelWrapper level)
@@ -101,12 +96,10 @@ public class ServerApi
if (serverWorld != null) if (serverWorld != null)
{ {
serverWorld.unloadLevel(level); serverWorld.unloadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
} }
} }
//=======================// //=======================//
// chunk modified events // // chunk modified events //
//=======================// //=======================//
@@ -55,6 +55,16 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
@Nullable @Nullable
private T apiValue; private T apiValue;
/**
* Will be null if un-set. <br> <br>
*
* Some options aren't supported on all Minecraft versions,
* in those cases this value will be set to override the
* config file option.
*/
@Nullable
private T mcVersionOverrideValue;
//=============// //=============//
@@ -127,7 +137,14 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
return this.allowApiOverride return this.allowApiOverride
&& this.apiValue != null; && this.apiValue != null;
} }
/** setting to null will allow the config to be used normally */
public void setMcVersionOverrideValue(@Nullable T value)
{ this.mcVersionOverrideValue = value; }
public boolean mcVersionOverridePresent()
{ return this.mcVersionOverrideValue != null; }
/** /**
* Should only be used when loading the config from file. <Br> * Should only be used when loading the config from file. <Br>
* Sets the value without informing the rest of the code (ie, it doesn't call listeners, or saving the value to file). * Sets the value without informing the rest of the code (ie, it doesn't call listeners, or saving the value to file).
@@ -183,6 +200,12 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
@Override @Override
public T get() public T get()
{ {
// always use the MC version specific option if defined
if (this.mcVersionOverrideValue != null)
{
return this.mcVersionOverrideValue;
}
if (this.allowApiOverride if (this.allowApiOverride
&& this.apiValue != null) && this.apiValue != null)
{ {
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.ColorUtil; import com.seibel.distanthorizons.coreapi.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
@@ -60,6 +61,12 @@ public class ColumnBox
// variable setup // // variable setup //
//================// //================//
IClientLevelWrapper clientLevelWrapper = clientLevel.getClientLevelWrapper();
if (clientLevelWrapper == null)
{
LodUtil.assertNotReach("addBoxQuadsToBuilder getClientLevelWrapper should always succeed");
}
short maxX = (short) (minX + width); short maxX = (short) (minX + width);
short maxY = (short) (minY + yHeight); short maxY = (short) (minY + yHeight);
short maxZ = (short) (minZ + width); short maxZ = (short) (minZ + width);
@@ -122,7 +129,7 @@ public class ColumnBox
&& !isTopTransparent; && !isTopTransparent;
if (!skipTop) if (!skipTop)
{ {
builder.addQuadUp(minX, maxY, minZ, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight); builder.addQuadUp(minX, maxY, minZ, width, ColorUtil.applyShade(color, clientLevelWrapper.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight);
} }
} }
@@ -133,7 +140,7 @@ public class ColumnBox
&& !isBottomTransparent; && !isBottomTransparent;
if (!skipBottom) if (!skipBottom)
{ {
builder.addQuadDown(minX, minY, minZ, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight); builder.addQuadDown(minX, minY, minZ, width, ColorUtil.applyShade(color, clientLevelWrapper.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight);
} }
} }
@@ -163,7 +170,7 @@ public class ColumnBox
else else
{ {
makeAdjVerticalQuad( makeAdjVerticalQuad(
builder, phantomArrayCheckout, builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH,
minX, minY, minZ, width, yHeight, minX, minY, minZ, width, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
@@ -188,7 +195,7 @@ public class ColumnBox
else else
{ {
makeAdjVerticalQuad( makeAdjVerticalQuad(
builder, phantomArrayCheckout, builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH,
minX, minY, maxZ, width, yHeight, minX, minY, maxZ, width, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
@@ -213,7 +220,7 @@ public class ColumnBox
else else
{ {
makeAdjVerticalQuad( makeAdjVerticalQuad(
builder, phantomArrayCheckout, builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST,
minX, minY, minZ, width, yHeight, minX, minY, minZ, width, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
@@ -238,7 +245,7 @@ public class ColumnBox
else else
{ {
makeAdjVerticalQuad( makeAdjVerticalQuad(
builder, phantomArrayCheckout, builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST,
maxX, minY, minZ, width, yHeight, maxX, minY, minZ, width, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
@@ -247,7 +254,7 @@ public class ColumnBox
} }
private static void makeAdjVerticalQuad( private static void makeAdjVerticalQuad(
LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, IClientLevelWrapper clientLevelWrapper,
@NotNull ColumnRenderView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction, @NotNull ColumnRenderView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
short x, short yMin, short z, short horizontalWidth, short ySize, short x, short yMin, short z, short horizontalWidth, short ySize,
int color, byte irisBlockMaterialId, byte blockLight) int color, byte irisBlockMaterialId, byte blockLight)
@@ -263,7 +270,7 @@ public class ColumnBox
// no adjacent data // // no adjacent data //
//==================// //==================//
color = ColorUtil.applyShade(color, MC_RENDER.getShade(direction)); color = ColorUtil.applyShade(color, clientLevelWrapper.getShade(direction));
if (adjColumnView.size == 0 if (adjColumnView.size == 0
|| RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0))) || RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0)))
@@ -394,7 +394,7 @@ public class LodQuadBuilder
// for horizontal and bottom faces of grass blocks, use the dirt color to // for horizontal and bottom faces of grass blocks, use the dirt color to
// prevent green cliff walls // prevent green cliff walls
color = this.clientLevelWrapper.getDirtBlockColor(); color = this.clientLevelWrapper.getDirtBlockColor();
color = ColorUtil.applyShade(color, MC_RENDER.getShade(quad.direction)); color = ColorUtil.applyShade(color, this.clientLevelWrapper.getShade(quad.direction));
} }
} }
} }
@@ -123,17 +123,6 @@ public class LodRequestModule implements Closeable
// if the world is read only don't generate anything // if the world is read only don't generate anything
shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.tryGetReadOnly(); shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.tryGetReadOnly();
// don't generate chunks for client levels that aren't being rendered
// (this can happen when moving between dimensions)
if (this.level instanceof IDhClientLevel)
{
boolean isRendering = ((IDhClientLevel) this.level).isRendering();
if (!isRendering)
{
shouldDoWorldGen = false;
}
}
boolean isWorldGenRunning = this.isWorldGenRunning(); boolean isWorldGenRunning = this.isWorldGenRunning();
@@ -9,7 +9,6 @@ import com.seibel.distanthorizons.core.multiplayer.server.FullDataSourceRequestH
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState; import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager; import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException; import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
import com.seibel.distanthorizons.core.network.exceptions.SectionRequiresSplittingException; import com.seibel.distanthorizons.core.network.exceptions.SectionRequiresSplittingException;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage; import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
@@ -200,26 +199,6 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
LodUtil.assertTrue(message.getSession().serverPlayer != null); LodUtil.assertTrue(message.getSession().serverPlayer != null);
// Check if the player is in this dimension,
// since handling multiple dimensions isn't allowed
if (message.getSession().serverPlayer.getLevel() != this.getLevelWrapper())
{
// If the message can be replied to - reply with an error, otherwise just ignore
if (message instanceof AbstractTrackableMessage)
{
((AbstractTrackableMessage) message).sendResponse(
new RequestRejectedException(
"Generation not allowed. " +
"Requested dimension: ["+((ILevelRelatedMessage) message).getLevelName()+"], " +
"player dimension: [" + message.getSession().serverPlayer.getLevel().getDhIdentifier() + "], " +
"handler dimension: [" + this.getLevelWrapper().getDhIdentifier() + "]"
)
);
}
return false;
}
return true; return true;
} }
@@ -21,10 +21,12 @@ package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc; import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree; import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
@@ -33,6 +35,7 @@ import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IImmersivePortalsAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
@@ -167,15 +167,17 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
} }
// Check this before decoding data to prevent errors if multiple client levels
// are receiving data at once (Immersive Portals compatibility).
boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
//NETWORK_LOGGER.debug("Buffer ["+message.payload.dtoBufferId+"] isSameLevel: ["+isSameLevel+"]");
if (!isSameLevel)
{
return;
}
try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(message.payload)) try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(message.payload))
{ {
boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
NETWORK_LOGGER.debug("Buffer ["+message.payload.dtoBufferId+"] isSameLevel: ["+isSameLevel+"]");
if (!isSameLevel)
{
return;
}
Executor executor = ThreadPoolUtil.getFileHandlerExecutor(); Executor executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor != null) if (executor != null)
@@ -219,6 +221,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
{ {
try try
{ {
if (MC_CLIENT.getWrappedClientLevel() == null || MC_CLIENT.getWrappedClientLevel().getDhLevel() != this) return;
this.clientside.clientTick(); this.clientside.clientTick();
if (this.syncOnLoadRequestQueue != null) if (this.syncOnLoadRequestQueue != null)
@@ -71,7 +71,11 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
//region //region
@Override @Override
public void clientTick() { this.clientside.clientTick(); } public void clientTick()
{
if (MC_CLIENT.getWrappedClientLevel() == null || MC_CLIENT.getWrappedClientLevel().getDhLevel() != this) return;
this.clientside.clientTick();
}
//endregion //endregion
@@ -13,6 +13,7 @@ import com.seibel.distanthorizons.core.network.event.ScopedNetworkEventSource;
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage; import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.messages.base.RequestLevelInitMessage;
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage; import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
@@ -164,7 +165,8 @@ public class ClientNetworkState implements Closeable
// send message // // send message //
//==============// //==============//
public void sendLevelInitRequest(String clientLevelKey)
{ this.getSession().sendMessage(new RequestLevelInitMessage(clientLevelKey)); }
public void sendConfigMessage() { this.sendConfigMessage(true); } public void sendConfigMessage() { this.sendConfigMessage(true); }
public void sendConfigMessage(boolean blocking) public void sendConfigMessage(boolean blocking)
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.multiplayer.server;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel; import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig; import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayloadSender; import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayloadSender;
@@ -9,20 +10,25 @@ import com.seibel.distanthorizons.core.multiplayer.fullData.SharedBandwidthLimit
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent;
import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage; import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage; import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.messages.base.RequestLevelInitMessage;
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage; import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException; import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession; import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateAndConcurrencyLimiter; import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateAndConcurrencyLimiter;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.Closeable; import java.io.Closeable;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
public class ServerPlayerState implements Closeable public class ServerPlayerState implements Closeable
{ {
private final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
private final ConfigChangeListener<String> levelKeyPrefixChangeListener private final ConfigChangeListener<String> levelKeyPrefixChangeListener
= new ConfigChangeListener<>(Config.Server.levelKeyPrefix, this::onLevelKeyPrefixConfigChanged); = new ConfigChangeListener<>(Config.Server.levelKeyPrefix, this::onLevelKeyPrefixConfigChanged);
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage); private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage);
@@ -66,6 +72,12 @@ public class ServerPlayerState implements Closeable
this.sendConfigMessage(); this.sendConfigMessage();
}); });
this.networkSession.registerHandler(RequestLevelInitMessage.class, (requestLevelKeyMessage) ->
{
sendLevelKey(requestLevelKeyMessage.clientLevelKey);
});
this.networkSession.registerHandler(CloseInternalEvent.class, event -> { this.networkSession.registerHandler(CloseInternalEvent.class, event -> {
// No-op. prevents "Unhandled message" log entries // No-op. prevents "Unhandled message" log entries
}); });
@@ -85,12 +97,27 @@ public class ServerPlayerState implements Closeable
//=================// //=================//
private void onLevelKeyPrefixConfigChanged(String newLevelKey) { this.sendLevelKey(); } private void onLevelKeyPrefixConfigChanged(String newLevelKey) { this.sendLevelKey(); }
private void sendLevelKey(String clientLevelKey)
{
sendLevelKey(() ->
MC_SHARED
.getWrappedServerLevel(clientLevelKey)
.getKeyedLevelDimensionName());
}
private void sendLevelKey() private void sendLevelKey()
{
sendLevelKey(() ->
this.getServerPlayer()
.getLevel()
.getKeyedLevelDimensionName());
}
private void sendLevelKey(Supplier<String> levelKeySupplier)
{ {
if (Config.Server.sendLevelKeys.get()) if (Config.Server.sendLevelKeys.get())
{ {
String levelKey = levelKeySupplier.get();
// let the client's know about the change // let the client's know about the change
String levelKey = this.getServerPlayer().getLevel().getKeyedLevelDimensionName();
if (!levelKey.equals(this.lastLevelKey)) if (!levelKey.equals(this.lastLevelKey))
{ {
this.lastLevelKey = levelKey; this.lastLevelKey = levelKey;
@@ -21,12 +21,9 @@ package com.seibel.distanthorizons.core.network.messages;
import com.google.common.collect.BiMap; import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap; import com.google.common.collect.HashBiMap;
import com.seibel.distanthorizons.core.network.messages.base.CodecCrashMessage; import com.seibel.distanthorizons.core.network.messages.base.*;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage; import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage;
import com.seibel.distanthorizons.core.network.messages.requests.ExceptionMessage; import com.seibel.distanthorizons.core.network.messages.requests.ExceptionMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
@@ -60,6 +57,7 @@ public class MessageRegistry
// Level keys // Level keys
this.registerMessage(LevelInitMessage.class, LevelInitMessage::new); this.registerMessage(LevelInitMessage.class, LevelInitMessage::new);
this.registerMessage(RequestLevelInitMessage.class, RequestLevelInitMessage::new);
// Config (for full DH support) // Config (for full DH support)
this.registerMessage(SessionConfigMessage.class, SessionConfigMessage::new); this.registerMessage(SessionConfigMessage.class, SessionConfigMessage::new);
@@ -0,0 +1,65 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages.base;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import io.netty.buffer.ByteBuf;
/** used for full DH support */
public class RequestLevelInitMessage extends AbstractNetworkMessage
{
public String clientLevelKey;
//=============//
// constructor //
//=============//
public RequestLevelInitMessage() { }
public RequestLevelInitMessage(String clientLevelKey) { this.clientLevelKey = clientLevelKey; }
//===============//
// serialization //
//===============//
@Override
public void encode(ByteBuf out) { this.writeString(this.clientLevelKey, out); }
@Override
public void decode(ByteBuf in) { this.clientLevelKey = this.readString(in); }
//================//
// base overrides //
//================//
@Override
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("levelKey", this.clientLevelKey);
}
}
@@ -77,6 +77,8 @@ public class DhApiRenderProxy implements IDhApiRenderProxy
} }
} }
// TODO clear tint handler too
return DhApiResult.createSuccess(); return DhApiResult.createSuccess();
} }
@@ -6,18 +6,14 @@ import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState; import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector; import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.jar.EPlatform;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.util.RenderUtil; import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.IDhClientWorld; import com.seibel.distanthorizons.core.world.IDhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor; import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
@@ -87,7 +83,7 @@ public class RenderParams extends DhApiRenderParam
this.dhClientWorld = SharedApi.tryGetDhClientWorld(); this.dhClientWorld = SharedApi.tryGetDhClientWorld();
if (this.dhClientWorld != null) if (this.dhClientWorld != null)
{ {
this.dhClientLevel = (IDhClientLevel) this.dhClientWorld.getLevel(clientLevelWrapper); this.dhClientLevel = this.dhClientWorld.getOrLoadClientLevel(clientLevelWrapper);
if (this.dhClientLevel != null) if (this.dhClientLevel != null)
{ {
this.renderBufferHandler = this.dhClientLevel.getRenderBufferHandler(); this.renderBufferHandler = this.dhClientLevel.getRenderBufferHandler();
@@ -163,7 +163,7 @@ public class RenderUtil
if (Config.Client.Advanced.Graphics.Culling.reduceOverdrawWithFastMovement.get()) if (Config.Client.Advanced.Graphics.Culling.reduceOverdrawWithFastMovement.get())
{ {
double avgSpeed = ClientApi.INSTANCE.cameraSpeedRollingAverage.getAverage(); double avgSpeed = ClientApi.INSTANCE.getAvgCameraSpeed();
if (avgSpeed >= DynamicOverdraw.MIN_SPEED) if (avgSpeed >= DynamicOverdraw.MIN_SPEED)
{ {
// if the player is moving fast enough, // if the player is moving fast enough,
@@ -170,7 +170,7 @@ public class ThreadPoolUtil
*/ */
public static boolean worldGenThreadsCanRun() public static boolean worldGenThreadsCanRun()
{ {
double cameraSpeed = ClientApi.INSTANCE.cameraSpeedRollingAverage.getAverage(); double cameraSpeed = ClientApi.INSTANCE.getAvgCameraSpeed();
// stop these threads if moving a little bit slower than max elytra speed // stop these threads if moving a little bit slower than max elytra speed
double maxAllowedSpeed = (LodUtil.ROCKET_ELYTRA_SPEED_IN_BLOCKS_PER_SEC - 10.0); double maxAllowedSpeed = (LodUtil.ROCKET_ELYTRA_SPEED_IN_BLOCKS_PER_SEC - 10.0);
if (cameraSpeed > maxAllowedSpeed) if (cameraSpeed > maxAllowedSpeed)
@@ -1,5 +1,6 @@
package com.seibel.distanthorizons.core.world; package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure; import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure;
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel; import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
@@ -8,6 +9,7 @@ import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManag
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
@@ -138,6 +140,7 @@ public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhSer
if (serverLevelWrapper != null) if (serverLevelWrapper != null)
{ {
serverLevelWrapper.onUnload(); serverLevelWrapper.onUnload();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(serverLevelWrapper));
} }
@@ -19,6 +19,8 @@
package com.seibel.distanthorizons.core.world; package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.level.DhClientServerLevel; import com.seibel.distanthorizons.core.level.DhClientServerLevel;
@@ -27,6 +29,7 @@ import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.*; import java.util.*;
@@ -34,7 +37,7 @@ import java.util.concurrent.CompletableFuture;
public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLevel> implements IDhClientWorld public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLevel> implements IDhClientWorld
{ {
private final Set<DhClientServerLevel> dhLevels = Collections.synchronizedSet(new HashSet<>()); private final Map<DhClientServerLevel, Set<ILevelWrapper>> dhLevels = Collections.synchronizedMap(new HashMap<>());
private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer"); private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
@@ -54,7 +57,7 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
@Override @Override
public void run() public void run()
{ {
DhClientServerWorld.this.dhLevels.forEach(DhClientServerLevel::clientTick); DhClientServerWorld.this.dhLevels.keySet().forEach(DhClientServerLevel::clientTick);
} }
}, 0, IDhClientWorld.TICK_RATE_IN_MS); }, 0, IDhClientWorld.TICK_RATE_IN_MS);
} }
@@ -75,7 +78,8 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
try try
{ {
DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper, this.getServerPlayerStateManager()); DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper, this.getServerPlayerStateManager());
this.dhLevels.add(level); this.dhLevels.computeIfAbsent(level, k -> Collections.synchronizedSet(new HashSet<>()));
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(wrapper));
return level; return level;
} }
catch (Exception e) catch (Exception e)
@@ -92,6 +96,10 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
} }
else else
{ {
if (wrapper instanceof IClientLevelWrapper)
{
((IClientLevelWrapper) wrapper).markAccessed();
}
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (levelWrapper) -> return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (levelWrapper) ->
{ {
IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper; IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper;
@@ -111,13 +119,14 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
level.startRenderer(); level.startRenderer();
clientLevelWrapper.setDhLevel(level); clientLevelWrapper.setDhLevel(level);
dhLevels.get(level).add(wrapper);
return level; return level;
}); });
} }
} }
@Override @Override
public void unloadLevel(@NotNull ILevelWrapper wrapper) public boolean unloadLevel(@NotNull ILevelWrapper wrapper)
{ {
if (this.dhLevelByLevelWrapper.containsKey(wrapper)) if (this.dhLevelByLevelWrapper.containsKey(wrapper))
{ {
@@ -135,9 +144,18 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
// If the level wrapper is a Client Level Wrapper, then that means the client side leaves the level, // If the level wrapper is a Client Level Wrapper, then that means the client side leaves the level,
// but note that the server side still has the level loaded. So, we don't want to unload the level, // but note that the server side still has the level loaded. So, we don't want to unload the level,
// we just want to stop rendering it. // we just want to stop rendering it.
this.dhLevelByLevelWrapper.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere. DhClientServerLevel level = this.dhLevelByLevelWrapper.remove(wrapper); // Ignore resource warning. The level obj is referenced elsewhere.
Set<ILevelWrapper> wrappers = dhLevels.get(level);
if (wrappers != null) wrappers.remove(wrapper);
if ((wrappers == null || wrappers.isEmpty()) && level.isRendering()) {
level.stopRenderer();
}
wrapper.onUnload(); // We still want to unload the wrapper though.
} }
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(wrapper));
return true;
} }
return false;
} }
@@ -155,13 +173,21 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
synchronized (this.dhLevels) synchronized (this.dhLevels)
{ {
// close each level // close each level
for (DhClientServerLevel level : this.dhLevels) for (DhClientServerLevel level : this.dhLevels.keySet())
{ {
// level wrapper shouldn't be null, but just in case // level wrapper shouldn't be null, but just in case
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper(); IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
if (serverLevelWrapper != null) if (serverLevelWrapper != null)
{ {
serverLevelWrapper.onUnload(); serverLevelWrapper.onUnload();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(serverLevelWrapper));
}
IClientLevelWrapper clientLevelWrapper = level.getClientLevelWrapper();
if (clientLevelWrapper != null)
{
clientLevelWrapper.onUnload();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(clientLevelWrapper));
} }
// close levels asynchronously to speed up // close levels asynchronously to speed up
@@ -19,6 +19,8 @@
package com.seibel.distanthorizons.core.world; package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure; import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
@@ -28,18 +30,17 @@ import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
{ {
private final ConcurrentHashMap<IClientLevelWrapper, DhClientLevel> levels; private final ConcurrentHashMap<String, DhClientLevel> levels;
private final Map<String, Set<IClientLevelWrapper>> levelWrappers = new ConcurrentHashMap<>();
public final ClientOnlySaveStructure saveStructure; public final ClientOnlySaveStructure saveStructure;
public final ClientNetworkState networkState = new ClientNetworkState(); public final ClientNetworkState networkState = new ClientNetworkState();
@@ -76,6 +77,32 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
// methods // // methods //
//=========// //=========//
private DhClientLevel createClientLevel(@NotNull IClientLevelWrapper clientLevelWrapper) {
try
{
if (!ClientApi.INSTANCE.canLoadClientLevel(clientLevelWrapper))
{
return null;
}
DhClientLevel level = new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState);
levelWrappers.computeIfAbsent(clientLevelWrapper.getDhIdentifier(), k -> Collections.synchronizedSet(new HashSet<>())).add(clientLevelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(clientLevelWrapper));
ClientApi.INSTANCE.loadWaitingChunksForLevel(clientLevelWrapper);
return level;
}
catch (Exception e)
{
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e);
ClientApi.INSTANCE.showChatMessageNextFrame(
MinecraftTextFormat.RED + "Distant Horizons: Client level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
return null;
}
}
@Override @Override
public DhClientLevel getOrLoadLevel(@NotNull ILevelWrapper wrapper) public DhClientLevel getOrLoadLevel(@NotNull ILevelWrapper wrapper)
{ {
@@ -83,25 +110,20 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
{ {
return null; return null;
} }
IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) wrapper;
return this.levels.computeIfAbsent((IClientLevelWrapper) wrapper, clientLevelWrapper.markAccessed();
(clientLevelWrapper) -> DhClientLevel storedLevel = this.levels.computeIfAbsent(wrapper.getDhIdentifier(),
(key) -> createClientLevel(clientLevelWrapper)
);
if (storedLevel != null && storedLevel.getClientLevelWrapper() != wrapper) {
unloadLevel(storedLevel.getLevelWrapper());
storedLevel = createClientLevel(clientLevelWrapper);
if (storedLevel != null)
{ {
try this.levels.put(wrapper.getDhIdentifier(), storedLevel);
{ }
return new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState); }
} return storedLevel;
catch (Exception e)
{
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e);
ClientApi.INSTANCE.showChatMessageNextFrame(
MinecraftTextFormat.RED + "Distant Horizons: Client level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
return null;
}
});
} }
@Override @Override
@@ -112,7 +134,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
return null; return null;
} }
return this.levels.get(wrapper); return this.levels.get(wrapper.getDhIdentifier());
} }
@Override @Override
@@ -121,19 +143,26 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
public int getLoadedLevelCount() { return this.levels.size(); } public int getLoadedLevelCount() { return this.levels.size(); }
@Override @Override
public void unloadLevel(@NotNull ILevelWrapper wrapper) public boolean unloadLevel(@NotNull ILevelWrapper wrapper)
{ {
if (!(wrapper instanceof IClientLevelWrapper)) if (!(wrapper instanceof IClientLevelWrapper))
{ {
return; return false;
} }
if (this.levels.containsKey(wrapper)) if (this.levels.containsKey(wrapper.getDhIdentifier()))
{ {
LOGGER.info("Unloading level " + this.levels.get(wrapper)); LOGGER.info("Unloading level " + this.levels.get(wrapper.getDhIdentifier()));
wrapper.onUnload(); wrapper.onUnload();
this.levels.remove(wrapper).close(); Set<IClientLevelWrapper> wrappers = this.levelWrappers.get(wrapper.getDhIdentifier());
wrappers.remove(wrapper);
if (wrappers.isEmpty()) {
this.levels.remove(wrapper.getDhIdentifier()).close();
}
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(wrapper));
return true;
} }
return false;
} }
@Override @Override
@@ -156,6 +185,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
if (clientLevelWrapper != null) if (clientLevelWrapper != null)
{ {
clientLevelWrapper.onUnload(); clientLevelWrapper.onUnload();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(clientLevelWrapper));
} }
@@ -178,6 +208,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
} }
this.levels.clear(); this.levels.clear();
this.levelWrappers.clear();
this.clientTickTimer.cancel(); this.clientTickTimer.cancel();
LOGGER.info("Closed DhWorld of type [" + this.environment + "]."); LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
} }
@@ -19,12 +19,15 @@
package com.seibel.distanthorizons.core.world; package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.generation.PregenManager; import com.seibel.distanthorizons.core.generation.PregenManager;
import com.seibel.distanthorizons.core.level.DhServerLevel; import com.seibel.distanthorizons.core.level.DhServerLevel;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -64,7 +67,9 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
{ {
try try
{ {
return new DhServerLevel(this.saveStructure, (IServerLevelWrapper) serverLevelWrapper, this.getServerPlayerStateManager()); DhServerLevel level = new DhServerLevel(this.saveStructure, (IServerLevelWrapper) serverLevelWrapper, this.getServerPlayerStateManager());
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(wrapper));
return level;
} }
catch (Exception e) catch (Exception e)
{ {
@@ -80,19 +85,21 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
} }
@Override @Override
public void unloadLevel(@NotNull ILevelWrapper wrapper) public boolean unloadLevel(@NotNull ILevelWrapper wrapper)
{ {
if (!(wrapper instanceof IServerLevelWrapper)) if (!(wrapper instanceof IServerLevelWrapper))
{ {
return; return false;
} }
if (this.dhLevelByLevelWrapper.containsKey(wrapper)) if (this.dhLevelByLevelWrapper.containsKey(wrapper))
{ {
DhServerLevel level = this.dhLevelByLevelWrapper.get(wrapper);
wrapper.onUnload(); wrapper.onUnload();
this.dhLevelByLevelWrapper.remove(wrapper).close(); this.dhLevelByLevelWrapper.remove(wrapper).close();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(wrapper));
return true;
} }
return false;
} }
@Override @Override
@@ -49,6 +49,6 @@ public interface IDhWorld extends Closeable
Iterable<? extends IDhLevel> getAllLoadedLevels(); Iterable<? extends IDhLevel> getAllLoadedLevels();
int getLoadedLevelCount(); int getLoadedLevelCount();
void unloadLevel(@NotNull ILevelWrapper levelWrapper); boolean unloadLevel(@NotNull ILevelWrapper levelWrapper);
} }
@@ -96,8 +96,6 @@ public interface IMinecraftRenderWrapper extends IBindable
@Nullable @Nullable
ILightMapWrapper getLightmapWrapper(@NotNull ILevelWrapper level); ILightMapWrapper getLightmapWrapper(@NotNull ILevelWrapper level);
float getShade(EDhDirection lodDirection);
} }
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft; package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import java.io.File; import java.io.File;
@@ -31,6 +32,6 @@ public interface IMinecraftSharedWrapper extends IBindable
int getPlayerCount(); int getPlayerCount();
IServerLevelWrapper getWrappedServerLevel(String levelKey);
} }
@@ -0,0 +1,47 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.Nullable;
public interface IImmersivePortalsAccessor extends IModAccessor
{
boolean isRenderingPortal();
boolean wasPortalRecentlyVisible();
@Nullable
DhBlockPos getOriginalPlayerBlockPos();
@Nullable
DhChunkPos getOriginalPlayerChunkPos();
@Nullable
IClientLevelWrapper getOriginalClientLevelWrapper();
@Nullable
Vec3d getOriginalCameraPos();
}
@@ -0,0 +1,369 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.jetbrains.annotations.NotNull;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.function.Supplier;
public abstract class ImmersivePortalsAbstractAccessor implements IImmersivePortalsAccessor
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static Class<?> portalClass;
private static MethodHandle isRenderingMethodHandle;
private static Method shouldSkipRenderingPortalMethod;
private static MethodHandle getGlobalPortalsMethodHandle;
private static long lastPortalMsTime = -1;
private static boolean portalVisible = false;
//=============//
// constructor //
//=============//
//region
public ImmersivePortalsAbstractAccessor()
{
LOGGER.info("Immersive Portals detected: some DH features will be disabled or may only partially function.");
BeforeRenderEvent event = new BeforeRenderEvent(this);
DhApi.events.bind(DhApiBeforeRenderEvent.class, event);
}
//endregion
//=====================//
// reflection handling //
//=====================//
//region
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
{
portalClass = Class.forName("qouteall.imm_ptl.core.portal.Portal");
}
catch (ClassNotFoundException first)
{
try
{
portalClass = Class.forName("com.qouteall.immersive_portals.portal.Portal"); // 1.16
}
catch (ClassNotFoundException second)
{
RuntimeException err = new RuntimeException(first);
err.addSuppressed(second);
throw err;
}
}
return portalClass;
}
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 Class<?> getIPCGlobalClass()
{
try
{
return Class.forName("qouteall.imm_ptl.core.IPCGlobal");
}
catch (ClassNotFoundException first)
{
try
{
return Class.forName("com.qouteall.immersive_portals.CGlobal"); // 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 (shouldSkipRenderingPortalMethod == null)
{
shouldSkipRenderingPortalMethod = getPortalRendererClass().getDeclaredMethod(
"shouldSkipRenderingPortal", getPortalClass(), Supplier.class
);
shouldSkipRenderingPortalMethod.setAccessible(true);
}
if (Modifier.isStatic(shouldSkipRenderingPortalMethod.getModifiers()))
{
return (boolean) shouldSkipRenderingPortalMethod.invoke(null, portal, frustumSupplier);
}
else
{
return (boolean) shouldSkipRenderingPortalMethod.invoke(
getIPCGlobalClass().getField("renderer").get(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 List<?> getGlobalPortals(Object level)
{
try
{
if (getGlobalPortalsMethodHandle == null)
{
getGlobalPortalsMethodHandle = MethodHandles.lookup().findStatic(
getGlobalPortalStorageClass(),
"getGlobalPortals", MethodType.methodType(List.class).appendParameterTypes(
getLevelClass()
)
);
}
return (List<?>) getGlobalPortalsMethodHandle.invoke(level);
}
catch (Throwable e)
{
throw new RuntimeException(e);
}
}
//endregion
//===========//
// overrides //
//===========//
//region
@Override
public boolean isRenderingPortal()
{
try
{
if (isRenderingMethodHandle == null)
{
isRenderingMethodHandle = MethodHandles.lookup().findStatic(
getPortalRenderingClass(),
"isRendering", MethodType.methodType(Boolean.TYPE)
);
}
return (boolean) isRenderingMethodHandle.invoke();
}
catch (Throwable e)
{
throw new RuntimeException(e);
}
}
@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)
{
lastPortalMsTime = System.currentTimeMillis();
portalVisible = true;
}
else if (portalVisible)
{
if (System.currentTimeMillis() - lastPortalMsTime > 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;
}
/** Essentially reimplements PortalRenderer::getPortalsToRender because it did not exist in 1.16. */
private boolean isPortalVisibleRightNow()
{
Supplier<?> frustumSupplier = getFrustumSupplier();
if (frustumSupplier == null)
{
return false;
}
for (Object portal : getGlobalPortals(getClientLevel()))
{
if (!shouldSkipRenderingPortal(portal, frustumSupplier))
{
return true;
}
}
for (Object entity : getEntitiesForRendering())
{
if (isPortalObject(entity)
&& !shouldSkipRenderingPortal(entity, frustumSupplier))
{
return true;
}
}
return false;
}
private static boolean isPortalObject(Object object) { return getPortalClass().isInstance(object); }
//endregion
@Override
public String getModName() { return "Immersive Portals"; }
//=======//
// event //
//=======//
//region
private static class BeforeRenderEvent extends DhApiBeforeRenderEvent
{
@NotNull
private final IImmersivePortalsAccessor immersivePortals;
public BeforeRenderEvent(@NotNull IImmersivePortalsAccessor portalAccessor) { this.immersivePortals = portalAccessor; }
@Override
public void beforeRender(DhApiCancelableEventParam<DhApiRenderParam> event)
{
// needed because otherwise DH doesn't render to the level anyway
// and will probably render the level the player is currently in instead
if (this.immersivePortals.isRenderingPortal())
{
event.cancelEvent();
}
}
}
//endregion
}
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.wrapperInterfaces.world; package com.seibel.distanthorizons.core.wrapperInterfaces.world;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -29,6 +30,9 @@ import java.awt.*;
public interface IClientLevelWrapper extends ILevelWrapper public interface IClientLevelWrapper extends ILevelWrapper
{ {
/** used to track when this level was last used for Immersive portals support */
void markAccessed();
@Nullable @Nullable
IServerLevelWrapper tryGetServerSideWrapper(); IServerLevelWrapper tryGetServerSideWrapper();
@@ -39,4 +43,6 @@ public interface IClientLevelWrapper extends ILevelWrapper
Color getCloudColor(float tickDelta); Color getCloudColor(float tickDelta);
float getShade(EDhDirection lodDirection);
} }
@@ -28,7 +28,10 @@
"API LOCK", "API LOCK",
"distanthorizons.general.disabledByApi.@tooltip": "distanthorizons.general.disabledByApi.@tooltip":
"This option is controlled by another mod via DH's API \nso it cannot be changed via the UI or config file.", "This option is controlled by another mod via DH's API \nso it cannot be changed via the UI or config file.",
"distanthorizons.general.unsupportedMcVersion":
"VER LOCK",
"distanthorizons.general.unsupportedMcVersion.@tooltip":
"DH doesn't support changing this option on this version of Minecraft. \nAny config file or API set values will be ignored.",
@@ -39,7 +39,13 @@ import org.junit.Test;
public class PooledDataSourceCheckoutTest public class PooledDataSourceCheckoutTest
{ {
@Test /**
* commented out for now since it has
* a chance of breaking if any other tests
* using the same pools run at the same time
* or before this one.
*/
//@Test
public void TestCheckouts() public void TestCheckouts()
{ {
// something like this should probably be called before starting the test to ensure // something like this should probably be called before starting the test to ensure