mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-12-25 01:59:15 +00:00
Compare commits
19 Commits
feat/mob_s
...
feat/headl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eff598d005 | ||
|
|
d86ec7b1cd | ||
|
|
0d9a45dfd9 | ||
|
|
8a55bbfd20 | ||
|
|
5934c43b70 | ||
|
|
11567b13d3 | ||
|
|
2087ba88b1 | ||
|
|
e9d1b9f18e | ||
|
|
6e84d38680 | ||
|
|
22622f6e8a | ||
|
|
735203aa95 | ||
|
|
013bc365a9 | ||
|
|
c2dfbac641 | ||
|
|
7d472c0b13 | ||
|
|
5b4ab0a3c1 | ||
|
|
e8dd81b014 | ||
|
|
d32cc281e3 | ||
|
|
2ff6b59271 | ||
|
|
3ff87566f5 |
38
build.gradle
38
build.gradle
@@ -1,5 +1,3 @@
|
||||
import xyz.jpenilla.runpaper.task.RunServer
|
||||
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2021 Arcane Arts (Volmit Software)
|
||||
@@ -32,11 +30,10 @@ plugins {
|
||||
id 'java-library'
|
||||
id "io.github.goooler.shadow" version "8.1.7"
|
||||
id "de.undercouch.download" version "5.0.1"
|
||||
id "xyz.jpenilla.run-paper" version "2.3.1"
|
||||
}
|
||||
|
||||
|
||||
version '3.6.6-1.20.1-1.21.4'
|
||||
version '3.6.5-1.20.1-1.21.4'
|
||||
|
||||
// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED
|
||||
// ======================== WINDOWS =============================
|
||||
@@ -56,11 +53,6 @@ registerCustomOutputTaskUnix('PixelMac', '/Users/test/Desktop/mcserver/plugins')
|
||||
registerCustomOutputTaskUnix('CrazyDev22LT', '/home/julian/Desktop/server/plugins')
|
||||
// ==============================================================
|
||||
|
||||
def MIN_HEAP_SIZE = "2G"
|
||||
def MAX_HEAP_SIZE = "8G"
|
||||
//Valid values are: none, truecolor, indexed256, indexed16, indexed8
|
||||
def COLOR = "truecolor"
|
||||
|
||||
def NMS_BINDINGS = Map.of(
|
||||
"v1_21_R3", "1.21.4-R0.1-SNAPSHOT",
|
||||
"v1_21_R2", "1.21.3-R0.1-SNAPSHOT",
|
||||
@@ -70,34 +62,21 @@ def NMS_BINDINGS = Map.of(
|
||||
"v1_20_R2", "1.20.2-R0.1-SNAPSHOT",
|
||||
"v1_20_R1", "1.20.1-R0.1-SNAPSHOT",
|
||||
)
|
||||
def JVM_VERSION = Map.<String, Integer>of()
|
||||
NMS_BINDINGS.forEach { key, value ->
|
||||
project(":nms:$key") {
|
||||
def JVM_VERSION = Map.of()
|
||||
NMS_BINDINGS.each { nms ->
|
||||
project(":nms:${nms.key}") {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'com.volmit.nmstools'
|
||||
|
||||
nmsTools {
|
||||
it.jvm = JVM_VERSION.getOrDefault(key, 21)
|
||||
it.version = value
|
||||
it.jvm = JVM_VERSION.getOrDefault(nms.key, 21)
|
||||
it.version = nms.value
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":core")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("runServer-$key", RunServer) {
|
||||
group("servers")
|
||||
minecraftVersion(value.split("-")[0])
|
||||
minHeapSize(MIN_HEAP_SIZE)
|
||||
maxHeapSize(MAX_HEAP_SIZE)
|
||||
pluginJars(tasks.shadowJar.archiveFile)
|
||||
javaLauncher = javaToolchains.launcherFor { it.languageVersion = JavaLanguageVersion.of(JVM_VERSION.getOrDefault(key, 21))}
|
||||
runDirectory.convention(layout.buildDirectory.dir("run/$key"))
|
||||
systemProperty("disable.watchdog", "")
|
||||
systemProperty("net.kyori.ansi.colorLevel", COLOR)
|
||||
systemProperty("com.mojang.eula.agree", true)
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
@@ -113,6 +92,11 @@ shadowJar {
|
||||
relocate 'net.kyori', 'com.volmit.iris.util.kyori'
|
||||
relocate 'org.bstats', 'com.volmit.util.metrics'
|
||||
archiveFileName.set("Iris-${project.version}.jar")
|
||||
|
||||
dependencies {
|
||||
exclude(dependency("org.ow2.asm:asm:"))
|
||||
exclude(dependency("org.jetbrains:"))
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -22,11 +22,14 @@ import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.ServerConfigurator;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||
import com.volmit.iris.core.pregenerator.methods.HeadlessPregenMethod;
|
||||
import com.volmit.iris.core.service.IrisEngineSVC;
|
||||
import com.volmit.iris.core.tools.IrisPackBenchmarking;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.engine.service.EngineStatusSVC;
|
||||
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.DecreeOrigin;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
@@ -36,6 +39,7 @@ import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.io.CountingDataInputStream;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.mantle.TectonicPlate;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.nbt.mca.MCAFile;
|
||||
import com.volmit.iris.util.nbt.mca.MCAUtil;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
@@ -48,6 +52,7 @@ import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
@@ -66,20 +71,53 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
|
||||
@Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, sync = true)
|
||||
public void EngineStatus() {
|
||||
var status = EngineStatusSVC.getStatus();
|
||||
List<World> IrisWorlds = new ArrayList<>();
|
||||
int TotalLoadedChunks = 0;
|
||||
int TotalQueuedTectonicPlates = 0;
|
||||
int TotalNotQueuedTectonicPlates = 0;
|
||||
int TotalTectonicPlates = 0;
|
||||
|
||||
sender().sendMessage("-------------------------");
|
||||
sender().sendMessage(C.DARK_PURPLE + "Engine Status");
|
||||
sender().sendMessage(C.DARK_PURPLE + "Total Engines: " + C.LIGHT_PURPLE + status.engineCount());
|
||||
sender().sendMessage(C.DARK_PURPLE + "Total Loaded Chunks: " + C.LIGHT_PURPLE + status.loadedChunks());
|
||||
sender().sendMessage(C.DARK_PURPLE + "Tectonic Limit: " + C.LIGHT_PURPLE + status.tectonicLimit());
|
||||
sender().sendMessage(C.DARK_PURPLE + "Tectonic Total Plates: " + C.LIGHT_PURPLE + status.tectonicPlates());
|
||||
sender().sendMessage(C.DARK_PURPLE + "Tectonic Active Plates: " + C.LIGHT_PURPLE + status.activeTectonicPlates());
|
||||
sender().sendMessage(C.DARK_PURPLE + "Tectonic ToUnload: " + C.LIGHT_PURPLE + status.queuedTectonicPlates());
|
||||
sender().sendMessage(C.DARK_PURPLE + "Lowest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(status.minTectonicUnloadDuration()));
|
||||
sender().sendMessage(C.DARK_PURPLE + "Highest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(status.maxTectonicUnloadDuration()));
|
||||
sender().sendMessage(C.DARK_PURPLE + "Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize()));
|
||||
sender().sendMessage("-------------------------");
|
||||
long lowestUnloadDuration = 0;
|
||||
long highestUnloadDuration = 0;
|
||||
|
||||
for (World world : Bukkit.getWorlds()) {
|
||||
try {
|
||||
if (IrisToolbelt.access(world).getEngine() != null) {
|
||||
IrisWorlds.add(world);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// no
|
||||
}
|
||||
}
|
||||
|
||||
for (World world : IrisWorlds) {
|
||||
Engine engine = IrisToolbelt.access(world).getEngine();
|
||||
TotalQueuedTectonicPlates += (int) engine.getMantle().getToUnload();
|
||||
TotalNotQueuedTectonicPlates += (int) engine.getMantle().getNotQueuedLoadedRegions();
|
||||
TotalTectonicPlates += engine.getMantle().getLoadedRegionCount();
|
||||
if (highestUnloadDuration <= (long) engine.getMantle().getTectonicDuration()) {
|
||||
highestUnloadDuration = (long) engine.getMantle().getTectonicDuration();
|
||||
}
|
||||
if (lowestUnloadDuration >= (long) engine.getMantle().getTectonicDuration()) {
|
||||
lowestUnloadDuration = (long) engine.getMantle().getTectonicDuration();
|
||||
}
|
||||
for (Chunk chunk : world.getLoadedChunks()) {
|
||||
if (chunk.isLoaded()) {
|
||||
TotalLoadedChunks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Iris.info("-------------------------");
|
||||
Iris.info(C.DARK_PURPLE + "Engine Status");
|
||||
Iris.info(C.DARK_PURPLE + "Total Loaded Chunks: " + C.LIGHT_PURPLE + TotalLoadedChunks);
|
||||
Iris.info(C.DARK_PURPLE + "Tectonic Limit: " + C.LIGHT_PURPLE + IrisEngineSVC.getTectonicLimit());
|
||||
Iris.info(C.DARK_PURPLE + "Tectonic Total Plates: " + C.LIGHT_PURPLE + TotalTectonicPlates);
|
||||
Iris.info(C.DARK_PURPLE + "Tectonic Active Plates: " + C.LIGHT_PURPLE + TotalNotQueuedTectonicPlates);
|
||||
Iris.info(C.DARK_PURPLE + "Tectonic ToUnload: " + C.LIGHT_PURPLE + TotalQueuedTectonicPlates);
|
||||
Iris.info(C.DARK_PURPLE + "Lowest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(lowestUnloadDuration));
|
||||
Iris.info(C.DARK_PURPLE + "Highest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(highestUnloadDuration));
|
||||
Iris.info(C.DARK_PURPLE + "Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize()));
|
||||
Iris.info("-------------------------");
|
||||
}
|
||||
|
||||
@Decree(description = "Test")
|
||||
@@ -107,12 +145,16 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
public void packBenchmark(
|
||||
@Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld")
|
||||
IrisDimension dimension,
|
||||
@Param(description = "Radius in regions", defaultValue = "2048")
|
||||
int radius,
|
||||
@Param(description = "Diameter in regions", defaultValue = "2048")
|
||||
int diameter,
|
||||
@Param(description = "Headless", defaultValue = "true")
|
||||
boolean headless,
|
||||
@Param(description = "Open GUI while benchmarking", defaultValue = "false")
|
||||
boolean gui
|
||||
) {
|
||||
new IrisPackBenchmarking(dimension, radius, gui);
|
||||
int rb = diameter << 9;
|
||||
Iris.info("Benchmarking pack " + dimension.getName() + " with diameter: " + rb + "(" + diameter + ")");
|
||||
new IrisPackBenchmarking(dimension, diameter, headless, gui);
|
||||
}
|
||||
|
||||
@Decree(description = "Upgrade to another Minecraft version")
|
||||
@@ -180,6 +222,42 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
sender.sendMessage(C.RED + "Failed to load " + failed.get() + " of " + keys.length + " objects");
|
||||
}
|
||||
|
||||
@Decree(description = "Pregenerate a world")
|
||||
public void headless(
|
||||
@Param(description = "The radius of the pregen in blocks", aliases = "size")
|
||||
int radius,
|
||||
@Param(description = "The world to pregen", contextual = true)
|
||||
World world,
|
||||
@Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0")
|
||||
Vector center
|
||||
) {
|
||||
try {
|
||||
var engine = Optional.ofNullable(IrisToolbelt.access(world))
|
||||
.map(PlatformChunkGenerator::getEngine)
|
||||
.orElse(null);
|
||||
|
||||
if (engine == null) {
|
||||
sender().sendMessage(C.RED + "The engine access for this world is null!");
|
||||
sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example.");
|
||||
}
|
||||
radius = Math.max(radius, 1024);
|
||||
IrisToolbelt.pregenerate(PregenTask
|
||||
.builder()
|
||||
.center(new Position2(center.getBlockX(), center.getBlockZ()))
|
||||
.gui(true)
|
||||
.radiusX(radius)
|
||||
.radiusZ(radius)
|
||||
.build(), new HeadlessPregenMethod(engine), engine);
|
||||
String msg = C.GREEN + "Headless Pregen started in " + C.GOLD + world.getName() + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ();
|
||||
sender().sendMessage(msg);
|
||||
Iris.info(msg);
|
||||
} catch (Throwable e) {
|
||||
sender().sendMessage(C.RED + "Epic fail. See console.");
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Test", aliases = {"ip"})
|
||||
public void network() {
|
||||
try {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@ package com.volmit.iris.core.commands;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.pregenerator.ChunkUpdater;
|
||||
import com.volmit.iris.core.service.IrisEngineSVC;
|
||||
import com.volmit.iris.core.tools.IrisPackBenchmarking;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
|
||||
@@ -44,7 +44,6 @@ import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -220,10 +219,6 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
return s.map(this::load);
|
||||
}
|
||||
|
||||
public Stream<T> streamAllPossible() {
|
||||
return streamAll(Arrays.stream(getPossibleKeys()));
|
||||
}
|
||||
|
||||
public KList<T> loadAll(KList<String> s) {
|
||||
KList<T> m = new KList<>();
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ package com.volmit.iris.core.nms;
|
||||
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
@@ -93,7 +95,6 @@ public interface INMSBinding {
|
||||
throw new IllegalStateException("Missing dimenstion types to create world");
|
||||
|
||||
try (var ignored = injectLevelStems()) {
|
||||
ignored.storeContext();
|
||||
return c.createWorld();
|
||||
}
|
||||
}
|
||||
@@ -128,15 +129,13 @@ public interface INMSBinding {
|
||||
return 441;
|
||||
}
|
||||
|
||||
IRegionStorage createRegionStorage(Engine engine);
|
||||
|
||||
KList<String> getStructureKeys();
|
||||
|
||||
AutoClosing injectLevelStems();
|
||||
|
||||
default AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
return null;
|
||||
}
|
||||
Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end);
|
||||
|
||||
boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end);
|
||||
|
||||
void removeCustomDimensions(World world);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.volmit.iris.core.nms.container;
|
||||
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.function.NastyRunnable;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@@ -8,7 +7,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class AutoClosing implements AutoCloseable {
|
||||
private static final KMap<Thread, AutoClosing> CONTEXTS = new KMap<>();
|
||||
private final AtomicBoolean closed = new AtomicBoolean();
|
||||
private final NastyRunnable action;
|
||||
|
||||
@@ -16,24 +14,9 @@ public class AutoClosing implements AutoCloseable {
|
||||
public void close() {
|
||||
if (closed.getAndSet(true)) return;
|
||||
try {
|
||||
removeContext();
|
||||
action.run();
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void storeContext() {
|
||||
CONTEXTS.put(Thread.currentThread(), this);
|
||||
}
|
||||
|
||||
public void removeContext() {
|
||||
CONTEXTS.values().removeIf(c -> c == this);
|
||||
}
|
||||
|
||||
public static void closeContext() {
|
||||
AutoClosing closing = CONTEXTS.remove(Thread.currentThread());
|
||||
if (closing == null) return;
|
||||
closing.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.volmit.iris.core.nms.headless;
|
||||
|
||||
import com.volmit.iris.util.documentation.ChunkCoordinates;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface IRegion extends AutoCloseable {
|
||||
|
||||
@ChunkCoordinates
|
||||
boolean exists(int x, int z);
|
||||
|
||||
void write(@NonNull SerializableChunk chunk) throws IOException;
|
||||
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.volmit.iris.core.nms.headless;
|
||||
|
||||
import com.volmit.iris.util.context.ChunkContext;
|
||||
import com.volmit.iris.util.documentation.ChunkCoordinates;
|
||||
import com.volmit.iris.util.documentation.RegionCoordinates;
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface IRegionStorage {
|
||||
|
||||
@ChunkCoordinates
|
||||
boolean exists(int x, int z);
|
||||
|
||||
@Nullable
|
||||
@RegionCoordinates
|
||||
IRegion getRegion(int x, int z, boolean existingOnly) throws IOException;
|
||||
|
||||
@NonNull
|
||||
@ChunkCoordinates
|
||||
SerializableChunk createChunk(int x, int z);
|
||||
|
||||
void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx);
|
||||
|
||||
void close();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.volmit.iris.core.nms.headless;
|
||||
|
||||
import com.volmit.iris.engine.data.chunk.TerrainChunk;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
|
||||
public interface SerializableChunk extends TerrainChunk {
|
||||
Position2 getPos();
|
||||
|
||||
Object serialize();
|
||||
|
||||
void mark();
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMSBinding;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
@@ -111,6 +112,11 @@ public class NMSBinding1X implements INMSBinding {
|
||||
return Color.GREEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRegionStorage createRegionStorage(Engine engine) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KList<String> getStructureKeys() {
|
||||
var list = StreamSupport.stream(Registry.STRUCTURE.spliterator(), false)
|
||||
@@ -126,8 +132,8 @@ public class NMSBinding1X implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
return injectLevelStems();
|
||||
public Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
return new Pair<>(0, new AutoClosing(() -> {}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,11 +141,6 @@ public class NMSBinding1X implements INMSBinding {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag serializeEntity(Entity location) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.volmit.iris.core.pregenerator;
|
||||
|
||||
public class EmptyListener implements PregenListener {
|
||||
public static PregenListener INSTANCE = new EmptyListener();
|
||||
|
||||
@Override
|
||||
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkGenerating(int x, int z) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkGenerated(int x, int z) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegionGenerated(int x, int z) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegionGenerating(int x, int z) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkCleaned(int x, int z) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegionSkipped(int x, int z) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkStarted(int x, int z) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkFailed(int x, int z) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkReclaim(int revert) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkGeneratedChunk(int x, int z) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkDownloaded(int x, int z) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaving() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkExistsInRegionGen(int x, int z) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2024 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.pregenerator.methods;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisHeadless;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
public class HeadlessPregenMethod implements PregeneratorMethod {
|
||||
private final Engine engine;
|
||||
private final IrisHeadless headless;
|
||||
private final Semaphore semaphore;
|
||||
private final int max;
|
||||
private final MultiBurst burst;
|
||||
|
||||
public HeadlessPregenMethod(Engine engine) {
|
||||
this(engine, IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism()));
|
||||
}
|
||||
|
||||
public HeadlessPregenMethod(Engine engine, int threads) {
|
||||
this.max = Math.max(threads, 4);
|
||||
this.engine = engine;
|
||||
this.headless = new IrisHeadless(engine);
|
||||
burst = new MultiBurst("HeadlessPregen", 8);
|
||||
this.semaphore = new Semaphore(max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
semaphore.acquire(max);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
try {
|
||||
headless.close();
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to close headless");
|
||||
e.printStackTrace();
|
||||
}
|
||||
burst.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRegions(int x, int z, PregenListener listener) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod(int x, int z) {
|
||||
return "Headless";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRegion(int x, int z, PregenListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateChunk(int x, int z, PregenListener listener) {
|
||||
try {
|
||||
semaphore.acquire();
|
||||
} catch (InterruptedException ignored) {
|
||||
return;
|
||||
}
|
||||
|
||||
burst.complete(() -> {
|
||||
try {
|
||||
listener.onChunkGenerating(x, z);
|
||||
headless.generateChunk(x, z);
|
||||
listener.onChunkGenerated(x, z);
|
||||
} finally {
|
||||
semaphore.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mantle getMantle() {
|
||||
return engine.getMantle().getMantle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
package com.volmit.iris.core.service;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.mantle.TectonicPlate;
|
||||
import com.volmit.iris.util.misc.getHardware;
|
||||
import com.volmit.iris.util.plugin.IrisService;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.Looper;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.server.PluginDisableEvent;
|
||||
import org.bukkit.event.server.ServerLoadEvent;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.checkerframework.checker.units.qual.A;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class IrisEngineSVC implements IrisService {
|
||||
public static IrisEngineSVC instance;
|
||||
public boolean isServerShuttingDown = false;
|
||||
public boolean isServerLoaded = false;
|
||||
private static final AtomicInteger tectonicLimit = new AtomicInteger(30);
|
||||
private ReentrantLock lastUseLock;
|
||||
private KMap<World, Long> lastUse;
|
||||
private List<World> IrisWorlds;
|
||||
private Looper cacheTicker;
|
||||
private Looper trimTicker;
|
||||
private Looper unloadTicker;
|
||||
private Looper updateTicker;
|
||||
private PrecisionStopwatch trimAlive;
|
||||
private PrecisionStopwatch unloadAlive;
|
||||
public PrecisionStopwatch trimActiveAlive;
|
||||
public PrecisionStopwatch unloadActiveAlive;
|
||||
private AtomicInteger TotalTectonicPlates;
|
||||
private AtomicInteger TotalQueuedTectonicPlates;
|
||||
private AtomicInteger TotalNotQueuedTectonicPlates;
|
||||
private AtomicBoolean IsUnloadAlive;
|
||||
private AtomicBoolean IsTrimAlive;
|
||||
ChronoLatch cl;
|
||||
|
||||
public List<World> corruptedIrisWorlds = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
this.cl = new ChronoLatch(5000);
|
||||
lastUse = new KMap<>();
|
||||
lastUseLock = new ReentrantLock();
|
||||
IrisWorlds = new ArrayList<>();
|
||||
IsUnloadAlive = new AtomicBoolean(true);
|
||||
IsTrimAlive = new AtomicBoolean(true);
|
||||
trimActiveAlive = new PrecisionStopwatch();
|
||||
unloadActiveAlive = new PrecisionStopwatch();
|
||||
trimAlive = new PrecisionStopwatch();
|
||||
unloadAlive = new PrecisionStopwatch();
|
||||
TotalTectonicPlates = new AtomicInteger();
|
||||
TotalQueuedTectonicPlates = new AtomicInteger();
|
||||
TotalNotQueuedTectonicPlates = new AtomicInteger();
|
||||
tectonicLimit.set(2);
|
||||
long t = getHardware.getProcessMemory();
|
||||
while (t > 200) {
|
||||
tectonicLimit.getAndAdd(1);
|
||||
t = t - 200;
|
||||
}
|
||||
this.setup();
|
||||
this.TrimLogic();
|
||||
this.UnloadLogic();
|
||||
|
||||
trimAlive.begin();
|
||||
unloadAlive.begin();
|
||||
trimActiveAlive.begin();
|
||||
unloadActiveAlive.begin();
|
||||
|
||||
updateTicker.start();
|
||||
cacheTicker.start();
|
||||
//trimTicker.start();
|
||||
//unloadTicker.start();
|
||||
instance = this;
|
||||
|
||||
}
|
||||
|
||||
public void engineStatus() {
|
||||
boolean trimAlive = trimTicker.isAlive();
|
||||
boolean unloadAlive = unloadTicker.isAlive();
|
||||
Iris.info("Status:");
|
||||
Iris.info("- Trim: " + trimAlive);
|
||||
Iris.info("- Unload: " + unloadAlive);
|
||||
|
||||
}
|
||||
|
||||
public static int getTectonicLimit() {
|
||||
return tectonicLimit.get();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldUnload(WorldUnloadEvent event) {
|
||||
updateWorlds();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldLoad(WorldLoadEvent event) {
|
||||
updateWorlds();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerBoot(ServerLoadEvent event) {
|
||||
isServerLoaded = true;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPluginDisable(PluginDisableEvent event) {
|
||||
if (event.getPlugin().equals(Iris.instance)) {
|
||||
isServerShuttingDown = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateWorlds() {
|
||||
for (World world : Bukkit.getWorlds()) {
|
||||
try {
|
||||
if (IrisToolbelt.access(world).getEngine() != null) {
|
||||
IrisWorlds.add(world);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// no
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
cacheTicker = new Looper() {
|
||||
@Override
|
||||
protected long loop() {
|
||||
long now = System.currentTimeMillis();
|
||||
lastUseLock.lock();
|
||||
try {
|
||||
for (World key : new ArrayList<>(lastUse.keySet())) {
|
||||
Long last = lastUse.get(key);
|
||||
if (last == null)
|
||||
continue;
|
||||
if (now - last > 60000) {
|
||||
lastUse.remove(key);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lastUseLock.unlock();
|
||||
}
|
||||
return 1000;
|
||||
}
|
||||
};
|
||||
|
||||
updateTicker = new Looper() {
|
||||
@Override
|
||||
protected long loop() {
|
||||
try {
|
||||
TotalQueuedTectonicPlates.set(0);
|
||||
TotalNotQueuedTectonicPlates.set(0);
|
||||
TotalTectonicPlates.set(0);
|
||||
for (World world : IrisWorlds) {
|
||||
Engine engine = Objects.requireNonNull(IrisToolbelt.access(world)).getEngine();
|
||||
TotalQueuedTectonicPlates.addAndGet((int) engine.getMantle().getToUnload());
|
||||
TotalNotQueuedTectonicPlates.addAndGet((int) engine.getMantle().getNotQueuedLoadedRegions());
|
||||
TotalTectonicPlates.addAndGet(engine.getMantle().getLoadedRegionCount());
|
||||
}
|
||||
if (!isServerShuttingDown && isServerLoaded) {
|
||||
if (!trimTicker.isAlive()) {
|
||||
Iris.info(C.RED + "TrimTicker found dead! Booting it up!");
|
||||
try {
|
||||
TrimLogic();
|
||||
} catch (Exception e) {
|
||||
Iris.error("What happened?");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (!unloadTicker.isAlive()) {
|
||||
Iris.info(C.RED + "UnloadTicker found dead! Booting it up!");
|
||||
try {
|
||||
UnloadLogic();
|
||||
} catch (Exception e) {
|
||||
Iris.error("What happened?");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
return 1000;
|
||||
}
|
||||
};
|
||||
}
|
||||
public void TrimLogic() {
|
||||
if (trimTicker == null || !trimTicker.isAlive()) {
|
||||
trimTicker = new Looper() {
|
||||
private final Supplier<Engine> supplier = createSupplier();
|
||||
|
||||
@Override
|
||||
protected long loop() {
|
||||
long start = System.currentTimeMillis();
|
||||
trimAlive.reset();
|
||||
try {
|
||||
Engine engine = supplier.get();
|
||||
if (engine != null) {
|
||||
engine.getMantle().trim(tectonicLimit.get() / lastUse.size());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
Iris.info(C.RED + "EngineSVC: Failed to trim.");
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int size = lastUse.size();
|
||||
long time = (size > 0 ? 1000 / size : 1000) - (System.currentTimeMillis() - start);
|
||||
if (time <= 0)
|
||||
return 0;
|
||||
return time;
|
||||
}
|
||||
};
|
||||
trimTicker.start();
|
||||
}
|
||||
}
|
||||
public void UnloadLogic() {
|
||||
if (unloadTicker == null || !unloadTicker.isAlive()) {
|
||||
unloadTicker = new Looper() {
|
||||
private final Supplier<Engine> supplier = createSupplier();
|
||||
|
||||
@Override
|
||||
protected long loop() {
|
||||
long start = System.currentTimeMillis();
|
||||
unloadAlive.reset();
|
||||
try {
|
||||
Engine engine = supplier.get();
|
||||
if (engine != null) {
|
||||
long unloadStart = System.currentTimeMillis();
|
||||
int count = engine.getMantle().unloadTectonicPlate(tectonicLimit.get() / lastUse.size());
|
||||
if (count > 0) {
|
||||
Iris.debug(C.GOLD + "Unloaded " + C.YELLOW + count + " TectonicPlates in " + C.RED + Form.duration(System.currentTimeMillis() - unloadStart, 2));
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
Iris.info(C.RED + "EngineSVC: Failed to unload.");
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int size = lastUse.size();
|
||||
long time = (size > 0 ? 1000 / size : 1000) - (System.currentTimeMillis() - start);
|
||||
if (time <= 0)
|
||||
return 0;
|
||||
return time;
|
||||
}
|
||||
};
|
||||
unloadTicker.start();
|
||||
}
|
||||
}
|
||||
|
||||
private Supplier<Engine> createSupplier() {
|
||||
AtomicInteger i = new AtomicInteger();
|
||||
return () -> {
|
||||
List<World> worlds = Bukkit.getWorlds();
|
||||
if (i.get() >= worlds.size()) {
|
||||
i.set(0);
|
||||
}
|
||||
try {
|
||||
for (int j = 0; j < worlds.size(); j++) {
|
||||
World world = worlds.get(i.getAndIncrement());
|
||||
PlatformChunkGenerator generator = IrisToolbelt.access(world);
|
||||
if (i.get() >= worlds.size()) {
|
||||
i.set(0);
|
||||
}
|
||||
|
||||
if (generator != null) {
|
||||
Engine engine = generator.getEngine();
|
||||
boolean closed = engine.getMantle().getData().isClosed();
|
||||
if (engine != null && !engine.isStudio() && !closed) {
|
||||
lastUseLock.lock();
|
||||
lastUse.put(world, System.currentTimeMillis());
|
||||
lastUseLock.unlock();
|
||||
return engine;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.info(C.RED + "EngineSVC: Failed to create supplier.");
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
cacheTicker.interrupt();
|
||||
trimTicker.interrupt();
|
||||
unloadTicker.interrupt();
|
||||
lastUse.clear();
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,11 @@ public class IrisCreator {
|
||||
* Benchmark mode
|
||||
*/
|
||||
private boolean benchmark = false;
|
||||
/**
|
||||
* Radius of chunks to pregenerate in the headless mode
|
||||
* if set to -1, headless mode is disabled
|
||||
*/
|
||||
private int headlessRadius = 10;
|
||||
|
||||
public static boolean removeFromBukkitYml(String name) throws IOException {
|
||||
YamlConfiguration yml = YamlConfiguration.loadConfiguration(BUKKIT_YML);
|
||||
@@ -127,7 +132,6 @@ public class IrisCreator {
|
||||
Iris.service(StudioSVC.class).installIntoWorld(sender, d.getLoadKey(), new File(Bukkit.getWorldContainer(), name()));
|
||||
}
|
||||
|
||||
PlatformChunkGenerator access;
|
||||
AtomicReference<World> world = new AtomicReference<>();
|
||||
AtomicDouble pp = new AtomicDouble(0);
|
||||
O<Boolean> done = new O<>();
|
||||
@@ -140,30 +144,56 @@ public class IrisCreator {
|
||||
.create();
|
||||
ServerConfigurator.installDataPacks(false);
|
||||
|
||||
access = (PlatformChunkGenerator) wc.generator();
|
||||
PlatformChunkGenerator finalAccess1 = access;
|
||||
PlatformChunkGenerator access = (PlatformChunkGenerator) wc.generator();
|
||||
if (access == null) {
|
||||
throw new IrisException("Access is null. Something bad happened.");
|
||||
}
|
||||
|
||||
J.a(() ->
|
||||
{
|
||||
Supplier<Integer> g = () -> {
|
||||
if (finalAccess1 == null || finalAccess1.getEngine() == null) {
|
||||
return 0;
|
||||
if (headlessRadius > 0 && !benchmark) {
|
||||
AtomicBoolean failed = new AtomicBoolean(false);
|
||||
J.a(() -> {
|
||||
int generated = access.getGenerated();
|
||||
double total = Math.pow(headlessRadius * 2 + 1, 2);
|
||||
|
||||
while (generated < total) {
|
||||
if (failed.get()) return;
|
||||
|
||||
double v = (double) generated / total;
|
||||
if (sender.isPlayer()) {
|
||||
sender.sendProgress(v, "Generating headless chunks");
|
||||
J.sleep(16);
|
||||
} else {
|
||||
sender.sendMessage(C.WHITE + "Generating headless chunks " + Form.pc(v) + ((C.GRAY + " (" + ((int) total - generated) + " Left)")));
|
||||
J.sleep(1000);
|
||||
}
|
||||
generated = access.getGenerated();
|
||||
}
|
||||
return finalAccess1.getEngine().getGenerated();
|
||||
};
|
||||
if(!benchmark) {
|
||||
if (finalAccess1 == null) return;
|
||||
int req = finalAccess1.getSpawnChunks().join();
|
||||
});
|
||||
|
||||
while (g.get() < req) {
|
||||
double v = (double) g.get() / (double) req;
|
||||
try {
|
||||
access.prepareSpawnChunks(seed, headlessRadius);
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to prepare spawn chunks for " + name);
|
||||
e.printStackTrace();
|
||||
failed.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
J.a(() -> {
|
||||
if(!benchmark) {
|
||||
int req = access.getSpawnChunks().join();
|
||||
|
||||
int generated = access.getGenerated();
|
||||
while (generated < req) {
|
||||
double v = (double) generated / (double) req;
|
||||
if (sender.isPlayer()) {
|
||||
sender.sendProgress(v, "Generating");
|
||||
J.sleep(16);
|
||||
} else {
|
||||
sender.sendMessage(C.WHITE + "Generating " + Form.pc(v) + ((C.GRAY + " (" + (req - g.get()) + " Left)")));
|
||||
sender.sendMessage(C.WHITE + "Generating " + Form.pc(v) + ((C.GRAY + " (" + (req - generated) + " Left)")));
|
||||
J.sleep(1000);
|
||||
}
|
||||
generated = access.getGenerated();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,13 +2,22 @@ package com.volmit.iris.core.tools;
|
||||
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||
import com.volmit.iris.core.pregenerator.methods.HeadlessPregenMethod;
|
||||
import com.volmit.iris.core.pregenerator.methods.HybridPregenMethod;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.engine.IrisEngine;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.framework.EngineTarget;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.engine.object.IrisWorld;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.exceptions.IrisException;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import lombok.Getter;
|
||||
@@ -17,11 +26,6 @@ import org.bukkit.Bukkit;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
@@ -33,13 +37,16 @@ public class IrisPackBenchmarking {
|
||||
public static boolean benchmarkInProgress = false;
|
||||
private final PrecisionStopwatch stopwatch = new PrecisionStopwatch();
|
||||
private final IrisDimension dimension;
|
||||
private final int radius;
|
||||
private final int diameter;
|
||||
private final boolean gui;
|
||||
private final boolean headless;
|
||||
private transient Engine engine;
|
||||
|
||||
public IrisPackBenchmarking(IrisDimension dimension, int radius, boolean gui) {
|
||||
public IrisPackBenchmarking(IrisDimension dimension, int diameter, boolean headless, boolean gui) {
|
||||
instance = this;
|
||||
this.dimension = dimension;
|
||||
this.radius = radius;
|
||||
this.diameter = diameter;
|
||||
this.headless = headless;
|
||||
this.gui = gui;
|
||||
runBenchmark();
|
||||
}
|
||||
@@ -50,12 +57,9 @@ public class IrisPackBenchmarking {
|
||||
.start(() -> {
|
||||
Iris.info("Setting up benchmark environment ");
|
||||
benchmarkInProgress = true;
|
||||
File file = new File("benchmark");
|
||||
if (file.exists()) {
|
||||
deleteDirectory(file.toPath());
|
||||
}
|
||||
IO.delete(new File(Bukkit.getWorldContainer(), "benchmark"));
|
||||
createBenchmark();
|
||||
while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
|
||||
while (!headless && !IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
|
||||
J.sleep(1000);
|
||||
Iris.debug("Iris PackBenchmark: Waiting...");
|
||||
}
|
||||
@@ -73,7 +77,6 @@ public class IrisPackBenchmarking {
|
||||
public void finishedBenchmark(KList<Integer> cps) {
|
||||
try {
|
||||
String time = Form.duration(stopwatch.getMillis());
|
||||
Engine engine = IrisToolbelt.access(Bukkit.getWorld("benchmark")).getEngine();
|
||||
Iris.info("-----------------");
|
||||
Iris.info("Results:");
|
||||
Iris.info("- Total time: " + time);
|
||||
@@ -114,12 +117,16 @@ public class IrisPackBenchmarking {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
J.s(() -> {
|
||||
var world = Bukkit.getWorld("benchmark");
|
||||
if (world == null) return;
|
||||
IrisToolbelt.evacuate(world);
|
||||
Bukkit.unloadWorld(world, true);
|
||||
});
|
||||
if (headless) {
|
||||
engine.close();
|
||||
} else {
|
||||
J.s(() -> {
|
||||
var world = Bukkit.getWorld("benchmark");
|
||||
if (world == null) return;
|
||||
IrisToolbelt.evacuate(world);
|
||||
Bukkit.unloadWorld(world, true);
|
||||
});
|
||||
}
|
||||
|
||||
stopwatch.end();
|
||||
} catch (Exception e) {
|
||||
@@ -130,13 +137,34 @@ public class IrisPackBenchmarking {
|
||||
|
||||
private void createBenchmark() {
|
||||
try {
|
||||
IrisToolbelt.createWorld()
|
||||
if (headless) {
|
||||
Iris.info("Using headless benchmark!");
|
||||
IrisWorld world = IrisWorld.builder()
|
||||
.name("benchmark")
|
||||
.minHeight(dimension.getMinHeight())
|
||||
.maxHeight(dimension.getMaxHeight())
|
||||
.seed(1337)
|
||||
.worldFolder(new File(Bukkit.getWorldContainer(), "benchmark"))
|
||||
.environment(dimension.getEnvironment())
|
||||
.build();
|
||||
Iris.service(StudioSVC.class).installIntoWorld(
|
||||
Iris.getSender(),
|
||||
dimension.getLoadKey(),
|
||||
world.worldFolder());
|
||||
var data = IrisData.get(new File(world.worldFolder(), "iris/pack"));
|
||||
var dim = data.getDimensionLoader().load(dimension.getLoadKey());
|
||||
engine = new IrisEngine(new EngineTarget(world, dim, data), false);
|
||||
return;
|
||||
}
|
||||
|
||||
engine = IrisToolbelt.access(IrisToolbelt.createWorld()
|
||||
.dimension(dimension.getLoadKey())
|
||||
.name("benchmark")
|
||||
.seed(1337)
|
||||
.studio(false)
|
||||
.benchmark(true)
|
||||
.create();
|
||||
.create())
|
||||
.getEngine();
|
||||
} catch (IrisException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -146,9 +174,15 @@ public class IrisPackBenchmarking {
|
||||
IrisToolbelt.pregenerate(PregenTask
|
||||
.builder()
|
||||
.gui(gui)
|
||||
.radiusX(radius)
|
||||
.radiusZ(radius)
|
||||
.build(), Bukkit.getWorld("benchmark")
|
||||
.radiusX(diameter)
|
||||
.radiusZ(diameter)
|
||||
.build(), headless ?
|
||||
new HeadlessPregenMethod(engine) :
|
||||
new HybridPregenMethod(
|
||||
engine.getWorld().realWorld(),
|
||||
IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())
|
||||
),
|
||||
engine
|
||||
);
|
||||
}
|
||||
|
||||
@@ -178,26 +212,4 @@ public class IrisPackBenchmarking {
|
||||
private int findHighest(KList<Integer> list) {
|
||||
return Collections.max(list);
|
||||
}
|
||||
|
||||
private boolean deleteDirectory(Path dir) {
|
||||
try {
|
||||
Files.walkFileTree(dir, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,6 @@ import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.hunk.Hunk;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.io.JarScanner;
|
||||
import com.volmit.iris.util.mantle.MantleFlag;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
@@ -61,11 +60,7 @@ import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -76,8 +71,6 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@EqualsAndHashCode(exclude = "context")
|
||||
@ToString(exclude = "context")
|
||||
public class IrisEngine implements Engine {
|
||||
private static final Map<Class<? extends IrisEngineService>, Constructor<? extends IrisEngineService>> SERVICES = scanServices();
|
||||
private final KMap<Class<? extends IrisEngineService>, IrisEngineService> services;
|
||||
private final AtomicInteger bud;
|
||||
private final AtomicInteger buds;
|
||||
private final AtomicInteger generated;
|
||||
@@ -118,7 +111,6 @@ public class IrisEngine implements Engine {
|
||||
getEngineData();
|
||||
verifySeed();
|
||||
this.seedManager = new SeedManager(target.getWorld().getRawWorldSeed());
|
||||
services = new KMap<>();
|
||||
bud = new AtomicInteger(0);
|
||||
buds = new AtomicInteger(0);
|
||||
metrics = new EngineMetrics(32);
|
||||
@@ -145,26 +137,6 @@ public class IrisEngine implements Engine {
|
||||
Iris.debug("Engine Initialized " + getCacheID());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<Class<? extends IrisEngineService>, Constructor<? extends IrisEngineService>> scanServices() {
|
||||
JarScanner js = new JarScanner(Iris.instance.getJarFile(), "com.volmit.iris.engine.service");
|
||||
J.attempt(js::scan);
|
||||
KMap<Class<? extends IrisEngineService>, Constructor<? extends IrisEngineService>> map = new KMap<>();
|
||||
js.getClasses()
|
||||
.stream()
|
||||
.filter(IrisEngineService.class::isAssignableFrom)
|
||||
.map(c -> (Class<? extends IrisEngineService>) c)
|
||||
.forEach(c -> {
|
||||
try {
|
||||
map.put(c, c.getConstructor(Engine.class));
|
||||
} catch (NoSuchMethodException e) {
|
||||
Iris.warn("Failed to load service " + c.getName() + " due to missing constructor");
|
||||
}
|
||||
});
|
||||
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
private void verifySeed() {
|
||||
if (getEngineData().getSeed() != null && getEngineData().getSeed() != target.getWorld().getRawWorldSeed()) {
|
||||
target.getWorld().setRawWorldSeed(getEngineData().getSeed());
|
||||
@@ -189,8 +161,6 @@ public class IrisEngine implements Engine {
|
||||
execution.close();
|
||||
effects.close();
|
||||
mode.close();
|
||||
services.values().forEach(s -> s.onDisable(true));
|
||||
services.values().forEach(Iris.instance::unregisterListener);
|
||||
|
||||
J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace());
|
||||
}
|
||||
@@ -199,24 +169,6 @@ public class IrisEngine implements Engine {
|
||||
try {
|
||||
Iris.debug("Setup Engine " + getCacheID());
|
||||
cacheId = RNG.r.nextInt();
|
||||
boolean hotload = true;
|
||||
if (services.isEmpty()) {
|
||||
SERVICES.forEach((s, c) -> {
|
||||
try {
|
||||
services.put(s, c.newInstance(this));
|
||||
} catch (InstantiationException | IllegalAccessException |
|
||||
InvocationTargetException e) {
|
||||
Iris.error("Failed to create service " + s.getName());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
hotload = false;
|
||||
}
|
||||
for (var service : services.values()) {
|
||||
service.onEnable(hotload);
|
||||
Iris.instance.registerListener(service);
|
||||
}
|
||||
|
||||
worldManager = new IrisWorldManager(this);
|
||||
complex = new IrisComplex(this);
|
||||
execution = new IrisExecutionEnvironment(this);
|
||||
@@ -352,6 +304,11 @@ public class IrisEngine implements Engine {
|
||||
return generated.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGenerated(int x, int z) {
|
||||
generated.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getGeneratedPerSecond() {
|
||||
if (perSecondLatch.flip()) {
|
||||
@@ -466,7 +423,6 @@ public class IrisEngine implements Engine {
|
||||
PregeneratorJob.shutdownInstance();
|
||||
closed = true;
|
||||
J.car(art);
|
||||
services.values().forEach(s -> s.onDisable(false));
|
||||
getWorldManager().close();
|
||||
getTarget().close();
|
||||
saveEngineData();
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.volmit.iris.engine.object.*;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.collection.KSet;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.mantle.MantleFlag;
|
||||
import com.volmit.iris.util.math.M;
|
||||
@@ -71,6 +72,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
private final Looper looper;
|
||||
private final int id;
|
||||
private final KList<Runnable> updateQueue = new KList<>();
|
||||
private final ChronoLatch cl;
|
||||
private final ChronoLatch clw;
|
||||
private final ChronoLatch ecl;
|
||||
private final ChronoLatch cln;
|
||||
@@ -81,10 +83,12 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
private long charge = 0;
|
||||
private int actuallySpawned = 0;
|
||||
private int cooldown = 0;
|
||||
private List<Entity> precount = new KList<>();
|
||||
private KSet<Position2> injectBiomes = new KSet<>();
|
||||
|
||||
public IrisWorldManager() {
|
||||
super(null);
|
||||
cl = null;
|
||||
ecl = null;
|
||||
cln = null;
|
||||
clw = null;
|
||||
@@ -99,6 +103,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
chunkUpdater = new ChronoLatch(3000);
|
||||
chunkDiscovery = new ChronoLatch(5000);
|
||||
cln = new ChronoLatch(60000);
|
||||
cl = new ChronoLatch(3000);
|
||||
ecl = new ChronoLatch(250);
|
||||
clw = new ChronoLatch(1000, true);
|
||||
id = engine.getCacheID();
|
||||
@@ -146,12 +151,27 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
engine.getEngineData().cleanup(getEngine());
|
||||
}
|
||||
|
||||
if (precount != null) {
|
||||
entityCount = 0;
|
||||
for (Entity i : precount) {
|
||||
if (i instanceof LivingEntity) {
|
||||
if (!i.isDead()) {
|
||||
entityCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
precount = null;
|
||||
}
|
||||
|
||||
if (energy < 650) {
|
||||
if (ecl.flip()) {
|
||||
energy *= 1 + (0.02 * M.clip((1D - getEntitySaturation()), 0D, 1D));
|
||||
fixEnergy();
|
||||
}
|
||||
}
|
||||
|
||||
onAsyncTick();
|
||||
}
|
||||
|
||||
return IrisSettings.get().getWorld().getAsyncTickIntervalMS();
|
||||
@@ -194,7 +214,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
int finalZ = c.getZ() + z;
|
||||
J.a(() -> getMantle().raiseFlag(finalX, finalZ, MantleFlag.INITIAL_SPAWNED_MARKER,
|
||||
() -> {
|
||||
J.a(() -> spawnIn(cx), RNG.r.i(5, 200));
|
||||
J.a(() -> spawnIn(cx, true), RNG.r.i(5, 200));
|
||||
getSpawnersFromMarkers(cx).forEach((blockf, spawners) -> {
|
||||
if (spawners.isEmpty()) {
|
||||
return;
|
||||
@@ -202,7 +222,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
|
||||
IrisPosition block = new IrisPosition(blockf.getX(), blockf.getY() + getEngine().getWorld().minHeight(), blockf.getZ());
|
||||
IrisSpawner s = new KList<>(spawners).getRandom();
|
||||
spawn(block, s);
|
||||
spawn(block, s, true);
|
||||
});
|
||||
}));
|
||||
}
|
||||
@@ -212,16 +232,73 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onAsyncTick() {
|
||||
if (getEngine().isClosed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
actuallySpawned = 0;
|
||||
|
||||
if (energy < 100) {
|
||||
J.sleep(200);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!getEngine().getWorld().hasRealWorld()) {
|
||||
Iris.debug("Can't spawn. No real world");
|
||||
J.sleep(5000);
|
||||
return false;
|
||||
}
|
||||
|
||||
double epx = getEntitySaturation();
|
||||
if (epx > IrisSettings.get().getWorld().getTargetSpawnEntitiesPerChunk()) {
|
||||
Iris.debug("Can't spawn. The entity per chunk ratio is at " + Form.pc(epx, 2) + " > 100% (total entities " + entityCount + ")");
|
||||
J.sleep(5000);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cl.flip()) {
|
||||
try {
|
||||
J.s(() -> precount = getEngine().getWorld().realWorld().getEntities());
|
||||
} catch (Throwable e) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
int spawnBuffer = RNG.r.i(2, 12);
|
||||
|
||||
Chunk[] cc = getEngine().getWorld().realWorld().getLoadedChunks();
|
||||
while (spawnBuffer-- > 0) {
|
||||
if (cc.length == 0) {
|
||||
Iris.debug("Can't spawn. No chunks!");
|
||||
return false;
|
||||
}
|
||||
|
||||
Chunk c = cc[RNG.r.nextInt(cc.length)];
|
||||
|
||||
if (!c.isLoaded() || !Chunks.isSafe(c.getWorld(), c.getX(), c.getZ())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
spawnIn(c, false);
|
||||
}
|
||||
|
||||
energy -= (actuallySpawned / 2D);
|
||||
return actuallySpawned > 0;
|
||||
}
|
||||
|
||||
private void fixEnergy() {
|
||||
energy = M.clip(energy, 1D, getDimension().getMaximumEnergy());
|
||||
}
|
||||
|
||||
private void spawnIn(Chunk c) {
|
||||
private void spawnIn(Chunk c, boolean initial) {
|
||||
if (getEngine().isClosed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
energy += 1.2;
|
||||
if (initial) {
|
||||
energy += 1.2;
|
||||
}
|
||||
|
||||
if (IrisSettings.get().getWorld().isMarkerEntitySpawningSystem()) {
|
||||
getSpawnersFromMarkers(c).forEach((blockf, spawners) -> {
|
||||
@@ -231,8 +308,9 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
|
||||
IrisPosition block = new IrisPosition(blockf.getX(), blockf.getY() + getEngine().getWorld().minHeight(), blockf.getZ());
|
||||
IrisSpawner s = new KList<>(spawners).getRandom();
|
||||
spawn(block, s, false);
|
||||
J.a(() -> getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.INITIAL_SPAWNED_MARKER,
|
||||
() -> spawn(block, s)));
|
||||
() -> spawn(block, s, true)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -263,7 +341,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
.stream()
|
||||
.filter(filter)))
|
||||
.filter(counter)
|
||||
.flatMap(this::stream)
|
||||
.flatMap((i) -> stream(i, initial))
|
||||
.collect(Collectors.toList()))
|
||||
.getRandom();
|
||||
//@done
|
||||
@@ -300,13 +378,13 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
}
|
||||
}
|
||||
|
||||
private Stream<IrisEntitySpawn> stream(IrisSpawner s) {
|
||||
for (IrisEntitySpawn i : s.getInitialSpawns()) {
|
||||
private Stream<IrisEntitySpawn> stream(IrisSpawner s, boolean initial) {
|
||||
for (IrisEntitySpawn i : initial ? s.getInitialSpawns() : s.getSpawns()) {
|
||||
i.setReferenceSpawner(s);
|
||||
i.setReferenceMarker(s.getReferenceMarker());
|
||||
}
|
||||
|
||||
return (s.getInitialSpawns()).stream();
|
||||
return (initial ? s.getInitialSpawns() : s.getSpawns()).stream();
|
||||
}
|
||||
|
||||
private KList<IrisEntitySpawn> spawnRandomly(List<IrisEntitySpawn> types) {
|
||||
@@ -353,7 +431,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void spawn(IrisPosition block, IrisSpawner spawner) {
|
||||
private void spawn(IrisPosition block, IrisSpawner spawner, boolean initial) {
|
||||
if (getEngine().isClosed()) {
|
||||
return;
|
||||
}
|
||||
@@ -362,7 +440,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
return;
|
||||
}
|
||||
|
||||
KList<IrisEntitySpawn> s = spawner.getInitialSpawns();
|
||||
KList<IrisEntitySpawn> s = initial ? spawner.getInitialSpawns() : spawner.getSpawns();
|
||||
if (s.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -610,6 +610,9 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
|
||||
int getGenerated();
|
||||
|
||||
@ChunkCoordinates
|
||||
void addGenerated(int x, int z);
|
||||
|
||||
CompletableFuture<Long> getHash32();
|
||||
|
||||
default <T> IrisPosition lookForStreamResult(T find, ProceduralStream<T> stream, Function2<T, T, Boolean> matcher, long timeout) {
|
||||
|
||||
@@ -2,15 +2,12 @@ package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.misc.ServerProperties;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.WorldInitEvent;
|
||||
|
||||
@@ -22,9 +19,14 @@ public class IrisContextInjector implements Listener {
|
||||
@Getter
|
||||
private static boolean missingDimensionTypes = false;
|
||||
private AutoClosing autoClosing = null;
|
||||
private final int totalWorlds;
|
||||
private int worldCounter = 0;
|
||||
|
||||
public IrisContextInjector() {
|
||||
if (!Bukkit.getWorlds().isEmpty()) return;
|
||||
if (!Bukkit.getWorlds().isEmpty()) {
|
||||
totalWorlds = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
String levelName = ServerProperties.LEVEL_NAME;
|
||||
List<String> irisWorlds = irisWorlds();
|
||||
@@ -32,20 +34,29 @@ public class IrisContextInjector implements Listener {
|
||||
boolean nether = irisWorlds.contains(levelName + "_nether");
|
||||
boolean end = irisWorlds.contains(levelName + "_end");
|
||||
|
||||
int i = 1;
|
||||
if (Bukkit.getAllowNether()) i++;
|
||||
if (Bukkit.getAllowEnd()) i++;
|
||||
|
||||
if (INMS.get().missingDimensionTypes(overworld, nether, end)) {
|
||||
missingDimensionTypes = true;
|
||||
totalWorlds = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (overworld || nether || end) {
|
||||
autoClosing = INMS.get().injectUncached(overworld, nether, end);
|
||||
var pair = INMS.get().injectUncached(overworld, nether, end);
|
||||
i += pair.getA() - 3;
|
||||
autoClosing = pair.getB();
|
||||
}
|
||||
|
||||
totalWorlds = i;
|
||||
instance.registerListener(this);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@EventHandler
|
||||
public void on(WorldInitEvent event) {
|
||||
if (++worldCounter < totalWorlds) return;
|
||||
if (autoClosing != null) {
|
||||
autoClosing.close();
|
||||
autoClosing = null;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public abstract class IrisEngineService implements Listener {
|
||||
protected final Engine engine;
|
||||
|
||||
public abstract void onEnable(boolean hotload);
|
||||
|
||||
public abstract void onDisable(boolean hotload);
|
||||
|
||||
public final void postShutdown(Runnable r) {
|
||||
Iris.instance.postShutdown(r);
|
||||
}
|
||||
}
|
||||
@@ -404,7 +404,7 @@ public class IrisEntity extends IrisRegistrant {
|
||||
});
|
||||
|
||||
|
||||
return e.isValid() ? e : null;
|
||||
return e;
|
||||
}
|
||||
|
||||
private int surfaceY(Location l) {
|
||||
|
||||
@@ -28,12 +28,19 @@ import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.math.Vector3d;
|
||||
import com.volmit.iris.util.matter.MatterMarker;
|
||||
import com.volmit.iris.util.matter.slices.MarkerMatter;
|
||||
import io.lumine.mythic.bukkit.adapters.BukkitEntity;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
|
||||
@Snippet("entity-spawn")
|
||||
@Accessors(chain = true)
|
||||
@@ -62,10 +69,6 @@ public class IrisEntitySpawn implements IRare {
|
||||
private transient IrisSpawner referenceSpawner;
|
||||
private transient IrisMarker referenceMarker;
|
||||
|
||||
public boolean check(Engine eng, IrisPosition c, ChunkSnapshot snapshot) {
|
||||
return getRealEntity(eng).getSurface().matches(snapshot.getBlockData(c.getX() & 15, c.getY(), c.getZ() & 15));
|
||||
}
|
||||
|
||||
public int spawn(Engine gen, Chunk c, RNG rng) {
|
||||
int spawns = minSpawns == maxSpawns ? minSpawns : rng.i(Math.min(minSpawns, maxSpawns), Math.max(minSpawns, maxSpawns));
|
||||
int s = 0;
|
||||
@@ -165,7 +168,7 @@ public class IrisEntitySpawn implements IRare {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ignoreSurfaces && !irisEntity.getSurface().matches(at.clone().subtract(0, 1, 0).getBlock().getBlockData())) {
|
||||
if (!ignoreSurfaces && !irisEntity.getSurface().matches(at.clone().subtract(0, 1, 0).getBlock())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -180,10 +183,6 @@ public class IrisEntitySpawn implements IRare {
|
||||
Entity e = irisEntity.spawn(g, at.add(0.5, 0.5, 0.5), rng.aquire(() -> new RNG(g.getSeedManager().getEntity())));
|
||||
if (e != null) {
|
||||
Iris.debug("Spawned " + C.DARK_AQUA + "Entity<" + getEntity() + "> " + C.GREEN + e.getType() + C.LIGHT_PURPLE + " @ " + C.GRAY + e.getLocation().getX() + ", " + e.getLocation().getY() + ", " + e.getLocation().getZ());
|
||||
|
||||
if (referenceSpawner != null) {
|
||||
referenceSpawner.getConditions().apply(e);
|
||||
}
|
||||
}
|
||||
|
||||
return e;
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2024 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.framework.EngineStage;
|
||||
import com.volmit.iris.engine.framework.WrongEngineBroException;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.context.ChunkContext;
|
||||
import com.volmit.iris.util.context.IrisContext;
|
||||
import com.volmit.iris.util.documentation.BlockCoordinates;
|
||||
import com.volmit.iris.util.documentation.RegionCoordinates;
|
||||
import com.volmit.iris.util.hunk.Hunk;
|
||||
import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder;
|
||||
import com.volmit.iris.util.hunk.view.SyncChunkDataHunkHolder;
|
||||
import com.volmit.iris.util.mantle.MantleFlag;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.Looper;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.atomic.AtomicReferenceArray;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class IrisHeadless {
|
||||
private final long KEEP_ALIVE = TimeUnit.SECONDS.toMillis(10L);
|
||||
private final Engine engine;
|
||||
private final IRegionStorage storage;
|
||||
private final ExecutorService executor = Executors.newCachedThreadPool();
|
||||
private final KMap<Long, Region> regions = new KMap<>();
|
||||
private final AtomicInteger loadedChunks = new AtomicInteger();
|
||||
private transient CompletingThread regionThread;
|
||||
private transient boolean closed = false;
|
||||
|
||||
public IrisHeadless(Engine engine) {
|
||||
this.engine = engine;
|
||||
this.storage = INMS.get().createRegionStorage(engine);
|
||||
if (storage == null) throw new IllegalStateException("Failed to create region storage!");
|
||||
engine.getWorld().headless(this);
|
||||
startRegionCleaner();
|
||||
}
|
||||
|
||||
private void startRegionCleaner() {
|
||||
var cleaner = new Looper() {
|
||||
@Override
|
||||
protected long loop() {
|
||||
if (closed) return -1;
|
||||
long time = M.ms() - KEEP_ALIVE;
|
||||
regions.values()
|
||||
.stream()
|
||||
.filter(r -> r.lastEntry < time)
|
||||
.forEach(Region::submit);
|
||||
return closed ? -1 : 1000;
|
||||
}
|
||||
};
|
||||
cleaner.setName("Iris Region Cleaner - " + engine.getWorld().name());
|
||||
cleaner.setPriority(Thread.MIN_PRIORITY);
|
||||
cleaner.start();
|
||||
}
|
||||
|
||||
public int getLoadedChunks() {
|
||||
return loadedChunks.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the mca plate is fully generated or not.
|
||||
*
|
||||
* @param x coord of the chunk
|
||||
* @param z coord of the chunk
|
||||
* @return true if the chunk exists in .mca
|
||||
*/
|
||||
public boolean exists(int x, int z) {
|
||||
if (closed) return false;
|
||||
if (engine.getWorld().hasRealWorld() && engine.getWorld().realWorld().isChunkLoaded(x, z))
|
||||
return true;
|
||||
return storage.exists(x, z);
|
||||
}
|
||||
|
||||
public synchronized CompletableFuture<Void> generateRegion(MultiBurst burst, int x, int z, int maxConcurrent, PregenListener listener) {
|
||||
if (closed) return CompletableFuture.completedFuture(null);
|
||||
if (regionThread != null && !regionThread.future.isDone())
|
||||
throw new IllegalStateException("Region generation already in progress");
|
||||
|
||||
regionThread = new CompletingThread(() -> {
|
||||
boolean listening = listener != null;
|
||||
Semaphore semaphore = new Semaphore(maxConcurrent);
|
||||
CountDownLatch latch = new CountDownLatch(1024);
|
||||
|
||||
iterateRegion(x, z, pos -> {
|
||||
try {
|
||||
semaphore.acquire();
|
||||
} catch (InterruptedException e) {
|
||||
semaphore.release();
|
||||
return;
|
||||
}
|
||||
|
||||
burst.complete(() -> {
|
||||
try {
|
||||
if (listening) listener.onChunkGenerating(pos.getX(), pos.getZ());
|
||||
generateChunk(pos.getX(), pos.getZ());
|
||||
if (listening) listener.onChunkGenerated(pos.getX(), pos.getZ());
|
||||
} finally {
|
||||
semaphore.release();
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
});
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException ignored) {}
|
||||
if (listening) listener.onRegionGenerated(x, z);
|
||||
}, "Region Generator - " + x + "," + z, Thread.MAX_PRIORITY);
|
||||
|
||||
return regionThread.future;
|
||||
}
|
||||
|
||||
@RegionCoordinates
|
||||
private static void iterateRegion(int x, int z, Consumer<Position2> chunkPos) {
|
||||
int cX = x << 5;
|
||||
int cZ = z << 5;
|
||||
for (int xx = 0; xx < 32; xx++) {
|
||||
for (int zz = 0; zz < 32; zz++) {
|
||||
chunkPos.accept(new Position2(cX + xx, cZ + zz));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void generateChunk(int x, int z) {
|
||||
if (closed || exists(x, z)) return;
|
||||
try {
|
||||
var chunk = storage.createChunk(x, z);
|
||||
loadedChunks.incrementAndGet();
|
||||
|
||||
SyncChunkDataHunkHolder blocks = new SyncChunkDataHunkHolder(chunk);
|
||||
BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(chunk, chunk.getMinHeight(), chunk.getMaxHeight());
|
||||
ChunkContext ctx = generate(engine, x << 4, z << 4, blocks, biomes);
|
||||
blocks.apply();
|
||||
biomes.apply();
|
||||
|
||||
storage.fillBiomes(chunk, ctx);
|
||||
chunk.mark();
|
||||
|
||||
long key = Cache.key(x >> 5, z >> 5);
|
||||
regions.computeIfAbsent(key, Region::new)
|
||||
.add(chunk);
|
||||
} catch (Throwable e) {
|
||||
loadedChunks.decrementAndGet();
|
||||
Iris.error("Failed to generate " + x + ", " + z);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@BlockCoordinates
|
||||
private ChunkContext generate(Engine engine, int x, int z, Hunk<BlockData> vblocks, Hunk<org.bukkit.block.Biome> vbiomes) throws WrongEngineBroException {
|
||||
if (engine.isClosed()) {
|
||||
throw new WrongEngineBroException();
|
||||
}
|
||||
|
||||
engine.getContext().touch();
|
||||
engine.getEngineData().getStatistics().generatedChunk();
|
||||
ChunkContext ctx = null;
|
||||
try {
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
Hunk<BlockData> blocks = vblocks.listen((xx, y, zz, t) -> engine.catchBlockUpdates(x + xx, y + engine.getMinHeight(), z + zz, t));
|
||||
|
||||
var dimension = engine.getDimension();
|
||||
if (dimension.isDebugChunkCrossSections() && ((x >> 4) % dimension.getDebugCrossSectionsMod() == 0 || (z >> 4) % dimension.getDebugCrossSectionsMod() == 0)) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int j = 0; j < 16; j++) {
|
||||
blocks.set(i, 0, j, Material.CRYING_OBSIDIAN.createBlockData());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx = new ChunkContext(x, z, engine.getComplex());
|
||||
IrisContext.getOr(engine).setChunkContext(ctx);
|
||||
|
||||
for (EngineStage i : engine.getMode().getStages()) {
|
||||
i.generate(x, z, blocks, vbiomes, false, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
engine.getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true);
|
||||
engine.getMetrics().getTotal().put(p.getMilliseconds());
|
||||
engine.addGenerated(x,z);
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
engine.fail("Failed to generate " + x + ", " + z, e);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (closed) return;
|
||||
try {
|
||||
if (regionThread != null) {
|
||||
regionThread.future.join();
|
||||
regionThread = null;
|
||||
}
|
||||
|
||||
regions.v().forEach(Region::submit);
|
||||
Iris.info("Waiting for " + loadedChunks.get() + " chunks to unload...");
|
||||
while (loadedChunks.get() > 0)
|
||||
J.sleep(1);
|
||||
Iris.info("All chunks unloaded");
|
||||
executor.shutdown();
|
||||
storage.close();
|
||||
engine.getWorld().headless(null);
|
||||
} finally {
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class Region implements Runnable {
|
||||
private final int x, z;
|
||||
private final long key;
|
||||
private final AtomicReferenceArray<SerializableChunk> chunks = new AtomicReferenceArray<>(1024);
|
||||
private final AtomicReference<Future<?>> full = new AtomicReference<>();
|
||||
private transient int size;
|
||||
private transient long lastEntry = M.ms();
|
||||
|
||||
public Region(long key) {
|
||||
this.x = Cache.keyX(key);
|
||||
this.z = Cache.keyZ(key);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try (IRegion region = storage.getRegion(x, z, false)) {
|
||||
assert region != null;
|
||||
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
SerializableChunk chunk = chunks.get(i);
|
||||
if (chunk == null)
|
||||
continue;
|
||||
|
||||
try {
|
||||
region.write(chunk);
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to save chunk " + chunk.getPos());
|
||||
e.printStackTrace();
|
||||
}
|
||||
loadedChunks.decrementAndGet();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to load region file " + x + ", " + z);
|
||||
e.printStackTrace();
|
||||
loadedChunks.addAndGet(-size);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void add(SerializableChunk chunk) {
|
||||
lastEntry = M.ms();
|
||||
if (chunks.getAndSet(index(chunk.getPos()), chunk) != null)
|
||||
throw new IllegalStateException("Chunk " + chunk.getPos() + " already exists");
|
||||
if (++size < 1024)
|
||||
return;
|
||||
submit();
|
||||
}
|
||||
|
||||
public void submit() {
|
||||
regions.remove(key);
|
||||
full.getAndUpdate(future -> {
|
||||
if (future != null) return future;
|
||||
return executor.submit(this);
|
||||
});
|
||||
}
|
||||
|
||||
private int index(Position2 chunk) {
|
||||
int x = chunk.getX() & 31;
|
||||
int z = chunk.getZ() & 31;
|
||||
return z * 32 + x;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CompletingThread extends Thread {
|
||||
private final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
private CompletingThread(Runnable task, String name, int priority) {
|
||||
super(task, name);
|
||||
setPriority(priority);
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
super.run();
|
||||
} finally {
|
||||
future.complete(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.engine.object.annotations.Desc;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Desc("Conditions for a spawner to be triggered")
|
||||
@Data
|
||||
public class IrisSpawnCondition {
|
||||
private static final NamespacedKey CATEGORY_KEY = new NamespacedKey(Iris.instance, "spawn_category");
|
||||
|
||||
private SpawnCategory category = SpawnCategory.AMBIENT;
|
||||
private int maxEntities = 60;
|
||||
|
||||
public boolean check(KMap<UUID, KMap<String, Boolean>> cache, KList<Entity> entities) {
|
||||
int entityCount = 0;
|
||||
for (Entity entity : entities) {
|
||||
var map = cache.computeIfAbsent(entity.getUniqueId(), k -> new KMap<>());
|
||||
if (check(map, "category_" + category.name(), () -> checkCategory(entity, category)) && ++entityCount >= maxEntities)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void apply(Entity entity) {
|
||||
var pdc = entity.getPersistentDataContainer();
|
||||
pdc.set(CATEGORY_KEY, PersistentDataType.STRING, category.name());
|
||||
}
|
||||
|
||||
private static boolean check(KMap<String, Boolean> cache, String key, BooleanSupplier predicate) {
|
||||
return cache.computeIfAbsent(key, k -> predicate.getAsBoolean()) == Boolean.TRUE;
|
||||
}
|
||||
|
||||
private static boolean checkCategory(Entity entity, SpawnCategory category) {
|
||||
if (entity.getSpawnCategory() == category)
|
||||
return true;
|
||||
|
||||
var pdc = entity.getPersistentDataContainer();
|
||||
if (!pdc.has(CATEGORY_KEY, PersistentDataType.STRING))
|
||||
return false;
|
||||
return category.name().equals(pdc.get(CATEGORY_KEY, PersistentDataType.STRING));
|
||||
}
|
||||
}
|
||||
@@ -74,9 +74,6 @@ public class IrisSpawner extends IrisRegistrant {
|
||||
@Desc("Where should these spawns be placed")
|
||||
private IrisSpawnGroup group = IrisSpawnGroup.NORMAL;
|
||||
|
||||
@Desc("Conditions for this spawner to be triggered")
|
||||
private IrisSpawnCondition conditions = new IrisSpawnCondition();
|
||||
|
||||
public boolean isValid(IrisBiome biome) {
|
||||
return switch (group) {
|
||||
case NORMAL -> switch (biome.getInferredType()) {
|
||||
|
||||
@@ -20,7 +20,7 @@ package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.engine.object.annotations.Desc;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.Waterlogged;
|
||||
|
||||
@Desc("The type of surface entities should spawn on")
|
||||
@@ -47,8 +47,8 @@ public enum IrisSurface {
|
||||
* @param state The blockstate
|
||||
* @return True if it matches
|
||||
*/
|
||||
public boolean matches(BlockData state) {
|
||||
Material type = state.getMaterial();
|
||||
public boolean matches(Block state) {
|
||||
Material type = state.getType();
|
||||
if (type.isSolid()) {
|
||||
return this == LAND || this == OVERWORLD || (this == ANIMAL
|
||||
&& (type == Material.GRASS_BLOCK || type == Material.DIRT
|
||||
|
||||
@@ -48,6 +48,7 @@ public class IrisWorld {
|
||||
private long seed;
|
||||
private World.Environment environment;
|
||||
private World realWorld;
|
||||
private IrisHeadless headless;
|
||||
private int minHeight;
|
||||
private int maxHeight;
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ package com.volmit.iris.engine.platform;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.pregenerator.EmptyListener;
|
||||
import com.volmit.iris.core.pregenerator.methods.HeadlessPregenMethod;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.engine.IrisEngine;
|
||||
import com.volmit.iris.engine.data.chunk.TerrainChunk;
|
||||
@@ -33,10 +34,13 @@ import com.volmit.iris.engine.object.StudioMode;
|
||||
import com.volmit.iris.engine.platform.studio.StudioGenerator;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.data.IrisBiomeStorage;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.hunk.Hunk;
|
||||
import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder;
|
||||
import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder;
|
||||
import com.volmit.iris.util.io.ReactiveFolder;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.Looper;
|
||||
@@ -49,7 +53,6 @@ import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.WorldInitEvent;
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
@@ -124,13 +127,11 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@EventHandler
|
||||
public void onWorldInit(WorldInitEvent event) {
|
||||
try {
|
||||
if (initialized || !world.name().equals(event.getWorld().getName()))
|
||||
return;
|
||||
AutoClosing.closeContext();
|
||||
INMS.get().removeCustomDimensions(event.getWorld());
|
||||
world.setRawWorldSeed(event.getWorld().getSeed());
|
||||
Engine engine = getEngine(event.getWorld());
|
||||
if (engine == null) {
|
||||
@@ -256,6 +257,10 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
}
|
||||
|
||||
private Engine getEngine(WorldInfo world) {
|
||||
return getEngine(world.getSeed());
|
||||
}
|
||||
|
||||
private Engine getEngine(long seed) {
|
||||
if (setup.get()) {
|
||||
return getEngine();
|
||||
}
|
||||
@@ -268,7 +273,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
}
|
||||
|
||||
|
||||
getWorld().setRawWorldSeed(world.getSeed());
|
||||
getWorld().setRawWorldSeed(seed);
|
||||
setupEngine();
|
||||
setup.set(true);
|
||||
this.hotloader = studio ? new Looper() {
|
||||
@@ -339,6 +344,21 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
getEngine(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareSpawnChunks(long seed, int radius) {
|
||||
if (radius < 0 || new File(world.worldFolder(), "level.dat").exists())
|
||||
return;
|
||||
var engine = getEngine(seed);
|
||||
var headless = new HeadlessPregenMethod(engine, 4);
|
||||
|
||||
for (int x = -radius; x <= radius; x++) {
|
||||
for (int z = -radius; z <= radius; z++) {
|
||||
headless.generateChunk(x, z, EmptyListener.INSTANCE);
|
||||
}
|
||||
}
|
||||
headless.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateNoise(@NotNull WorldInfo world, @NotNull Random random, int x, int z, @NotNull ChunkGenerator.ChunkData d) {
|
||||
try {
|
||||
|
||||
@@ -49,4 +49,12 @@ public interface PlatformChunkGenerator extends Hotloadable, DataProvider {
|
||||
void touch(World world);
|
||||
|
||||
CompletableFuture<Integer> getSpawnChunks();
|
||||
|
||||
void prepareSpawnChunks(long seed, int radius);
|
||||
|
||||
default int getGenerated() {
|
||||
Engine engine = getEngine();
|
||||
if (engine == null) return 0;
|
||||
return engine.getGenerated();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,340 +0,0 @@
|
||||
package com.volmit.iris.engine.service;
|
||||
|
||||
import com.google.common.util.concurrent.AtomicDouble;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.engine.IrisWorldManager;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.*;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.collection.KSet;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.math.BlockPosition;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.matter.MatterMarker;
|
||||
import com.volmit.iris.util.parallel.Sync;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import lombok.SneakyThrows;
|
||||
import org.bukkit.ChunkSnapshot;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class EngineMobHandlerSVC extends IrisEngineService {
|
||||
private static final List<String> CAVE_TAGS = List.of("cave_floor", "cave_ceiling");
|
||||
private static final int SAFE_RADIUS = 16;
|
||||
private static final int MAX_RADIUS = 128;
|
||||
|
||||
private final AtomicLong currentTick = new AtomicLong();
|
||||
private final Sync<Long> sync = new Sync<>();
|
||||
private final Set<Player> players = ConcurrentHashMap.newKeySet();
|
||||
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||
private transient KList<Entity> entities = new KList<>();
|
||||
private transient Thread asyncTicker = null;
|
||||
private transient Thread entityCollector = null;
|
||||
private transient boolean charge = false;
|
||||
private transient int task = -1;
|
||||
|
||||
public EngineMobHandlerSVC(Engine engine) {
|
||||
super(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable(boolean hotload) {
|
||||
if (running.get()) {
|
||||
running.set(false);
|
||||
cancel(asyncTicker);
|
||||
cancel(entityCollector);
|
||||
}
|
||||
|
||||
running.set(true);
|
||||
charge = hotload;
|
||||
asyncTicker = Thread.ofPlatform()
|
||||
.name("Iris Async Mob Spawning - " + engine.getWorld().name())
|
||||
.priority(9)
|
||||
.start(() -> {
|
||||
while (mayLoop()) {
|
||||
try {
|
||||
asyncTick();
|
||||
} catch (Throwable e) {
|
||||
if (isInterrupted(e))
|
||||
return;
|
||||
Iris.error("Error in async tick for " + engine.getWorld().name());
|
||||
e.printStackTrace();
|
||||
|
||||
J.sleep(100);
|
||||
}
|
||||
}
|
||||
});
|
||||
entityCollector = Thread.ofVirtual()
|
||||
.name("Iris Async Entity Collector - " + engine.getWorld().name())
|
||||
.start(() -> {
|
||||
while (mayLoop()) {
|
||||
try {
|
||||
sync.next().get();
|
||||
var world = engine.getWorld().realWorld();
|
||||
if (world == null) continue;
|
||||
J.s(() -> entities = new KList<>(world.getEntities()));
|
||||
} catch (Throwable e) {
|
||||
if (isInterrupted(e))
|
||||
return;
|
||||
|
||||
Iris.error("Error in async tick for " + engine.getWorld().name());
|
||||
e.printStackTrace();
|
||||
|
||||
J.sleep(100);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (task != -1) J.csr(task);
|
||||
task = J.sr(() -> sync.advance(currentTick.getAndIncrement()), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable(boolean hotload) {
|
||||
running.set(false);
|
||||
cancel(asyncTicker);
|
||||
cancel(entityCollector);
|
||||
if (!hotload) J.csr(task);
|
||||
}
|
||||
|
||||
private void asyncTick() throws Throwable {
|
||||
long tick = sync.next().get();
|
||||
var manager = (IrisWorldManager) engine.getWorldManager();
|
||||
if (charge) {
|
||||
manager.chargeEnergy();
|
||||
charge = false;
|
||||
}
|
||||
|
||||
var world = engine.getWorld().realWorld();
|
||||
if (world == null
|
||||
|| noSpawning()
|
||||
|| Boolean.FALSE.equals(world.getGameRuleValue(GameRule.DO_MOB_SPAWNING))
|
||||
|| players.isEmpty()
|
||||
|| manager.getEnergy() < 100)
|
||||
return;
|
||||
|
||||
var p = PrecisionStopwatch.start();
|
||||
var entities = new KList<>(this.entities);
|
||||
|
||||
var conditionCache = new KMap<UUID, KMap<String, Boolean>>();
|
||||
var data = engine.getData();
|
||||
var invalid = data.getSpawnerLoader()
|
||||
.streamAllPossible()
|
||||
.filter(Predicate.not(spawner -> spawner.canSpawn(engine)
|
||||
&& spawner.getConditions().check(conditionCache, entities)))
|
||||
.map(IrisSpawner::getLoadKey)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
var centers = players.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.filter(Player::isOnline)
|
||||
.map(Player::getLocation)
|
||||
.map(BlockPosition::fromLocation)
|
||||
.collect(KList.collector())
|
||||
.shuffle();
|
||||
|
||||
if (centers.isEmpty())
|
||||
return;
|
||||
|
||||
AtomicDouble delta = new AtomicDouble();
|
||||
int actuallySpawned = 0;
|
||||
|
||||
KMap<Position2, Pair<Entity[], ChunkSnapshot>> cache = new KMap<>();
|
||||
while (centers.isNotEmpty()) {
|
||||
var center = centers.pop();
|
||||
var spawned = trySpawn(world, invalid, cache, center, delta);
|
||||
if (spawned == 0 && p.getMilliseconds() < 1000)
|
||||
centers.add(center);
|
||||
actuallySpawned += spawned;
|
||||
}
|
||||
manager.setEnergy(manager.getEnergy() - delta.get());
|
||||
if (actuallySpawned > 0) {
|
||||
Iris.info("Async Mob Spawning " + world.getName() + " used " + delta + " energy and took " + Form.duration((long) p.getMilliseconds()));
|
||||
}
|
||||
}
|
||||
|
||||
private int trySpawn(
|
||||
World world,
|
||||
Set<String> invalid,
|
||||
KMap<Position2, Pair<Entity[], ChunkSnapshot>> cache,
|
||||
BlockPosition center,
|
||||
AtomicDouble delta
|
||||
) {
|
||||
var pos = center.randomPoint(MAX_RADIUS, SAFE_RADIUS);
|
||||
if (pos.getY() < world.getMinHeight() || pos.getY() >= world.getMaxHeight())
|
||||
return 0;
|
||||
|
||||
var chunkPos = new Position2(center.getX() >> 4, center.getZ() >> 4);
|
||||
var pair = cache.computeIfAbsent(chunkPos, cPos -> {
|
||||
try {
|
||||
return PaperLib.getChunkAtAsync(world, cPos.getX(), cPos.getZ(), false)
|
||||
.thenApply(c -> c != null ? new Pair<>(c.getEntities(), c.getChunkSnapshot(false, false, false)) : null)
|
||||
.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
if (pair == null)
|
||||
return 0;
|
||||
|
||||
var spawners = spawnersAt(pair.getB(), pos, invalid);
|
||||
spawners.removeIf(i -> !i.canSpawn(engine, chunkPos.getX(), chunkPos.getZ()));
|
||||
|
||||
if (spawners.isEmpty())
|
||||
return 0;
|
||||
|
||||
IrisPosition irisPos = new IrisPosition(pos.getX(), pos.getY(), pos.getZ());
|
||||
for (var spawner : spawners) {
|
||||
var spawns = spawner.getSpawns().copy();
|
||||
spawns.removeIf(spawn -> !spawn.check(engine, irisPos, pair.getB()));
|
||||
|
||||
var entity = IRare.pick(spawns, RNG.r.nextDouble());
|
||||
if (entity == null)
|
||||
continue;
|
||||
|
||||
entity.setReferenceSpawner(spawner);
|
||||
entity.setReferenceMarker(spawner.getReferenceMarker());
|
||||
int spawned = entity.spawn(engine, irisPos, RNG.r);
|
||||
if (spawned == 0)
|
||||
continue;
|
||||
|
||||
delta.addAndGet(spawned * ((entity.getEnergyMultiplier() * spawner.getEnergyMultiplier() * 1)));
|
||||
spawner.spawn(engine, chunkPos.getX(), chunkPos.getZ());
|
||||
if (!spawner.canSpawn(engine))
|
||||
invalid.add(spawner.getLoadKey());
|
||||
return spawned;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private KSet<IrisSpawner> spawnersAt(ChunkSnapshot chunk, BlockPosition pos, Set<String> invalid) {
|
||||
KSet<IrisSpawner> spawners = markerAt(chunk, pos, invalid);
|
||||
|
||||
var loader = engine.getData().getSpawnerLoader();
|
||||
int y = pos.getY() - engine.getWorld().minHeight();
|
||||
Stream.concat(engine.getRegion(pos.getX(), pos.getZ())
|
||||
.getEntitySpawners()
|
||||
.stream(),
|
||||
engine.getBiomeOrMantle(pos.getX(), y, pos.getZ())
|
||||
.getEntitySpawners()
|
||||
.stream())
|
||||
.filter(Predicate.not(invalid::contains))
|
||||
.map(loader::load)
|
||||
.forEach(spawners::add);
|
||||
|
||||
return spawners;
|
||||
}
|
||||
|
||||
private KSet<IrisSpawner> markerAt(ChunkSnapshot chunk, BlockPosition pos, Set<String> invalid) {
|
||||
if (!IrisSettings.get().getWorld().isMarkerEntitySpawningSystem())
|
||||
return new KSet<>();
|
||||
|
||||
int y = pos.getY() - engine.getWorld().minHeight();
|
||||
Mantle mantle = engine.getMantle().getMantle();
|
||||
MatterMarker matter = mantle.get(pos.getX(), y, pos.getZ(), MatterMarker.class);
|
||||
if (matter == null || CAVE_TAGS.contains(matter.getTag()))
|
||||
return new KSet<>();
|
||||
IrisData data = engine.getData();
|
||||
IrisMarker mark = data.getMarkerLoader().load(matter.getTag());
|
||||
if (mark == null)
|
||||
return new KSet<>();
|
||||
|
||||
if (mark.isEmptyAbove()) {
|
||||
int x = pos.getX() & 15, z = pos.getZ() & 15;
|
||||
boolean remove = chunk.getBlockData(x, pos.getY() + 1, z).getMaterial().isSolid() || chunk.getBlockData(x, pos.getY() + 2, z).getMaterial().isSolid();
|
||||
if (remove) {
|
||||
mantle.remove(pos.getX(), y, pos.getZ(), MatterMarker.class);
|
||||
return new KSet<>();
|
||||
}
|
||||
}
|
||||
|
||||
KSet<IrisSpawner> spawners = new KSet<>();
|
||||
for (String key : mark.getSpawners()) {
|
||||
if (invalid.contains(key))
|
||||
continue;
|
||||
|
||||
IrisSpawner spawner = data.getSpawnerLoader().load(key);
|
||||
if (spawner == null) {
|
||||
Iris.error("Cannot load spawner: " + key + " for marker " + matter.getTag());
|
||||
continue;
|
||||
}
|
||||
|
||||
spawner.setReferenceMarker(mark);
|
||||
spawners.add(spawner);
|
||||
}
|
||||
return spawners;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void on(PlayerJoinEvent event) {
|
||||
var player = event.getPlayer();
|
||||
if (player.getWorld() != engine.getWorld().realWorld())
|
||||
return;
|
||||
players.add(player);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void on(PlayerQuitEvent event) {
|
||||
players.remove(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void on(PlayerChangedWorldEvent event) {
|
||||
var player = event.getPlayer();
|
||||
if (player.getWorld() == engine.getWorld().realWorld())
|
||||
players.add(player);
|
||||
else
|
||||
players.remove(player);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static void cancel(Thread thread) {
|
||||
if (thread == null || !thread.isAlive()) return;
|
||||
thread.interrupt();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
private static boolean noSpawning() {
|
||||
var world = IrisSettings.get().getWorld();
|
||||
return !world.isMarkerEntitySpawningSystem() && !world.isAnbientEntitySpawningSystem();
|
||||
}
|
||||
|
||||
private boolean mayLoop() {
|
||||
return !engine.isClosed() && running.get() && !Thread.interrupted();
|
||||
}
|
||||
|
||||
private static boolean isInterrupted(Throwable e) {
|
||||
while (e != null) {
|
||||
if (e instanceof InterruptedException)
|
||||
return true;
|
||||
e = e.getCause();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.volmit.iris.engine.service;
|
||||
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisEngineService;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
|
||||
public class EngineStatusSVC extends IrisEngineService {
|
||||
private static final KList<EngineStatusSVC> INSTANCES = new KList<>();
|
||||
|
||||
public EngineStatusSVC(Engine engine) {
|
||||
super(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable(boolean hotload) {
|
||||
if (hotload) return;
|
||||
synchronized (INSTANCES) {
|
||||
INSTANCES.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable(boolean hotload) {
|
||||
if (hotload) return;
|
||||
|
||||
synchronized (INSTANCES) {
|
||||
INSTANCES.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getEngineCount() {
|
||||
return INSTANCES.size();
|
||||
}
|
||||
|
||||
public static Status getStatus() {
|
||||
synchronized (INSTANCES) {
|
||||
long loadedChunks = 0;
|
||||
long tectonicPlates = 0;
|
||||
long activeTectonicPlates = 0;
|
||||
long queuedTectonicPlates = 0;
|
||||
long minTectonicUnloadDuration = Long.MAX_VALUE;
|
||||
long maxTectonicUnloadDuration = Long.MIN_VALUE;
|
||||
|
||||
for (var service : INSTANCES) {
|
||||
var world = service.engine.getWorld();
|
||||
if (world.hasRealWorld())
|
||||
loadedChunks += world.realWorld().getLoadedChunks().length;
|
||||
|
||||
tectonicPlates += service.engine.getMantle().getLoadedRegionCount();
|
||||
activeTectonicPlates += service.engine.getMantle().getNotQueuedLoadedRegions();
|
||||
queuedTectonicPlates += service.engine.getMantle().getToUnload();
|
||||
minTectonicUnloadDuration = Math.min(minTectonicUnloadDuration, (long) service.engine.getMantle().getTectonicDuration());
|
||||
maxTectonicUnloadDuration = Math.max(maxTectonicUnloadDuration, (long) service.engine.getMantle().getTectonicDuration());
|
||||
}
|
||||
return new Status(INSTANCES.size(), loadedChunks, MantleCleanerSVC.getTectonicLimit(), tectonicPlates, activeTectonicPlates, queuedTectonicPlates, minTectonicUnloadDuration, maxTectonicUnloadDuration);
|
||||
}
|
||||
}
|
||||
|
||||
public record Status(int engineCount, long loadedChunks, int tectonicLimit,
|
||||
long tectonicPlates, long activeTectonicPlates,
|
||||
long queuedTectonicPlates,
|
||||
long minTectonicUnloadDuration,
|
||||
long maxTectonicUnloadDuration) {
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package com.volmit.iris.engine.service;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisEngineService;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.misc.getHardware;
|
||||
import com.volmit.iris.util.scheduling.Looper;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
public class MantleCleanerSVC extends IrisEngineService {
|
||||
private static final AtomicInteger tectonicLimit = new AtomicInteger(30);
|
||||
|
||||
static {
|
||||
// todo: Redo this
|
||||
tectonicLimit.set(2);
|
||||
long t = getHardware.getProcessMemory();
|
||||
while (t > 200) {
|
||||
tectonicLimit.incrementAndGet();
|
||||
t = t - 200;
|
||||
}
|
||||
}
|
||||
|
||||
private Ticker trimmer;
|
||||
private Ticker unloader;
|
||||
|
||||
public MantleCleanerSVC(Engine engine) {
|
||||
super(engine);
|
||||
}
|
||||
|
||||
public static int getTectonicLimit() {
|
||||
return tectonicLimit.get();
|
||||
}
|
||||
|
||||
private static Ticker createTrimmer(Engine engine) {
|
||||
return new Ticker(() -> {
|
||||
if (engine.isClosed()) return -1;
|
||||
long start = M.ms();
|
||||
try {
|
||||
engine.getMantle().trim(tectonicLimit.get() / getEngineCount());
|
||||
} catch (Throwable e) {
|
||||
Iris.debug(C.RED + "Mantle: Failed to trim.");
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (engine.isClosed()) return -1;
|
||||
int size = getEngineCount();
|
||||
return Math.max(1000 / size - (M.ms() - start), 0);
|
||||
}, "Iris Mantle Trimmer - " + engine.getWorld().name());
|
||||
}
|
||||
|
||||
private static Ticker createUnloader(Engine engine) {
|
||||
return new Ticker(() -> {
|
||||
if (engine.isClosed()) return -1;
|
||||
long start = M.ms();
|
||||
try {
|
||||
engine.getMantle().unloadTectonicPlate(tectonicLimit.get() / getEngineCount());
|
||||
} catch (Throwable e) {
|
||||
Iris.debug(C.RED + "Mantle: Failed to unload.");
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (engine.isClosed()) return -1;
|
||||
int size = getEngineCount();
|
||||
return Math.max(1000 / size - (M.ms() - start), 0);
|
||||
}, "Iris Mantle Unloader - " + engine.getWorld().name());
|
||||
}
|
||||
|
||||
private static int getEngineCount() {
|
||||
return Math.max(EngineStatusSVC.getEngineCount(), 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable(boolean hotload) {
|
||||
if (engine.isStudio() && !IrisSettings.get().getPerformance().trimMantleInStudio)
|
||||
return;
|
||||
if (trimmer == null || !trimmer.isAlive())
|
||||
trimmer = createTrimmer(engine);
|
||||
if (unloader == null || !unloader.isAlive())
|
||||
unloader = createUnloader(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable(boolean hotload) {
|
||||
if (hotload) return;
|
||||
if (trimmer != null) trimmer.await();
|
||||
if (unloader != null) unloader.await();
|
||||
}
|
||||
|
||||
private static class Ticker extends Looper {
|
||||
private final LongSupplier supplier;
|
||||
|
||||
private Ticker(LongSupplier supplier, String name) {
|
||||
this.supplier = supplier;
|
||||
setPriority(Thread.MIN_PRIORITY);
|
||||
setName(name);
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long loop() {
|
||||
try {
|
||||
return supplier.getAsLong();
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Exception in Looper " + getName());
|
||||
StringWriter sw = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(sw));
|
||||
Iris.error(sw.toString());
|
||||
return 3000;
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void await() {
|
||||
join();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,6 @@ import com.volmit.iris.util.math.RNG;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("ALL")
|
||||
public class KList<T> extends ArrayList<T> implements List<T> {
|
||||
@@ -67,10 +65,6 @@ public class KList<T> extends ArrayList<T> implements List<T> {
|
||||
return s;
|
||||
}
|
||||
|
||||
public static <T> Collector<T, ?, KList<T>> collector() {
|
||||
return Collectors.toCollection(KList::new);
|
||||
}
|
||||
|
||||
public static KList<String> asStringList(List<?> oo) {
|
||||
KList<String> s = new KList<String>();
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.volmit.iris.util.hunk.view;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.util.hunk.storage.ArrayHunk;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
|
||||
public class SyncChunkDataHunkHolder extends ArrayHunk<BlockData> {
|
||||
private static final BlockData AIR = Material.AIR.createBlockData();
|
||||
private final ChunkGenerator.ChunkData chunk;
|
||||
private final Thread mainThread = Thread.currentThread();
|
||||
|
||||
public SyncChunkDataHunkHolder(ChunkGenerator.ChunkData chunk) {
|
||||
super(16, chunk.getMaxHeight() - chunk.getMinHeight(), 16);
|
||||
this.chunk = chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRaw(int x, int y, int z, BlockData data) {
|
||||
if (Thread.currentThread() != mainThread)
|
||||
Iris.warn("SyncChunkDataHunkHolder is not on the main thread");
|
||||
super.setRaw(x, y, z, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockData getRaw(int x, int y, int z) {
|
||||
if (Thread.currentThread() != mainThread)
|
||||
Iris.warn("SyncChunkDataHunkHolder is not on the main thread");
|
||||
BlockData b = super.getRaw(x, y, z);
|
||||
|
||||
return b != null ? b : AIR;
|
||||
}
|
||||
|
||||
public void apply() {
|
||||
for (int i = getHeight()-1; i >= 0; i--) {
|
||||
for (int j = 0; j < getWidth(); j++) {
|
||||
for (int k = 0; k < getDepth(); k++) {
|
||||
BlockData b = super.getRaw(j, i, k);
|
||||
|
||||
if (b != null) {
|
||||
chunk.setBlock(j, i + chunk.getMinHeight(), k, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package com.volmit.iris.util.mantle;
|
||||
import com.google.common.util.concurrent.AtomicDouble;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.service.IrisEngineSVC;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
@@ -424,7 +425,7 @@ public class Mantle {
|
||||
ioTrim.set(true);
|
||||
unloadLock.lock();
|
||||
try {
|
||||
if (lastUse != null) {
|
||||
if (lastUse != null && IrisEngineSVC.instance != null) {
|
||||
if (!lastUse.isEmpty()) {
|
||||
Iris.debug("Trimming Tectonic Plates older than " + Form.duration(adjustedIdleDuration.get(), 0));
|
||||
for (long i : new ArrayList<>(lastUse.keySet())) {
|
||||
@@ -434,6 +435,7 @@ public class Mantle {
|
||||
if (lastUseTime != null && M.ms() - lastUseTime >= finalAdjustedIdleDuration) {
|
||||
toUnload.add(i);
|
||||
Iris.debug("Tectonic Region added to unload");
|
||||
IrisEngineSVC.instance.trimActiveAlive.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -452,49 +454,53 @@ public class Mantle {
|
||||
AtomicInteger i = new AtomicInteger();
|
||||
unloadLock.lock();
|
||||
BurstExecutor burst = null;
|
||||
try {
|
||||
KList<Long> copy = toUnload.copy();
|
||||
if (!disableClear) toUnload.clear();
|
||||
burst = MultiBurst.burst.burst(copy.size());
|
||||
burst.setMulticore(copy.size() > tectonicLimit);
|
||||
for (int j = 0; j < copy.size(); j++) {
|
||||
Long id = copy.get(j);
|
||||
if (id == null) {
|
||||
Iris.error("Null id in unloadTectonicPlate at index " + j);
|
||||
continue;
|
||||
}
|
||||
if (IrisEngineSVC.instance != null) {
|
||||
try {
|
||||
KList<Long> copy = toUnload.copy();
|
||||
if (!disableClear) toUnload.clear();
|
||||
burst = MultiBurst.burst.burst(copy.size());
|
||||
burst.setMulticore(copy.size() > tectonicLimit);
|
||||
for (int j = 0; j < copy.size(); j++) {
|
||||
Long id = copy.get(j);
|
||||
if (id == null) {
|
||||
Iris.error("Null id in unloadTectonicPlate at index " + j);
|
||||
continue;
|
||||
}
|
||||
|
||||
burst.queue(() ->
|
||||
hyperLock.withLong(id, () -> {
|
||||
TectonicPlate m = loadedRegions.get(id);
|
||||
if (m != null) {
|
||||
if (m.inUse()) {
|
||||
Iris.debug("Tectonic Plate was added to unload while in use " + C.DARK_GREEN + m.getX() + " " + m.getZ());
|
||||
if (disableClear) toUnload.remove(id);
|
||||
lastUse.put(id, M.ms());
|
||||
return;
|
||||
burst.queue(() ->
|
||||
hyperLock.withLong(id, () -> {
|
||||
TectonicPlate m = loadedRegions.get(id);
|
||||
if (m != null) {
|
||||
if (m.inUse()) {
|
||||
Iris.debug("Tectonic Plate was added to unload while in use " + C.DARK_GREEN + m.getX() + " " + m.getZ());
|
||||
if (disableClear) toUnload.remove(id);
|
||||
lastUse.put(id, M.ms());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
m.write(fileForRegion(dataFolder, id));
|
||||
loadedRegions.remove(id);
|
||||
lastUse.remove(id);
|
||||
if (disableClear) toUnload.remove(id);
|
||||
i.incrementAndGet();
|
||||
Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id));
|
||||
IrisEngineSVC.instance.unloadActiveAlive.reset();
|
||||
} catch (IOException e) {
|
||||
Iris.reportError(e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
m.write(fileForRegion(dataFolder, id));
|
||||
loadedRegions.remove(id);
|
||||
lastUse.remove(id);
|
||||
if (disableClear) toUnload.remove(id);
|
||||
i.incrementAndGet();
|
||||
Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id));
|
||||
} catch (IOException e) {
|
||||
Iris.reportError(e);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
burst.complete();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
if (burst != null)
|
||||
}));
|
||||
}
|
||||
burst.complete();
|
||||
} finally {
|
||||
unloadLock.unlock();
|
||||
ioTectonicUnload.set(true);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
if (burst != null)
|
||||
burst.complete();
|
||||
} finally {
|
||||
unloadLock.unlock();
|
||||
ioTectonicUnload.set(true);
|
||||
}
|
||||
return i.get();
|
||||
}
|
||||
return i.get();
|
||||
}
|
||||
|
||||
@@ -19,14 +19,11 @@
|
||||
package com.volmit.iris.util.math;
|
||||
|
||||
import lombok.Data;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.volmit.iris.util.math.RNG.r;
|
||||
|
||||
@Data
|
||||
public class BlockPosition {
|
||||
//Magic numbers
|
||||
@@ -46,10 +43,6 @@ public class BlockPosition {
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public static BlockPosition fromLocation(Location loc) {
|
||||
return new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
||||
}
|
||||
|
||||
public static long toLong(int x, int y, int z) {
|
||||
long var3 = 0L;
|
||||
var3 |= (x & m4) << m3;
|
||||
@@ -115,18 +108,4 @@ public class BlockPosition {
|
||||
setY(Math.max(i.getY(), getY()));
|
||||
setZ(Math.max(i.getZ(), getZ()));
|
||||
}
|
||||
|
||||
public BlockPosition randomPoint(int radius, int innerRadius) {
|
||||
int max = radius * radius;
|
||||
int min = innerRadius * innerRadius;
|
||||
|
||||
while (true) {
|
||||
int x = r.nextInt(-radius, radius + 1);
|
||||
int y = r.nextInt(-radius, radius + 1);
|
||||
int z = r.nextInt(-radius, radius + 1);
|
||||
double dist = x * x + y * y + z * z;
|
||||
if (dist < min || dist > max) continue;
|
||||
return add(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ package com.volmit.iris.util.math;
|
||||
import com.volmit.iris.engine.object.IrisPosition;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class Position2 {
|
||||
private int x;
|
||||
private int z;
|
||||
@@ -94,4 +96,8 @@ public class Position2 {
|
||||
public IrisPosition toIris() {
|
||||
return new IrisPosition(x, 23, z);
|
||||
}
|
||||
|
||||
public <T> T convert(BiFunction<Integer, Integer, T> constructor) {
|
||||
return constructor.apply(x, z);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.volmit.iris.util.parallel;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class Sync<T> {
|
||||
private final AtomicReference<CompletableFuture<T>> tick = new AtomicReference<>(new CompletableFuture<>());
|
||||
|
||||
public void cancel(boolean mayInterruptIfRunning) {
|
||||
tick.getAndSet(new CompletableFuture<>()).cancel(mayInterruptIfRunning);
|
||||
}
|
||||
|
||||
public CompletableFuture<T> next() {
|
||||
return tick.get();
|
||||
}
|
||||
|
||||
public void advance(T value) {
|
||||
tick.getAndSet(new CompletableFuture<>()).complete(value);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMSBinding;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.v1_20_R1.headless.RegionStorage;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
@@ -645,44 +647,42 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public IRegionStorage createRegionStorage(Engine engine) {
|
||||
return new RegionStorage(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
return inject(this::supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
public com.volmit.iris.core.nms.container.Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
|
||||
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
|
||||
)
|
||||
);
|
||||
|
||||
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
return new com.volmit.iris.core.nms.container.Pair<>(
|
||||
injected.size(),
|
||||
new AutoClosing(() -> {
|
||||
closing.close();
|
||||
field.set(reg, old);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -694,9 +694,31 @@ public class NMSBinding implements INMSBinding {
|
||||
return overworld || nether || end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().K.customDimensions = null;
|
||||
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
|
||||
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, transformer.apply(old));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
@@ -720,7 +742,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R1.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.BiomeBaseInjector;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.data.IrisCustomData;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import lombok.Data;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.util.CraftMagicNumbers;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.volmit.iris.core.nms.v1_20_R1.headless.RegionStorage.registryAccess;
|
||||
|
||||
@Data
|
||||
public final class DirectTerrainChunk implements SerializableChunk {
|
||||
private final ProtoChunk access;
|
||||
private final int minHeight, maxHeight;
|
||||
private final Registry<net.minecraft.world.level.biome.Biome> biomes;
|
||||
|
||||
public DirectTerrainChunk(ProtoChunk access) {
|
||||
this.access = access;
|
||||
this.minHeight = access.getMinBuildHeight();
|
||||
this.maxHeight = access.getMaxBuildHeight();
|
||||
this.biomes = registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeBaseInjector getBiomeBaseInjector() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int z) {
|
||||
return getBiome(x, 0, z);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
|
||||
return CraftBlock.biomeBaseToBiome(biomes, access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int z, Biome bio) {
|
||||
for (int y = minHeight; y < maxHeight; y += 4) {
|
||||
setBiome(x, y, z, bio);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int y, int z, Biome bio) {
|
||||
if (y < minHeight || y > maxHeight) return;
|
||||
access.setBiome(x & 15, y, z & 15, CraftBlock.biomeToBiomeBase(biomes, bio));
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, Material material) {
|
||||
this.setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, MaterialData material) {
|
||||
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, BlockData blockData) {
|
||||
if (blockData == null) {
|
||||
Iris.error("NULL BD");
|
||||
}
|
||||
if (blockData instanceof IrisCustomData data)
|
||||
blockData = data.getBase();
|
||||
if (!(blockData instanceof CraftBlockData craftBlockData))
|
||||
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
|
||||
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGenerator.ChunkData getRaw() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRaw(ChunkGenerator.ChunkData data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inject(ChunkGenerator.BiomeGrid biome) {
|
||||
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
|
||||
return;
|
||||
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
|
||||
return;
|
||||
|
||||
for (int y = yMin; y < yMax; ++y) {
|
||||
for (int x = xMin; x < xMax; ++x) {
|
||||
for (int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public BlockState getTypeId(int x, int y, int z) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
}
|
||||
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return;
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position2 getPos() {
|
||||
return new Position2(access.getPos().x, access.getPos().z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize() {
|
||||
return RegionStorage.serialize(access);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark() {
|
||||
access.setStatus(ChunkStatus.FULL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R1.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import lombok.NonNull;
|
||||
import lombok.Synchronized;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class Region implements IRegion, Comparable<Region> {
|
||||
private final RegionFile regionFile;
|
||||
transient long references;
|
||||
transient long lastUsed;
|
||||
|
||||
Region(Path path, Path folder) throws IOException {
|
||||
this.regionFile = new RegionFile(path, folder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public boolean exists(int x, int z) {
|
||||
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
|
||||
if (din == null) return false;
|
||||
return !"empty".equals(NbtIo.read(din).getString("Status"));
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public void write(@NonNull SerializableChunk chunk) throws IOException {
|
||||
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
|
||||
NbtIo.write((CompoundTag) chunk.serialize(), dos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
--references;
|
||||
lastUsed = M.ms();
|
||||
}
|
||||
|
||||
public boolean unused() {
|
||||
return references <= 0;
|
||||
}
|
||||
|
||||
public boolean remove() {
|
||||
if (!unused()) return false;
|
||||
try {
|
||||
regionFile.close();
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to close region file");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Region o) {
|
||||
return Long.compare(lastUsed, o.lastUsed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R1.headless;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.context.ChunkContext;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import net.minecraft.FileUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
import net.minecraft.world.level.chunk.*;
|
||||
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
|
||||
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.packOffsets;
|
||||
|
||||
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
|
||||
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
|
||||
private final KMap<Long, Region> regions = new KMap<>();
|
||||
private final Path folder;
|
||||
|
||||
private final Engine engine;
|
||||
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
|
||||
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
|
||||
private final RNG biomeRng;
|
||||
private final @Getter int minBuildHeight;
|
||||
private final @Getter int height;
|
||||
|
||||
private transient boolean closed = false;
|
||||
|
||||
public RegionStorage(Engine engine) {
|
||||
this.engine = engine;
|
||||
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
|
||||
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
|
||||
|
||||
this.minBuildHeight = engine.getDimension().getMinHeight();
|
||||
this.height = engine.getDimension().getMaxHeight() - minBuildHeight;
|
||||
|
||||
AtomicInteger failed = new AtomicInteger();
|
||||
var dimKey = engine.getDimension().getLoadKey();
|
||||
for (var biome : engine.getAllBiomes()) {
|
||||
if (!biome.isCustom()) continue;
|
||||
for (var custom : biome.getCustomDerivitives()) {
|
||||
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
|
||||
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
|
||||
failed.incrementAndGet();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (failed.get() > 0) {
|
||||
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
|
||||
}
|
||||
|
||||
Registry<Biome> registry = registryAccess().registryOrThrow(Registries.BIOME);
|
||||
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
|
||||
.filter(biome -> biome != org.bukkit.block.Biome.CUSTOM)
|
||||
.collect(Collectors.toMap(Function.identity(), b -> CraftBlock.biomeToBiomeBase(registry, b))));
|
||||
minecraftBiomes.values().removeAll(customBiomes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int x, int z) {
|
||||
try (IRegion region = getRegion(x >> 5, z >> 5, true)) {
|
||||
return region != null && region.exists(x, z);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
|
||||
AtomicReference<IOException> exception = new AtomicReference<>();
|
||||
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
|
||||
trim();
|
||||
|
||||
try {
|
||||
FileUtil.createDirectoriesSafe(this.folder);
|
||||
Path path = folder.resolve("r." + x + "." + z + ".mca");
|
||||
if (existingOnly && !Files.exists(path)) {
|
||||
return null;
|
||||
} else {
|
||||
return new Region(path, this.folder);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception.set(e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
if (region == null) {
|
||||
if (exception.get() != null)
|
||||
throw exception.get();
|
||||
return null;
|
||||
}
|
||||
region.references++;
|
||||
return region;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SerializableChunk createChunk(int x, int z) {
|
||||
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().registryOrThrow(Registries.BIOME), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
|
||||
if (!(chunk instanceof DirectTerrainChunk tc))
|
||||
return;
|
||||
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (closed) return;
|
||||
|
||||
while (!regions.isEmpty()) {
|
||||
regions.values().removeIf(Region::remove);
|
||||
J.sleep(1);
|
||||
}
|
||||
|
||||
closed = true;
|
||||
customBiomes.clear();
|
||||
minecraftBiomes.clear();
|
||||
}
|
||||
|
||||
private void trim() {
|
||||
int size = regions.size();
|
||||
if (size < 256) return;
|
||||
int remove = size - 255;
|
||||
|
||||
var list = regions.values()
|
||||
.stream()
|
||||
.filter(Region::unused)
|
||||
.sorted()
|
||||
.collect(Collectors.toList())
|
||||
.reversed();
|
||||
|
||||
int skip = list.size() - remove;
|
||||
if (skip > 0) list.subList(0, skip).clear();
|
||||
|
||||
if (list.isEmpty()) return;
|
||||
regions.values().removeIf(r -> list.contains(r) && r.remove());
|
||||
}
|
||||
|
||||
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
|
||||
int m = y - engine.getMinHeight();
|
||||
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
|
||||
if (ib.isCustom()) {
|
||||
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
|
||||
} else {
|
||||
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
|
||||
}
|
||||
}
|
||||
|
||||
static RegistryAccess registryAccess() {
|
||||
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
|
||||
}
|
||||
|
||||
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
|
||||
return registryAccess().registryOrThrow(Registries.BIOME).getHolder(ResourceKey.create(Registries.BIOME, new ResourceLocation(namespace, path)));
|
||||
}
|
||||
|
||||
static CompoundTag serialize(ChunkAccess chunk) {
|
||||
ChunkPos chunkPos = chunk.getPos();
|
||||
CompoundTag tag = NbtUtils.addCurrentDataVersion(new CompoundTag());
|
||||
tag.putInt("xPos", chunkPos.x);
|
||||
tag.putInt("yPos", chunk.getMinSection());
|
||||
tag.putInt("zPos", chunkPos.z);
|
||||
tag.putLong("LastUpdate", 0);
|
||||
tag.putLong("InhabitedTime", chunk.getInhabitedTime());
|
||||
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
|
||||
BlendingData blendingdata = chunk.getBlendingData();
|
||||
if (blendingdata != null) {
|
||||
DataResult<Tag> dataresult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
|
||||
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("blending_data", nbt));
|
||||
}
|
||||
|
||||
BelowZeroRetrogen retrogen = chunk.getBelowZeroRetrogen();
|
||||
if (retrogen != null) {
|
||||
DataResult<Tag> dataresult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, retrogen);
|
||||
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("below_zero_retrogen", nbt));
|
||||
}
|
||||
|
||||
UpgradeData upgradeData = chunk.getUpgradeData();
|
||||
if (!upgradeData.isEmpty()) {
|
||||
tag.put("UpgradeData", upgradeData.write());
|
||||
}
|
||||
|
||||
LevelChunkSection[] sections = chunk.getSections();
|
||||
ListTag sectionsTag = new ListTag();
|
||||
Registry<Biome> biomeRegistry = registryAccess().registryOrThrow(Registries.BIOME);
|
||||
Codec<PalettedContainerRO<Holder<Biome>>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
|
||||
boolean flag = chunk.isLightCorrect();
|
||||
|
||||
int minLightSection = chunk.getMinSection() - 1;
|
||||
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
|
||||
for(int y = minLightSection; y < maxLightSection; ++y) {
|
||||
int j = chunk.getSectionIndexFromSectionY(y);
|
||||
if (j < 0 || j >= sections.length)
|
||||
continue;
|
||||
CompoundTag sectionTag = new CompoundTag();
|
||||
LevelChunkSection section = sections[j];
|
||||
sectionTag.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow(false, LogUtils.getLogger()::error));
|
||||
sectionTag.put("biomes", codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow(false, LogUtils.getLogger()::error));
|
||||
|
||||
if (!sectionTag.isEmpty()) {
|
||||
sectionTag.putByte("Y", (byte) y);
|
||||
sectionsTag.add(sectionTag);
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("sections", sectionsTag);
|
||||
if (flag) {
|
||||
tag.putBoolean("isLightOn", true);
|
||||
}
|
||||
|
||||
ListTag blockEntities = new ListTag();
|
||||
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
|
||||
CompoundTag entityNbt = chunk.getBlockEntityNbtForSaving(blockPos);
|
||||
if (entityNbt != null) {
|
||||
blockEntities.add(entityNbt);
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("block_entities", blockEntities);
|
||||
if (chunk.getStatus().getChunkType() == ChunkStatus.ChunkType.PROTOCHUNK) {
|
||||
ProtoChunk protochunk = (ProtoChunk)chunk;
|
||||
ListTag entities = new ListTag();
|
||||
entities.addAll(protochunk.getEntities());
|
||||
tag.put("entities", entities);
|
||||
CompoundTag carvingMasks = new CompoundTag();
|
||||
|
||||
for(GenerationStep.Carving carving : GenerationStep.Carving.values()) {
|
||||
CarvingMask mask = protochunk.getCarvingMask(carving);
|
||||
if (mask != null) {
|
||||
carvingMasks.putLongArray(carving.toString(), mask.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("CarvingMasks", carvingMasks);
|
||||
}
|
||||
|
||||
saveTicks(tag, chunk.getTicksForSerialization());
|
||||
tag.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
|
||||
CompoundTag heightMaps = new CompoundTag();
|
||||
|
||||
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
|
||||
if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) {
|
||||
heightMaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("Heightmaps", heightMaps);
|
||||
|
||||
CompoundTag structureData = new CompoundTag();
|
||||
structureData.put("starts", new CompoundTag());
|
||||
structureData.put("References", new CompoundTag());
|
||||
tag.put("structures", structureData);
|
||||
if (!chunk.persistentDataContainer.isEmpty()) {
|
||||
tag.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
private static void saveTicks(CompoundTag tag, ChunkAccess.TicksToSave ticks) {
|
||||
tag.put("block_ticks", ticks.blocks().save(0, (block) -> BuiltInRegistries.BLOCK.getKey(block).toString()));
|
||||
tag.put("fluid_ticks", ticks.fluids().save(0, (fluid) -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.v1_20_R2.headless.RegionStorage;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.SneakyThrows;
|
||||
import net.minecraft.core.*;
|
||||
@@ -646,44 +648,42 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public IRegionStorage createRegionStorage(Engine engine) {
|
||||
return new RegionStorage(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
return inject(this::supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
public com.volmit.iris.core.nms.container.Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
|
||||
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
|
||||
)
|
||||
);
|
||||
|
||||
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
return new com.volmit.iris.core.nms.container.Pair<>(
|
||||
injected.size(),
|
||||
new AutoClosing(() -> {
|
||||
closing.close();
|
||||
field.set(reg, old);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -695,9 +695,31 @@ public class NMSBinding implements INMSBinding {
|
||||
return overworld || nether || end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().K.customDimensions = null;
|
||||
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
|
||||
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, transformer.apply(old));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
@@ -721,7 +743,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R2.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.BiomeBaseInjector;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.data.IrisCustomData;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import lombok.Data;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.util.CraftMagicNumbers;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Data
|
||||
public final class DirectTerrainChunk implements SerializableChunk {
|
||||
private final ProtoChunk access;
|
||||
private final int minHeight, maxHeight;
|
||||
|
||||
public DirectTerrainChunk(ProtoChunk access) {
|
||||
this.access = access;
|
||||
this.minHeight = access.getMinBuildHeight();
|
||||
this.maxHeight = access.getMaxBuildHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeBaseInjector getBiomeBaseInjector() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int z) {
|
||||
return getBiome(x, 0, z);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
|
||||
return CraftBiome.minecraftHolderToBukkit(access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int z, Biome bio) {
|
||||
for (int y = minHeight; y < maxHeight; y += 4) {
|
||||
setBiome(x, y, z, bio);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int y, int z, Biome bio) {
|
||||
if (y < minHeight || y > maxHeight) return;
|
||||
access.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, Material material) {
|
||||
this.setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, MaterialData material) {
|
||||
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, BlockData blockData) {
|
||||
if (blockData == null) {
|
||||
Iris.error("NULL BD");
|
||||
}
|
||||
if (blockData instanceof IrisCustomData data)
|
||||
blockData = data.getBase();
|
||||
if (!(blockData instanceof CraftBlockData craftBlockData))
|
||||
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
|
||||
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGenerator.ChunkData getRaw() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRaw(ChunkGenerator.ChunkData data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inject(ChunkGenerator.BiomeGrid biome) {
|
||||
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
|
||||
return;
|
||||
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
|
||||
return;
|
||||
|
||||
for (int y = yMin; y < yMax; ++y) {
|
||||
for (int x = xMin; x < xMax; ++x) {
|
||||
for (int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public BlockState getTypeId(int x, int y, int z) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
}
|
||||
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return;
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position2 getPos() {
|
||||
return new Position2(access.getPos().x, access.getPos().z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize() {
|
||||
return RegionStorage.serialize(access);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark() {
|
||||
access.setStatus(ChunkStatus.FULL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R2.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import lombok.NonNull;
|
||||
import lombok.Synchronized;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class Region implements IRegion, Comparable<Region> {
|
||||
private final RegionFile regionFile;
|
||||
transient long references;
|
||||
transient long lastUsed;
|
||||
|
||||
Region(Path path, Path folder) throws IOException {
|
||||
this.regionFile = new RegionFile(path, folder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public boolean exists(int x, int z) {
|
||||
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
|
||||
if (din == null) return false;
|
||||
return !"empty".equals(NbtIo.read(din).getString("Status"));
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public void write(@NonNull SerializableChunk chunk) throws IOException {
|
||||
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
|
||||
NbtIo.write((CompoundTag) chunk.serialize(), dos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
--references;
|
||||
lastUsed = M.ms();
|
||||
}
|
||||
|
||||
public boolean unused() {
|
||||
return references <= 0;
|
||||
}
|
||||
|
||||
public boolean remove() {
|
||||
if (!unused()) return false;
|
||||
try {
|
||||
regionFile.close();
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to close region file");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Region o) {
|
||||
return Long.compare(lastUsed, o.lastUsed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R2.headless;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.context.ChunkContext;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import net.minecraft.FileUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
import net.minecraft.world.level.chunk.*;
|
||||
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.block.CraftBiome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
|
||||
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.packOffsets;
|
||||
|
||||
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
|
||||
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
|
||||
private final KMap<Long, Region> regions = new KMap<>();
|
||||
private final Path folder;
|
||||
|
||||
private final Engine engine;
|
||||
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
|
||||
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
|
||||
private final RNG biomeRng;
|
||||
private final @Getter int minBuildHeight;
|
||||
private final @Getter int height;
|
||||
|
||||
private transient boolean closed = false;
|
||||
|
||||
public RegionStorage(Engine engine) {
|
||||
this.engine = engine;
|
||||
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
|
||||
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
|
||||
|
||||
this.minBuildHeight = engine.getDimension().getMinHeight();
|
||||
this.height = engine.getDimension().getMaxHeight() - minBuildHeight;
|
||||
|
||||
AtomicInteger failed = new AtomicInteger();
|
||||
var dimKey = engine.getDimension().getLoadKey();
|
||||
for (var biome : engine.getAllBiomes()) {
|
||||
if (!biome.isCustom()) continue;
|
||||
for (var custom : biome.getCustomDerivitives()) {
|
||||
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
|
||||
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
|
||||
failed.incrementAndGet();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (failed.get() > 0) {
|
||||
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
|
||||
}
|
||||
|
||||
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
|
||||
.filter(biome -> biome != org.bukkit.block.Biome.CUSTOM)
|
||||
.collect(Collectors.toMap(Function.identity(), CraftBiome::bukkitToMinecraftHolder)));
|
||||
minecraftBiomes.values().removeAll(customBiomes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int x, int z) {
|
||||
try (IRegion region = getRegion(x >> 5, z >> 5, true)) {
|
||||
return region != null && region.exists(x, z);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
|
||||
AtomicReference<IOException> exception = new AtomicReference<>();
|
||||
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
|
||||
trim();
|
||||
|
||||
try {
|
||||
FileUtil.createDirectoriesSafe(this.folder);
|
||||
Path path = folder.resolve("r." + x + "." + z + ".mca");
|
||||
if (existingOnly && !Files.exists(path)) {
|
||||
return null;
|
||||
} else {
|
||||
return new Region(path, this.folder);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception.set(e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
if (region == null) {
|
||||
if (exception.get() != null)
|
||||
throw exception.get();
|
||||
return null;
|
||||
}
|
||||
region.references++;
|
||||
return region;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SerializableChunk createChunk(int x, int z) {
|
||||
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().registryOrThrow(Registries.BIOME), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
|
||||
if (!(chunk instanceof DirectTerrainChunk tc))
|
||||
return;
|
||||
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (closed) return;
|
||||
|
||||
while (!regions.isEmpty()) {
|
||||
regions.values().removeIf(Region::remove);
|
||||
J.sleep(1);
|
||||
}
|
||||
|
||||
closed = true;
|
||||
customBiomes.clear();
|
||||
minecraftBiomes.clear();
|
||||
}
|
||||
|
||||
private void trim() {
|
||||
int size = regions.size();
|
||||
if (size < 256) return;
|
||||
int remove = size - 255;
|
||||
|
||||
var list = regions.values()
|
||||
.stream()
|
||||
.filter(Region::unused)
|
||||
.sorted()
|
||||
.collect(Collectors.toList())
|
||||
.reversed();
|
||||
|
||||
int skip = list.size() - remove;
|
||||
if (skip > 0) list.subList(0, skip).clear();
|
||||
|
||||
if (list.isEmpty()) return;
|
||||
regions.values().removeIf(r -> list.contains(r) && r.remove());
|
||||
}
|
||||
|
||||
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
|
||||
int m = y - engine.getMinHeight();
|
||||
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
|
||||
if (ib.isCustom()) {
|
||||
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
|
||||
} else {
|
||||
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
|
||||
}
|
||||
}
|
||||
|
||||
private static RegistryAccess registryAccess() {
|
||||
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
|
||||
}
|
||||
|
||||
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
|
||||
return registryAccess().registryOrThrow(Registries.BIOME).getHolder(ResourceKey.create(Registries.BIOME, new ResourceLocation(namespace, path)));
|
||||
}
|
||||
|
||||
static CompoundTag serialize(ChunkAccess chunk) {
|
||||
ChunkPos chunkPos = chunk.getPos();
|
||||
CompoundTag tag = NbtUtils.addCurrentDataVersion(new CompoundTag());
|
||||
tag.putInt("xPos", chunkPos.x);
|
||||
tag.putInt("yPos", chunk.getMinSection());
|
||||
tag.putInt("zPos", chunkPos.z);
|
||||
tag.putLong("LastUpdate", 0);
|
||||
tag.putLong("InhabitedTime", chunk.getInhabitedTime());
|
||||
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
|
||||
BlendingData blendingdata = chunk.getBlendingData();
|
||||
if (blendingdata != null) {
|
||||
DataResult<Tag> dataresult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
|
||||
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("blending_data", nbt));
|
||||
}
|
||||
|
||||
BelowZeroRetrogen retrogen = chunk.getBelowZeroRetrogen();
|
||||
if (retrogen != null) {
|
||||
DataResult<Tag> dataresult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, retrogen);
|
||||
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("below_zero_retrogen", nbt));
|
||||
}
|
||||
|
||||
UpgradeData upgradeData = chunk.getUpgradeData();
|
||||
if (!upgradeData.isEmpty()) {
|
||||
tag.put("UpgradeData", upgradeData.write());
|
||||
}
|
||||
|
||||
LevelChunkSection[] sections = chunk.getSections();
|
||||
ListTag sectionsTag = new ListTag();
|
||||
Registry<Biome> biomeRegistry = registryAccess().registryOrThrow(Registries.BIOME);
|
||||
Codec<PalettedContainerRO<Holder<Biome>>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
|
||||
boolean flag = chunk.isLightCorrect();
|
||||
|
||||
int minLightSection = chunk.getMinSection() - 1;
|
||||
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
|
||||
for(int y = minLightSection; y < maxLightSection; ++y) {
|
||||
int j = chunk.getSectionIndexFromSectionY(y);
|
||||
if (j < 0 || j >= sections.length)
|
||||
continue;
|
||||
CompoundTag sectionTag = new CompoundTag();
|
||||
LevelChunkSection section = sections[j];
|
||||
sectionTag.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow(false, LogUtils.getLogger()::error));
|
||||
sectionTag.put("biomes", codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow(false, LogUtils.getLogger()::error));
|
||||
|
||||
if (!sectionTag.isEmpty()) {
|
||||
sectionTag.putByte("Y", (byte) y);
|
||||
sectionsTag.add(sectionTag);
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("sections", sectionsTag);
|
||||
if (flag) {
|
||||
tag.putBoolean("isLightOn", true);
|
||||
}
|
||||
|
||||
ListTag blockEntities = new ListTag();
|
||||
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
|
||||
CompoundTag entityNbt = chunk.getBlockEntityNbtForSaving(blockPos);
|
||||
if (entityNbt != null) {
|
||||
blockEntities.add(entityNbt);
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("block_entities", blockEntities);
|
||||
if (chunk.getStatus().getChunkType() == ChunkStatus.ChunkType.PROTOCHUNK) {
|
||||
ProtoChunk protochunk = (ProtoChunk)chunk;
|
||||
ListTag entities = new ListTag();
|
||||
entities.addAll(protochunk.getEntities());
|
||||
tag.put("entities", entities);
|
||||
CompoundTag carvingMasks = new CompoundTag();
|
||||
|
||||
for(GenerationStep.Carving carving : GenerationStep.Carving.values()) {
|
||||
CarvingMask mask = protochunk.getCarvingMask(carving);
|
||||
if (mask != null) {
|
||||
carvingMasks.putLongArray(carving.toString(), mask.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("CarvingMasks", carvingMasks);
|
||||
}
|
||||
|
||||
saveTicks(tag, chunk.getTicksForSerialization());
|
||||
tag.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
|
||||
CompoundTag heightMaps = new CompoundTag();
|
||||
|
||||
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
|
||||
if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) {
|
||||
heightMaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("Heightmaps", heightMaps);
|
||||
|
||||
CompoundTag structureData = new CompoundTag();
|
||||
structureData.put("starts", new CompoundTag());
|
||||
structureData.put("References", new CompoundTag());
|
||||
tag.put("structures", structureData);
|
||||
if (!chunk.persistentDataContainer.isEmpty()) {
|
||||
tag.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
private static void saveTicks(CompoundTag tag, ChunkAccess.TicksToSave ticks) {
|
||||
tag.put("block_ticks", ticks.blocks().save(0, (block) -> BuiltInRegistries.BLOCK.getKey(block).toString()));
|
||||
tag.put("fluid_ticks", ticks.fluids().save(0, (fluid) -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.v1_20_R3.headless.RegionStorage;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.SneakyThrows;
|
||||
import net.minecraft.core.*;
|
||||
@@ -647,44 +649,42 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public IRegionStorage createRegionStorage(Engine engine) {
|
||||
return new RegionStorage(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
return inject(this::supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
public com.volmit.iris.core.nms.container.Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
|
||||
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
|
||||
)
|
||||
);
|
||||
|
||||
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
return new com.volmit.iris.core.nms.container.Pair<>(
|
||||
injected.size(),
|
||||
new AutoClosing(() -> {
|
||||
closing.close();
|
||||
field.set(reg, old);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -696,9 +696,31 @@ public class NMSBinding implements INMSBinding {
|
||||
return overworld || nether || end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().K.customDimensions = null;
|
||||
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
|
||||
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, transformer.apply(old));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
@@ -722,7 +744,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.BiomeBaseInjector;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.data.IrisCustomData;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import lombok.Data;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockType;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.util.CraftMagicNumbers;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Data
|
||||
public final class DirectTerrainChunk implements SerializableChunk {
|
||||
private final ProtoChunk access;
|
||||
private final int minHeight, maxHeight;
|
||||
|
||||
public DirectTerrainChunk(ProtoChunk access) {
|
||||
this.access = access;
|
||||
this.minHeight = access.getMinBuildHeight();
|
||||
this.maxHeight = access.getMaxBuildHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeBaseInjector getBiomeBaseInjector() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int z) {
|
||||
return getBiome(x, 0, z);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
|
||||
return CraftBiome.minecraftHolderToBukkit(access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int z, Biome bio) {
|
||||
for (int y = minHeight; y < maxHeight; y += 4) {
|
||||
setBiome(x, y, z, bio);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int y, int z, Biome bio) {
|
||||
if (y < minHeight || y > maxHeight) return;
|
||||
access.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, Material material) {
|
||||
this.setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, MaterialData material) {
|
||||
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, BlockData blockData) {
|
||||
if (blockData == null) {
|
||||
Iris.error("NULL BD");
|
||||
}
|
||||
if (blockData instanceof IrisCustomData data)
|
||||
blockData = data.getBase();
|
||||
if (!(blockData instanceof CraftBlockData craftBlockData))
|
||||
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
|
||||
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGenerator.ChunkData getRaw() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRaw(ChunkGenerator.ChunkData data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inject(ChunkGenerator.BiomeGrid biome) {
|
||||
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
|
||||
return;
|
||||
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
|
||||
return;
|
||||
|
||||
for (int y = yMin; y < yMax; ++y) {
|
||||
for (int x = xMin; x < xMax; ++x) {
|
||||
for (int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public BlockState getTypeId(int x, int y, int z) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
}
|
||||
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return;
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position2 getPos() {
|
||||
return new Position2(access.getPos().x, access.getPos().z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize() {
|
||||
return RegionStorage.serialize(access);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark() {
|
||||
access.setStatus(ChunkStatus.FULL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import lombok.NonNull;
|
||||
import lombok.Synchronized;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class Region implements IRegion, Comparable<Region> {
|
||||
private final RegionFile regionFile;
|
||||
transient long references;
|
||||
transient long lastUsed;
|
||||
|
||||
Region(Path path, Path folder) throws IOException {
|
||||
this.regionFile = new RegionFile(path, folder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public boolean exists(int x, int z) {
|
||||
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
|
||||
if (din == null) return false;
|
||||
return !"empty".equals(NbtIo.read(din).getString("Status"));
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public void write(@NonNull SerializableChunk chunk) throws IOException {
|
||||
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
|
||||
NbtIo.write((CompoundTag) chunk.serialize(), dos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
--references;
|
||||
lastUsed = M.ms();
|
||||
}
|
||||
|
||||
public boolean unused() {
|
||||
return references <= 0;
|
||||
}
|
||||
|
||||
public boolean remove() {
|
||||
if (!unused()) return false;
|
||||
try {
|
||||
regionFile.close();
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to close region file");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Region o) {
|
||||
return Long.compare(lastUsed, o.lastUsed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3.headless;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.context.ChunkContext;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import net.minecraft.FileUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
import net.minecraft.world.level.chunk.*;
|
||||
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBiome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
|
||||
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.packOffsets;
|
||||
|
||||
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
|
||||
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
|
||||
private final KMap<Long, Region> regions = new KMap<>();
|
||||
private final Path folder;
|
||||
|
||||
private final Engine engine;
|
||||
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
|
||||
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
|
||||
private final RNG biomeRng;
|
||||
private final @Getter int minBuildHeight;
|
||||
private final @Getter int height;
|
||||
|
||||
private transient boolean closed = false;
|
||||
|
||||
public RegionStorage(Engine engine) {
|
||||
this.engine = engine;
|
||||
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
|
||||
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
|
||||
|
||||
this.minBuildHeight = engine.getDimension().getMinHeight();
|
||||
this.height = engine.getDimension().getMaxHeight() - minBuildHeight;
|
||||
|
||||
AtomicInteger failed = new AtomicInteger();
|
||||
var dimKey = engine.getDimension().getLoadKey();
|
||||
for (var biome : engine.getAllBiomes()) {
|
||||
if (!biome.isCustom()) continue;
|
||||
for (var custom : biome.getCustomDerivitives()) {
|
||||
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
|
||||
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
|
||||
failed.incrementAndGet();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (failed.get() > 0) {
|
||||
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
|
||||
}
|
||||
|
||||
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
|
||||
.filter(biome -> biome != org.bukkit.block.Biome.CUSTOM)
|
||||
.collect(Collectors.toMap(Function.identity(), CraftBiome::bukkitToMinecraftHolder)));
|
||||
minecraftBiomes.values().removeAll(customBiomes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int x, int z) {
|
||||
try (IRegion region = getRegion(x >> 5, z >> 5, true)) {
|
||||
return region != null && region.exists(x, z);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
|
||||
AtomicReference<IOException> exception = new AtomicReference<>();
|
||||
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
|
||||
trim();
|
||||
|
||||
try {
|
||||
FileUtil.createDirectoriesSafe(this.folder);
|
||||
Path path = folder.resolve("r." + x + "." + z + ".mca");
|
||||
if (existingOnly && !Files.exists(path)) {
|
||||
return null;
|
||||
} else {
|
||||
return new Region(path, this.folder);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception.set(e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
if (region == null) {
|
||||
if (exception.get() != null)
|
||||
throw exception.get();
|
||||
return null;
|
||||
}
|
||||
region.references++;
|
||||
return region;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SerializableChunk createChunk(int x, int z) {
|
||||
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().registryOrThrow(Registries.BIOME), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
|
||||
if (!(chunk instanceof DirectTerrainChunk tc))
|
||||
return;
|
||||
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (closed) return;
|
||||
|
||||
while (!regions.isEmpty()) {
|
||||
regions.values().removeIf(Region::remove);
|
||||
J.sleep(1);
|
||||
}
|
||||
|
||||
closed = true;
|
||||
customBiomes.clear();
|
||||
minecraftBiomes.clear();
|
||||
}
|
||||
|
||||
private void trim() {
|
||||
int size = regions.size();
|
||||
if (size < 256) return;
|
||||
int remove = size - 255;
|
||||
|
||||
var list = regions.values()
|
||||
.stream()
|
||||
.filter(Region::unused)
|
||||
.sorted()
|
||||
.collect(Collectors.toList())
|
||||
.reversed();
|
||||
|
||||
int skip = list.size() - remove;
|
||||
if (skip > 0) list.subList(0, skip).clear();
|
||||
|
||||
if (list.isEmpty()) return;
|
||||
regions.values().removeIf(r -> list.contains(r) && r.remove());
|
||||
}
|
||||
|
||||
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
|
||||
int m = y - engine.getMinHeight();
|
||||
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
|
||||
if (ib.isCustom()) {
|
||||
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
|
||||
} else {
|
||||
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
|
||||
}
|
||||
}
|
||||
|
||||
private static RegistryAccess registryAccess() {
|
||||
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
|
||||
}
|
||||
|
||||
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
|
||||
return registryAccess().registryOrThrow(Registries.BIOME).getHolder(ResourceKey.create(Registries.BIOME, new ResourceLocation(namespace, path)));
|
||||
}
|
||||
|
||||
static CompoundTag serialize(ChunkAccess chunk) {
|
||||
ChunkPos chunkPos = chunk.getPos();
|
||||
CompoundTag tag = NbtUtils.addCurrentDataVersion(new CompoundTag());
|
||||
tag.putInt("xPos", chunkPos.x);
|
||||
tag.putInt("yPos", chunk.getMinSection());
|
||||
tag.putInt("zPos", chunkPos.z);
|
||||
tag.putLong("LastUpdate", 0);
|
||||
tag.putLong("InhabitedTime", chunk.getInhabitedTime());
|
||||
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
|
||||
BlendingData blendingdata = chunk.getBlendingData();
|
||||
if (blendingdata != null) {
|
||||
DataResult<Tag> dataresult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
|
||||
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("blending_data", nbt));
|
||||
}
|
||||
|
||||
BelowZeroRetrogen retrogen = chunk.getBelowZeroRetrogen();
|
||||
if (retrogen != null) {
|
||||
DataResult<Tag> dataresult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, retrogen);
|
||||
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("below_zero_retrogen", nbt));
|
||||
}
|
||||
|
||||
UpgradeData upgradeData = chunk.getUpgradeData();
|
||||
if (!upgradeData.isEmpty()) {
|
||||
tag.put("UpgradeData", upgradeData.write());
|
||||
}
|
||||
|
||||
LevelChunkSection[] sections = chunk.getSections();
|
||||
ListTag sectionsTag = new ListTag();
|
||||
Registry<Biome> biomeRegistry = registryAccess().registryOrThrow(Registries.BIOME);
|
||||
Codec<PalettedContainerRO<Holder<Biome>>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
|
||||
boolean flag = chunk.isLightCorrect();
|
||||
|
||||
int minLightSection = chunk.getMinSection() - 1;
|
||||
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
|
||||
for(int y = minLightSection; y < maxLightSection; ++y) {
|
||||
int j = chunk.getSectionIndexFromSectionY(y);
|
||||
if (j < 0 || j >= sections.length)
|
||||
continue;
|
||||
CompoundTag sectionTag = new CompoundTag();
|
||||
LevelChunkSection section = sections[j];
|
||||
sectionTag.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow(false, LogUtils.getLogger()::error));
|
||||
sectionTag.put("biomes", codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow(false, LogUtils.getLogger()::error));
|
||||
|
||||
if (!sectionTag.isEmpty()) {
|
||||
sectionTag.putByte("Y", (byte) y);
|
||||
sectionsTag.add(sectionTag);
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("sections", sectionsTag);
|
||||
if (flag) {
|
||||
tag.putBoolean("isLightOn", true);
|
||||
}
|
||||
|
||||
ListTag blockEntities = new ListTag();
|
||||
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
|
||||
CompoundTag entityNbt = chunk.getBlockEntityNbtForSaving(blockPos);
|
||||
if (entityNbt != null) {
|
||||
blockEntities.add(entityNbt);
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("block_entities", blockEntities);
|
||||
if (chunk.getStatus().getChunkType() == ChunkStatus.ChunkType.PROTOCHUNK) {
|
||||
ProtoChunk protochunk = (ProtoChunk)chunk;
|
||||
ListTag entities = new ListTag();
|
||||
entities.addAll(protochunk.getEntities());
|
||||
tag.put("entities", entities);
|
||||
CompoundTag carvingMasks = new CompoundTag();
|
||||
|
||||
for(GenerationStep.Carving carving : GenerationStep.Carving.values()) {
|
||||
CarvingMask mask = protochunk.getCarvingMask(carving);
|
||||
if (mask != null) {
|
||||
carvingMasks.putLongArray(carving.toString(), mask.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("CarvingMasks", carvingMasks);
|
||||
}
|
||||
|
||||
saveTicks(tag, chunk.getTicksForSerialization());
|
||||
tag.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
|
||||
CompoundTag heightMaps = new CompoundTag();
|
||||
|
||||
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
|
||||
if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) {
|
||||
heightMaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("Heightmaps", heightMaps);
|
||||
|
||||
CompoundTag structureData = new CompoundTag();
|
||||
structureData.put("starts", new CompoundTag());
|
||||
structureData.put("References", new CompoundTag());
|
||||
tag.put("structures", structureData);
|
||||
if (!chunk.persistentDataContainer.isEmpty()) {
|
||||
tag.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
private static void saveTicks(CompoundTag tag, ChunkAccess.TicksToSave ticks) {
|
||||
tag.put("block_ticks", ticks.blocks().save(0, (block) -> BuiltInRegistries.BLOCK.getKey(block).toString()));
|
||||
tag.put("fluid_ticks", ticks.fluids().save(0, (fluid) -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.v1_20_R4.headless.RegionStorage;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.SneakyThrows;
|
||||
@@ -672,44 +674,42 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public IRegionStorage createRegionStorage(Engine engine) {
|
||||
return new RegionStorage(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
return inject(this::supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
public com.volmit.iris.core.nms.container.Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
|
||||
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
|
||||
)
|
||||
);
|
||||
|
||||
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
return new com.volmit.iris.core.nms.container.Pair<>(
|
||||
injected.size(),
|
||||
new AutoClosing(() -> {
|
||||
closing.close();
|
||||
field.set(reg, old);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -721,9 +721,31 @@ public class NMSBinding implements INMSBinding {
|
||||
return overworld || nether || end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().K.customDimensions = null;
|
||||
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
|
||||
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, transformer.apply(old));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
@@ -747,7 +769,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R4.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.BiomeBaseInjector;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.data.IrisCustomData;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import lombok.Data;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockType;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.util.CraftMagicNumbers;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Data
|
||||
public final class DirectTerrainChunk implements SerializableChunk {
|
||||
private final ProtoChunk access;
|
||||
private final int minHeight, maxHeight;
|
||||
|
||||
public DirectTerrainChunk(ProtoChunk access) {
|
||||
this.access = access;
|
||||
this.minHeight = access.getMinBuildHeight();
|
||||
this.maxHeight = access.getMaxBuildHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeBaseInjector getBiomeBaseInjector() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int z) {
|
||||
return getBiome(x, 0, z);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
|
||||
return CraftBiome.minecraftHolderToBukkit(access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int z, Biome bio) {
|
||||
for (int y = minHeight; y < maxHeight; y += 4) {
|
||||
setBiome(x, y, z, bio);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int y, int z, Biome bio) {
|
||||
if (y < minHeight || y > maxHeight) return;
|
||||
access.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, Material material) {
|
||||
this.setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, MaterialData material) {
|
||||
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, BlockData blockData) {
|
||||
if (blockData == null) {
|
||||
Iris.error("NULL BD");
|
||||
}
|
||||
if (blockData instanceof IrisCustomData data)
|
||||
blockData = data.getBase();
|
||||
if (!(blockData instanceof CraftBlockData craftBlockData))
|
||||
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
|
||||
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGenerator.ChunkData getRaw() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRaw(ChunkGenerator.ChunkData data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inject(ChunkGenerator.BiomeGrid biome) {
|
||||
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
|
||||
return;
|
||||
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
|
||||
return;
|
||||
|
||||
for (int y = yMin; y < yMax; ++y) {
|
||||
for (int x = xMin; x < xMax; ++x) {
|
||||
for (int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public BlockState getTypeId(int x, int y, int z) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
}
|
||||
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return;
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position2 getPos() {
|
||||
return new Position2(access.getPos().x, access.getPos().z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize() {
|
||||
return RegionStorage.serialize(access);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark() {
|
||||
access.setStatus(ChunkStatus.FULL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R4.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import lombok.NonNull;
|
||||
import lombok.Synchronized;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class Region implements IRegion, Comparable<Region> {
|
||||
private static final RegionStorageInfo info = new RegionStorageInfo("headless", Level.OVERWORLD, "headless");
|
||||
private final RegionFile regionFile;
|
||||
transient long references;
|
||||
transient long lastUsed;
|
||||
|
||||
Region(Path path, Path folder) throws IOException {
|
||||
this.regionFile = new RegionFile(info, path, folder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public boolean exists(int x, int z) {
|
||||
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
|
||||
if (din == null) return false;
|
||||
return !"empty".equals(NbtIo.read(din).getString("Status"));
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public void write(@NonNull SerializableChunk chunk) throws IOException {
|
||||
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
|
||||
NbtIo.write((CompoundTag) chunk.serialize(), dos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
--references;
|
||||
lastUsed = M.ms();
|
||||
}
|
||||
|
||||
public boolean unused() {
|
||||
return references <= 0;
|
||||
}
|
||||
|
||||
public boolean remove() {
|
||||
if (!unused()) return false;
|
||||
try {
|
||||
regionFile.close();
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to close region file");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Region o) {
|
||||
return Long.compare(lastUsed, o.lastUsed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R4.headless;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.context.ChunkContext;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import net.minecraft.FileUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
import net.minecraft.world.level.chunk.*;
|
||||
import net.minecraft.world.level.chunk.status.ChunkType;
|
||||
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.block.CraftBiome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
|
||||
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.packOffsets;
|
||||
|
||||
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
|
||||
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
|
||||
private final KMap<Long, Region> regions = new KMap<>();
|
||||
private final Path folder;
|
||||
|
||||
private final Engine engine;
|
||||
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
|
||||
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
|
||||
private final RNG biomeRng;
|
||||
private final @Getter int minBuildHeight;
|
||||
private final @Getter int height;
|
||||
|
||||
private transient boolean closed = false;
|
||||
|
||||
public RegionStorage(Engine engine) {
|
||||
this.engine = engine;
|
||||
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
|
||||
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
|
||||
|
||||
this.minBuildHeight = engine.getDimension().getMinHeight();
|
||||
this.height = engine.getDimension().getMaxHeight() - minBuildHeight;
|
||||
|
||||
AtomicInteger failed = new AtomicInteger();
|
||||
var dimKey = engine.getDimension().getLoadKey();
|
||||
for (var biome : engine.getAllBiomes()) {
|
||||
if (!biome.isCustom()) continue;
|
||||
for (var custom : biome.getCustomDerivitives()) {
|
||||
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
|
||||
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
|
||||
failed.incrementAndGet();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (failed.get() > 0) {
|
||||
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
|
||||
}
|
||||
|
||||
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
|
||||
.filter(biome -> biome != org.bukkit.block.Biome.CUSTOM)
|
||||
.collect(Collectors.toMap(Function.identity(), CraftBiome::bukkitToMinecraftHolder)));
|
||||
minecraftBiomes.values().removeAll(customBiomes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int x, int z) {
|
||||
try (IRegion region = getRegion(x >> 5, z >> 5, true)) {
|
||||
return region != null && region.exists(x, z);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
|
||||
AtomicReference<IOException> exception = new AtomicReference<>();
|
||||
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
|
||||
trim();
|
||||
|
||||
try {
|
||||
FileUtil.createDirectoriesSafe(this.folder);
|
||||
Path path = folder.resolve("r." + x + "." + z + ".mca");
|
||||
if (existingOnly && !Files.exists(path)) {
|
||||
return null;
|
||||
} else {
|
||||
return new Region(path, this.folder);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception.set(e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
if (region == null) {
|
||||
if (exception.get() != null)
|
||||
throw exception.get();
|
||||
return null;
|
||||
}
|
||||
region.references++;
|
||||
return region;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SerializableChunk createChunk(int x, int z) {
|
||||
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().registryOrThrow(Registries.BIOME), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
|
||||
if (!(chunk instanceof DirectTerrainChunk tc))
|
||||
return;
|
||||
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (closed) return;
|
||||
|
||||
while (!regions.isEmpty()) {
|
||||
regions.values().removeIf(Region::remove);
|
||||
J.sleep(1);
|
||||
}
|
||||
|
||||
closed = true;
|
||||
customBiomes.clear();
|
||||
minecraftBiomes.clear();
|
||||
}
|
||||
|
||||
private void trim() {
|
||||
int size = regions.size();
|
||||
if (size < 256) return;
|
||||
int remove = size - 255;
|
||||
|
||||
var list = regions.values()
|
||||
.stream()
|
||||
.filter(Region::unused)
|
||||
.sorted()
|
||||
.collect(Collectors.toList())
|
||||
.reversed();
|
||||
|
||||
int skip = list.size() - remove;
|
||||
if (skip > 0) list.subList(0, skip).clear();
|
||||
|
||||
if (list.isEmpty()) return;
|
||||
regions.values().removeIf(r -> list.contains(r) && r.remove());
|
||||
}
|
||||
|
||||
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
|
||||
int m = y - engine.getMinHeight();
|
||||
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
|
||||
if (ib.isCustom()) {
|
||||
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
|
||||
} else {
|
||||
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
|
||||
}
|
||||
}
|
||||
|
||||
private static RegistryAccess registryAccess() {
|
||||
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
|
||||
}
|
||||
|
||||
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
|
||||
return registryAccess().registryOrThrow(Registries.BIOME).getHolder(new ResourceLocation(namespace, path));
|
||||
}
|
||||
|
||||
static CompoundTag serialize(ChunkAccess chunk) {
|
||||
ChunkPos chunkPos = chunk.getPos();
|
||||
CompoundTag tag = NbtUtils.addCurrentDataVersion(new CompoundTag());
|
||||
tag.putInt("xPos", chunkPos.x);
|
||||
tag.putInt("yPos", chunk.getMinSection());
|
||||
tag.putInt("zPos", chunkPos.z);
|
||||
tag.putLong("LastUpdate", 0);
|
||||
tag.putLong("InhabitedTime", chunk.getInhabitedTime());
|
||||
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
|
||||
BlendingData blendingdata = chunk.getBlendingData();
|
||||
if (blendingdata != null) {
|
||||
DataResult<Tag> dataresult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
|
||||
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("blending_data", nbt));
|
||||
}
|
||||
|
||||
BelowZeroRetrogen retrogen = chunk.getBelowZeroRetrogen();
|
||||
if (retrogen != null) {
|
||||
DataResult<Tag> dataresult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, retrogen);
|
||||
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("below_zero_retrogen", nbt));
|
||||
}
|
||||
|
||||
UpgradeData upgradeData = chunk.getUpgradeData();
|
||||
if (!upgradeData.isEmpty()) {
|
||||
tag.put("UpgradeData", upgradeData.write());
|
||||
}
|
||||
|
||||
LevelChunkSection[] sections = chunk.getSections();
|
||||
ListTag sectionsTag = new ListTag();
|
||||
Registry<Biome> biomeRegistry = registryAccess().registryOrThrow(Registries.BIOME);
|
||||
Codec<PalettedContainerRO<Holder<Biome>>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
|
||||
boolean flag = chunk.isLightCorrect();
|
||||
|
||||
int minLightSection = chunk.getMinSection() - 1;
|
||||
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
|
||||
for(int y = minLightSection; y < maxLightSection; ++y) {
|
||||
int j = chunk.getSectionIndexFromSectionY(y);
|
||||
if (j < 0 || j >= sections.length)
|
||||
continue;
|
||||
CompoundTag sectionTag = new CompoundTag();
|
||||
LevelChunkSection section = sections[j];
|
||||
sectionTag.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow());
|
||||
sectionTag.put("biomes", codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow());
|
||||
|
||||
if (!sectionTag.isEmpty()) {
|
||||
sectionTag.putByte("Y", (byte) y);
|
||||
sectionsTag.add(sectionTag);
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("sections", sectionsTag);
|
||||
if (flag) {
|
||||
tag.putBoolean("isLightOn", true);
|
||||
}
|
||||
|
||||
ListTag blockEntities = new ListTag();
|
||||
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
|
||||
CompoundTag entityNbt = chunk.getBlockEntityNbtForSaving(blockPos, registryAccess());
|
||||
if (entityNbt != null) {
|
||||
blockEntities.add(entityNbt);
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("block_entities", blockEntities);
|
||||
if (chunk.getStatus().getChunkType() == ChunkType.PROTOCHUNK) {
|
||||
ProtoChunk protochunk = (ProtoChunk)chunk;
|
||||
ListTag entities = new ListTag();
|
||||
entities.addAll(protochunk.getEntities());
|
||||
tag.put("entities", entities);
|
||||
CompoundTag carvingMasks = new CompoundTag();
|
||||
|
||||
for(GenerationStep.Carving carving : GenerationStep.Carving.values()) {
|
||||
CarvingMask mask = protochunk.getCarvingMask(carving);
|
||||
if (mask != null) {
|
||||
carvingMasks.putLongArray(carving.toString(), mask.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("CarvingMasks", carvingMasks);
|
||||
}
|
||||
|
||||
saveTicks(tag, chunk.getTicksForSerialization());
|
||||
tag.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
|
||||
CompoundTag heightMaps = new CompoundTag();
|
||||
|
||||
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
|
||||
if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) {
|
||||
heightMaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("Heightmaps", heightMaps);
|
||||
|
||||
CompoundTag structureData = new CompoundTag();
|
||||
structureData.put("starts", new CompoundTag());
|
||||
structureData.put("References", new CompoundTag());
|
||||
tag.put("structures", structureData);
|
||||
if (!chunk.persistentDataContainer.isEmpty()) {
|
||||
tag.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
private static void saveTicks(CompoundTag tag, ChunkAccess.TicksToSave ticks) {
|
||||
tag.put("block_ticks", ticks.blocks().save(0, (block) -> BuiltInRegistries.BLOCK.getKey(block).toString()));
|
||||
tag.put("fluid_ticks", ticks.fluids().save(0, (fluid) -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.v1_21_R1.headless.RegionStorage;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.SneakyThrows;
|
||||
import net.minecraft.core.*;
|
||||
@@ -676,44 +678,42 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public IRegionStorage createRegionStorage(Engine engine) {
|
||||
return new RegionStorage(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
return inject(this::supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
public com.volmit.iris.core.nms.container.Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
|
||||
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
|
||||
)
|
||||
);
|
||||
|
||||
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
return new com.volmit.iris.core.nms.container.Pair<>(
|
||||
injected.size(),
|
||||
new AutoClosing(() -> {
|
||||
closing.close();
|
||||
field.set(reg, old);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -725,9 +725,31 @@ public class NMSBinding implements INMSBinding {
|
||||
return overworld || nether || end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().K.customDimensions = null;
|
||||
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
|
||||
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, transformer.apply(old));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
@@ -751,7 +773,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.volmit.iris.core.nms.v1_21_R1.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.BiomeBaseInjector;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.data.IrisCustomData;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import lombok.Data;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.block.CraftBlockType;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.util.CraftMagicNumbers;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Data
|
||||
public final class DirectTerrainChunk implements SerializableChunk {
|
||||
private final ProtoChunk access;
|
||||
private final int minHeight, maxHeight;
|
||||
|
||||
public DirectTerrainChunk(ProtoChunk access) {
|
||||
this.access = access;
|
||||
this.minHeight = access.getMinBuildHeight();
|
||||
this.maxHeight = access.getMaxBuildHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeBaseInjector getBiomeBaseInjector() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int z) {
|
||||
return getBiome(x, 0, z);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
|
||||
return CraftBiome.minecraftHolderToBukkit(access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int z, Biome bio) {
|
||||
for (int y = minHeight; y < maxHeight; y += 4) {
|
||||
setBiome(x, y, z, bio);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int y, int z, Biome bio) {
|
||||
if (y < minHeight || y > maxHeight) return;
|
||||
access.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, Material material) {
|
||||
this.setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, MaterialData material) {
|
||||
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, BlockData blockData) {
|
||||
if (blockData == null) {
|
||||
Iris.error("NULL BD");
|
||||
}
|
||||
if (blockData instanceof IrisCustomData data)
|
||||
blockData = data.getBase();
|
||||
if (!(blockData instanceof CraftBlockData craftBlockData))
|
||||
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
|
||||
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGenerator.ChunkData getRaw() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRaw(ChunkGenerator.ChunkData data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inject(ChunkGenerator.BiomeGrid biome) {
|
||||
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
|
||||
return;
|
||||
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
|
||||
return;
|
||||
|
||||
for (int y = yMin; y < yMax; ++y) {
|
||||
for (int x = xMin; x < xMax; ++x) {
|
||||
for (int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public BlockState getTypeId(int x, int y, int z) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
}
|
||||
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return;
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position2 getPos() {
|
||||
return new Position2(access.getPos().x, access.getPos().z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize() {
|
||||
return RegionStorage.serialize(access);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark() {
|
||||
access.setPersistedStatus(ChunkStatus.FULL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.volmit.iris.core.nms.v1_21_R1.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import lombok.NonNull;
|
||||
import lombok.Synchronized;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class Region implements IRegion, Comparable<Region> {
|
||||
private static final RegionStorageInfo info = new RegionStorageInfo("headless", Level.OVERWORLD, "headless");
|
||||
private final RegionFile regionFile;
|
||||
transient long references;
|
||||
transient long lastUsed;
|
||||
|
||||
Region(Path path, Path folder) throws IOException {
|
||||
this.regionFile = new RegionFile(info, path, folder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public boolean exists(int x, int z) {
|
||||
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
|
||||
if (din == null) return false;
|
||||
return !"empty".equals(NbtIo.read(din).getString("Status"));
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public void write(@NonNull SerializableChunk chunk) throws IOException {
|
||||
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
|
||||
NbtIo.write((CompoundTag) chunk.serialize(), dos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
--references;
|
||||
lastUsed = M.ms();
|
||||
}
|
||||
|
||||
public boolean unused() {
|
||||
return references <= 0;
|
||||
}
|
||||
|
||||
public boolean remove() {
|
||||
if (!unused()) return false;
|
||||
try {
|
||||
regionFile.close();
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to close region file");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Region o) {
|
||||
return Long.compare(lastUsed, o.lastUsed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
package com.volmit.iris.core.nms.v1_21_R1.headless;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.context.ChunkContext;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import net.minecraft.FileUtil;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
import net.minecraft.world.level.chunk.*;
|
||||
import net.minecraft.world.level.chunk.status.ChunkType;
|
||||
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.block.CraftBiome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
|
||||
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.packOffsets;
|
||||
|
||||
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
|
||||
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
|
||||
private final KMap<Long, Region> regions = new KMap<>();
|
||||
private final Path folder;
|
||||
|
||||
private final Engine engine;
|
||||
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
|
||||
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
|
||||
private final RNG biomeRng;
|
||||
private final @Getter int minBuildHeight;
|
||||
private final @Getter int height;
|
||||
|
||||
private transient boolean closed = false;
|
||||
|
||||
public RegionStorage(Engine engine) {
|
||||
this.engine = engine;
|
||||
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
|
||||
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
|
||||
|
||||
this.minBuildHeight = engine.getDimension().getMinHeight();
|
||||
this.height = engine.getDimension().getMaxHeight() - minBuildHeight;
|
||||
|
||||
AtomicInteger failed = new AtomicInteger();
|
||||
var dimKey = engine.getDimension().getLoadKey();
|
||||
for (var biome : engine.getAllBiomes()) {
|
||||
if (!biome.isCustom()) continue;
|
||||
for (var custom : biome.getCustomDerivitives()) {
|
||||
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
|
||||
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
|
||||
failed.incrementAndGet();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (failed.get() > 0) {
|
||||
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
|
||||
}
|
||||
|
||||
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
|
||||
.filter(biome -> biome != org.bukkit.block.Biome.CUSTOM)
|
||||
.collect(Collectors.toMap(Function.identity(), CraftBiome::bukkitToMinecraftHolder)));
|
||||
minecraftBiomes.values().removeAll(customBiomes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int x, int z) {
|
||||
try (IRegion region = getRegion(x >> 5, z >> 5, true)) {
|
||||
return region != null && region.exists(x, z);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
|
||||
AtomicReference<IOException> exception = new AtomicReference<>();
|
||||
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
|
||||
trim();
|
||||
|
||||
try {
|
||||
FileUtil.createDirectoriesSafe(this.folder);
|
||||
Path path = folder.resolve("r." + x + "." + z + ".mca");
|
||||
if (existingOnly && !Files.exists(path)) {
|
||||
return null;
|
||||
} else {
|
||||
return new Region(path, this.folder);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception.set(e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
if (region == null) {
|
||||
if (exception.get() != null)
|
||||
throw exception.get();
|
||||
return null;
|
||||
}
|
||||
region.references++;
|
||||
return region;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SerializableChunk createChunk(int x, int z) {
|
||||
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().registryOrThrow(Registries.BIOME), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
|
||||
if (!(chunk instanceof DirectTerrainChunk tc))
|
||||
return;
|
||||
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (closed) return;
|
||||
|
||||
while (!regions.isEmpty()) {
|
||||
regions.values().removeIf(Region::remove);
|
||||
J.sleep(1);
|
||||
}
|
||||
|
||||
closed = true;
|
||||
customBiomes.clear();
|
||||
minecraftBiomes.clear();
|
||||
}
|
||||
|
||||
private void trim() {
|
||||
int size = regions.size();
|
||||
if (size < 256) return;
|
||||
int remove = size - 255;
|
||||
|
||||
var list = regions.values()
|
||||
.stream()
|
||||
.filter(Region::unused)
|
||||
.sorted()
|
||||
.collect(Collectors.toList())
|
||||
.reversed();
|
||||
|
||||
int skip = list.size() - remove;
|
||||
if (skip > 0) list.subList(0, skip).clear();
|
||||
|
||||
if (list.isEmpty()) return;
|
||||
regions.values().removeIf(r -> list.contains(r) && r.remove());
|
||||
}
|
||||
|
||||
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
|
||||
int m = y - engine.getMinHeight();
|
||||
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
|
||||
if (ib.isCustom()) {
|
||||
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
|
||||
} else {
|
||||
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
|
||||
}
|
||||
}
|
||||
|
||||
private static RegistryAccess registryAccess() {
|
||||
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
|
||||
}
|
||||
|
||||
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
|
||||
return registryAccess().registryOrThrow(Registries.BIOME).getHolder(ResourceLocation.fromNamespaceAndPath(namespace, path));
|
||||
}
|
||||
|
||||
static CompoundTag serialize(ChunkAccess chunk) {
|
||||
ChunkPos chunkPos = chunk.getPos();
|
||||
CompoundTag tag = NbtUtils.addCurrentDataVersion(new CompoundTag());
|
||||
tag.putInt("xPos", chunkPos.x);
|
||||
tag.putInt("yPos", chunk.getMinSection());
|
||||
tag.putInt("zPos", chunkPos.z);
|
||||
tag.putLong("LastUpdate", 0);
|
||||
tag.putLong("InhabitedTime", chunk.getInhabitedTime());
|
||||
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getPersistedStatus()).toString());
|
||||
BlendingData blendingdata = chunk.getBlendingData();
|
||||
if (blendingdata != null) {
|
||||
DataResult<Tag> dataresult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
|
||||
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("blending_data", nbt));
|
||||
}
|
||||
|
||||
BelowZeroRetrogen retrogen = chunk.getBelowZeroRetrogen();
|
||||
if (retrogen != null) {
|
||||
DataResult<Tag> dataresult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, retrogen);
|
||||
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("below_zero_retrogen", nbt));
|
||||
}
|
||||
|
||||
UpgradeData upgradeData = chunk.getUpgradeData();
|
||||
if (!upgradeData.isEmpty()) {
|
||||
tag.put("UpgradeData", upgradeData.write());
|
||||
}
|
||||
|
||||
LevelChunkSection[] sections = chunk.getSections();
|
||||
ListTag sectionsTag = new ListTag();
|
||||
Registry<Biome> biomeRegistry = registryAccess().registryOrThrow(Registries.BIOME);
|
||||
Codec<PalettedContainerRO<Holder<Biome>>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
|
||||
boolean flag = chunk.isLightCorrect();
|
||||
|
||||
int minLightSection = chunk.getMinSection() - 1;
|
||||
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
|
||||
for(int y = minLightSection; y < maxLightSection; ++y) {
|
||||
int j = chunk.getSectionIndexFromSectionY(y);
|
||||
if (j < 0 || j >= sections.length)
|
||||
continue;
|
||||
CompoundTag sectionTag = new CompoundTag();
|
||||
LevelChunkSection section = sections[j];
|
||||
sectionTag.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow());
|
||||
sectionTag.put("biomes", codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow());
|
||||
|
||||
if (!sectionTag.isEmpty()) {
|
||||
sectionTag.putByte("Y", (byte) y);
|
||||
sectionsTag.add(sectionTag);
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("sections", sectionsTag);
|
||||
if (flag) {
|
||||
tag.putBoolean("isLightOn", true);
|
||||
}
|
||||
|
||||
ListTag blockEntities = new ListTag();
|
||||
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
|
||||
CompoundTag entityNbt = chunk.getBlockEntityNbtForSaving(blockPos, registryAccess());
|
||||
if (entityNbt != null) {
|
||||
blockEntities.add(entityNbt);
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("block_entities", blockEntities);
|
||||
if (chunk.getPersistedStatus().getChunkType() == ChunkType.PROTOCHUNK) {
|
||||
ProtoChunk protochunk = (ProtoChunk)chunk;
|
||||
ListTag entities = new ListTag();
|
||||
entities.addAll(protochunk.getEntities());
|
||||
tag.put("entities", entities);
|
||||
CompoundTag carvingMasks = new CompoundTag();
|
||||
|
||||
for(GenerationStep.Carving carving : GenerationStep.Carving.values()) {
|
||||
CarvingMask mask = protochunk.getCarvingMask(carving);
|
||||
if (mask != null) {
|
||||
carvingMasks.putLongArray(carving.toString(), mask.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("CarvingMasks", carvingMasks);
|
||||
}
|
||||
|
||||
saveTicks(tag, chunk.getTicksForSerialization());
|
||||
tag.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
|
||||
CompoundTag heightMaps = new CompoundTag();
|
||||
|
||||
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
|
||||
if (chunk.getPersistedStatus().heightmapsAfter().contains(entry.getKey())) {
|
||||
heightMaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
|
||||
}
|
||||
}
|
||||
|
||||
tag.put("Heightmaps", heightMaps);
|
||||
|
||||
CompoundTag structureData = new CompoundTag();
|
||||
structureData.put("starts", new CompoundTag());
|
||||
structureData.put("References", new CompoundTag());
|
||||
tag.put("structures", structureData);
|
||||
if (!chunk.persistentDataContainer.isEmpty()) {
|
||||
tag.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
private static void saveTicks(CompoundTag tag, ChunkAccess.TicksToSave ticks) {
|
||||
tag.put("block_ticks", ticks.blocks().save(0, (block) -> BuiltInRegistries.BLOCK.getKey(block).toString()));
|
||||
tag.put("fluid_ticks", ticks.fluids().save(0, (fluid) -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.v1_21_R2.headless.RegionStorage;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.SneakyThrows;
|
||||
import net.minecraft.core.*;
|
||||
@@ -666,44 +668,42 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public IRegionStorage createRegionStorage(Engine engine) {
|
||||
return new RegionStorage(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
return inject(this::supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
public Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.lookupOrThrow(Registries.LEVEL_STEM);
|
||||
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
|
||||
)
|
||||
);
|
||||
|
||||
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
return new Pair<>(
|
||||
injected.size(),
|
||||
new AutoClosing(() -> {
|
||||
closing.close();
|
||||
field.set(reg, old);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -715,9 +715,31 @@ public class NMSBinding implements INMSBinding {
|
||||
return overworld || nether || end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().L.customDimensions = null;
|
||||
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
|
||||
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, transformer.apply(old));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
@@ -741,7 +763,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
if (copy) copy(fake, access.lookupOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.volmit.iris.core.nms.v1_21_R2.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.BiomeBaseInjector;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.data.IrisCustomData;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import lombok.Data;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.block.CraftBlockType;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.util.CraftMagicNumbers;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Data
|
||||
public final class DirectTerrainChunk implements SerializableChunk {
|
||||
private final ProtoChunk access;
|
||||
private final int minHeight, maxHeight;
|
||||
|
||||
public DirectTerrainChunk(ProtoChunk access) {
|
||||
this.access = access;
|
||||
this.minHeight = access.getMinY();
|
||||
this.maxHeight = access.getMaxY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeBaseInjector getBiomeBaseInjector() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int z) {
|
||||
return getBiome(x, 0, z);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
|
||||
return CraftBiome.minecraftHolderToBukkit(access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int z, Biome bio) {
|
||||
for (int y = minHeight; y < maxHeight; y += 4) {
|
||||
setBiome(x, y, z, bio);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int y, int z, Biome bio) {
|
||||
if (y < minHeight || y > maxHeight) return;
|
||||
access.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, Material material) {
|
||||
this.setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, MaterialData material) {
|
||||
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, BlockData blockData) {
|
||||
if (blockData == null) {
|
||||
Iris.error("NULL BD");
|
||||
}
|
||||
if (blockData instanceof IrisCustomData data)
|
||||
blockData = data.getBase();
|
||||
if (!(blockData instanceof CraftBlockData craftBlockData))
|
||||
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
|
||||
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGenerator.ChunkData getRaw() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRaw(ChunkGenerator.ChunkData data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inject(ChunkGenerator.BiomeGrid biome) {
|
||||
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
|
||||
return;
|
||||
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
|
||||
return;
|
||||
|
||||
for (int y = yMin; y < yMax; ++y) {
|
||||
for (int x = xMin; x < xMax; ++x) {
|
||||
for (int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public BlockState getTypeId(int x, int y, int z) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
}
|
||||
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return;
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position2 getPos() {
|
||||
return new Position2(access.getPos().x, access.getPos().z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize() {
|
||||
return RegionStorage.serialize(access);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark() {
|
||||
access.setPersistedStatus(ChunkStatus.FULL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.volmit.iris.core.nms.v1_21_R2.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import lombok.NonNull;
|
||||
import lombok.Synchronized;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class Region implements IRegion, Comparable<Region> {
|
||||
private static final RegionStorageInfo info = new RegionStorageInfo("headless", Level.OVERWORLD, "headless");
|
||||
private final RegionFile regionFile;
|
||||
transient long references;
|
||||
transient long lastUsed;
|
||||
|
||||
Region(Path path, Path folder) throws IOException {
|
||||
this.regionFile = new RegionFile(info, path, folder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public boolean exists(int x, int z) {
|
||||
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
|
||||
if (din == null) return false;
|
||||
return !"empty".equals(NbtIo.read(din).getString("Status"));
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public void write(@NonNull SerializableChunk chunk) throws IOException {
|
||||
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
|
||||
NbtIo.write((CompoundTag) chunk.serialize(), dos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
--references;
|
||||
lastUsed = M.ms();
|
||||
}
|
||||
|
||||
public boolean unused() {
|
||||
return references <= 0;
|
||||
}
|
||||
|
||||
public boolean remove() {
|
||||
if (!unused()) return false;
|
||||
try {
|
||||
regionFile.close();
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to close region file");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Region o) {
|
||||
return Long.compare(lastUsed, o.lastUsed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package com.volmit.iris.core.nms.v1_21_R2.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.context.ChunkContext;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
||||
import it.unimi.dsi.fastutil.shorts.ShortList;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import net.minecraft.FileUtil;
|
||||
import net.minecraft.Optionull;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.block.CraftBiome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
|
||||
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
|
||||
private final KMap<Long, Region> regions = new KMap<>();
|
||||
private final Path folder;
|
||||
|
||||
private final Engine engine;
|
||||
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
|
||||
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
|
||||
private final RNG biomeRng;
|
||||
private final @Getter int minY;
|
||||
private final @Getter int height;
|
||||
|
||||
private transient boolean closed = false;
|
||||
|
||||
public RegionStorage(Engine engine) {
|
||||
this.engine = engine;
|
||||
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
|
||||
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
|
||||
|
||||
this.minY = engine.getDimension().getMinHeight();
|
||||
this.height = engine.getDimension().getMaxHeight() - minY;
|
||||
|
||||
AtomicInteger failed = new AtomicInteger();
|
||||
var dimKey = engine.getDimension().getLoadKey();
|
||||
for (var biome : engine.getAllBiomes()) {
|
||||
if (!biome.isCustom()) continue;
|
||||
for (var custom : biome.getCustomDerivitives()) {
|
||||
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
|
||||
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
|
||||
failed.incrementAndGet();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (failed.get() > 0) {
|
||||
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
|
||||
}
|
||||
|
||||
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
|
||||
.collect(Collectors.toMap(Function.identity(), CraftBiome::bukkitToMinecraftHolder)));
|
||||
minecraftBiomes.values().removeAll(customBiomes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int x, int z) {
|
||||
try (IRegion region = getRegion(x >> 5, z >> 5, true)) {
|
||||
return region != null && region.exists(x, z);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
|
||||
AtomicReference<IOException> exception = new AtomicReference<>();
|
||||
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
|
||||
trim();
|
||||
|
||||
try {
|
||||
FileUtil.createDirectoriesSafe(this.folder);
|
||||
Path path = folder.resolve("r." + x + "." + z + ".mca");
|
||||
if (existingOnly && !Files.exists(path)) {
|
||||
return null;
|
||||
} else {
|
||||
return new Region(path, this.folder);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception.set(e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
if (region == null) {
|
||||
if (exception.get() != null)
|
||||
throw exception.get();
|
||||
return null;
|
||||
}
|
||||
region.references++;
|
||||
return region;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SerializableChunk createChunk(int x, int z) {
|
||||
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().lookupOrThrow(Registries.BIOME), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
|
||||
if (!(chunk instanceof DirectTerrainChunk tc))
|
||||
return;
|
||||
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (closed) return;
|
||||
|
||||
while (!regions.isEmpty()) {
|
||||
regions.values().removeIf(Region::remove);
|
||||
J.sleep(1);
|
||||
}
|
||||
|
||||
closed = true;
|
||||
customBiomes.clear();
|
||||
minecraftBiomes.clear();
|
||||
}
|
||||
|
||||
private void trim() {
|
||||
int size = regions.size();
|
||||
if (size < 256) return;
|
||||
int remove = size - 255;
|
||||
|
||||
var list = regions.values()
|
||||
.stream()
|
||||
.filter(Region::unused)
|
||||
.sorted()
|
||||
.collect(Collectors.toList())
|
||||
.reversed();
|
||||
|
||||
int skip = list.size() - remove;
|
||||
if (skip > 0) list.subList(0, skip).clear();
|
||||
|
||||
if (list.isEmpty()) return;
|
||||
regions.values().removeIf(r -> list.contains(r) && r.remove());
|
||||
}
|
||||
|
||||
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
|
||||
int m = y - engine.getMinHeight();
|
||||
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
|
||||
if (ib.isCustom()) {
|
||||
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
|
||||
} else {
|
||||
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
|
||||
}
|
||||
}
|
||||
|
||||
private static RegistryAccess registryAccess() {
|
||||
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
|
||||
}
|
||||
|
||||
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
|
||||
return registryAccess().lookupOrThrow(Registries.BIOME).get(ResourceLocation.fromNamespaceAndPath(namespace, path));
|
||||
}
|
||||
|
||||
static CompoundTag serialize(ChunkAccess chunk) {
|
||||
RegistryAccess access = registryAccess();
|
||||
List<SerializableChunkData.SectionData> list = new ArrayList<>();
|
||||
LevelChunkSection[] sections = chunk.getSections();
|
||||
|
||||
int minLightSection = chunk.getMinSectionY() - 1;
|
||||
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
|
||||
for(int y = minLightSection; y < maxLightSection; ++y) {
|
||||
int index = chunk.getSectionIndexFromSectionY(y);
|
||||
if (index < 0 || index >= sections.length) continue;
|
||||
LevelChunkSection section = sections[index].copy();
|
||||
list.add(new SerializableChunkData.SectionData(y, section, null, null));
|
||||
}
|
||||
|
||||
List<CompoundTag> blockEntities = new ArrayList<>(chunk.getBlockEntitiesPos().size());
|
||||
|
||||
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
|
||||
CompoundTag nbt = chunk.getBlockEntityNbtForSaving(blockPos, access);
|
||||
if (nbt != null) {
|
||||
blockEntities.add(nbt);
|
||||
}
|
||||
}
|
||||
Map<Heightmap.Types, long[]> heightMap = new EnumMap<>(Heightmap.Types.class);
|
||||
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
|
||||
if (chunk.getPersistedStatus().heightmapsAfter().contains(entry.getKey())) {
|
||||
heightMap.put(entry.getKey(), entry.getValue().getRawData().clone());
|
||||
}
|
||||
}
|
||||
|
||||
ChunkAccess.PackedTicks packedTicks = chunk.getTicksForSerialization(0);
|
||||
ShortList[] postProcessing = Arrays.stream(chunk.getPostProcessing()).map((shortlist) -> shortlist != null ? new ShortArrayList(shortlist) : null).toArray(ShortList[]::new);
|
||||
CompoundTag structureData = new CompoundTag();
|
||||
structureData.put("starts", new CompoundTag());
|
||||
structureData.put("References", new CompoundTag());
|
||||
|
||||
CompoundTag persistentDataContainer = null;
|
||||
if (!chunk.persistentDataContainer.isEmpty()) {
|
||||
persistentDataContainer = chunk.persistentDataContainer.toTagCompound();
|
||||
}
|
||||
|
||||
return new SerializableChunkData(access.lookupOrThrow(Registries.BIOME), chunk.getPos(),
|
||||
chunk.getMinSectionY(), 0, chunk.getInhabitedTime(), chunk.getPersistedStatus(),
|
||||
Optionull.map(chunk.getBlendingData(), BlendingData::pack), chunk.getBelowZeroRetrogen(),
|
||||
chunk.getUpgradeData().copy(), null, heightMap, packedTicks, postProcessing,
|
||||
chunk.isLightCorrect(), list, new ArrayList<>(), blockEntities, structureData, persistentDataContainer)
|
||||
.write();
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public WeightedRandomList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
|
||||
return WeightedRandomList.create();
|
||||
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.v1_21_R3.headless.RegionStorage;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
@@ -665,44 +667,42 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public IRegionStorage createRegionStorage(Engine engine) {
|
||||
return new RegionStorage(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
return inject(this::supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
public Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.lookupOrThrow(Registries.LEVEL_STEM);
|
||||
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
|
||||
)
|
||||
);
|
||||
|
||||
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
return new Pair<>(
|
||||
injected.size(),
|
||||
new AutoClosing(() -> {
|
||||
closing.close();
|
||||
field.set(reg, old);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -714,9 +714,31 @@ public class NMSBinding implements INMSBinding {
|
||||
return overworld || nether || end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().L.customDimensions = null;
|
||||
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
|
||||
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, transformer.apply(old));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
@@ -740,7 +762,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
if (copy) copy(fake, access.lookupOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.volmit.iris.core.nms.v1_21_R3.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.BiomeBaseInjector;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.data.IrisCustomData;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import lombok.Data;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.block.CraftBlockType;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.util.CraftMagicNumbers;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Data
|
||||
public final class DirectTerrainChunk implements SerializableChunk {
|
||||
private final ProtoChunk access;
|
||||
private final int minHeight, maxHeight;
|
||||
|
||||
public DirectTerrainChunk(ProtoChunk access) {
|
||||
this.access = access;
|
||||
this.minHeight = access.getMinY();
|
||||
this.maxHeight = access.getMaxY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeBaseInjector getBiomeBaseInjector() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int z) {
|
||||
return getBiome(x, 0, z);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
|
||||
return CraftBiome.minecraftHolderToBukkit(access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int z, Biome bio) {
|
||||
for (int y = minHeight; y < maxHeight; y += 4) {
|
||||
setBiome(x, y, z, bio);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int y, int z, Biome bio) {
|
||||
if (y < minHeight || y > maxHeight) return;
|
||||
access.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, Material material) {
|
||||
this.setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, MaterialData material) {
|
||||
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, BlockData blockData) {
|
||||
if (blockData == null) {
|
||||
Iris.error("NULL BD");
|
||||
}
|
||||
if (blockData instanceof IrisCustomData data)
|
||||
blockData = data.getBase();
|
||||
if (!(blockData instanceof CraftBlockData craftBlockData))
|
||||
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
|
||||
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGenerator.ChunkData getRaw() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRaw(ChunkGenerator.ChunkData data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inject(ChunkGenerator.BiomeGrid biome) {
|
||||
|
||||
}
|
||||
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
|
||||
return;
|
||||
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
|
||||
return;
|
||||
|
||||
for (int y = yMin; y < yMax; ++y) {
|
||||
for (int x = xMin; x < xMax; ++x) {
|
||||
for (int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public BlockState getTypeId(int x, int y, int z) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
}
|
||||
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||
return;
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position2 getPos() {
|
||||
return new Position2(access.getPos().x, access.getPos().z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize() {
|
||||
return RegionStorage.serialize(access);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark() {
|
||||
access.setPersistedStatus(ChunkStatus.FULL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.volmit.iris.core.nms.v1_21_R3.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import lombok.NonNull;
|
||||
import lombok.Synchronized;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class Region implements IRegion, Comparable<Region> {
|
||||
private static final RegionStorageInfo info = new RegionStorageInfo("headless", Level.OVERWORLD, "headless");
|
||||
private final RegionFile regionFile;
|
||||
transient long references;
|
||||
transient long lastUsed;
|
||||
|
||||
Region(Path path, Path folder) throws IOException {
|
||||
this.regionFile = new RegionFile(info, path, folder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public boolean exists(int x, int z) {
|
||||
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
|
||||
if (din == null) return false;
|
||||
return !"empty".equals(NbtIo.read(din).getString("Status"));
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public void write(@NonNull SerializableChunk chunk) throws IOException {
|
||||
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
|
||||
NbtIo.write((CompoundTag) chunk.serialize(), dos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
--references;
|
||||
lastUsed = M.ms();
|
||||
}
|
||||
|
||||
public boolean unused() {
|
||||
return references <= 0;
|
||||
}
|
||||
|
||||
public boolean remove() {
|
||||
if (!unused()) return false;
|
||||
try {
|
||||
regionFile.close();
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to close region file");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Region o) {
|
||||
return Long.compare(lastUsed, o.lastUsed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package com.volmit.iris.core.nms.v1_21_R3.headless;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.headless.IRegion;
|
||||
import com.volmit.iris.core.nms.headless.IRegionStorage;
|
||||
import com.volmit.iris.core.nms.headless.SerializableChunk;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.context.ChunkContext;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
||||
import it.unimi.dsi.fastutil.shorts.ShortList;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import net.minecraft.FileUtil;
|
||||
import net.minecraft.Optionull;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.block.CraftBiome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
|
||||
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
|
||||
private final KMap<Long, Region> regions = new KMap<>();
|
||||
private final Path folder;
|
||||
|
||||
private final Engine engine;
|
||||
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
|
||||
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
|
||||
private final RNG biomeRng;
|
||||
private final @Getter int minY;
|
||||
private final @Getter int height;
|
||||
|
||||
private transient boolean closed = false;
|
||||
|
||||
public RegionStorage(Engine engine) {
|
||||
this.engine = engine;
|
||||
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
|
||||
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
|
||||
|
||||
this.minY = engine.getDimension().getMinHeight();
|
||||
this.height = engine.getDimension().getMaxHeight() - minY;
|
||||
|
||||
AtomicInteger failed = new AtomicInteger();
|
||||
var dimKey = engine.getDimension().getLoadKey();
|
||||
for (var biome : engine.getAllBiomes()) {
|
||||
if (!biome.isCustom()) continue;
|
||||
for (var custom : biome.getCustomDerivitives()) {
|
||||
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
|
||||
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
|
||||
failed.incrementAndGet();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (failed.get() > 0) {
|
||||
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
|
||||
}
|
||||
|
||||
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
|
||||
.collect(Collectors.toMap(Function.identity(), CraftBiome::bukkitToMinecraftHolder)));
|
||||
minecraftBiomes.values().removeAll(customBiomes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int x, int z) {
|
||||
try (IRegion region = getRegion(x >> 5, z >> 5, true)) {
|
||||
return region != null && region.exists(x, z);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
|
||||
AtomicReference<IOException> exception = new AtomicReference<>();
|
||||
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
|
||||
trim();
|
||||
|
||||
try {
|
||||
FileUtil.createDirectoriesSafe(this.folder);
|
||||
Path path = folder.resolve("r." + x + "." + z + ".mca");
|
||||
if (existingOnly && !Files.exists(path)) {
|
||||
return null;
|
||||
} else {
|
||||
return new Region(path, this.folder);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception.set(e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
if (region == null) {
|
||||
if (exception.get() != null)
|
||||
throw exception.get();
|
||||
return null;
|
||||
}
|
||||
region.references++;
|
||||
return region;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SerializableChunk createChunk(int x, int z) {
|
||||
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().lookupOrThrow(Registries.BIOME), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
|
||||
if (!(chunk instanceof DirectTerrainChunk tc))
|
||||
return;
|
||||
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (closed) return;
|
||||
|
||||
while (!regions.isEmpty()) {
|
||||
regions.values().removeIf(Region::remove);
|
||||
J.sleep(1);
|
||||
}
|
||||
|
||||
closed = true;
|
||||
customBiomes.clear();
|
||||
minecraftBiomes.clear();
|
||||
}
|
||||
|
||||
private void trim() {
|
||||
int size = regions.size();
|
||||
if (size < 256) return;
|
||||
int remove = size - 255;
|
||||
|
||||
var list = regions.values()
|
||||
.stream()
|
||||
.filter(Region::unused)
|
||||
.sorted()
|
||||
.collect(Collectors.toList())
|
||||
.reversed();
|
||||
|
||||
int skip = list.size() - remove;
|
||||
if (skip > 0) list.subList(0, skip).clear();
|
||||
|
||||
if (list.isEmpty()) return;
|
||||
regions.values().removeIf(r -> list.contains(r) && r.remove());
|
||||
}
|
||||
|
||||
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
|
||||
int m = y - engine.getMinHeight();
|
||||
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
|
||||
if (ib.isCustom()) {
|
||||
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
|
||||
} else {
|
||||
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
|
||||
}
|
||||
}
|
||||
|
||||
private static RegistryAccess registryAccess() {
|
||||
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
|
||||
}
|
||||
|
||||
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
|
||||
return registryAccess().lookupOrThrow(Registries.BIOME).get(ResourceLocation.fromNamespaceAndPath(namespace, path));
|
||||
}
|
||||
|
||||
static CompoundTag serialize(ChunkAccess chunk) {
|
||||
RegistryAccess access = registryAccess();
|
||||
List<SerializableChunkData.SectionData> list = new ArrayList<>();
|
||||
LevelChunkSection[] sections = chunk.getSections();
|
||||
|
||||
int minLightSection = chunk.getMinSectionY() - 1;
|
||||
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
|
||||
for(int y = minLightSection; y < maxLightSection; ++y) {
|
||||
int index = chunk.getSectionIndexFromSectionY(y);
|
||||
if (index < 0 || index >= sections.length) continue;
|
||||
LevelChunkSection section = sections[index].copy();
|
||||
list.add(new SerializableChunkData.SectionData(y, section, null, null));
|
||||
}
|
||||
|
||||
List<CompoundTag> blockEntities = new ArrayList<>(chunk.getBlockEntitiesPos().size());
|
||||
|
||||
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
|
||||
CompoundTag nbt = chunk.getBlockEntityNbtForSaving(blockPos, access);
|
||||
if (nbt != null) {
|
||||
blockEntities.add(nbt);
|
||||
}
|
||||
}
|
||||
Map<Heightmap.Types, long[]> heightMap = new EnumMap<>(Heightmap.Types.class);
|
||||
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
|
||||
if (chunk.getPersistedStatus().heightmapsAfter().contains(entry.getKey())) {
|
||||
heightMap.put(entry.getKey(), entry.getValue().getRawData().clone());
|
||||
}
|
||||
}
|
||||
|
||||
ChunkAccess.PackedTicks packedTicks = chunk.getTicksForSerialization(0);
|
||||
ShortList[] postProcessing = Arrays.stream(chunk.getPostProcessing()).map((shortlist) -> shortlist != null ? new ShortArrayList(shortlist) : null).toArray(ShortList[]::new);
|
||||
CompoundTag structureData = new CompoundTag();
|
||||
structureData.put("starts", new CompoundTag());
|
||||
structureData.put("References", new CompoundTag());
|
||||
|
||||
CompoundTag persistentDataContainer = null;
|
||||
if (!chunk.persistentDataContainer.isEmpty()) {
|
||||
persistentDataContainer = chunk.persistentDataContainer.toTagCompound();
|
||||
}
|
||||
|
||||
return new SerializableChunkData(access.lookupOrThrow(Registries.BIOME), chunk.getPos(),
|
||||
chunk.getMinSectionY(), 0, chunk.getInhabitedTime(), chunk.getPersistedStatus(),
|
||||
Optionull.map(chunk.getBlendingData(), BlendingData::pack), chunk.getBelowZeroRetrogen(),
|
||||
chunk.getUpgradeData().copy(), null, heightMap, packedTicks, postProcessing,
|
||||
chunk.isLightCorrect(), list, new ArrayList<>(), blockEntities, structureData, persistentDataContainer)
|
||||
.write();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user