From eeae8dbdc56837cfe7f7be67f0fbe17f585cdad6 Mon Sep 17 00:00:00 2001
From: s809 <43530948+s809@users.noreply.github.com>
Date: Tue, 12 Nov 2024 12:25:29 +0500
Subject: [PATCH] WIP generation distance limiting
---
.../distanthorizons/core/config/Config.java | 72 ++++++++++++++-----
.../generation/RemoteWorldRetrievalQueue.java | 2 +
.../core/level/AbstractDhServerLevel.java | 15 +++-
.../AbstractFullDataNetworkRequestQueue.java | 41 ++++++-----
.../client/SyncOnLoadRequestQueue.java | 2 +
.../multiplayer/config/SessionConfig.java | 27 +++++--
...n.java => RequestOutOfRangeException.java} | 7 +-
.../messages/requests/ExceptionMessage.java | 4 +-
.../core/pos/DhSectionPos.java | 10 ++-
9 files changed, 127 insertions(+), 53 deletions(-)
rename core/src/main/java/com/seibel/distanthorizons/core/network/exceptions/{InvalidLevelException.java => RequestOutOfRangeException.java} (80%)
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java
index 848b0b856..709b185e3 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java
@@ -33,7 +33,6 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
-import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger;
@@ -43,7 +42,6 @@ import java.awt.*;
import java.io.File;
import java.util.*;
import java.util.List;
-import java.util.concurrent.ThreadLocalRandom;
/**
* This handles any configuration the user has access to.
@@ -1512,23 +1510,9 @@ public class Config
}
- public static class Server
+ public static class Server
{
- public static ConfigEntry realTimeUpdateDistanceRadiusInChunks = new ConfigEntry.Builder()
- .setServersideShortName("renderDistanceRadius")
- .setMinDefaultMax(32, 256, 4096)
- .comment("" +
- "Defines the distance players will receive real-time updates for if enabled. \n" +
- "\n" +
- "Note: \n" +
- "This setting does not prevent players from generating farther out. \n" +
- "If you want to limit performance impact, change rate limits \n" +
- "and thread count/runtime ratio settings instead. \n" +
- "It also does not affect the visuals on clients. \n" +
- "")
- .setPerformance(EConfigEntryPerformance.HIGH)
- .build();
-
+ // Level keys
public static ConfigEntry sendLevelKeys = new ConfigEntry.Builder()
.setServersideShortName("sendLevelKeys")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
@@ -1549,6 +1533,8 @@ public class Config
+ "")
.build();
+
+ // Generation
public static ConfigEntry generationRequestRateLimit = new ConfigEntry.Builder()
.setServersideShortName("generationRequestRateLimit")
.setMinDefaultMax(1, 20, 100)
@@ -1558,6 +1544,23 @@ public class Config
+ "")
.build();
+ public static ConfigEntry maxGenerationRequestDistance = new ConfigEntry.Builder()
+ .setServersideShortName("maxGenerationRequestDistance")
+ .setMinDefaultMax(32, 1024, 4096)
+ .comment("" +
+ "Defines the distance players will receive real-time updates for if enabled. \n" +
+ "\n" +
+ "Note: \n" +
+ "This setting does not prevent players from generating farther out. \n" +
+ "If you want to limit performance impact, change rate limits \n" +
+ "and thread count/runtime ratio settings instead. \n" +
+ "It also does not affect the visuals on clients. \n" +
+ "")
+ .setPerformance(EConfigEntryPerformance.HIGH)
+ .build();
+
+
+ // Real-time updates
public static ConfigEntry enableRealTimeUpdates = new ConfigEntry.Builder()
.setServersideShortName("enableRealTimeUpdates")
.set(true)
@@ -1566,7 +1569,23 @@ public class Config
+ "")
.build();
+ public static ConfigEntry realTimeUpdateDistanceRadiusInChunks = new ConfigEntry.Builder()
+ .setServersideShortName("realTimeUpdateDistanceRadius")
+ .setMinDefaultMax(32, 256, 4096)
+ .comment("" +
+ "Defines the distance players will receive real-time updates for if enabled. \n" +
+ "\n" +
+ "Note: \n" +
+ "This setting does not prevent players from generating farther out. \n" +
+ "If you want to limit performance impact, change rate limits \n" +
+ "and thread count/runtime ratio settings instead. \n" +
+ "It also does not affect the visuals on clients. \n" +
+ "")
+ .setPerformance(EConfigEntryPerformance.HIGH)
+ .build();
+
+ // Sync on load
public static ConfigEntry synchronizeOnLoad = new ConfigEntry.Builder()
.setServersideShortName("synchronizeOnLoad")
.set(true)
@@ -1584,6 +1603,23 @@ public class Config
+ "")
.build();
+ public static ConfigEntry maxSyncOnLoadRequestDistance = new ConfigEntry.Builder()
+ .setServersideShortName("maxSyncOnLoadRequestDistance")
+ .setMinDefaultMax(32, 1024, 4096)
+ .comment("" +
+ "Defines the distance players will receive real-time updates for if enabled. \n" +
+ "\n" +
+ "Note: \n" +
+ "This setting does not prevent players from generating farther out. \n" +
+ "If you want to limit performance impact, change rate limits \n" +
+ "and thread count/runtime ratio settings instead. \n" +
+ "It also does not affect the visuals on clients. \n" +
+ "")
+ .setPerformance(EConfigEntryPerformance.HIGH)
+ .build();
+
+
+ // Common
public static ConfigEntry maxDataTransferSpeed = new ConfigEntry.Builder()
.setServersideShortName("maxDataTransferSpeed")
.setMinDefaultMax(0, 500, 1000000 /* 1 GB/s */)
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/RemoteWorldRetrievalQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/RemoteWorldRetrievalQueue.java
index 56b23490f..18a5c4ad9 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/generation/RemoteWorldRetrievalQueue.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/RemoteWorldRetrievalQueue.java
@@ -64,6 +64,8 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
@Override
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getGenerationRequestRateLimit(); }
+ @Override
+ protected int getMaxRequestDistance() { return this.networkState.sessionConfig.getMaxGenerationRequestDistance(); }
@Override
protected String getQueueName() { return "World Remote Generation Queue"; }
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhServerLevel.java
index 059697189..47cfc5e98 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhServerLevel.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhServerLevel.java
@@ -9,7 +9,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
-import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
+import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
@@ -194,6 +194,13 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
return;
}
+ Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
+ int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(message.sectionPos, new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
+ if (distanceFromPlayer > Config.Server.maxGenerationRequestDistance.get())
+ {
+ message.sendResponse(new RequestOutOfRangeException("Distance too large: " + distanceFromPlayer + " > " + Config.Server.maxGenerationRequestDistance.get()));
+ return;
+ }
ServerPlayerState.RateLimiterSet rateLimiterSet = serverPlayerState.getRateLimiterSet(this);
@@ -241,6 +248,8 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
}
private void queueLodSyncForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
{
+
+
if (!serverPlayerState.sessionConfig.getSynchronizeOnLoad())
{
message.sendResponse(new RequestRejectedException("Operation is disabled in config."));
@@ -350,7 +359,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
if (message instanceof AbstractTrackableMessage)
{
((AbstractTrackableMessage) message).sendResponse(
- new InvalidLevelException(
+ new RequestRejectedException(
"Generation not allowed. " +
"Requested dimension: ["+((ILevelRelatedMessage) message).getLevelName()+"], " +
"player dimension: [" + message.getSession().serverPlayer.getLevel().getDhIdentifier() + "], " +
@@ -446,7 +455,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
}
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
- int distanceFromPlayer = DhSectionPos.getChebyshevBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
+ int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
if (distanceFromPlayer >= serverPlayerState.getServerPlayer().getViewDistance()
&& distanceFromPlayer <= serverPlayerState.sessionConfig.getMaxUpdateDistanceRadius())
{
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/AbstractFullDataNetworkRequestQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/AbstractFullDataNetworkRequestQueue.java
index 70779b70d..7812129a9 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/AbstractFullDataNetworkRequestQueue.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/AbstractFullDataNetworkRequestQueue.java
@@ -7,8 +7,8 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
-import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
+import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
import com.seibel.distanthorizons.core.network.session.SessionClosedException;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
@@ -71,6 +71,8 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
private final SupplierBasedRateLimiter rateLimiter = new SupplierBasedRateLimiter<>(this::getRequestRateLimit);
+ private DhBlockPos2D lastTargetPos = new DhBlockPos2D(0, 0);
+
//=============//
@@ -95,6 +97,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
//==================//
protected abstract int getRequestRateLimit();
+ protected abstract int getMaxRequestDistance();
protected abstract String getQueueName();
@@ -138,6 +141,8 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
return false;
}
+ this.lastTargetPos = targetPos;
+
// queue requests until the queue is full
while (this.getInProgressTaskCount() < this.getWaitingTaskCount()
&& this.getInProgressTaskCount() < this.getRequestRateLimit()
@@ -158,7 +163,8 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
{
Map.Entry mapEntry = this.waitingTasksBySectionPos.entrySet().stream()
.filter(task -> task.getValue().networkDataSourceFuture == null)
- .min(Comparator.comparingInt(x -> posDistanceSquared(targetPos, x.getKey())))
+ .filter(task -> DhSectionPos.getChebyshevSignedBlockDistance(task.getKey(), targetPos) <= this.getMaxRequestDistance())
+ .min(Comparator.comparingInt(task -> DhSectionPos.getChebyshevSignedBlockDistance(task.getKey(), targetPos)))
.orElse(null);
if (mapEntry == null)
@@ -220,16 +226,15 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
LodUtil.assertTrue(this.changedOnly, "Received empty data source response for not changes-only request");
}
}
- catch (InvalidLevelException | RequestRejectedException ignored)
- {
- // We're too late / some cases might trigger a bunch of expected rejections
- return entry.future.complete(false);
- }
catch (SessionClosedException | CancellationException ignored)
{
- // Triggered when level is unloaded
return entry.future.cancel(false);
}
+ catch (RequestRejectedException e)
+ {
+ LOGGER.info("Request rejected by the server: " + e.getMessage());
+ return entry.future.complete(false);
+ }
catch (RateLimitedException e)
{
LOGGER.warn("Rate limited by server, re-queueing task [" + DhSectionPos.toString(sectionPos) + "]: " + e.getMessage());
@@ -240,6 +245,13 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
entry.networkDataSourceFuture = null;
return null;
}
+ catch (RequestOutOfRangeException e)
+ {
+ LOGGER.warn("Out of range, re-queueing task [" + DhSectionPos.toString(sectionPos) + "]: " + e.getMessage());
+
+ entry.networkDataSourceFuture = null;
+ return null;
+ }
catch (Throwable e)
{
entry.retryAttempts--;
@@ -352,22 +364,15 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
for (Map.Entry mapEntry : this.waitingTasksBySectionPos.entrySet())
{
renderer.renderBox(new DebugRenderer.Box(mapEntry.getKey(), -32f, 64f, 0.05f,
- mapEntry.getValue().networkDataSourceFuture != null ? Color.red : Color.gray
+ mapEntry.getValue().networkDataSourceFuture != null ? Color.red
+ : DhSectionPos.getChebyshevSignedBlockDistance(mapEntry.getKey(), this.lastTargetPos) <= this.getMaxRequestDistance() ? Color.gray
+ : Color.lightGray
));
}
}
- //================//
- // helper methods //
- //================//
-
- protected static int posDistanceSquared(DhBlockPos2D targetPos, long pos)
- { return (int) DhSectionPos.getCenterBlockPos(pos).distSquared(targetPos); }
-
-
-
//================//
// helper classes //
//================//
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/SyncOnLoadRequestQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/SyncOnLoadRequestQueue.java
index 5ae421c75..522f04d54 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/SyncOnLoadRequestQueue.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/SyncOnLoadRequestQueue.java
@@ -31,6 +31,8 @@ public class SyncOnLoadRequestQueue extends AbstractFullDataNetworkRequestQueue
@Override
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getSyncOnLoginRateLimit(); }
+ @Override
+ protected int getMaxRequestDistance() { return this.networkState.sessionConfig.getMaxSyncOnLoadDistance(); }
@Override
protected String getQueueName() { return "Sync On Login Queue"; }
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/SessionConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/SessionConfig.java
index 790bffccc..6ace4c042 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/SessionConfig.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/SessionConfig.java
@@ -31,14 +31,15 @@ public class SessionConfig implements INetworkObject
{
// Note: config values are transmitted in the insertion order
- registerConfigEntry(Config.Server.realTimeUpdateDistanceRadiusInChunks, Math::min);
-
- registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration, (x, y) -> x && y);
+ registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration, Boolean::logicalAnd);
+ registerConfigEntry(Config.Server.maxGenerationRequestDistance, Math::min);
registerConfigEntry(Config.Server.generationRequestRateLimit, Math::min);
- registerConfigEntry(Config.Server.enableRealTimeUpdates, (x, y) -> x && y);
+ registerConfigEntry(Config.Server.enableRealTimeUpdates, Boolean::logicalAnd);
+ registerConfigEntry(Config.Server.realTimeUpdateDistanceRadiusInChunks, Math::min);
- registerConfigEntry(Config.Server.synchronizeOnLoad, (x, y) -> x && y);
+ registerConfigEntry(Config.Server.synchronizeOnLoad, Boolean::logicalAnd);
+ registerConfigEntry(Config.Server.maxSyncOnLoadRequestDistance, Math::min);
registerConfigEntry(Config.Server.syncOnLoadRateLimit, Math::min);
registerConfigEntry(Config.Server.maxDataTransferSpeed, (x, y) -> {
@@ -62,12 +63,17 @@ public class SessionConfig implements INetworkObject
// public values //
//===============//
- public int getMaxUpdateDistanceRadius() { return this.getValue(Config.Server.realTimeUpdateDistanceRadiusInChunks); }
public boolean isDistantGenerationEnabled() { return this.getValue(Config.Common.WorldGenerator.enableDistantGeneration); }
+ public int getMaxGenerationRequestDistance() { return this.getValue(Config.Server.maxGenerationRequestDistance); }
public int getGenerationRequestRateLimit() { return this.getValue(Config.Server.generationRequestRateLimit); }
+
public boolean isRealTimeUpdatesEnabled() { return this.getValue(Config.Server.enableRealTimeUpdates); }
+ public int getMaxUpdateDistanceRadius() { return this.getValue(Config.Server.realTimeUpdateDistanceRadiusInChunks); }
+
public boolean getSynchronizeOnLoad() { return this.getValue(Config.Server.synchronizeOnLoad); }
+ public int getMaxSyncOnLoadDistance() { return this.getValue(Config.Server.maxSyncOnLoadRequestDistance); }
public int getSyncOnLoginRateLimit() { return this.getValue(Config.Server.syncOnLoadRateLimit); }
+
public int getMaxDataTransferSpeed() { return this.getValue(Config.Server.maxDataTransferSpeed); }
@@ -78,7 +84,14 @@ public class SessionConfig implements INetworkObject
private static void registerConfigEntry(ConfigEntry configEntry, BiFunction valueConstrainer)
{
- CONFIG_ENTRIES.put(Objects.requireNonNull(configEntry.getServersideShortName()), new Entry(configEntry, valueConstrainer));
+ CONFIG_ENTRIES.compute(Objects.requireNonNull(configEntry.getServersideShortName()), (key, existingEntry) -> {
+ if (existingEntry != null)
+ {
+ throw new IllegalArgumentException("Attempted to register config entry with duplicate serversideShortName: " + key);
+ }
+
+ return new Entry(configEntry, valueConstrainer);
+ });
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/exceptions/InvalidLevelException.java b/core/src/main/java/com/seibel/distanthorizons/core/network/exceptions/RequestOutOfRangeException.java
similarity index 80%
rename from core/src/main/java/com/seibel/distanthorizons/core/network/exceptions/InvalidLevelException.java
rename to core/src/main/java/com/seibel/distanthorizons/core/network/exceptions/RequestOutOfRangeException.java
index 9a7d20a80..fdd32e611 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/network/exceptions/InvalidLevelException.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/network/exceptions/RequestOutOfRangeException.java
@@ -19,8 +19,9 @@
package com.seibel.distanthorizons.core.network.exceptions;
-/** Fired if a user attempts to run an operation in a level they aren't currently in. */
-public class InvalidLevelException extends Exception
+/** Fired if the client attempts to request an LOD out of allowed range. */
+public class RequestOutOfRangeException extends Exception
{
- public InvalidLevelException(String message) { super(message); }
+ public RequestOutOfRangeException(String message) { super(message); }
+
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/requests/ExceptionMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/requests/ExceptionMessage.java
index c6b4bae46..c0679fc40 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/requests/ExceptionMessage.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/requests/ExceptionMessage.java
@@ -20,8 +20,8 @@
package com.seibel.distanthorizons.core.network.messages.requests;
import com.google.common.base.MoreObjects;
-import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
+import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
import io.netty.buffer.ByteBuf;
@@ -36,7 +36,7 @@ public class ExceptionMessage extends AbstractTrackableMessage
{{
// All exceptions here must include constructor: (String)
this.add(RateLimitedException.class);
- this.add(InvalidLevelException.class);
+ this.add(RequestOutOfRangeException.class);
this.add(RequestRejectedException.class);
}};
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java b/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java
index 3fb1252af..64bf74423 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java
@@ -261,12 +261,18 @@ public class DhSectionPos
+ Math.abs(getCenterBlockPosZ(pos) - blockPos.z);
}
- public static int getChebyshevBlockDistance(long pos, DhBlockPos2D blockPos)
+ /**
+ * Returns the signed distance from a given block to a given section.
+ * Essentially acts like a distance from the block to the nearest edge of the section,
+ * except inside the section it's negative.
+ * Useful for detail level insensitive distance comparisons.
+ */
+ public static int getChebyshevSignedBlockDistance(long pos, DhBlockPos2D blockPos)
{
return Math.max(
Math.abs(getCenterBlockPosX(pos) - blockPos.x),
Math.abs(getCenterBlockPosZ(pos) - blockPos.z)
- );
+ ) - getBlockWidth(pos) / 2;
}