mirror of
https://gitlab.com/distant-horizons-team/distant-horizons-server-plugin.git
synced 2026-11-06 01:12:33 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0004539b41 | ||
|
|
9ef0f09dd2 | ||
|
|
79cb5aa302 | ||
|
|
5e9760c3d1 | ||
|
|
637636b5e8 | ||
|
|
0ffe620f92 | ||
|
|
2bcf9c7ca9 | ||
|
|
222221f9dc | ||
|
|
d086458546 | ||
|
|
12b1580535 | ||
|
|
81326b415d | ||
|
|
328684cf1d | ||
|
|
d89cfbfec5 | ||
|
|
413e2a6e70 | ||
|
|
ee385ea5af | ||
|
|
37db471c4f | ||
|
|
3e9165b7a3 | ||
|
|
da893ec1f0 | ||
|
|
320c04aeb7 | ||
|
|
fb8f98c52f | ||
|
|
99cee46ae5 | ||
|
|
a05ee01b8b | ||
|
|
b6878aaaaf | ||
|
|
0fbc612eef | ||
|
|
da2dba88b1 | ||
|
|
35822cb8a7 | ||
|
|
9625035edd | ||
|
|
ecda68771c | ||
|
|
f7822bc204 | ||
|
|
ca4a3a6bcc | ||
|
|
a189487623 | ||
|
|
721d4c2027 | ||
|
|
577a45be5c | ||
|
|
8bc102a26f | ||
|
|
8aa9fa9c31 | ||
|
|
3644051706 | ||
|
|
2786f4b8c7 | ||
|
|
930483ee0c | ||
|
|
64b24aba31 | ||
|
|
22447f5bd5 | ||
|
|
697ca18299 | ||
|
|
bef45fe732 | ||
|
|
f60736f21e | ||
|
|
a36102c16e | ||
|
|
ed98a7e0d4 | ||
|
|
1b4138f537 | ||
|
|
a387c7730d | ||
|
|
dbb9d8a2cf | ||
|
|
3c76e8f229 |
No files matched your search
+2
-2
@@ -4,7 +4,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = 'no.jckf.dhs'
|
||||
version = '0.12.0'
|
||||
version = '0.14.0'
|
||||
description = 'Distant Horizons Support'
|
||||
|
||||
java {
|
||||
@@ -104,4 +104,4 @@ paperVersions.each { mcVersion ->
|
||||
}
|
||||
|
||||
build.dependsOn(taskName)
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ public class BukkitScheduler implements Scheduler
|
||||
@Override
|
||||
public boolean canReadWorldAsync()
|
||||
{
|
||||
return !this.foliaLib.isFolia();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,9 +24,9 @@ import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import no.jckf.dhsupport.core.configuration.WorldConfiguration;
|
||||
import no.jckf.dhsupport.core.scheduling.Scheduler;
|
||||
import no.jckf.dhsupport.core.world.MaterialRecord;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Beacon;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
@@ -56,7 +56,16 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
|
||||
protected Map<Integer, String> dummyColumn;
|
||||
|
||||
protected Map<String, ChunkSnapshot> chunks = new HashMap<>();
|
||||
protected boolean trustHeightMap = true;
|
||||
|
||||
private Integer sectionX;
|
||||
private Integer sectionZ;
|
||||
|
||||
private final ChunkSnapshot[] chunks = new ChunkSnapshot[16];
|
||||
|
||||
protected EnumMap<Material, MaterialRecord> materialCache = new EnumMap<>(Material.class);
|
||||
|
||||
protected Map<String, Map<String, String>> propertyCache = new HashMap<>();
|
||||
|
||||
protected UnsafeValues unsafeValues;
|
||||
|
||||
@@ -69,12 +78,17 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
@Nullable
|
||||
protected Method getChunkAtAsync;
|
||||
|
||||
protected int lightOffset = 0;
|
||||
|
||||
public BukkitWorldInterface(DhSupportBukkitPlugin plugin, World world, Configuration config)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
this.world = world;
|
||||
this.config = config;
|
||||
this.worldConfig = new WorldConfiguration(this, config);
|
||||
|
||||
this.lightOffset = this.getConfig().getInt(DhsConfig.LIGHT_OFFSET_HACK, this.plugin.getDhSupport().getGameVersion().equals("1.20.1") ? this.world.getMinHeight() : 0);
|
||||
this.trustHeightMap = this.getConfig().getBool(DhsConfig.TRUST_HEIGHT_MAP, this.trustHeightMap);
|
||||
}
|
||||
|
||||
public void setLogger(Logger logger)
|
||||
@@ -140,13 +154,26 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
int chunkX = Coordinates.blockToChunk(x);
|
||||
int chunkZ = Coordinates.blockToChunk(z);
|
||||
|
||||
String key = chunkX + "x" + chunkZ;
|
||||
int sectionX = Coordinates.chunkToSection(chunkX);
|
||||
int sectionZ = Coordinates.chunkToSection(chunkZ);
|
||||
|
||||
if (this.chunks.containsKey(key)) {
|
||||
return this.chunks.get(key);
|
||||
if (this.sectionX == null) {
|
||||
this.sectionX = sectionX;
|
||||
this.sectionZ = sectionZ;
|
||||
} else if (this.sectionX != sectionX || this.sectionZ != sectionZ) {
|
||||
throw new IllegalStateException("We appear to be leaking world interface instances across LOD sections");
|
||||
}
|
||||
|
||||
ChunkSnapshot chunk;
|
||||
int baseChunkX = Coordinates.sectionToChunk(sectionX);
|
||||
int baseChunkZ = Coordinates.sectionToChunk(sectionZ);
|
||||
|
||||
int index = (chunkX - baseChunkX) + ((chunkZ - baseChunkZ) << 2);
|
||||
|
||||
ChunkSnapshot chunk = this.chunks[index];
|
||||
|
||||
if (chunk != null) {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
Scheduler scheduler = this.plugin.getDhSupport().getScheduler();
|
||||
|
||||
@@ -158,11 +185,16 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
).join();
|
||||
}
|
||||
|
||||
this.chunks.put(key, chunk);
|
||||
this.chunks[index] = chunk;
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public boolean isInsideSection(int x, int z)
|
||||
{
|
||||
return Coordinates.blockToSection(x) == this.sectionX && Coordinates.blockToSection(z) == this.sectionZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId()
|
||||
{
|
||||
@@ -491,8 +523,12 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
@Override
|
||||
public int getHighestYAt(int x, int z)
|
||||
{
|
||||
if (!this.trustHeightMap) {
|
||||
return this.getMaxY() - 1;
|
||||
}
|
||||
|
||||
if (this.isDummyMode()) {
|
||||
return Collections.max(this.getDummyColumn().keySet());
|
||||
return this.getDummyColumn().isEmpty() ? this.getMinY() : Collections.max(this.getDummyColumn().keySet());
|
||||
}
|
||||
|
||||
return this.getChunk(x, z).getHighestBlockYAt(Coordinates.blockToChunkRelative(x), Coordinates.blockToChunkRelative(z));
|
||||
@@ -529,45 +565,98 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
public String getMaterialAt(int x, int y, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return this.getDummyMaterialAt(y);
|
||||
return Utils.namespaceKey(this.getDummyMaterialAt(y));
|
||||
}
|
||||
|
||||
String material = this.getChunk(x, z).getBlockType(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z)).getKey().toString();
|
||||
return this.getMaterialRecordAt(x, y, z).toString();
|
||||
}
|
||||
|
||||
return Utils.namespaceKey(this.config.getString(DhsConfig.MATERIAL_MAP + "." + Utils.unNamespaceKey(material), material));
|
||||
public MaterialRecord getMaterialRecordAt(int x, int y, int z)
|
||||
{
|
||||
return this.materialCache.computeIfAbsent(
|
||||
this.getChunk(x, z).getBlockType(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z)),
|
||||
(mat) -> {
|
||||
String key = Utils.unNamespaceKey(mat.getKey().toString());
|
||||
String mapped = this.config.getString(DhsConfig.MATERIAL_MAP + '.' + key, key);
|
||||
|
||||
return new MaterialRecord(
|
||||
Utils.namespaceKey(mapped == null ? key : mapped),
|
||||
!key.equals(mapped)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBlockStateAsStringAt(int x, int y, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return "";
|
||||
MaterialRecord materialRecord = this.getMaterialRecordAt(x, y, z);
|
||||
|
||||
if (this.isDummyMode() || materialRecord.isMapped()) {
|
||||
return materialRecord.toString();
|
||||
}
|
||||
|
||||
return this.getChunk(x, z).getBlockData(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z)).getAsString();
|
||||
Map<String, String> properties = this.getBlockPropertiesAt(x, y, z);
|
||||
|
||||
if (properties.isEmpty()) {
|
||||
return materialRecord.toString();
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append(materialRecord.toString())
|
||||
.append('[');
|
||||
|
||||
for (Map.Entry<String, String> property : properties.entrySet()) {
|
||||
builder.append(property.getKey())
|
||||
.append('=')
|
||||
.append(property.getValue())
|
||||
.append(',');
|
||||
}
|
||||
|
||||
builder.setLength(builder.length() - 1);
|
||||
builder.append(']');
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getBlockPropertiesAt(int x, int y, int z)
|
||||
{
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
if (this.isDummyMode()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
String dataString = this.getBlockStateAsStringAt(x, y, z);
|
||||
String dataString = this.getChunk(x, z).getBlockData(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z)).getAsString();
|
||||
|
||||
int kvStart = dataString.indexOf("[");
|
||||
Map<String, String> cached = this.propertyCache.get(dataString);
|
||||
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
int kvStart = dataString.indexOf('[');
|
||||
|
||||
if (kvStart == -1) {
|
||||
return properties;
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
String[] kvStrings = dataString.substring(kvStart + 1, dataString.length() - 1).split(",");
|
||||
|
||||
Arrays.sort(kvStrings);
|
||||
|
||||
Map<String, String> properties = new LinkedHashMap<>(kvStrings.length);
|
||||
|
||||
for (String kvString : kvStrings) {
|
||||
String[] kv = kvString.split("=", 2);
|
||||
|
||||
properties.put(kv[0], kv[1]);
|
||||
}
|
||||
|
||||
properties = Collections.unmodifiableMap(properties);
|
||||
|
||||
this.propertyCache.put(dataString, properties);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -578,7 +667,7 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (byte) this.getChunk(x, z).getBlockEmittedLight(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z));
|
||||
return (byte) this.getChunk(x, z).getBlockEmittedLight(Coordinates.blockToChunkRelative(x), Math.clamp(y + this.lightOffset, this.getMinY(), this.getMaxY() - 1), Coordinates.blockToChunkRelative(z));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -588,7 +677,7 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
return (byte) (this.getDummyMaterialAt(y + 1).equals("minecraft:air") ? 15 : 0);
|
||||
}
|
||||
|
||||
return (byte) this.getChunk(x, z).getBlockSkyLight(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z));
|
||||
return (byte) this.getChunk(x, z).getBlockSkyLight(Coordinates.blockToChunkRelative(x), Math.clamp(y + this.lightOffset, this.getMinY(), this.getMaxY() - 1), Coordinates.blockToChunkRelative(z));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -604,15 +693,63 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.getMaterialAt(x, y, z).equals("minecraft:beacon")) {
|
||||
if (!this.getChunk(x, z).getBlockType(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z)).equals(Material.BEACON)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.plugin.getDhSupport().getScheduler()
|
||||
.runOnRegionThread(this.world.getUID(), x, z, () ->
|
||||
((Beacon) this.world.getBlockAt(x, y, z).getState()).getTier() > 0
|
||||
)
|
||||
.join();
|
||||
return this.hasTierOneBeaconBase(x, y, z) && this.hasClearBeaconBeamPath(x, y, z);
|
||||
}
|
||||
|
||||
public boolean hasTierOneBeaconBase(int x, int y, int z)
|
||||
{
|
||||
int baseY = y - 1;
|
||||
|
||||
if (baseY < this.getMinY()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int checkX = x - 1; checkX <= x + 1; checkX++) {
|
||||
for (int checkZ = z - 1; checkZ <= z + 1; checkZ++) {
|
||||
if (this.isInsideSection(checkX, checkZ) && !this.isBeaconBaseBlock(this.getMaterialAt(checkX, baseY, checkZ))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isBeaconBaseBlock(String material)
|
||||
{
|
||||
switch (material) {
|
||||
case "minecraft:iron_block":
|
||||
case "minecraft:gold_block":
|
||||
case "minecraft:diamond_block":
|
||||
case "minecraft:emerald_block":
|
||||
case "minecraft:netherite_block":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasClearBeaconBeamPath(int x, int y, int z)
|
||||
{
|
||||
for (int checkY = y + 1; checkY < this.getMaxY(); checkY++) {
|
||||
String material = this.getMaterialAt(x, checkY, z);
|
||||
|
||||
switch (material) {
|
||||
case "minecraft:air":
|
||||
case "minecraft:cave_air":
|
||||
case "minecraft:void_air":
|
||||
case "minecraft:glass":
|
||||
continue;
|
||||
default:
|
||||
return material.endsWith("_stained_glass");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -626,7 +763,7 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
|
||||
int maxY = this.getMaxY();
|
||||
|
||||
while (y < maxY) {
|
||||
while (y + 1 < maxY) {
|
||||
y++;
|
||||
|
||||
String material = Utils.unNamespaceKey(this.getMaterialAt(x, y, z)).toUpperCase();
|
||||
|
||||
@@ -25,14 +25,20 @@ import no.jckf.dhsupport.bukkit.handler.PlayerHandler;
|
||||
import no.jckf.dhsupport.bukkit.handler.PluginMessageProxy;
|
||||
import no.jckf.dhsupport.bukkit.handler.WorldHandler;
|
||||
import no.jckf.dhsupport.core.DhSupport;
|
||||
import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.MultiLineChart;
|
||||
import org.bstats.charts.SingleLineChart;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class DhSupportBukkitPlugin extends JavaPlugin
|
||||
{
|
||||
@@ -60,13 +66,27 @@ public class DhSupportBukkitPlugin extends JavaPlugin
|
||||
|
||||
}
|
||||
|
||||
this.getLogger().info("I am DHS " + pluginVersion + ".");
|
||||
String gameVersion = this.getGameVersion();
|
||||
|
||||
this.dhSupport = new DhSupport(pluginVersion);
|
||||
this.getLogger().info("I am DHS " + pluginVersion + " running on " + this.getServer().getName() + " for Minecraft " + gameVersion + ".");
|
||||
|
||||
this.dhSupport = new DhSupport(pluginVersion, gameVersion);
|
||||
this.dhSupport.setLogger(this.getLogger());
|
||||
this.dhSupport.setDataDirectory(this.getDataFolder().getAbsolutePath());
|
||||
|
||||
this.metrics = new Metrics(this, 21843);
|
||||
this.metrics.addCustomChart(new SingleLineChart("dh_users", () -> this.dhSupport.getPlayerConfigurations().size()));
|
||||
this.metrics.addCustomChart(new MultiLineChart("dh_render_distance", () -> {
|
||||
Map<String, Integer> renderDistances = new HashMap<>();
|
||||
|
||||
for (Configuration playerConfig : this.dhSupport.getPlayerConfigurations().values()) {
|
||||
String rd = String.valueOf(playerConfig.getInt(DhsConfig.RENDER_DISTANCE));
|
||||
|
||||
renderDistances.put(rd, renderDistances.getOrDefault(rd, 0) + 1);
|
||||
}
|
||||
|
||||
return renderDistances;
|
||||
}));
|
||||
|
||||
this.loadDhsConfig();
|
||||
|
||||
@@ -97,6 +117,10 @@ public class DhSupportBukkitPlugin extends JavaPlugin
|
||||
@Override
|
||||
public void onDisable()
|
||||
{
|
||||
if (this.metrics != null) {
|
||||
this.metrics.shutdown();
|
||||
}
|
||||
|
||||
if (this.scheduler != null) {
|
||||
this.scheduler.cancelTasks();
|
||||
}
|
||||
@@ -140,6 +164,21 @@ public class DhSupportBukkitPlugin extends JavaPlugin
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getGameVersion()
|
||||
{
|
||||
try {
|
||||
Class<? extends Server> serverClass = this.getServer().getClass();
|
||||
|
||||
serverClass.getMethod("getMinecraftVersion");
|
||||
|
||||
return this.getServer().getMinecraftVersion();
|
||||
} catch (NoSuchMethodException exception) {
|
||||
// If we're here, that probably means we're not running Paper or any Paper fork.
|
||||
}
|
||||
|
||||
return this.getServer().getBukkitVersion().split("-", 2)[0];
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DhSupport getDhSupport()
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PlayerHandler implements Listener
|
||||
@@ -51,7 +52,7 @@ public class PlayerHandler implements Listener
|
||||
return null;
|
||||
}
|
||||
|
||||
for (String message : dhSupport.getConfig().getStringList(DhsConfig.AD_MESSAGE, new ArrayList<>())) {
|
||||
for (String message : dhSupport.getConfig().getStringList(DhsConfig.AD_MESSAGE, Collections.emptyList())) {
|
||||
player.sendMessage(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ package no.jckf.dhsupport.bukkit.handler;
|
||||
import no.jckf.dhsupport.bukkit.BukkitWorldInterface;
|
||||
import no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
@@ -40,7 +41,7 @@ import java.util.Map;
|
||||
|
||||
public class WorldHandler implements Listener
|
||||
{
|
||||
protected static final String[] EVENT_METHOD_NAMES = { "blockList", "getBlocks", "getBlock" };
|
||||
protected static final String[] EVENT_METHOD_NAMES = { "blockList", "getBlocks", "getBlock", "getLocation", "getChunk" };
|
||||
|
||||
protected DhSupportBukkitPlugin plugin;
|
||||
|
||||
@@ -137,7 +138,11 @@ public class WorldHandler implements Listener
|
||||
try {
|
||||
Object result = method.invoke(event);
|
||||
|
||||
if (result instanceof List) {
|
||||
if (result instanceof Location location) {
|
||||
list = List.of(location.getBlock());
|
||||
} else if (result instanceof Chunk chunk) {
|
||||
list = List.of(chunk.getBlock(0, 0, 0));
|
||||
} else if (result instanceof List) {
|
||||
list = (List<?>) result;
|
||||
} else {
|
||||
list = List.of(result);
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
package no.jckf.dhsupport.core;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
// Block -> chunk -> section -> region
|
||||
public class Coordinates
|
||||
{
|
||||
@@ -101,4 +103,39 @@ public class Coordinates
|
||||
{
|
||||
return value & 0x0F;
|
||||
}
|
||||
|
||||
// PACK/UNPACK
|
||||
|
||||
public static long pack(int x, int z)
|
||||
{
|
||||
return ((long) x & 0xffffffffL)
|
||||
| (((long) z & 0xffffffffL) << 32);
|
||||
}
|
||||
|
||||
public static int unpackX(long packed)
|
||||
{
|
||||
return (int) packed;
|
||||
}
|
||||
|
||||
public static int unpackZ(long packed)
|
||||
{
|
||||
return (int) (packed >> 32);
|
||||
}
|
||||
|
||||
// ???
|
||||
|
||||
public static void foreachChunkInSection(int sectionX, int sectionZ, BiConsumer<Integer, Integer> consumer)
|
||||
{
|
||||
int worldX = sectionToBlock(sectionX);
|
||||
int worldZ = sectionToBlock(sectionZ);
|
||||
|
||||
for (int xMultiplier = 0; xMultiplier < 4; xMultiplier++) {
|
||||
for (int zMultiplier = 0; zMultiplier < 4; zMultiplier++) {
|
||||
consumer.accept(
|
||||
worldX + 16 * xMultiplier,
|
||||
worldZ + 16 * zMultiplier
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,8 @@ public class DhSupport implements Configurable
|
||||
{
|
||||
protected String pluginVersion;
|
||||
|
||||
protected String gameVersion;
|
||||
|
||||
protected String dataDirectory;
|
||||
|
||||
protected Database database;
|
||||
@@ -89,9 +91,10 @@ public class DhSupport implements Configurable
|
||||
|
||||
protected Map<UUID, PreGenerator> preGenerators = new HashMap<>();
|
||||
|
||||
public DhSupport(String pluginVersion)
|
||||
public DhSupport(String pluginVersion, String gameVersion)
|
||||
{
|
||||
this.pluginVersion = pluginVersion;
|
||||
this.gameVersion = gameVersion;
|
||||
|
||||
this.configuration = new Configuration();
|
||||
|
||||
@@ -244,6 +247,16 @@ public class DhSupport implements Configurable
|
||||
return this.lodRepository;
|
||||
}
|
||||
|
||||
public String getPluginVersion()
|
||||
{
|
||||
return this.pluginVersion;
|
||||
}
|
||||
|
||||
public String getGameVersion()
|
||||
{
|
||||
return this.gameVersion;
|
||||
}
|
||||
|
||||
public Configuration getConfig()
|
||||
{
|
||||
return this.configuration;
|
||||
@@ -340,7 +353,11 @@ public class DhSupport implements Configurable
|
||||
return this.queuedBuilders.get(key);
|
||||
}
|
||||
|
||||
CompletableFuture<Lod> queued = this.getScheduler().runOnSeparateThread(builder::generate)
|
||||
CompletableFuture<Lod> queued = this.getScheduler().runOnSeparateThread(() -> {
|
||||
this.joinPauseState();
|
||||
|
||||
return builder.generate();
|
||||
})
|
||||
.thenApply((lod) -> {
|
||||
this.queuedBuilders.remove(key);
|
||||
|
||||
@@ -376,8 +393,6 @@ public class DhSupport implements Configurable
|
||||
|
||||
protected CompletableFuture<LodModel> generateLod(UUID worldId, SectionPosition position, boolean manageChunks)
|
||||
{
|
||||
this.joinPauseState();
|
||||
|
||||
int worldX = Coordinates.sectionToBlock(position.getX());
|
||||
int worldZ = Coordinates.sectionToBlock(position.getZ());
|
||||
|
||||
@@ -385,24 +400,19 @@ public class DhSupport implements Configurable
|
||||
|
||||
boolean generateNewChunks = world.getConfig().getBool(DhsConfig.GENERATE_NEW_CHUNKS, true);
|
||||
|
||||
Map<String, CompletableFuture<Boolean>> loads = new HashMap<>();
|
||||
Map<Long, CompletableFuture<Boolean>> loads = new HashMap<>();
|
||||
|
||||
if (manageChunks) {
|
||||
// Load all the chunks we need for this request.
|
||||
for (int xMultiplier = 0; xMultiplier < 4; xMultiplier++) {
|
||||
for (int zMultiplier = 0; zMultiplier < 4; zMultiplier++) {
|
||||
int chunkX = worldX + 16 * xMultiplier;
|
||||
int chunkZ = worldZ + 16 * zMultiplier;
|
||||
|
||||
if (!world.isChunkLoaded(chunkX, chunkZ)) {
|
||||
if (generateNewChunks) {
|
||||
loads.put(worldX + "x" + worldZ, world.loadOrGenerateChunkAsync(chunkX, chunkZ));
|
||||
} else {
|
||||
loads.put(worldX + "x" + worldZ, world.loadChunkAsync(chunkX, chunkZ));
|
||||
}
|
||||
Coordinates.foreachChunkInSection(position.getX(), position.getZ(), (chunkX, chunkZ) -> {
|
||||
if (!world.isChunkLoaded(chunkX, chunkZ)) {
|
||||
if (generateNewChunks) {
|
||||
loads.put(Coordinates.pack(chunkX, chunkZ), world.loadOrGenerateChunkAsync(chunkX, chunkZ));
|
||||
} else {
|
||||
loads.put(Coordinates.pack(chunkX, chunkZ), world.loadChunkAsync(chunkX, chunkZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for chunk loads, then...
|
||||
@@ -421,52 +431,36 @@ public class DhSupport implements Configurable
|
||||
.anyMatch(Predicate.isEqual(false));
|
||||
|
||||
if (loadRejected) {
|
||||
// Discard the chunks we loaded.
|
||||
this.getScheduler().runOnRegionThread(worldId, worldX, worldZ, () -> {
|
||||
for (String key : loads.keySet()) {
|
||||
String[] xz = key.split("x", 2);
|
||||
|
||||
world.discardChunk(
|
||||
Integer.parseInt(xz[0]),
|
||||
Integer.parseInt(xz[1])
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
// If no dummy chunk is configured, reject the build request.
|
||||
if (world.getConfig().getStringList(DhsConfig.DUMMY_CHUNK).isEmpty()) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
// Use dummy chunks instead of real world data.
|
||||
world.setDummyMode(true);
|
||||
} else {
|
||||
// Prime chunk data in world interface.
|
||||
Coordinates.foreachChunkInSection(position.getX(), position.getZ(), (chunkX, chunkZ) -> {
|
||||
world.getMaterialAt(chunkX, world.getSeaLevel(), chunkZ);
|
||||
});
|
||||
}
|
||||
|
||||
// Discard the chunks we loaded.
|
||||
this.getScheduler().runOnRegionThread(worldId, worldX, worldZ, () -> {
|
||||
for (long packed : loads.keySet()) {
|
||||
world.discardChunk(
|
||||
Coordinates.unpackX(packed),
|
||||
Coordinates.unpackZ(packed)
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
// No LOD was found. Start building a new one.
|
||||
CompletableFuture<Lod> lodFuture = this.queueBuilder(worldId, position, this.getBuilder(world, position));
|
||||
|
||||
// Combine the LOD and beacons and save the result in the database.
|
||||
return lodFuture.thenApply((lod) -> {
|
||||
// Discard the chunks we loaded.
|
||||
this.getScheduler().runOnRegionThread(worldId, worldX, worldZ, () -> {
|
||||
for (String key : loads.keySet()) {
|
||||
String[] xz = key.split("x", 2);
|
||||
|
||||
world.discardChunk(
|
||||
Integer.parseInt(xz[0]),
|
||||
Integer.parseInt(xz[1])
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
Encoder lodEncoder = new Encoder();
|
||||
Encoder lodEncoder = new Encoder(8192);
|
||||
lod.encode(lodEncoder);
|
||||
|
||||
Encoder beaconEncoder = new Encoder();
|
||||
Encoder beaconEncoder = new Encoder(16 + lod.getBeacons().size() * 16);
|
||||
beaconEncoder.writeCollection(lod.getBeacons());
|
||||
|
||||
this.generationTracker.ping();
|
||||
@@ -577,7 +571,7 @@ public class DhSupport implements Configurable
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!playerConfig.getBool(DhsConfig.DISTANT_GENERATION_ENABLED) || !playerConfig.getBool(DhsConfig.REAL_TIME_UPDATES_ENABLED)) {
|
||||
if (!playerConfig.getBool(DhsConfig.REAL_TIME_UPDATES_ENABLED)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -612,11 +606,11 @@ public class DhSupport implements Configurable
|
||||
FullDataChunkMessage chunkResponse = new FullDataChunkMessage();
|
||||
chunkResponse.setBufferId(myBufferId);
|
||||
chunkResponse.setIsFirst(chunkNo == 0);
|
||||
chunkResponse.setData(Arrays.copyOfRange(
|
||||
chunkResponse.setData(
|
||||
data,
|
||||
LodHandler.CHUNK_SIZE * chunkNo,
|
||||
Math.min(LodHandler.CHUNK_SIZE * chunkNo + LodHandler.CHUNK_SIZE, data.length)
|
||||
));
|
||||
Math.min(LodHandler.CHUNK_SIZE, data.length - LodHandler.CHUNK_SIZE * chunkNo)
|
||||
);
|
||||
|
||||
this.pluginMessageHandler.sendPluginMessage(player.getUniqueId(), chunkResponse);
|
||||
}
|
||||
@@ -682,7 +676,7 @@ public class DhSupport implements Configurable
|
||||
{
|
||||
WorldInterface world = this.getWorldInterface(worldId);
|
||||
|
||||
List<SectionPosition> positions = new ArrayList<>();
|
||||
List<SectionPosition> positions = new ArrayList<>(concurrency);
|
||||
|
||||
for (int i = 0; i < concurrency; i++) {
|
||||
SectionPosition position = new SectionPosition();
|
||||
@@ -698,7 +692,7 @@ public class DhSupport implements Configurable
|
||||
.toArray(CompletableFuture[]::new)
|
||||
)
|
||||
.thenCompose((foo) -> {
|
||||
List<CompletableFuture<?>> chains = new ArrayList<>();
|
||||
List<CompletableFuture<?>> chains = new ArrayList<>(positions.size());
|
||||
|
||||
long ts = System.currentTimeMillis();
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ public class PreGenerator implements Runnable
|
||||
int[][] directions = {{ 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 }};
|
||||
int dirIndex = 0;
|
||||
|
||||
List<CompletableFuture<LodModel>> requests = new ArrayList<>();
|
||||
List<CompletableFuture<LodModel>> requests = new ArrayList<>(rateLimit);
|
||||
|
||||
this.startTime = System.currentTimeMillis();
|
||||
|
||||
|
||||
@@ -21,11 +21,16 @@ package no.jckf.dhsupport.core;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class Utils
|
||||
{
|
||||
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
private static final Map<String, String> NAMESPACE_CACHE = new ConcurrentHashMap<>();
|
||||
private static final Map<String, String> UNNAMESPACE_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public static String bytesToHex(byte[] bytes)
|
||||
{
|
||||
byte[] hexChars = new byte[bytes.length * 2];
|
||||
@@ -115,9 +120,12 @@ public class Utils
|
||||
return builder.substring(0, builder.length() - 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated For awareness: This method caches results by key only.
|
||||
*/
|
||||
public static String namespaceKey(String key, String defaultNamespace)
|
||||
{
|
||||
return key.contains(":") ? key : defaultNamespace + ":" + key;
|
||||
return NAMESPACE_CACHE.computeIfAbsent(key, (k) -> k.contains(":") ? k : defaultNamespace + ":" + k);
|
||||
}
|
||||
|
||||
public static String namespaceKey(String key)
|
||||
@@ -125,9 +133,12 @@ public class Utils
|
||||
return namespaceKey(key, "minecraft");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated For awareness: This method caches results by key only.
|
||||
*/
|
||||
public static String unNamespaceKey(String key, String namespace)
|
||||
{
|
||||
return key.startsWith(namespace + ":") ? key.substring(namespace.length() + 1) : key;
|
||||
return UNNAMESPACE_CACHE.computeIfAbsent(key, (k) -> k.startsWith(namespace + ":") ? k.substring(namespace.length() + 1) : k);
|
||||
}
|
||||
|
||||
public static String unNamespaceKey(String key)
|
||||
|
||||
@@ -110,7 +110,7 @@ public class Decoder
|
||||
{
|
||||
int size = this.readInt();
|
||||
|
||||
List<T> items = new ArrayList<>();
|
||||
List<T> items = new ArrayList<>(Math.min(size, 128));
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
T item = this.readObject(className);
|
||||
|
||||
@@ -33,11 +33,21 @@ public class Encoder
|
||||
this.output = ByteStreams.newDataOutput();
|
||||
}
|
||||
|
||||
public Encoder(int size)
|
||||
{
|
||||
this.output = ByteStreams.newDataOutput(size);
|
||||
}
|
||||
|
||||
public void write(byte[] data)
|
||||
{
|
||||
this.output.write(data);
|
||||
}
|
||||
|
||||
public void write(byte[] data, int offset, int length)
|
||||
{
|
||||
this.output.write(data, offset, length);
|
||||
}
|
||||
|
||||
public void writeByte(byte value)
|
||||
{
|
||||
this.output.writeByte(value);
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -35,6 +36,11 @@ public class Configuration
|
||||
this.variables.clear();
|
||||
}
|
||||
|
||||
public boolean has(String key)
|
||||
{
|
||||
return this.variables.containsKey(key);
|
||||
}
|
||||
|
||||
public void set(String key, @Nullable Object value)
|
||||
{
|
||||
if (value instanceof String stringValue) {
|
||||
@@ -106,7 +112,7 @@ public class Configuration
|
||||
|
||||
public List<String> getStringList(String key)
|
||||
{
|
||||
return this.getStringList(key, new ArrayList<>());
|
||||
return this.getStringList(key, Collections.emptyList());
|
||||
}
|
||||
|
||||
public int increment(String key)
|
||||
|
||||
@@ -70,6 +70,8 @@ public abstract class DhsConfig
|
||||
|
||||
public static String BUILDER_TYPE = "builder_type";
|
||||
|
||||
public static String TRUST_HEIGHT_MAP = "trust_height_map";
|
||||
|
||||
public static String BUILDER_RESOLUTION = "builder_resolution";
|
||||
|
||||
public static String SCAN_TO_SEA_LEVEL= "scan_to_sea_level";
|
||||
@@ -91,4 +93,6 @@ public abstract class DhsConfig
|
||||
public static String MATERIAL_MAP = "material_map";
|
||||
|
||||
public static String AD_MESSAGE = "ad_message";
|
||||
|
||||
public static String LIGHT_OFFSET_HACK = "light_offset_hack";
|
||||
}
|
||||
@@ -102,6 +102,8 @@ public class LodRepository
|
||||
statement.setInt(3, sectionZ);
|
||||
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
result.next();
|
||||
|
||||
byte[] data = result.getBytes("data");
|
||||
|
||||
if (data == null) {
|
||||
@@ -135,6 +137,8 @@ public class LodRepository
|
||||
statement.setInt(3, sectionZ);
|
||||
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
result.next();
|
||||
|
||||
return result.getInt(1) == 1;
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
|
||||
@@ -76,16 +76,9 @@ public class DataPoint extends DataObject
|
||||
this.data |= ((long) startY & START_Y_MASK) << START_Y_OFFSET;
|
||||
}
|
||||
|
||||
|
||||
public int getStartY()
|
||||
{
|
||||
int raw = (int) ((this.data >> START_Y_OFFSET) & START_Y_MASK);
|
||||
|
||||
if ((raw & 0x0800) != 0) {
|
||||
raw |= (int) ~START_Y_MASK;
|
||||
}
|
||||
|
||||
return raw;
|
||||
return (int) ((this.data >> START_Y_OFFSET) & START_Y_MASK);
|
||||
}
|
||||
|
||||
public void setSkyLight(byte skyLight)
|
||||
|
||||
@@ -42,6 +42,10 @@ public class Lod extends DataObject
|
||||
|
||||
public static int width = 64;
|
||||
|
||||
protected static volatile byte[] compressedGenerationStep;
|
||||
|
||||
protected static volatile byte[] compressedWorldCompressionType;
|
||||
|
||||
protected WorldInterface worldInterface;
|
||||
|
||||
protected SectionPosition position;
|
||||
@@ -63,7 +67,7 @@ public class Lod extends DataObject
|
||||
|
||||
protected void encodeData(Encoder encoder)
|
||||
{
|
||||
Encoder toCompress = new Encoder();
|
||||
Encoder toCompress = new Encoder(8192);
|
||||
|
||||
for (List<DataPoint> column : this.columns) {
|
||||
toCompress.writeShort(column.size());
|
||||
@@ -89,10 +93,15 @@ public class Lod extends DataObject
|
||||
|
||||
protected void encodeColumnGenerationStep(Encoder encoder)
|
||||
{
|
||||
byte[] bytesToCompress = new byte[Lod.width * Lod.width];
|
||||
Arrays.fill(bytesToCompress, (byte) GenerationStep.LIGHT.value);
|
||||
byte[] compressed = Lod.compressedGenerationStep;
|
||||
|
||||
byte[] compressed = this.compress(bytesToCompress);
|
||||
if (compressed == null) {
|
||||
byte[] bytesToCompress = new byte[Lod.width * Lod.width];
|
||||
Arrays.fill(bytesToCompress, (byte) GenerationStep.LIGHT.value);
|
||||
|
||||
compressed = this.compress(bytesToCompress);
|
||||
Lod.compressedGenerationStep = compressed;
|
||||
}
|
||||
|
||||
encoder.writeInt(compressed.length);
|
||||
encoder.write(compressed);
|
||||
@@ -100,9 +109,14 @@ public class Lod extends DataObject
|
||||
|
||||
protected void encodeWorldCompressionType(Encoder encoder)
|
||||
{
|
||||
byte[] bytesToCompress = new byte[Lod.width * Lod.width]; // All zeroes means WorldCompressionType.STRICT
|
||||
byte[] compressed = Lod.compressedWorldCompressionType;
|
||||
|
||||
byte[] compressed = this.compress(bytesToCompress);
|
||||
if (compressed == null) {
|
||||
byte[] bytesToCompress = new byte[Lod.width * Lod.width]; // All zeroes means WorldCompressionType.STRICT
|
||||
|
||||
compressed = this.compress(bytesToCompress);
|
||||
Lod.compressedWorldCompressionType = compressed;
|
||||
}
|
||||
|
||||
encoder.writeInt(compressed.length);
|
||||
encoder.write(compressed);
|
||||
@@ -110,7 +124,7 @@ public class Lod extends DataObject
|
||||
|
||||
protected void encodeMappings(Encoder encoder)
|
||||
{
|
||||
Encoder toCompress = new Encoder();
|
||||
Encoder toCompress = new Encoder(1024);
|
||||
|
||||
toCompress.writeInt(this.idMappings.size());
|
||||
|
||||
@@ -155,7 +169,11 @@ public class Lod extends DataObject
|
||||
ByteArrayOutputStream compressedStream = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
OutputStream compressorStream = new XZOutputStream(compressedStream, new LZMA2Options(3), XZ.CHECK_CRC64);
|
||||
LZMA2Options options = new LZMA2Options(3);
|
||||
|
||||
options.setDictSize(Math.max(LZMA2Options.DICT_SIZE_MIN, Math.min(uncompressedData.length, 1 << 20)));
|
||||
|
||||
OutputStream compressorStream = new XZOutputStream(compressedStream, options, XZ.CHECK_CRC64);
|
||||
|
||||
compressorStream.write(uncompressedData);
|
||||
compressorStream.flush();
|
||||
|
||||
@@ -30,7 +30,6 @@ import no.jckf.dhsupport.core.message.plugin.FullDataSourceResponseMessage;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
public class LodHandler
|
||||
@@ -91,6 +90,15 @@ public class LodHandler
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.dhSupport.isPaused() && !this.dhSupport.getLodRepository().lodExists(worldUuid, position.getX(), position.getZ())) {
|
||||
ExceptionMessage exceptionMessage = new ExceptionMessage();
|
||||
exceptionMessage.isResponseTo(requestMessage);
|
||||
exceptionMessage.setTypeId(ExceptionMessage.TYPE_REQUEST_REJECTED);
|
||||
exceptionMessage.setMessage("Server has paused Distant Horizons");
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), exceptionMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
int worldX = Coordinates.sectionToBlock(position.getX());
|
||||
int worldZ = Coordinates.sectionToBlock(position.getZ());
|
||||
|
||||
@@ -165,11 +173,11 @@ public class LodHandler
|
||||
FullDataChunkMessage chunkResponse = new FullDataChunkMessage();
|
||||
chunkResponse.setBufferId(myBufferId);
|
||||
chunkResponse.setIsFirst(chunkNo == 0);
|
||||
chunkResponse.setData(Arrays.copyOfRange(
|
||||
chunkResponse.setData(
|
||||
data,
|
||||
CHUNK_SIZE * chunkNo,
|
||||
Math.min(CHUNK_SIZE * chunkNo + CHUNK_SIZE, data.length)
|
||||
));
|
||||
Math.min(CHUNK_SIZE, data.length - CHUNK_SIZE * chunkNo)
|
||||
);
|
||||
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), chunkResponse);
|
||||
}
|
||||
|
||||
@@ -22,11 +22,17 @@ import no.jckf.dhsupport.core.DhSupport;
|
||||
import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import no.jckf.dhsupport.core.message.plugin.LevelInitMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.LevelInitRequestMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.RemotePlayerConfigMessage;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerConfigHandler
|
||||
{
|
||||
public final static String USAGE_PERMISSION = "distant_horizons.worlds.%s";
|
||||
@@ -43,6 +49,46 @@ public class PlayerConfigHandler
|
||||
|
||||
public void register()
|
||||
{
|
||||
this.pluginMessageHandler.getEventBus().registerHandler(LevelInitRequestMessage.class, (initRequestMessage) -> {
|
||||
// TODO: Some sort of Player wrapper or interface object. Bukkit classes should not be imported here.
|
||||
Player player = Bukkit.getPlayer(initRequestMessage.getSender());
|
||||
|
||||
NamespacedKey worldKey = NamespacedKey.fromString(initRequestMessage.getWorldKey());
|
||||
|
||||
// TODO: This too 👎
|
||||
UUID worldUuid = null;
|
||||
|
||||
for (World world : Bukkit.getServer().getWorlds()) {
|
||||
if (world.getKey().equals(worldKey)) {
|
||||
worldUuid = world.getUID();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (worldUuid == null) {
|
||||
this.dhSupport.warning("Player " + player.getName() + " requested init for unknown world " + worldKey + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
WorldInterface world = this.dhSupport.getWorldInterface(worldUuid);
|
||||
|
||||
Configuration dhsConfig = world.getConfig();
|
||||
|
||||
String levelKeyPrefix = dhsConfig.getString(DhsConfig.LEVEL_KEY_PREFIX);
|
||||
String levelKey = world.getKey();
|
||||
|
||||
if (levelKeyPrefix != null) {
|
||||
levelKey = levelKeyPrefix + levelKey;
|
||||
}
|
||||
|
||||
LevelInitMessage levelInitResponse = new LevelInitMessage();
|
||||
levelInitResponse.setNamespacedKey(worldKey.toString());
|
||||
levelInitResponse.setServerKey(dhsConfig.getString(DhsConfig.SERVER_KEY, ""));
|
||||
levelInitResponse.setLevelKey(levelKey);
|
||||
levelInitResponse.setTime(System.currentTimeMillis());
|
||||
this.pluginMessageHandler.sendPluginMessage(initRequestMessage.getSender(), levelInitResponse);
|
||||
});
|
||||
|
||||
this.pluginMessageHandler.getEventBus().registerHandler(RemotePlayerConfigMessage.class, (configMessage) -> {
|
||||
// TODO: Some sort of Player wrapper or interface object. Bukkit classes should not be imported here.
|
||||
Player player = Bukkit.getPlayer(configMessage.getSender());
|
||||
@@ -54,21 +100,6 @@ public class PlayerConfigHandler
|
||||
|
||||
Configuration dhsConfig = world.getConfig();
|
||||
|
||||
String levelKeyPrefix = dhsConfig.getString(DhsConfig.LEVEL_KEY_PREFIX);
|
||||
String levelKey = world.getKey();
|
||||
|
||||
if (levelKeyPrefix != null) {
|
||||
levelKey = levelKeyPrefix + levelKey;
|
||||
}
|
||||
|
||||
//this.dhSupport.info("Received DH config for " + player.getName() + " in " + levelKey);
|
||||
|
||||
LevelInitMessage levelInitResponse = new LevelInitMessage();
|
||||
levelInitResponse.setServerKey(dhsConfig.getString(DhsConfig.SERVER_KEY, ""));
|
||||
levelInitResponse.setLevelKey(levelKey);
|
||||
levelInitResponse.setTime(System.currentTimeMillis());
|
||||
this.pluginMessageHandler.sendPluginMessage(configMessage.getSender(), levelInitResponse);
|
||||
|
||||
Configuration clientConfig = configMessage.toConfiguration();
|
||||
|
||||
// This is not very flexible, but will do for now.
|
||||
@@ -79,9 +110,19 @@ public class PlayerConfigHandler
|
||||
Object clientValue = clientConfig.get(key);
|
||||
Object keepValue = null;
|
||||
|
||||
if (key.equals(DhsConfig.DISTANT_GENERATION_ENABLED) && !dhUseIsAllowed) {
|
||||
this.dhSupport.info("Player " + player.getName() + " has been denied the use of Distant Horizons through permissions.");
|
||||
if (key.equals(DhsConfig.DISTANT_GENERATION_ENABLED)) {
|
||||
if (dhUseIsAllowed) {
|
||||
if (!(boolean) clientValue) {
|
||||
player.sendMessage(ChatColor.GOLD + "This server supports Distant Horizons, but you have disabled Distant Generation in your DH settings.");
|
||||
}
|
||||
} else {
|
||||
this.dhSupport.info("Player " + player.getName() + " has been denied the use of Distant Horizons through permissions.");
|
||||
|
||||
keepValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (key.equals(DhsConfig.REAL_TIME_UPDATES_ENABLED) && !dhUseIsAllowed) {
|
||||
keepValue = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@ public class PluginMessageHandler
|
||||
|
||||
private MessageTypeRegistry messageTypeRegistry;
|
||||
|
||||
public final String pluginChannel = "distant_horizons:message";
|
||||
public final String pluginChannel = "distant_horizons:msg";
|
||||
|
||||
public final short protocolVersion = 13;
|
||||
public final short protocolVersion = 15;
|
||||
|
||||
private EventBus<PluginMessage> eventBus;
|
||||
|
||||
@@ -53,13 +53,14 @@ public class PluginMessageHandler
|
||||
this.messageTypeRegistry.registerMessageType(0, null);
|
||||
this.messageTypeRegistry.registerMessageType(1, CloseReasonMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(2, LevelInitMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(3, RemotePlayerConfigMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(4, CancelMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(5, ExceptionMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(6, FullDataSourceRequestMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(7, FullDataSourceResponseMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(8, FullDataPartialUpdateMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(9, FullDataChunkMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(3, LevelInitRequestMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(4, RemotePlayerConfigMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(5, CancelMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(6, ExceptionMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(7, FullDataSourceRequestMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(8, FullDataSourceResponseMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(9, FullDataPartialUpdateMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(10, FullDataChunkMessage.class);
|
||||
}
|
||||
|
||||
public void onEnable()
|
||||
@@ -153,19 +154,7 @@ public class PluginMessageHandler
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
|
||||
try {
|
||||
Encoder encoder = new Encoder();
|
||||
message.encode(encoder);
|
||||
data = encoder.toByteArray();
|
||||
} catch (Exception exception) {
|
||||
this.dhSupport.warning("Failed to encode " + message.getClass().getSimpleName());
|
||||
exception.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Encoder encoder = new Encoder();
|
||||
Encoder encoder = new Encoder(message.bufferSize());
|
||||
|
||||
encoder.writeShort(this.protocolVersion);
|
||||
encoder.writeShort(messageTypeId);
|
||||
@@ -174,7 +163,13 @@ public class PluginMessageHandler
|
||||
encoder.writeInt(((TrackablePluginMessage) message).getTracker());
|
||||
}
|
||||
|
||||
encoder.write(data);
|
||||
try {
|
||||
message.encode(encoder);
|
||||
} catch (Exception exception) {
|
||||
this.dhSupport.warning("Failed to encode " + message.getClass().getSimpleName());
|
||||
exception.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] fullMessage = encoder.toByteArray();
|
||||
|
||||
|
||||
@@ -56,12 +56,12 @@ public class FastOverworldBuilder extends LodBuilder
|
||||
boolean includeNonCollidingTopLayer = this.worldInterface.getConfig().getBool(DhsConfig.INCLUDE_NON_COLLIDING_TOP_LAYER, true);
|
||||
boolean sampleBiomes3d = this.worldInterface.getConfig().getBool(DhsConfig.SAMPLE_BIOMES_3D, false);
|
||||
|
||||
List<IdMapping> idMappings = new ArrayList<>();
|
||||
Map<String, Integer> mapMap = new HashMap<>();
|
||||
List<IdMapping> idMappings = new ArrayList<>(64);
|
||||
Map<String, Map<String, Integer>> mapMap = new HashMap<>();
|
||||
|
||||
List<List<DataPoint>> columns = new ArrayList<>();
|
||||
List<List<DataPoint>> columns = new ArrayList<>(Lod.width * Lod.width);
|
||||
|
||||
List<Beacon> beacons = new ArrayList<>();
|
||||
List<Beacon> beacons = new ArrayList<>(8);
|
||||
|
||||
for (int relativeX = 0; relativeX < Lod.width; relativeX++) {
|
||||
for (int relativeZ = 0; relativeZ < Lod.width; relativeZ++) {
|
||||
@@ -100,7 +100,7 @@ public class FastOverworldBuilder extends LodBuilder
|
||||
biome = this.worldInterface.getBiomeAt(worldX, worldZ);
|
||||
}
|
||||
|
||||
List<DataPoint> column = new ArrayList<>();
|
||||
List<DataPoint> column = new ArrayList<>(8);
|
||||
|
||||
@Nullable
|
||||
DataPoint previous = null;
|
||||
@@ -158,15 +158,18 @@ public class FastOverworldBuilder extends LodBuilder
|
||||
biome = this.worldInterface.getBiomeAt(worldX, highWorldY, worldZ);
|
||||
}
|
||||
|
||||
String compositeKey = biome + "|" + material;
|
||||
Map<String, Integer> biomeMap = mapMap.get(biome);
|
||||
if (biomeMap == null) {
|
||||
biomeMap = new HashMap<>();
|
||||
mapMap.put(biome, biomeMap);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Integer id = mapMap.get(compositeKey);
|
||||
Integer id = biomeMap.get(material);
|
||||
|
||||
if (id == null) {
|
||||
idMappings.add(new IdMapping(biome, material, null));
|
||||
id = idMappings.size() - 1;
|
||||
mapMap.put(compositeKey, id);
|
||||
biomeMap.put(material, id);
|
||||
}
|
||||
|
||||
DataPoint point;
|
||||
@@ -184,10 +187,8 @@ public class FastOverworldBuilder extends LodBuilder
|
||||
point.setHeight(thisStep);
|
||||
point.setMappingId(id);
|
||||
|
||||
if (highWorldY + 1 < maxY) {
|
||||
point.setSkyLight(this.worldInterface.getSkyLightAt(worldX, highWorldY + 1, worldZ));
|
||||
point.setBlockLight(this.worldInterface.getBlockLightAt(worldX, highWorldY + 1, worldZ));
|
||||
}
|
||||
point.setSkyLight(this.worldInterface.getSkyLightAt(worldX, highWorldY + 1, worldZ));
|
||||
point.setBlockLight(this.worldInterface.getBlockLightAt(worldX, highWorldY + 1, worldZ));
|
||||
|
||||
if (material.equals("minecraft:air") || material.equals("minecraft:void_air")) {
|
||||
// Start by filling the top of the column with air, then jump down to the top layer.
|
||||
|
||||
@@ -52,12 +52,12 @@ public class FullBuilder extends LodBuilder
|
||||
boolean performUnderglowHack = this.worldInterface.getConfig().getBool(DhsConfig.PERFORM_UNDERGLOW_HACK, false);
|
||||
boolean sampleBiomes3d = this.worldInterface.getConfig().getBool(DhsConfig.SAMPLE_BIOMES_3D, false);
|
||||
|
||||
List<IdMapping> idMappings = new ArrayList<>();
|
||||
Map<String, Integer> mapMap = new HashMap<>();
|
||||
List<IdMapping> idMappings = new ArrayList<>(64);
|
||||
Map<String, Map<String, Map<String, Integer>>> mapMap = new HashMap<>();
|
||||
|
||||
List<List<DataPoint>> columns = new ArrayList<>();
|
||||
List<List<DataPoint>> columns = new ArrayList<>(Lod.width * Lod.width);
|
||||
|
||||
List<Beacon> beacons = new ArrayList<>();
|
||||
List<Beacon> beacons = new ArrayList<>(8);
|
||||
|
||||
for (int relativeX = 0; relativeX < Lod.width; relativeX++) {
|
||||
for (int relativeZ = 0; relativeZ < Lod.width; relativeZ++) {
|
||||
@@ -96,7 +96,7 @@ public class FullBuilder extends LodBuilder
|
||||
biome = this.worldInterface.getBiomeAt(worldX, worldZ);
|
||||
}
|
||||
|
||||
List<DataPoint> column = new ArrayList<>();
|
||||
List<DataPoint> column = new ArrayList<>(32);
|
||||
|
||||
@Nullable
|
||||
DataPoint previous = null;
|
||||
@@ -138,15 +138,26 @@ public class FullBuilder extends LodBuilder
|
||||
biome = this.worldInterface.getBiomeAt(worldX, highWorldY, worldZ);
|
||||
}
|
||||
|
||||
String compositeKey = biome + "|" + material + "|" + this.worldInterface.getBlockStateAsStringAt(worldX, highWorldY, worldZ);
|
||||
Map<String, Map<String, Integer>> biomeMap = mapMap.get(biome);
|
||||
if (biomeMap == null) {
|
||||
biomeMap = new HashMap<>();
|
||||
mapMap.put(biome, biomeMap);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Integer id = mapMap.get(compositeKey);
|
||||
Map<String, Integer> materialMap = biomeMap.get(material);
|
||||
if (materialMap == null) {
|
||||
materialMap = new HashMap<>();
|
||||
biomeMap.put(material, materialMap);
|
||||
}
|
||||
|
||||
String blockState = this.worldInterface.getBlockStateAsStringAt(worldX, highWorldY, worldZ);
|
||||
|
||||
Integer id = materialMap.get(blockState);
|
||||
|
||||
if (id == null) {
|
||||
idMappings.add(new IdMapping(biome, material, this.worldInterface.getBlockPropertiesAt(worldX, highWorldY, worldZ)));
|
||||
id = idMappings.size() - 1;
|
||||
mapMap.put(compositeKey, id);
|
||||
materialMap.put(blockState, id);
|
||||
}
|
||||
|
||||
DataPoint point;
|
||||
@@ -164,10 +175,8 @@ public class FullBuilder extends LodBuilder
|
||||
point.setHeight(thisStep);
|
||||
point.setMappingId(id);
|
||||
|
||||
if (highWorldY + 1 < maxY) {
|
||||
point.setSkyLight(this.worldInterface.getSkyLightAt(worldX, highWorldY + 1, worldZ));
|
||||
point.setBlockLight(this.worldInterface.getBlockLightAt(worldX, highWorldY + 1, worldZ));
|
||||
}
|
||||
point.setSkyLight(this.worldInterface.getSkyLightAt(worldX, highWorldY + 1, worldZ));
|
||||
point.setBlockLight(this.worldInterface.getBlockLightAt(worldX, highWorldY + 1, worldZ));
|
||||
|
||||
if (material.equals("minecraft:air") || material.equals("minecraft:void_air")) {
|
||||
// Start by filling the top of the column with air, then jump down to the top layer.
|
||||
|
||||
@@ -25,6 +25,11 @@ import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public abstract class Message implements Encodable, Decodable
|
||||
{
|
||||
public int bufferSize()
|
||||
{
|
||||
return 64;
|
||||
}
|
||||
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
@@ -26,6 +26,10 @@ public class FullDataChunkMessage extends PluginMessage
|
||||
|
||||
protected byte[] data;
|
||||
|
||||
protected int offset;
|
||||
|
||||
protected int length;
|
||||
|
||||
protected boolean isFirst = true;
|
||||
|
||||
public void setBufferId(int bufferId)
|
||||
@@ -38,9 +42,11 @@ public class FullDataChunkMessage extends PluginMessage
|
||||
return this.bufferId;
|
||||
}
|
||||
|
||||
public void setData(byte[] data)
|
||||
public void setData(byte[] data, int offset, int length)
|
||||
{
|
||||
this.data = data;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public byte[] getData()
|
||||
@@ -58,12 +64,18 @@ public class FullDataChunkMessage extends PluginMessage
|
||||
return this.isFirst;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bufferSize()
|
||||
{
|
||||
return 32 + this.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeInt(this.bufferId);
|
||||
encoder.writeInt(this.data.length);
|
||||
encoder.write(this.data);
|
||||
encoder.writeInt(this.length);
|
||||
encoder.write(this.data, this.offset, this.length);
|
||||
encoder.writeBoolean(this.isFirst);
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,12 @@ public class FullDataPartialUpdateMessage extends PluginMessage
|
||||
return this.beacons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bufferSize()
|
||||
{
|
||||
return 32 + this.beacons.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
|
||||
@@ -48,6 +48,12 @@ public class FullDataSourceResponseMessage extends TrackablePluginMessage
|
||||
return this.beacons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bufferSize()
|
||||
{
|
||||
return this.bufferId == null ? super.bufferSize() : 16 + this.beacons.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
|
||||
@@ -21,14 +21,26 @@ package no.jckf.dhsupport.core.message.plugin;
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class LevelInitMessage extends PluginMessage
|
||||
public class LevelInitMessage extends PluginMessage // TODO: Should probably eventually be a TrackablePluginMessage
|
||||
{
|
||||
protected String namespacedKey;
|
||||
|
||||
protected String serverKey;
|
||||
|
||||
protected String levelKey;
|
||||
|
||||
protected long time;
|
||||
|
||||
public void setNamespacedKey(String namespacedKey)
|
||||
{
|
||||
this.namespacedKey = namespacedKey;
|
||||
}
|
||||
|
||||
public String getNamespacedKey()
|
||||
{
|
||||
return namespacedKey;
|
||||
}
|
||||
|
||||
public void setServerKey(String serverKey)
|
||||
{
|
||||
this.serverKey = serverKey;
|
||||
@@ -62,6 +74,7 @@ public class LevelInitMessage extends PluginMessage
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeShortString(this.namespacedKey);
|
||||
encoder.writeShortString(this.serverKey);
|
||||
encoder.writeShortString(this.levelKey);
|
||||
encoder.writeLong(this.time);
|
||||
@@ -70,6 +83,7 @@ public class LevelInitMessage extends PluginMessage
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.namespacedKey = decoder.readShortString();
|
||||
this.serverKey = decoder.readShortString();
|
||||
this.levelKey = decoder.readShortString();
|
||||
this.time = decoder.readLong();
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
|
||||
public class LevelInitRequestMessage extends PluginMessage
|
||||
{
|
||||
protected String worldKey;
|
||||
|
||||
public String getWorldKey()
|
||||
{
|
||||
return worldKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.worldKey = decoder.readShortString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.world;
|
||||
|
||||
public class MaterialRecord
|
||||
{
|
||||
protected final String key;
|
||||
|
||||
protected final boolean isMapped;
|
||||
|
||||
public MaterialRecord(String key, boolean mapped)
|
||||
{
|
||||
this.key = key;
|
||||
this.isMapped = mapped;
|
||||
}
|
||||
|
||||
public boolean isMapped()
|
||||
{
|
||||
return this.isMapped;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return this.key;
|
||||
}
|
||||
}
|
||||
@@ -85,8 +85,16 @@ public interface WorldInterface extends Configurable
|
||||
|
||||
String getMaterialAt(int x, int y, int z);
|
||||
|
||||
/**
|
||||
* Get the string representation of the block state at the given coordinates.
|
||||
* Block state properties are ordered in ascending order.
|
||||
*/
|
||||
String getBlockStateAsStringAt(int x, int y, int z);
|
||||
|
||||
/**
|
||||
* Get block properties as key/value pairs.
|
||||
* Ordered in ascending order.
|
||||
*/
|
||||
Map<String, String> getBlockPropertiesAt(int x, int y, int z);
|
||||
|
||||
byte getBlockLightAt(int x, int y, int z);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Default: "{datadir}/data.sqlite"
|
||||
database_path: "{datadir}/data.sqlite"
|
||||
|
||||
# Maximum supported render distance. Clients will be capped to this value.
|
||||
# Maximum supported render distance, given as a radius in number of chunks. Clients will be capped to this value.
|
||||
# Required.
|
||||
# Default: 1024
|
||||
render_distance: 1024
|
||||
@@ -24,7 +24,7 @@ full_data_request_concurrency_limit: 20
|
||||
# Default: true
|
||||
real_time_updates_enabled: true
|
||||
|
||||
# How close to an update players have to be in order to receive it.
|
||||
# How close to an update players have to be in order to receive it. Given as a radius in number of chunks.
|
||||
# Required.
|
||||
# Default: 128
|
||||
real_time_update_radius: 128
|
||||
@@ -34,7 +34,7 @@ real_time_update_radius: 128
|
||||
# Default: true
|
||||
login_data_sync_enabled: true
|
||||
|
||||
# How far away can a player request updates for during login sync?
|
||||
# How far away can a player request updates for during login sync? Given as a radius in number of chunks.
|
||||
# Required.
|
||||
# Default: 128
|
||||
login_data_sync_radius: 128
|
||||
@@ -70,7 +70,7 @@ max_data_transfer_speed: 0
|
||||
|
||||
# How frequently (in seconds) should LODs be updated if world changes are detected?
|
||||
# Required.
|
||||
# Default: 5
|
||||
# Default: 15
|
||||
lod_refresh_interval: 15
|
||||
|
||||
# Select which LOD builder to use.
|
||||
@@ -79,6 +79,13 @@ lod_refresh_interval: 15
|
||||
# Default: FullBuilder
|
||||
builder_type: FullBuilder
|
||||
|
||||
# Trust world height map.
|
||||
# Disable if you're using a world that was generated with an external tool and your LODs come out broken.
|
||||
# WARNING: This will make LOD building MUCH slower!
|
||||
# Optional.
|
||||
# Default: true
|
||||
#trust_height_map: false
|
||||
|
||||
# Number of blocks to advance between each sample on the Y axis when generating LODs.
|
||||
# A setting of 1 will sample every block, while 2 will sample every other block, and so on.
|
||||
# CURRENTLY BROKEN. THIS VALUE IS IGNORED.
|
||||
@@ -92,7 +99,7 @@ builder_resolution: 2
|
||||
# Default: true
|
||||
#include_non-colliding_top_layer: false
|
||||
|
||||
# If your server is accessible via multiple different addresses, consider setting a level key to let clients know
|
||||
# If your server is accessible via multiple different addresses, consider setting a server key to let clients know
|
||||
# that even though the address is different; it's the same server.
|
||||
# This value must be GLOBALLY UNIQUE! No other server on planet Earth should have the same server key as you.
|
||||
# Optional.
|
||||
@@ -129,6 +136,14 @@ material_map:
|
||||
iron_ore: stone
|
||||
diamond_ore: stone
|
||||
|
||||
# Vertical distance to offset light data.
|
||||
# This is a hack to work around SPIGOT-7498. If you're somehow on a 1.20.1 server _with_ this patch, set the value to 0.
|
||||
# Optional.
|
||||
# Default:
|
||||
# - On 1.20.1: Minimum build height (-64 for vanilla worlds)
|
||||
# - Any other version: 0
|
||||
#light_offset_hack: 0
|
||||
|
||||
# World specific settings.
|
||||
# You can override any of the above settings on a per-world basis.
|
||||
worlds:
|
||||
@@ -154,7 +169,7 @@ worlds:
|
||||
# Default: true
|
||||
#fast_underfill: false
|
||||
|
||||
# Limit LOD generation to within this area.
|
||||
# Limit LOD generation to within this area. Alle three values are in blocks.
|
||||
# Optional.
|
||||
#border_center_x: 0
|
||||
#border_center_z: 0
|
||||
@@ -190,6 +205,7 @@ update_events:
|
||||
# - org.bukkit.event.block.SpongeAbsorbEvent
|
||||
# - org.bukkit.event.inventory.FurnaceBurnEvent
|
||||
- org.bukkit.event.world.StructureGrowEvent
|
||||
- org.bukkit.event.world.ChunkPopulateEvent
|
||||
|
||||
# Number of worker threads used to generate LODs.
|
||||
# Required.
|
||||
|
||||
@@ -54,7 +54,7 @@ public class DataPointTest extends TestCase
|
||||
this.testHeight();
|
||||
}
|
||||
|
||||
public void testPositiveStartY()
|
||||
public void testStartY()
|
||||
{
|
||||
int randomNumber = (int) Math.floor(Math.random() * 100);
|
||||
|
||||
@@ -63,9 +63,9 @@ public class DataPointTest extends TestCase
|
||||
assertEquals(randomNumber, this.dataPoint.getStartY());
|
||||
}
|
||||
|
||||
public void testNegativeStartY()
|
||||
public void testHighStartY()
|
||||
{
|
||||
int randomNumber = (int) Math.floor(0 - Math.random() * 100);
|
||||
int randomNumber = 4000; // Chosen by fair dice roll. Guaranteed to be random.
|
||||
|
||||
this.dataPoint.setStartY(randomNumber);
|
||||
|
||||
@@ -74,7 +74,7 @@ public class DataPointTest extends TestCase
|
||||
|
||||
public void testStartYAgain()
|
||||
{
|
||||
this.testPositiveStartY();
|
||||
this.testStartY();
|
||||
}
|
||||
|
||||
public void testSkyLight()
|
||||
|
||||
Reference in new issue
Block a user