mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-17 12:23:48 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eff598d005 | |||
| d86ec7b1cd | |||
| 0d9a45dfd9 | |||
| 8a55bbfd20 | |||
| 5934c43b70 | |||
| 11567b13d3 | |||
| 2087ba88b1 | |||
| e9d1b9f18e | |||
| 6e84d38680 | |||
| 22622f6e8a | |||
| 735203aa95 | |||
| 013bc365a9 | |||
| c2dfbac641 | |||
| 7d472c0b13 | |||
| 5b4ab0a3c1 | |||
| e8dd81b014 | |||
| d32cc281e3 | |||
| 2ff6b59271 | |||
| 3ff87566f5 |
+11
-27
@@ -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.8-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 {
|
||||
|
||||
@@ -624,22 +624,12 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
Iris.warn("=");
|
||||
Iris.warn("============================================");
|
||||
}
|
||||
|
||||
try {
|
||||
Class.forName("io.papermc.paper.configuration.PaperConfigurations");
|
||||
} catch (ClassNotFoundException e) {
|
||||
Iris.info(C.RED + "Iris requires paper or above to function properly..");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Class.forName("org.purpurmc.purpur.PurpurConfig");
|
||||
} catch (ClassNotFoundException e) {
|
||||
if (!instance.getServer().getVersion().contains("Purpur")) {
|
||||
passed = false;
|
||||
Iris.info("We recommend using Purpur for the best experience with Iris.");
|
||||
Iris.info("Purpur is a fork of Paper that is optimized for performance and stability.");
|
||||
Iris.info("Plugins that work on Spigot / Paper work on Purpur.");
|
||||
Iris.info("You can download it here: https://purpurmc.org");
|
||||
return false;
|
||||
}
|
||||
return passed;
|
||||
}
|
||||
@@ -864,6 +854,13 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
Iris.info("Server type & version: " + C.RED + Bukkit.getVersion());
|
||||
} else { Iris.info("Server type & version: " + Bukkit.getVersion()); }
|
||||
Iris.info("Java: " + getJava());
|
||||
if (!instance.getServer().getVersion().contains("Purpur")) {
|
||||
if (instance.getServer().getVersion().contains("Spigot") && instance.getServer().getVersion().contains("Bukkit")) {
|
||||
Iris.info(C.RED + " Iris requires paper or above to function properly..");
|
||||
} else {
|
||||
Iris.info(C.YELLOW + "Purpur is recommended to use with iris.");
|
||||
}
|
||||
}
|
||||
if (getHardware.getProcessMemory() < 5999) {
|
||||
Iris.warn("6GB+ Ram is recommended");
|
||||
Iris.warn("Process Memory: " + getHardware.getProcessMemory() + " MB");
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.json.JSONException;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -44,7 +45,6 @@ public class IrisSettings {
|
||||
private IrisSettingsStudio studio = new IrisSettingsStudio();
|
||||
private IrisSettingsPerformance performance = new IrisSettingsPerformance();
|
||||
private IrisSettingsUpdater updater = new IrisSettingsUpdater();
|
||||
private IrisSettingsPregen pregen = new IrisSettingsPregen();
|
||||
|
||||
public static int getThreadCount(int c) {
|
||||
return switch (c) {
|
||||
@@ -130,26 +130,11 @@ public class IrisSettings {
|
||||
public boolean markerEntitySpawningSystem = true;
|
||||
public boolean effectSystem = true;
|
||||
public boolean worldEditWandCUI = true;
|
||||
public boolean globalPregenCache = true;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsConcurrency {
|
||||
public int parallelism = -1;
|
||||
public int worldGenParallelism = -1;
|
||||
|
||||
public int getWorldGenThreads() {
|
||||
return getThreadCount(worldGenParallelism);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsPregen {
|
||||
public boolean useCacheByDefault = true;
|
||||
public boolean useHighPriority = false;
|
||||
public boolean useVirtualThreads = false;
|
||||
public boolean useTicketQueue = false;
|
||||
public int maxConcurrency = 256;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
@@ -71,12 +71,10 @@ public class ServerConfigurator {
|
||||
f.load(spigotConfig);
|
||||
long tt = f.getLong("settings.timeout-time");
|
||||
|
||||
long spigotTimeout = TimeUnit.MINUTES.toSeconds(5);
|
||||
|
||||
if (tt < spigotTimeout) {
|
||||
Iris.warn("Updating spigot.yml timeout-time: " + tt + " -> " + spigotTimeout + " (5 minutes)");
|
||||
if (tt < TimeUnit.MINUTES.toSeconds(5)) {
|
||||
Iris.warn("Updating spigot.yml timeout-time: " + tt + " -> " + TimeUnit.MINUTES.toSeconds(20) + " (5 minutes)");
|
||||
Iris.warn("You can disable this change (autoconfigureServer) in Iris settings, then change back the value.");
|
||||
f.set("settings.timeout-time", spigotTimeout);
|
||||
f.set("settings.timeout-time", TimeUnit.MINUTES.toSeconds(5));
|
||||
f.save(spigotConfig);
|
||||
}
|
||||
}
|
||||
@@ -86,11 +84,10 @@ public class ServerConfigurator {
|
||||
f.load(spigotConfig);
|
||||
long tt = f.getLong("watchdog.early-warning-delay");
|
||||
|
||||
long watchdog = TimeUnit.MINUTES.toMillis(3);
|
||||
if (tt < watchdog) {
|
||||
Iris.warn("Updating paper.yml watchdog early-warning-delay: " + tt + " -> " + watchdog + " (3 minutes)");
|
||||
if (tt < TimeUnit.MINUTES.toMillis(3)) {
|
||||
Iris.warn("Updating paper.yml watchdog early-warning-delay: " + tt + " -> " + TimeUnit.MINUTES.toMillis(15) + " (3 minutes)");
|
||||
Iris.warn("You can disable this change (autoconfigureServer) in Iris settings, then change back the value.");
|
||||
f.set("watchdog.early-warning-delay", watchdog);
|
||||
f.set("watchdog.early-warning-delay", TimeUnit.MINUTES.toMillis(3));
|
||||
f.save(spigotConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 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.commands;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.pregenerator.DeepSearchPregenerator;
|
||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||
import com.volmit.iris.core.pregenerator.TurboPregenerator;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.util.data.Dimension;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@Decree(name = "DeepSearch", aliases = "search", description = "Pregenerate your Iris worlds!")
|
||||
public class CommandDeepSearch implements DecreeExecutor {
|
||||
public String worldName;
|
||||
@Decree(description = "DeepSearch a world")
|
||||
public void start(
|
||||
@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
|
||||
) {
|
||||
|
||||
worldName = world.getName();
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File TurboFile = new File(worldDirectory, "DeepSearch.json");
|
||||
if (TurboFile.exists()) {
|
||||
if (DeepSearchPregenerator.getInstance() != null) {
|
||||
sender().sendMessage(C.BLUE + "DeepSearch is already in progress");
|
||||
Iris.info(C.YELLOW + "DeepSearch is already in progress");
|
||||
return;
|
||||
} else {
|
||||
try {
|
||||
TurboFile.delete();
|
||||
} catch (Exception e){
|
||||
Iris.error("Failed to delete the old instance file of DeepSearch!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (sender().isPlayer() && access() == 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.");
|
||||
}
|
||||
|
||||
DeepSearchPregenerator.DeepSearchJob DeepSearchJob = DeepSearchPregenerator.DeepSearchJob.builder()
|
||||
.world(world)
|
||||
.radiusBlocks(radius)
|
||||
.position(0)
|
||||
.build();
|
||||
|
||||
File SearchGenFile = new File(worldDirectory, "DeepSearch.json");
|
||||
DeepSearchPregenerator pregenerator = new DeepSearchPregenerator(DeepSearchJob, SearchGenFile);
|
||||
pregenerator.start();
|
||||
|
||||
String msg = C.GREEN + "DeepSearch started in " + C.GOLD + worldName + 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 = "Stop the active DeepSearch task", aliases = "x")
|
||||
public void stop(@Param(aliases = "world", description = "The world to pause") World world) throws IOException {
|
||||
DeepSearchPregenerator DeepSearchInstance = DeepSearchPregenerator.getInstance();
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File turboFile = new File(worldDirectory, "DeepSearch.json");
|
||||
|
||||
if (DeepSearchInstance != null) {
|
||||
DeepSearchInstance.shutdownInstance(world);
|
||||
sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName());
|
||||
} else if (turboFile.exists() && turboFile.delete()) {
|
||||
sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName());
|
||||
} else if (turboFile.exists()) {
|
||||
Iris.error("Failed to delete the old instance file of Turbo Pregen!");
|
||||
} else {
|
||||
sender().sendMessage(C.YELLOW + "No active pregeneration tasks to stop");
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"})
|
||||
public void pause(
|
||||
@Param(aliases = "world", description = "The world to pause")
|
||||
World world
|
||||
) {
|
||||
if (TurboPregenerator.getInstance() != null) {
|
||||
TurboPregenerator.setPausedTurbo(world);
|
||||
sender().sendMessage(C.GREEN + "Paused/unpaused Turbo Pregen, now: " + (TurboPregenerator.isPausedTurbo(world) ? "Paused" : "Running") + ".");
|
||||
} else {
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File TurboFile = new File(worldDirectory, "DeepSearch.json");
|
||||
if (TurboFile.exists()){
|
||||
TurboPregenerator.loadTurboGenerator(world.getName());
|
||||
sender().sendMessage(C.YELLOW + "Started DeepSearch back up!");
|
||||
} else {
|
||||
sender().sendMessage(C.YELLOW + "No active DeepSearch tasks to pause/unpause.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.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,7 +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.M;
|
||||
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;
|
||||
@@ -49,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;
|
||||
@@ -63,7 +67,6 @@ import java.util.zip.GZIPOutputStream;
|
||||
@Decree(name = "Developer", origin = DecreeOrigin.BOTH, description = "Iris World Manager", aliases = {"dev"})
|
||||
public class CommandDeveloper implements DecreeExecutor {
|
||||
private CommandTurboPregen turboPregen;
|
||||
private CommandLazyPregen lazyPregen;
|
||||
private CommandUpdater updater;
|
||||
|
||||
@Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, sync = true)
|
||||
@@ -117,42 +120,6 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
Iris.info("-------------------------");
|
||||
}
|
||||
|
||||
@Decree(description = "Test")
|
||||
public void dumpThreads() {
|
||||
try {
|
||||
File fi = Iris.instance.getDataFile("dump", "td-" + new java.sql.Date(M.ms()) + ".txt");
|
||||
FileOutputStream fos = new FileOutputStream(fi);
|
||||
Map<Thread, StackTraceElement[]> f = Thread.getAllStackTraces();
|
||||
PrintWriter pw = new PrintWriter(fos);
|
||||
|
||||
pw.println(Thread.activeCount() + "/" + f.size());
|
||||
var run = Runtime.getRuntime();
|
||||
pw.println("Memory:");
|
||||
pw.println("\tMax: " + run.maxMemory());
|
||||
pw.println("\tTotal: " + run.totalMemory());
|
||||
pw.println("\tFree: " + run.freeMemory());
|
||||
pw.println("\tUsed: " + (run.totalMemory() - run.freeMemory()));
|
||||
|
||||
for (Thread i : f.keySet()) {
|
||||
pw.println("========================================");
|
||||
pw.println("Thread: '" + i.getName() + "' ID: " + i.getId() + " STATUS: " + i.getState().name());
|
||||
|
||||
for (StackTraceElement j : f.get(i)) {
|
||||
pw.println(" @ " + j.toString());
|
||||
}
|
||||
|
||||
pw.println("========================================");
|
||||
pw.println();
|
||||
pw.println();
|
||||
}
|
||||
|
||||
pw.close();
|
||||
Iris.info("DUMPED! See " + fi.getAbsolutePath());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Test")
|
||||
public void benchmarkMantle(
|
||||
@Param(description = "The world to bench", aliases = {"world"})
|
||||
@@ -178,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")
|
||||
@@ -251,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
@@ -39,9 +39,7 @@ public class CommandPregen implements DecreeExecutor {
|
||||
@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,
|
||||
@Param(description = "Open the Iris pregen gui", defaultValue = "true")
|
||||
boolean gui
|
||||
Vector center
|
||||
) {
|
||||
try {
|
||||
if (sender().isPlayer() && access() == null) {
|
||||
@@ -52,7 +50,7 @@ public class CommandPregen implements DecreeExecutor {
|
||||
IrisToolbelt.pregenerate(PregenTask
|
||||
.builder()
|
||||
.center(new Position2(center.getBlockX(), center.getBlockZ()))
|
||||
.gui(gui)
|
||||
.gui(true)
|
||||
.radiusX(radius)
|
||||
.radiusZ(radius)
|
||||
.build(), world);
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 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.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;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.DecreeOrigin;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.mantle.TectonicPlate;
|
||||
import com.volmit.iris.util.misc.Hastebin;
|
||||
import com.volmit.iris.util.misc.Platform;
|
||||
import com.volmit.iris.util.misc.getHardware;
|
||||
import com.volmit.iris.util.nbt.mca.MCAFile;
|
||||
import com.volmit.iris.util.nbt.mca.MCAUtil;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import net.jpountz.lz4.LZ4BlockInputStream;
|
||||
import net.jpountz.lz4.LZ4BlockOutputStream;
|
||||
import net.jpountz.lz4.LZ4FrameInputStream;
|
||||
import net.jpountz.lz4.LZ4FrameOutputStream;
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import oshi.SystemInfo;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
@Decree(name = "Support", origin = DecreeOrigin.BOTH, description = "Iris World Manager", aliases = {"support"})
|
||||
public class CommandSupport implements DecreeExecutor {
|
||||
|
||||
@Decree(description = "report")
|
||||
public void report() {
|
||||
try {
|
||||
if (sender().isPlayer()) sender().sendMessage(C.GOLD + "Creating report..");
|
||||
if (!sender().isPlayer()) Iris.info(C.GOLD + "Creating report..");
|
||||
Hastebin.enviornment(sender());
|
||||
|
||||
} catch (Exception e) {
|
||||
Iris.info(C.RED + "Something went wrong: ");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,6 @@ import java.awt.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
@@ -66,7 +64,6 @@ public class PregeneratorJob implements PregenListener {
|
||||
private final Position2 max;
|
||||
private final ChronoLatch cl = new ChronoLatch(TimeUnit.MINUTES.toMillis(1));
|
||||
private final Engine engine;
|
||||
private final ExecutorService service;
|
||||
private JFrame frame;
|
||||
private PregenRenderer renderer;
|
||||
private int rgc = 0;
|
||||
@@ -99,7 +96,6 @@ public class PregeneratorJob implements PregenListener {
|
||||
}, "Iris Pregenerator");
|
||||
t.setPriority(Thread.MIN_PRIORITY);
|
||||
t.start();
|
||||
service = Executors.newVirtualThreadPerTaskExecutor();
|
||||
}
|
||||
|
||||
public static boolean shutdownInstance() {
|
||||
@@ -223,10 +219,10 @@ public class PregeneratorJob implements PregenListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) {
|
||||
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method) {
|
||||
info = new String[]{
|
||||
(paused() ? "PAUSED" : (saving ? "Saving... " : "Generating")) + " " + Form.f(generated) + " of " + Form.f(totalChunks) + " (" + Form.pc(percent, 0) + " Complete)",
|
||||
"Speed: " + (cached ? "Cached " : "") + Form.f(chunksPerSecond, 0) + " Chunks/s, " + Form.f(regionsPerMinute, 1) + " Regions/m, " + Form.f(chunksPerMinute, 0) + " Chunks/m",
|
||||
"Speed: " + Form.f(chunksPerSecond, 0) + " Chunks/s, " + Form.f(regionsPerMinute, 1) + " Regions/m, " + Form.f(chunksPerMinute, 0) + " Chunks/m",
|
||||
Form.duration(eta, 2) + " Remaining " + " (" + Form.duration(elapsed, 2) + " Elapsed)",
|
||||
"Generation Method: " + method,
|
||||
"Memory: " + Form.memSize(monitor.getUsedBytes(), 2) + " (" + Form.pc(monitor.getUsagePercent(), 0) + ") Pressure: " + Form.memSize(monitor.getPressure(), 0) + "/s",
|
||||
@@ -244,16 +240,13 @@ public class PregeneratorJob implements PregenListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkGenerated(int x, int z, boolean cached) {
|
||||
if (renderer == null || frame == null || !frame.isVisible()) return;
|
||||
service.submit(() -> {
|
||||
if (engine != null) {
|
||||
draw(x, z, engine.draw((x << 4) + 8, (z << 4) + 8));
|
||||
return;
|
||||
}
|
||||
public void onChunkGenerated(int x, int z) {
|
||||
if (engine != null) {
|
||||
draw(x, z, engine.draw((x << 4) + 8, (z << 4) + 8));
|
||||
return;
|
||||
}
|
||||
|
||||
draw(x, z, COLOR_GENERATED);
|
||||
});
|
||||
draw(x, z, COLOR_GENERATED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -313,7 +306,6 @@ public class PregeneratorJob implements PregenListener {
|
||||
close();
|
||||
instance = null;
|
||||
whenDone.forEach(Runnable::run);
|
||||
service.shutdownNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -31,33 +31,28 @@ import com.volmit.iris.util.math.RollingSequence;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.Looper;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
|
||||
public class IrisPregenerator {
|
||||
private static final double INVALID = 9223372036854775807d;
|
||||
private final PregenTask task;
|
||||
private final PregeneratorMethod generator;
|
||||
private final PregenListener listener;
|
||||
private final Looper ticker;
|
||||
private final AtomicBoolean paused;
|
||||
private final AtomicBoolean shutdown;
|
||||
private final RollingSequence cachedPerSecond;
|
||||
private final RollingSequence chunksPerSecond;
|
||||
private final RollingSequence chunksPerMinute;
|
||||
private final RollingSequence regionsPerMinute;
|
||||
private final KList<Integer> chunksPerSecondHistory;
|
||||
private final AtomicLong generated;
|
||||
private final AtomicLong generatedLast;
|
||||
private final AtomicLong generatedLastMinute;
|
||||
private final AtomicLong cached;
|
||||
private final AtomicLong cachedLast;
|
||||
private final AtomicLong cachedLastMinute;
|
||||
private final AtomicLong totalChunks;
|
||||
private static AtomicInteger generated;
|
||||
private final AtomicInteger generatedLast;
|
||||
private final AtomicInteger generatedLastMinute;
|
||||
private static AtomicInteger totalChunks;
|
||||
private final AtomicLong startTime;
|
||||
private final ChronoLatch minuteLatch;
|
||||
private final AtomicReference<String> currentGeneratorMethod;
|
||||
@@ -79,71 +74,46 @@ public class IrisPregenerator {
|
||||
net = new KSet<>();
|
||||
currentGeneratorMethod = new AtomicReference<>("Void");
|
||||
minuteLatch = new ChronoLatch(60000, false);
|
||||
cachedPerSecond = new RollingSequence(5);
|
||||
chunksPerSecond = new RollingSequence(10);
|
||||
chunksPerMinute = new RollingSequence(10);
|
||||
regionsPerMinute = new RollingSequence(10);
|
||||
chunksPerSecondHistory = new KList<>();
|
||||
generated = new AtomicLong(0);
|
||||
generatedLast = new AtomicLong(0);
|
||||
generatedLastMinute = new AtomicLong(0);
|
||||
cached = new AtomicLong();
|
||||
cachedLast = new AtomicLong(0);
|
||||
cachedLastMinute = new AtomicLong(0);
|
||||
totalChunks = new AtomicLong(0);
|
||||
generated = new AtomicInteger(0);
|
||||
generatedLast = new AtomicInteger(0);
|
||||
generatedLastMinute = new AtomicInteger(0);
|
||||
totalChunks = new AtomicInteger(0);
|
||||
task.iterateAllChunks((_a, _b) -> totalChunks.incrementAndGet());
|
||||
startTime = new AtomicLong(M.ms());
|
||||
ticker = new Looper() {
|
||||
@Override
|
||||
protected long loop() {
|
||||
long eta = computeETA();
|
||||
|
||||
long secondCached = cached.get() - cachedLast.get();
|
||||
cachedLast.set(cached.get());
|
||||
cachedPerSecond.put(secondCached);
|
||||
|
||||
long secondGenerated = generated.get() - generatedLast.get() - secondCached;
|
||||
int secondGenerated = generated.get() - generatedLast.get();
|
||||
generatedLast.set(generated.get());
|
||||
if (secondCached == 0 || secondGenerated != 0) {
|
||||
chunksPerSecond.put(secondGenerated);
|
||||
chunksPerSecondHistory.add((int) secondGenerated);
|
||||
}
|
||||
chunksPerSecond.put(secondGenerated);
|
||||
chunksPerSecondHistory.add(secondGenerated);
|
||||
|
||||
if (minuteLatch.flip()) {
|
||||
long minuteCached = cached.get() - cachedLastMinute.get();
|
||||
cachedLastMinute.set(cached.get());
|
||||
|
||||
long minuteGenerated = generated.get() - generatedLastMinute.get() - minuteCached;
|
||||
int minuteGenerated = generated.get() - generatedLastMinute.get();
|
||||
generatedLastMinute.set(generated.get());
|
||||
if (minuteCached == 0 || minuteGenerated != 0) {
|
||||
chunksPerMinute.put(minuteGenerated);
|
||||
regionsPerMinute.put((double) minuteGenerated / 1024D);
|
||||
}
|
||||
chunksPerMinute.put(minuteGenerated);
|
||||
regionsPerMinute.put((double) minuteGenerated / 1024D);
|
||||
}
|
||||
boolean cached = cachedPerSecond.getAverage() != 0;
|
||||
|
||||
listener.onTick(
|
||||
cached ? cachedPerSecond.getAverage() : chunksPerSecond.getAverage(),
|
||||
chunksPerMinute.getAverage(),
|
||||
listener.onTick(chunksPerSecond.getAverage(), chunksPerMinute.getAverage(),
|
||||
regionsPerMinute.getAverage(),
|
||||
(double) generated.get() / (double) totalChunks.get(), generated.get(),
|
||||
totalChunks.get(),
|
||||
totalChunks.get() - generated.get(), eta, M.ms() - startTime.get(), currentGeneratorMethod.get(),
|
||||
cached);
|
||||
(double) generated.get() / (double) totalChunks.get(),
|
||||
generated.get(), totalChunks.get(),
|
||||
totalChunks.get() - generated.get(),
|
||||
eta, M.ms() - startTime.get(), currentGeneratorMethod.get());
|
||||
|
||||
if (cl.flip()) {
|
||||
double percentage = ((double) generated.get() / (double) totalChunks.get()) * 100;
|
||||
|
||||
Iris.info("%s: %s of %s (%.0f%%), %s/s ETA: %s",
|
||||
IrisPackBenchmarking.benchmarkInProgress ? "Benchmarking" : "Pregen",
|
||||
Form.f(generated.get()),
|
||||
Form.f(totalChunks.get()),
|
||||
percentage,
|
||||
cached ?
|
||||
"Cached " + Form.f((int) cachedPerSecond.getAverage()) :
|
||||
Form.f((int) chunksPerSecond.getAverage()),
|
||||
Form.duration(eta, 2)
|
||||
);
|
||||
if (!IrisPackBenchmarking.benchmarkInProgress) {
|
||||
Iris.info("Pregen: " + Form.f(generated.get()) + " of " + Form.f(totalChunks.get()) + " (%.0f%%) " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration(eta, 2), percentage);
|
||||
} else {
|
||||
Iris.info("Benchmarking: " + Form.f(generated.get()) + " of " + Form.f(totalChunks.get()) + " (%.0f%%) " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration(eta, 2), percentage);
|
||||
}
|
||||
}
|
||||
return 1000;
|
||||
}
|
||||
@@ -151,13 +121,12 @@ public class IrisPregenerator {
|
||||
}
|
||||
|
||||
private long computeETA() {
|
||||
double d = (long) (totalChunks.get() > 1024 ? // Generated chunks exceed 1/8th of total?
|
||||
return (long) (totalChunks.get() > 1024 ? // Generated chunks exceed 1/8th of total?
|
||||
// If yes, use smooth function (which gets more accurate over time since its less sensitive to outliers)
|
||||
((totalChunks.get() - generated.get() - cached.get()) * ((double) (M.ms() - startTime.get()) / ((double) generated.get() - cached.get()))) :
|
||||
((totalChunks.get() - generated.get()) * ((double) (M.ms() - startTime.get()) / (double) generated.get())) :
|
||||
// If no, use quick function (which is less accurate over time but responds better to the initial delay)
|
||||
((totalChunks.get() - generated.get() - cached.get()) / chunksPerSecond.getAverage()) * 1000
|
||||
((totalChunks.get() - generated.get()) / chunksPerSecond.getAverage()) * 1000
|
||||
);
|
||||
return Double.isFinite(d) && d != INVALID ? (long) d : 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -169,10 +138,8 @@ public class IrisPregenerator {
|
||||
init();
|
||||
ticker.start();
|
||||
checkRegions();
|
||||
var p = PrecisionStopwatch.start();
|
||||
task.iterateRegions((x, z) -> visitRegion(x, z, true));
|
||||
task.iterateRegions((x, z) -> visitRegion(x, z, false));
|
||||
Iris.info("Pregen took " + Form.duration((long) p.getMilliseconds()));
|
||||
shutdown();
|
||||
if (!IrisPackBenchmarking.benchmarkInProgress) {
|
||||
Iris.info(C.IRIS + "Pregen stopped.");
|
||||
@@ -267,8 +234,8 @@ public class IrisPregenerator {
|
||||
private PregenListener listenify(PregenListener listener) {
|
||||
return new PregenListener() {
|
||||
@Override
|
||||
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) {
|
||||
listener.onTick(chunksPerSecond, chunksPerMinute, regionsPerMinute, percent, generated, totalChunks, chunksRemaining, eta, elapsed, method, cached);
|
||||
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method) {
|
||||
listener.onTick(chunksPerSecond, chunksPerMinute, regionsPerMinute, percent, generated, totalChunks, chunksRemaining, eta, elapsed, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -277,10 +244,9 @@ public class IrisPregenerator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkGenerated(int x, int z, boolean c) {
|
||||
listener.onChunkGenerated(x, z, c);
|
||||
public void onChunkGenerated(int x, int z) {
|
||||
listener.onChunkGenerated(x, z);
|
||||
generated.addAndGet(1);
|
||||
if (c) cached.addAndGet(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,15 +19,11 @@
|
||||
package com.volmit.iris.core.pregenerator;
|
||||
|
||||
public interface PregenListener {
|
||||
void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached);
|
||||
void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method);
|
||||
|
||||
void onChunkGenerating(int x, int z);
|
||||
|
||||
default void onChunkGenerated(int x, int z) {
|
||||
onChunkGenerated(x, z, false);
|
||||
}
|
||||
|
||||
void onChunkGenerated(int x, int z, boolean cached);
|
||||
void onChunkGenerated(int x, int z);
|
||||
|
||||
void onRegionGenerated(int x, int z);
|
||||
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
package com.volmit.iris.core.pregenerator.cache;
|
||||
|
||||
import com.volmit.iris.util.documentation.ChunkCoordinates;
|
||||
import com.volmit.iris.util.documentation.RegionCoordinates;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface PregenCache {
|
||||
default boolean isThreadSafe() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ChunkCoordinates
|
||||
boolean isChunkCached(int x, int z);
|
||||
|
||||
@RegionCoordinates
|
||||
boolean isRegionCached(int x, int z);
|
||||
|
||||
@ChunkCoordinates
|
||||
void cacheChunk(int x, int z);
|
||||
|
||||
@RegionCoordinates
|
||||
void cacheRegion(int x, int z);
|
||||
|
||||
void write();
|
||||
|
||||
static PregenCache create(File directory) {
|
||||
if (directory == null) return EMPTY;
|
||||
return new PregenCacheImpl(directory);
|
||||
}
|
||||
|
||||
default PregenCache sync() {
|
||||
if (isThreadSafe()) return this;
|
||||
return new SynchronizedCache(this);
|
||||
}
|
||||
|
||||
PregenCache EMPTY = new PregenCache() {
|
||||
@Override
|
||||
public boolean isThreadSafe() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChunkCached(int x, int z) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegionCached(int x, int z) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cacheChunk(int x, int z) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cacheRegion(int x, int z) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write() {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
-215
@@ -1,215 +0,0 @@
|
||||
package com.volmit.iris.core.pregenerator.cache;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.github.benmanes.caffeine.cache.RemovalCause;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.util.data.Varint;
|
||||
import com.volmit.iris.util.documentation.ChunkCoordinates;
|
||||
import com.volmit.iris.util.documentation.RegionCoordinates;
|
||||
import com.volmit.iris.util.parallel.HyperLock;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.jpountz.lz4.LZ4BlockInputStream;
|
||||
import net.jpountz.lz4.LZ4BlockOutputStream;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
import java.io.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@NotThreadSafe
|
||||
@RequiredArgsConstructor
|
||||
class PregenCacheImpl implements PregenCache {
|
||||
private static final int SIZE = 32;
|
||||
private final File directory;
|
||||
private final HyperLock hyperLock = new HyperLock(SIZE * 2, true);
|
||||
private final LoadingCache<Pos, Plate> cache = Caffeine.newBuilder()
|
||||
.expireAfterAccess(10, TimeUnit.SECONDS)
|
||||
.maximumSize(SIZE)
|
||||
.removalListener(this::onRemoval)
|
||||
.evictionListener(this::onRemoval)
|
||||
.build(this::load);
|
||||
|
||||
@ChunkCoordinates
|
||||
public boolean isChunkCached(int x, int z) {
|
||||
var plate = cache.get(new Pos(x >> 10, z >> 10));
|
||||
if (plate == null) return false;
|
||||
return plate.isCached((x >> 5) & 31, (z >> 5) & 31, r -> r.isCached(x & 31, z & 31));
|
||||
}
|
||||
|
||||
@RegionCoordinates
|
||||
public boolean isRegionCached(int x, int z) {
|
||||
var plate = cache.get(new Pos(x >> 5, z >> 5));
|
||||
if (plate == null) return false;
|
||||
return plate.isCached(x & 31, z & 31, Region::isCached);
|
||||
}
|
||||
|
||||
@ChunkCoordinates
|
||||
public void cacheChunk(int x, int z) {
|
||||
var plate = cache.get(new Pos(x >> 10, z >> 10));
|
||||
plate.cache((x >> 5) & 31, (z >> 5) & 31, r -> r.cache(x & 31, z & 31));
|
||||
}
|
||||
|
||||
@RegionCoordinates
|
||||
public void cacheRegion(int x, int z) {
|
||||
var plate = cache.get(new Pos(x >> 5, z >> 5));
|
||||
plate.cache(x & 31, z & 31, Region::cache);
|
||||
}
|
||||
|
||||
public void write() {
|
||||
cache.asMap().values().forEach(this::write);
|
||||
}
|
||||
|
||||
private Plate load(Pos key) {
|
||||
hyperLock.lock(key.x, key.z);
|
||||
try {
|
||||
File file = fileForPlate(key);
|
||||
if (!file.exists()) return new Plate(key);
|
||||
try (var in = new DataInputStream(new LZ4BlockInputStream(new FileInputStream(file)))) {
|
||||
return new Plate(key, in);
|
||||
} catch (IOException e){
|
||||
Iris.error("Failed to read pregen cache " + file);
|
||||
e.printStackTrace();
|
||||
return new Plate(key);
|
||||
}
|
||||
} finally {
|
||||
hyperLock.unlock(key.x, key.z);
|
||||
}
|
||||
}
|
||||
|
||||
private void write(Plate plate) {
|
||||
hyperLock.lock(plate.pos.x, plate.pos.z);
|
||||
try {
|
||||
File file = fileForPlate(plate.pos);
|
||||
try (var out = new DataOutputStream(new LZ4BlockOutputStream(new FileOutputStream(file)))) {
|
||||
plate.write(out);
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to write pregen cache " + file);
|
||||
e.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
hyperLock.unlock(plate.pos.x, plate.pos.z);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRemoval(@Nullable Pos key, @Nullable Plate plate, RemovalCause cause) {
|
||||
if (plate == null) return;
|
||||
write(plate);
|
||||
}
|
||||
|
||||
private File fileForPlate(Pos pos) {
|
||||
if (!directory.exists() && !directory.mkdirs())
|
||||
throw new IllegalStateException("Cannot create directory: " + directory.getAbsolutePath());
|
||||
return new File(directory, "c." + pos.x + "." + pos.z + ".lz4b");
|
||||
}
|
||||
|
||||
private static class Plate {
|
||||
private final Pos pos;
|
||||
private short count;
|
||||
private Region[] regions;
|
||||
|
||||
public Plate(Pos pos) {
|
||||
this.pos = pos;
|
||||
count = 0;
|
||||
regions = new Region[1024];
|
||||
}
|
||||
|
||||
public Plate(Pos pos, DataInput in) throws IOException {
|
||||
this.pos = pos;
|
||||
count = (short) Varint.readSignedVarInt(in);
|
||||
if (count == 1024) return;
|
||||
regions = new Region[1024];
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
if (in.readBoolean()) continue;
|
||||
regions[i] = new Region(in);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCached(int x, int z, Predicate<Region> predicate) {
|
||||
if (count == 1024) return true;
|
||||
Region region = regions[x * 32 + z];
|
||||
if (region == null) return false;
|
||||
return predicate.test(region);
|
||||
}
|
||||
|
||||
public void cache(int x, int z, Predicate<Region> predicate) {
|
||||
if (count == 1024) return;
|
||||
Region region = regions[x * 32 + z];
|
||||
if (region == null) regions[x * 32 + z] = region = new Region();
|
||||
if (predicate.test(region)) count++;
|
||||
}
|
||||
|
||||
public void write(DataOutput out) throws IOException {
|
||||
Varint.writeSignedVarInt(count, out);
|
||||
if (count == 1024) return;
|
||||
for (Region region : regions) {
|
||||
out.writeBoolean(region == null);
|
||||
if (region == null) continue;
|
||||
region.write(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Region {
|
||||
private short count;
|
||||
private long[] words;
|
||||
|
||||
public Region() {
|
||||
count = 0;
|
||||
words = new long[64];
|
||||
}
|
||||
|
||||
public Region(DataInput in) throws IOException {
|
||||
count = (short) Varint.readSignedVarInt(in);
|
||||
if (count == 1024) return;
|
||||
words = new long[64];
|
||||
for (int i = 0; i < 64; i++) {
|
||||
words[i] = Varint.readUnsignedVarLong(in);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean cache() {
|
||||
if (count == 1024) return false;
|
||||
count = 1024;
|
||||
words = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean cache(int x, int z) {
|
||||
if (count == 1024) return false;
|
||||
|
||||
int i = x * 32 + z;
|
||||
int w = i >> 6;
|
||||
long b = 1L << (i & 63);
|
||||
|
||||
var cur = (words[w] & b) != 0;
|
||||
if (cur) return false;
|
||||
|
||||
if (++count == 1024) {
|
||||
words = null;
|
||||
return true;
|
||||
} else words[w] |= b;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCached() {
|
||||
return count == 1024;
|
||||
}
|
||||
|
||||
public boolean isCached(int x, int z) {
|
||||
int i = x * 32 + z;
|
||||
return count == 1024 || (words[i >> 6] & 1L << (i & 63)) != 0;
|
||||
}
|
||||
|
||||
public void write(DataOutput out) throws IOException {
|
||||
Varint.writeSignedVarInt(count, out);
|
||||
if (isCached()) return;
|
||||
for (long word : words) {
|
||||
Varint.writeUnsignedVarLong(word, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record Pos(int x, int z) {}
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
package com.volmit.iris.core.pregenerator.cache;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
class SynchronizedCache implements PregenCache {
|
||||
private final PregenCache cache;
|
||||
|
||||
@Override
|
||||
public boolean isThreadSafe() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChunkCached(int x, int z) {
|
||||
synchronized (cache) {
|
||||
return cache.isChunkCached(x, z);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegionCached(int x, int z) {
|
||||
synchronized (cache) {
|
||||
return cache.isRegionCached(x, z);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cacheChunk(int x, int z) {
|
||||
synchronized (cache) {
|
||||
cache.cacheChunk(x, z);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cacheRegion(int x, int z) {
|
||||
synchronized (cache) {
|
||||
cache.cacheRegion(x, z);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write() {
|
||||
synchronized (cache) {
|
||||
cache.write();
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
-120
@@ -19,7 +19,6 @@
|
||||
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.core.tools.IrisToolbelt;
|
||||
@@ -32,32 +31,24 @@ import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
private static final AtomicInteger THREAD_COUNT = new AtomicInteger();
|
||||
private final World world;
|
||||
private final Executor executor;
|
||||
private final MultiBurst burst;
|
||||
private final Semaphore semaphore;
|
||||
private final int threads;
|
||||
private final boolean urgent;
|
||||
private final Map<Chunk, Long> lastUse;
|
||||
|
||||
public AsyncPregenMethod(World world, int unusedThreads) {
|
||||
public AsyncPregenMethod(World world, int threads) {
|
||||
if (!PaperLib.isPaper()) {
|
||||
throw new UnsupportedOperationException("Cannot use PaperAsync on non paper!");
|
||||
}
|
||||
|
||||
this.world = world;
|
||||
this.executor = IrisSettings.get().getPregen().isUseTicketQueue() ? new TicketExecutor() : new ServiceExecutor();
|
||||
this.threads = IrisSettings.get().getPregen().getMaxConcurrency();
|
||||
this.semaphore = new Semaphore(this.threads, true);
|
||||
this.urgent = IrisSettings.get().getPregen().useHighPriority;
|
||||
burst = new MultiBurst("Iris Async Pregen", Thread.MIN_PRIORITY);
|
||||
semaphore = new Semaphore(256);
|
||||
this.lastUse = new KMap<>();
|
||||
}
|
||||
|
||||
@@ -69,18 +60,13 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
return;
|
||||
}
|
||||
|
||||
long minTime = M.ms() - 10_000;
|
||||
lastUse.entrySet().removeIf(i -> {
|
||||
final Chunk chunk = i.getKey();
|
||||
final Long lastUseTime = i.getValue();
|
||||
if (!chunk.isLoaded() || lastUseTime == null)
|
||||
return true;
|
||||
if (lastUseTime < minTime) {
|
||||
chunk.unload();
|
||||
return true;
|
||||
for (Chunk i : new ArrayList<>(lastUse.keySet())) {
|
||||
Long lastUseTime = lastUse.get(i);
|
||||
if (!i.isLoaded() || (lastUseTime != null && M.ms() - lastUseTime >= 10000)) {
|
||||
i.unload();
|
||||
lastUse.remove(i);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
world.save();
|
||||
}).get();
|
||||
} catch (Throwable e) {
|
||||
@@ -88,10 +74,24 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
}
|
||||
}
|
||||
|
||||
private void completeChunk(int x, int z, PregenListener listener) {
|
||||
try {
|
||||
PaperLib.getChunkAtAsync(world, x, z, true).thenAccept((i) -> {
|
||||
lastUse.put(i, M.ms());
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
}).get();
|
||||
} catch (InterruptedException ignored) {
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
semaphore.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
unloadAndSaveAllChunks();
|
||||
increaseWorkerThreads();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,10 +101,9 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
semaphore.acquireUninterruptibly(threads);
|
||||
semaphore.acquireUninterruptibly(256);
|
||||
unloadAndSaveAllChunks();
|
||||
executor.shutdown();
|
||||
resetWorkerThreads();
|
||||
burst.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,7 +129,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
executor.generate(x, z, listener);
|
||||
burst.complete(() -> completeChunk(x, z, listener));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,94 +140,4 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void increaseWorkerThreads() {
|
||||
THREAD_COUNT.updateAndGet(i -> {
|
||||
if (i > 0) return 1;
|
||||
var adjusted = IrisSettings.get().getConcurrency().getWorldGenThreads();
|
||||
try {
|
||||
var field = Class.forName("ca.spottedleaf.moonrise.common.util.MoonriseCommon").getDeclaredField("WORKER_POOL");
|
||||
var pool = field.get(null);
|
||||
var threads = ((Thread[]) pool.getClass().getDeclaredMethod("getCoreThreads").invoke(pool)).length;
|
||||
if (threads >= adjusted) return 0;
|
||||
|
||||
pool.getClass().getDeclaredMethod("adjustThreadCount", int.class).invoke(pool, adjusted);
|
||||
return threads;
|
||||
} catch (Throwable e) {
|
||||
Iris.warn("Failed to increase worker threads, if you are on paper or a fork of it please increase it manually to " + adjusted);
|
||||
Iris.warn("For more information see https://docs.papermc.io/paper/reference/global-configuration#chunk_system_worker_threads");
|
||||
if (e instanceof InvocationTargetException) e.printStackTrace();
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
public static void resetWorkerThreads() {
|
||||
THREAD_COUNT.updateAndGet(i -> {
|
||||
if (i == 0) return 0;
|
||||
try {
|
||||
var field = Class.forName("ca.spottedleaf.moonrise.common.util.MoonriseCommon").getDeclaredField("WORKER_POOL");
|
||||
var pool = field.get(null);
|
||||
var method = pool.getClass().getDeclaredMethod("adjustThreadCount", int.class);
|
||||
method.invoke(pool, i);
|
||||
return 0;
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to reset worker threads");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return i;
|
||||
});
|
||||
}
|
||||
|
||||
private interface Executor {
|
||||
void generate(int x, int z, PregenListener listener);
|
||||
default void shutdown() {}
|
||||
}
|
||||
|
||||
private class ServiceExecutor implements Executor {
|
||||
private final ExecutorService service = IrisSettings.get().getPregen().isUseVirtualThreads() ?
|
||||
Executors.newVirtualThreadPerTaskExecutor() :
|
||||
new MultiBurst("Iris Async Pregen", Thread.MIN_PRIORITY);
|
||||
|
||||
public void generate(int x, int z, PregenListener listener) {
|
||||
service.submit(() -> {
|
||||
try {
|
||||
PaperLib.getChunkAtAsync(world, x, z, true, urgent).thenAccept((i) -> {
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
if (i == null) return;
|
||||
lastUse.put(i, M.ms());
|
||||
}).get();
|
||||
} catch (InterruptedException ignored) {
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
semaphore.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
service.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private class TicketExecutor implements Executor {
|
||||
@Override
|
||||
public void generate(int x, int z, PregenListener listener) {
|
||||
PaperLib.getChunkAtAsync(world, x, z, true, urgent)
|
||||
.exceptionally(e -> {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
})
|
||||
.thenAccept(i -> {
|
||||
semaphore.release();
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
if (i == null) return;
|
||||
lastUse.put(i, M.ms());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
package com.volmit.iris.core.pregenerator.methods;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
|
||||
import com.volmit.iris.core.pregenerator.cache.PregenCache;
|
||||
import com.volmit.iris.core.service.GlobalCacheSVC;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class CachedPregenMethod implements PregeneratorMethod {
|
||||
private final PregeneratorMethod method;
|
||||
private final PregenCache cache;
|
||||
|
||||
public CachedPregenMethod(PregeneratorMethod method, String worldName) {
|
||||
this.method = method;
|
||||
var cache = Iris.service(GlobalCacheSVC.class).get(worldName);
|
||||
if (cache == null) {
|
||||
Iris.debug("Could not find existing cache for " + worldName + " creating fallback");
|
||||
cache = GlobalCacheSVC.createDefault(worldName);
|
||||
}
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
method.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
method.close();
|
||||
cache.write();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
method.save();
|
||||
cache.write();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRegions(int x, int z, PregenListener listener) {
|
||||
return cache.isRegionCached(x, z) || method.supportsRegions(x, z, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod(int x, int z) {
|
||||
return method.getMethod(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRegion(int x, int z, PregenListener listener) {
|
||||
if (cache.isRegionCached(x, z)) {
|
||||
listener.onRegionGenerated(x, z);
|
||||
|
||||
int rX = x << 5, rZ = z << 5;
|
||||
for (int cX = 0; cX < 32; cX++) {
|
||||
for (int cZ = 0; cZ < 32; cZ++) {
|
||||
listener.onChunkGenerated(rX + cX, rZ + cZ, true);
|
||||
listener.onChunkCleaned(rX + cX, rZ + cZ);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
method.generateRegion(x, z, listener);
|
||||
cache.cacheRegion(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateChunk(int x, int z, PregenListener listener) {
|
||||
if (cache.isChunkCached(x, z)) {
|
||||
listener.onChunkGenerated(x, z, true);
|
||||
listener.onChunkCleaned(x, z);
|
||||
return;
|
||||
}
|
||||
method.generateChunk(x, z, listener);
|
||||
cache.cacheChunk(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mantle getMantle() {
|
||||
return method.getMantle();
|
||||
}
|
||||
}
|
||||
+112
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -160,9 +160,13 @@ public class ServerBootSFG {
|
||||
}
|
||||
|
||||
public static boolean enoughDiskSpace() {
|
||||
File freeSpace = Bukkit.getWorldContainer();
|
||||
File freeSpace = new File(Bukkit.getWorldContainer() + ".");
|
||||
double gigabytes = freeSpace.getFreeSpace() / (1024.0 * 1024.0 * 1024.0);
|
||||
return gigabytes > 3;
|
||||
if (gigabytes > 3){
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkJavac(String path) {
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
package com.volmit.iris.core.service;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.pregenerator.cache.PregenCache;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.plugin.IrisService;
|
||||
import lombok.NonNull;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.world.ChunkLoadEvent;
|
||||
import org.bukkit.event.world.WorldInitEvent;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class GlobalCacheSVC implements IrisService {
|
||||
private static final Cache<String, PregenCache> REFERENCE_CACHE = Caffeine.newBuilder().weakValues().build();
|
||||
private final KMap<String, PregenCache> globalCache = new KMap<>();
|
||||
private transient boolean lastState;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
lastState = !IrisSettings.get().getWorld().isGlobalPregenCache();
|
||||
if (lastState) return;
|
||||
Bukkit.getWorlds().forEach(this::createCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
globalCache.values().forEach(PregenCache::write);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PregenCache get(@NonNull World world) {
|
||||
return globalCache.get(world.getName());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PregenCache get(@NonNull String world) {
|
||||
return globalCache.get(world);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void on(WorldInitEvent event) {
|
||||
if (isDisabled()) return;
|
||||
createCache(event.getWorld());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void on(WorldUnloadEvent event) {
|
||||
var cache = globalCache.remove(event.getWorld().getName());
|
||||
if (cache == null) return;
|
||||
cache.write();
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void on(ChunkLoadEvent event) {
|
||||
var cache = get(event.getWorld());
|
||||
if (cache == null) return;
|
||||
cache.cacheChunk(event.getChunk().getX(), event.getChunk().getZ());
|
||||
}
|
||||
|
||||
private void createCache(World world) {
|
||||
globalCache.computeIfAbsent(world.getName(), GlobalCacheSVC::createDefault);
|
||||
}
|
||||
|
||||
private boolean isDisabled() {
|
||||
boolean conf = IrisSettings.get().getWorld().isGlobalPregenCache();
|
||||
if (lastState != conf)
|
||||
return lastState;
|
||||
|
||||
if (conf) {
|
||||
Bukkit.getWorlds().forEach(this::createCache);
|
||||
} else {
|
||||
globalCache.values().removeIf(cache -> {
|
||||
cache.write();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return lastState = !conf;
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public static PregenCache createCache(@NonNull String worldName, @NonNull Function<String, PregenCache> provider) {
|
||||
return REFERENCE_CACHE.get(worldName, provider);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static PregenCache createDefault(@NonNull String worldName) {
|
||||
return createCache(worldName, GlobalCacheSVC::createDefault0);
|
||||
}
|
||||
|
||||
private static PregenCache createDefault0(String worldName) {
|
||||
return PregenCache.create(new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pregen"))).sync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,625 @@
|
||||
package com.volmit.iris.core.tools;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.CentralProcessor;
|
||||
import oshi.hardware.GlobalMemory;
|
||||
import oshi.hardware.HWDiskStore;
|
||||
import oshi.software.os.OperatingSystem;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.MemoryUsage;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import static com.google.common.math.LongMath.isPrime;
|
||||
import static com.volmit.iris.util.misc.getHardware.getCPUModel;
|
||||
public class IrisBenchmarking {
|
||||
static String ServerOS;
|
||||
static String filePath = "benchmark.dat";
|
||||
static double avgWriteSpeedMBps;
|
||||
static double avgReadSpeedMBps;
|
||||
static double highestWriteSpeedMBps;
|
||||
static double highestReadSpeedMBps;
|
||||
static double lowestWriteSpeedMBps;
|
||||
static double lowestReadSpeedMBps;
|
||||
static double calculateIntegerMath;
|
||||
static double calculateFloatingPoint;
|
||||
static double calculatePrimeNumbers;
|
||||
static double calculateStringSorting;
|
||||
static double calculateDataEncryption;
|
||||
static double calculateDataCompression;
|
||||
static String currentRunning = "None";
|
||||
static int BenchmarksCompleted = 0;
|
||||
static int BenchmarksTotal = 7;
|
||||
static int totalTasks = 10;
|
||||
static int currentTasks = 0;
|
||||
static double WindowsCPUCompression;
|
||||
static double WindowsCPUEncryption;
|
||||
static double WindowsCPUCSHA1;
|
||||
static double elapsedTimeNs;
|
||||
static boolean Winsat = false;
|
||||
static boolean WindowsDiskSpeed = false;
|
||||
public static boolean inProgress = false;
|
||||
static double startTime;
|
||||
// Good enough for now. . .
|
||||
|
||||
public static void runBenchmark() throws InterruptedException {
|
||||
inProgress = true;
|
||||
getServerOS();
|
||||
deleteTestFile(filePath);
|
||||
AtomicReference<Double> doneCalculateDiskSpeed = new AtomicReference<>((double) 0);
|
||||
startBenchmarkTimer();
|
||||
Iris.info("Benchmark Started!");
|
||||
Iris.warn("Although it may seem momentarily paused, it's actively processing.");
|
||||
BenchmarksCompleted = 0;
|
||||
|
||||
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||
currentRunning = "calculateDiskSpeed";
|
||||
progressBar();
|
||||
if (ServerOS.contains("Windows") && isRunningAsAdmin()) {
|
||||
WindowsDiskSpeed = true;
|
||||
WindowsDiskSpeedTest();
|
||||
} else {
|
||||
warningFallback();
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
doneCalculateDiskSpeed.set(roundToTwoDecimalPlaces(calculateDiskSpeed()));
|
||||
BenchmarksCompleted++;
|
||||
}
|
||||
|
||||
|
||||
}).thenRun(() -> {
|
||||
currentRunning = "WindowsCpuSpeedTest";
|
||||
progressBar();
|
||||
if (ServerOS.contains("Windows") && isRunningAsAdmin()) {
|
||||
Winsat = true;
|
||||
WindowsCpuSpeedTest();
|
||||
} else {
|
||||
Iris.info("Skipping:" + C.BLUE + " Windows System Assessment Tool Benchmarks");
|
||||
if (!ServerOS.contains("Windows")) {
|
||||
Iris.info("Required Software:" + C.BLUE + " Windows");
|
||||
BenchmarksTotal = 6;
|
||||
}
|
||||
if (!isRunningAsAdmin()) {
|
||||
Iris.info(C.RED + "ERROR: " + C.DARK_RED + "Elevated privileges missing");
|
||||
BenchmarksTotal = 6;
|
||||
}
|
||||
}
|
||||
|
||||
}).thenRun(() -> {
|
||||
currentRunning = "calculateIntegerMath";
|
||||
progressBar();
|
||||
calculateIntegerMath = roundToTwoDecimalPlaces(calculateIntegerMath());
|
||||
BenchmarksCompleted++;
|
||||
}).thenRun(() -> {
|
||||
currentRunning = "calculateFloatingPoint";
|
||||
progressBar();
|
||||
calculateFloatingPoint = roundToTwoDecimalPlaces(calculateFloatingPoint());
|
||||
BenchmarksCompleted++;
|
||||
}).thenRun(() -> {
|
||||
currentRunning = "calculateStringSorting";
|
||||
progressBar();
|
||||
calculateStringSorting = roundToTwoDecimalPlaces(calculateStringSorting());
|
||||
BenchmarksCompleted++;
|
||||
}).thenRun(() -> {
|
||||
currentRunning = "calculatePrimeNumbers";
|
||||
progressBar();
|
||||
calculatePrimeNumbers = roundToTwoDecimalPlaces(calculatePrimeNumbers());
|
||||
BenchmarksCompleted++;
|
||||
}).thenRun(() -> {
|
||||
currentRunning = "calculateDataEncryption";
|
||||
progressBar();
|
||||
calculateDataEncryption = roundToTwoDecimalPlaces(calculateDataEncryption());
|
||||
BenchmarksCompleted++;
|
||||
}).thenRun(() -> {
|
||||
currentRunning = "calculateDataCompression";
|
||||
progressBar();
|
||||
calculateDataCompression = roundToTwoDecimalPlaces(calculateDataCompression());
|
||||
BenchmarksCompleted++;
|
||||
}).thenRun(() -> {
|
||||
elapsedTimeNs = stopBenchmarkTimer();
|
||||
results();
|
||||
inProgress = false;
|
||||
});
|
||||
|
||||
try {
|
||||
future.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void progressBar() {
|
||||
Iris.info("-----------------------------------------------------");
|
||||
Iris.info("Currently Running: " + C.BLUE + currentRunning);
|
||||
// Iris.info("Tasks: " + "Current Tasks: " + C.BLUE + currentTasks + C.WHITE + " / " + "Total Tasks: " + C.BLUE + totalTasks);
|
||||
Iris.info("Benchmarks Completed: " + C.BLUE + BenchmarksCompleted + C.WHITE + " / " + "Total: " + C.BLUE + BenchmarksTotal);
|
||||
Iris.info("-----------------------------------------------------");
|
||||
}
|
||||
|
||||
public static void results() {
|
||||
|
||||
SystemInfo systemInfo = new SystemInfo();
|
||||
GlobalMemory globalMemory = systemInfo.getHardware().getMemory();
|
||||
long totalMemoryMB = globalMemory.getTotal() / (1024 * 1024);
|
||||
long availableMemoryMB = globalMemory.getAvailable() / (1024 * 1024);
|
||||
long totalPageSize = globalMemory.getPageSize() / (1024 * 1024);
|
||||
long usedMemoryMB = totalMemoryMB - availableMemoryMB;
|
||||
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
|
||||
|
||||
Iris.info("OS: " + ServerOS);
|
||||
if (!isRunningAsAdmin() || !ServerOS.contains("Windows")) {
|
||||
Iris.info(C.GOLD + "For the full results use Windows + Admin Rights..");
|
||||
}
|
||||
Iris.info("CPU Model: " + getCPUModel());
|
||||
Iris.info("CPU Score: " + "WIP");
|
||||
Iris.info("- Integer Math: " + calculateIntegerMath + " MOps/Sec");
|
||||
Iris.info("- Floating Point Math: " + calculateFloatingPoint + " MOps/Sec");
|
||||
Iris.info("- Find Prime Numbers: " + calculatePrimeNumbers + " Primes/Sec");
|
||||
Iris.info("- Random String Sorting: " + calculateStringSorting + " Thousand Strings/Sec");
|
||||
Iris.info("- Data Encryption: " + formatDouble(calculateDataEncryption) + " MBytes/Sec");
|
||||
Iris.info("- Data Compression: " + formatDouble(calculateDataCompression) + " MBytes/Sec");
|
||||
|
||||
if (WindowsDiskSpeed) {
|
||||
//Iris.info("Disk Model: " + getDiskModel());
|
||||
Iris.info(C.BLUE + "- Running with Windows System Assessment Tool");
|
||||
Iris.info("- Sequential 64.0 Write: " + C.BLUE + formatDouble(avgWriteSpeedMBps) + " Mbps");
|
||||
Iris.info("- Sequential 64.0 Read: " + C.BLUE + formatDouble(avgReadSpeedMBps) + " Mbps");
|
||||
} else {
|
||||
// Iris.info("Disk Model: " + getDiskModel());
|
||||
Iris.info(C.GREEN + "- Running in Native Mode");
|
||||
Iris.info("- Average Write Speed: " + C.GREEN + formatDouble(avgWriteSpeedMBps) + " Mbps");
|
||||
Iris.info("- Average Read Speed: " + C.GREEN + formatDouble(avgReadSpeedMBps) + " Mbps");
|
||||
Iris.info("- Highest Write Speed: " + formatDouble(highestWriteSpeedMBps) + " Mbps");
|
||||
Iris.info("- Highest Read Speed: " + formatDouble(highestReadSpeedMBps) + " Mbps");
|
||||
Iris.info("- Lowest Write Speed: " + formatDouble(lowestWriteSpeedMBps) + " Mbps");
|
||||
Iris.info("- Lowest Read Speed: " + formatDouble(lowestReadSpeedMBps) + " Mbps");
|
||||
}
|
||||
Iris.info("Ram Usage: ");
|
||||
Iris.info("- Total Ram: " + totalMemoryMB + " MB");
|
||||
Iris.info("- Used Ram: " + usedMemoryMB + " MB");
|
||||
Iris.info("- Total Process Ram: " + C.BLUE + getMaxMemoryUsage() + " MB");
|
||||
Iris.info("- Total Paging Size: " + totalPageSize + " MB");
|
||||
if (Winsat) {
|
||||
Iris.info(C.BLUE + "Windows System Assessment Tool: ");
|
||||
Iris.info("- CPU LZW Compression:" + C.BLUE + formatDouble(WindowsCPUCompression) + " MB/s");
|
||||
Iris.info("- CPU AES256 Encryption: " + C.BLUE + formatDouble(WindowsCPUEncryption) + " MB/s");
|
||||
Iris.info("- CPU SHA1 Hash: " + C.BLUE + formatDouble(WindowsCPUCSHA1) + " MB/s");
|
||||
Iris.info("Duration: " + roundToTwoDecimalPlaces(elapsedTimeNs) + " Seconds");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static long getMaxMemoryUsage() {
|
||||
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
|
||||
MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
|
||||
MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage();
|
||||
long maxHeapMemory = heapMemoryUsage.getMax();
|
||||
long maxNonHeapMemory = nonHeapMemoryUsage.getMax();
|
||||
long maxMemoryUsageMB = (maxHeapMemory + maxNonHeapMemory) / (1024 * 1024);
|
||||
return maxMemoryUsageMB;
|
||||
}
|
||||
|
||||
public static void getServerOS() {
|
||||
SystemInfo systemInfo = new SystemInfo();
|
||||
OperatingSystem os = systemInfo.getOperatingSystem();
|
||||
ServerOS = os.toString();
|
||||
}
|
||||
|
||||
public static boolean isRunningAsAdmin() {
|
||||
if (ServerOS.contains("Windows")) {
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec("winsat disk");
|
||||
process.waitFor();
|
||||
return process.exitValue() == 0;
|
||||
} catch (IOException | InterruptedException e) {
|
||||
// Hmm
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void warningFallback() {
|
||||
Iris.info(C.RED + "Using the " + C.DARK_RED + "FALLBACK" + C.RED + " method due to compatibility issues. ");
|
||||
Iris.info(C.RED + "Please note that this may result in less accurate results.");
|
||||
}
|
||||
|
||||
private static String formatDouble(double value) {
|
||||
return String.format("%.2f", value);
|
||||
}
|
||||
|
||||
private static void startBenchmarkTimer() {
|
||||
startTime = System.nanoTime();
|
||||
}
|
||||
|
||||
private static double stopBenchmarkTimer() {
|
||||
long endTime = System.nanoTime();
|
||||
return (endTime - startTime) / 1_000_000_000.0;
|
||||
}
|
||||
|
||||
private static double calculateIntegerMath() {
|
||||
final int numIterations = 1_000_000_000;
|
||||
final int numRuns = 30;
|
||||
double totalMopsPerSec = 0;
|
||||
|
||||
for (int run = 0; run < numRuns; run++) {
|
||||
long startTime = System.nanoTime();
|
||||
int result = 0;
|
||||
|
||||
for (int i = 0; i < numIterations; i++) {
|
||||
result += i * 2;
|
||||
result -= i / 2;
|
||||
result ^= i;
|
||||
result <<= 1;
|
||||
result >>= 1;
|
||||
}
|
||||
|
||||
long endTime = System.nanoTime();
|
||||
double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0;
|
||||
double mopsPerSec = (numIterations / elapsedSeconds) / 1_000_000.0;
|
||||
|
||||
totalMopsPerSec += mopsPerSec;
|
||||
}
|
||||
|
||||
double averageMopsPerSec = totalMopsPerSec / numRuns;
|
||||
return averageMopsPerSec;
|
||||
}
|
||||
|
||||
private static double calculateFloatingPoint() {
|
||||
long numIterations = 85_000_000;
|
||||
int numRuns = 30;
|
||||
double totalMopsPerSec = 0;
|
||||
for (int run = 0; run < numRuns; run++) {
|
||||
double result = 0;
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
for (int i = 0; i < numIterations; i++) {
|
||||
result += Math.sqrt(i) * Math.sin(i) / (i + 1);
|
||||
}
|
||||
|
||||
long endTime = System.nanoTime();
|
||||
double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0;
|
||||
double mopsPerSec = (numIterations / elapsedSeconds) / 1_000_000.0;
|
||||
|
||||
totalMopsPerSec += mopsPerSec;
|
||||
}
|
||||
|
||||
double averageMopsPerSec = totalMopsPerSec / numRuns;
|
||||
return averageMopsPerSec;
|
||||
}
|
||||
|
||||
private static double calculatePrimeNumbers() {
|
||||
int primeCount;
|
||||
long numIterations = 1_000_000;
|
||||
int numRuns = 30;
|
||||
double totalMopsPerSec = 0;
|
||||
|
||||
for (int run = 0; run < numRuns; run++) {
|
||||
primeCount = 0;
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
for (int num = 2; primeCount < numIterations; num++) {
|
||||
if (isPrime(num)) {
|
||||
primeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
long endTime = System.nanoTime();
|
||||
double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0;
|
||||
double mopsPerSec = (primeCount / elapsedSeconds) / 1_000_000.0;
|
||||
|
||||
totalMopsPerSec += mopsPerSec;
|
||||
}
|
||||
|
||||
double averageMopsPerSec = totalMopsPerSec / numRuns;
|
||||
return averageMopsPerSec;
|
||||
}
|
||||
|
||||
private static double calculateStringSorting() {
|
||||
int stringCount = 1_000_000;
|
||||
int stringLength = 100;
|
||||
int numRuns = 30;
|
||||
double totalMopsPerSec = 0;
|
||||
|
||||
for (int run = 0; run < numRuns; run++) {
|
||||
List<String> randomStrings = generateRandomStrings(stringCount, stringLength);
|
||||
long startTime = System.nanoTime();
|
||||
randomStrings.sort(String::compareTo);
|
||||
long endTime = System.nanoTime();
|
||||
|
||||
double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0;
|
||||
double mopsPerSec = (stringCount / elapsedSeconds) / 1_000.0;
|
||||
|
||||
totalMopsPerSec += mopsPerSec;
|
||||
}
|
||||
|
||||
double averageMopsPerSec = totalMopsPerSec / numRuns;
|
||||
return averageMopsPerSec;
|
||||
}
|
||||
|
||||
public static double calculateDataEncryption() {
|
||||
int dataSizeMB = 100;
|
||||
byte[] dataToEncrypt = generateRandomData(dataSizeMB * 1024 * 1024);
|
||||
int numRuns = 20;
|
||||
double totalMBytesPerSec = 0;
|
||||
|
||||
for (int run = 0; run < numRuns; run++) {
|
||||
long startTime = System.nanoTime();
|
||||
byte[] encryptedData = performEncryption(dataToEncrypt, 1);
|
||||
|
||||
long endTime = System.nanoTime();
|
||||
double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0;
|
||||
double mbytesPerSec = (dataToEncrypt.length / (1024 * 1024.0)) / elapsedSeconds;
|
||||
|
||||
totalMBytesPerSec += mbytesPerSec;
|
||||
}
|
||||
|
||||
double averageMBytesPerSec = totalMBytesPerSec / numRuns;
|
||||
return averageMBytesPerSec;
|
||||
}
|
||||
|
||||
private static byte[] performEncryption(byte[] data, int numRuns) {
|
||||
byte[] key = "MyEncryptionKey".getBytes();
|
||||
byte[] result = Arrays.copyOf(data, data.length);
|
||||
for (int run = 0; run < numRuns; run++) {
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] ^= key[i % key.length];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static double calculateDataCompression() {
|
||||
int dataSizeMB = 500;
|
||||
byte[] dataToCompress = generateRandomData(dataSizeMB * 1024 * 1024);
|
||||
long startTime = System.nanoTime();
|
||||
byte[] compressedData = performCompression(dataToCompress);
|
||||
long endTime = System.nanoTime();
|
||||
|
||||
double elapsedSeconds = (endTime - startTime) / 1e9;
|
||||
double mbytesPerSec = (compressedData.length / (1024.0 * 1024.0)) / elapsedSeconds;
|
||||
|
||||
return mbytesPerSec;
|
||||
}
|
||||
|
||||
private static byte[] performCompression(byte[] data) {
|
||||
Deflater deflater = new Deflater();
|
||||
deflater.setInput(data);
|
||||
deflater.finish();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
while (!deflater.finished()) {
|
||||
int count = deflater.deflate(buffer);
|
||||
outputStream.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
deflater.end();
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
private static List<String> generateRandomStrings(int count, int length) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
List<String> randomStrings = new ArrayList<>();
|
||||
|
||||
IntStream.range(0, count).forEach(i -> {
|
||||
byte[] bytes = new byte[length];
|
||||
random.nextBytes(bytes);
|
||||
randomStrings.add(Base64.getEncoder().encodeToString(bytes));
|
||||
});
|
||||
return randomStrings;
|
||||
}
|
||||
|
||||
private static byte[] generateRandomData(int size) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] data = new byte[size];
|
||||
random.nextBytes(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
private static double roundToTwoDecimalPlaces(double value) {
|
||||
return Double.parseDouble(String.format("%.2f", value));
|
||||
}
|
||||
|
||||
private static double calculateCPUScore(long elapsedTimeNs) {
|
||||
return 1.0 / (elapsedTimeNs / 1_000_000.0);
|
||||
}
|
||||
|
||||
public static double calculateDiskSpeed() {
|
||||
int numRuns = 10;
|
||||
int fileSizeMB = 1000;
|
||||
|
||||
double[] writeSpeeds = new double[numRuns];
|
||||
double[] readSpeeds = new double[numRuns];
|
||||
|
||||
for (int run = 0; run < numRuns; run++) {
|
||||
long writeStartTime = System.nanoTime();
|
||||
deleteTestFile(filePath);
|
||||
createTestFile(filePath, fileSizeMB);
|
||||
long writeEndTime = System.nanoTime();
|
||||
|
||||
long readStartTime = System.nanoTime();
|
||||
readTestFile(filePath);
|
||||
long readEndTime = System.nanoTime();
|
||||
|
||||
double writeSpeed = calculateDiskSpeedMBps(fileSizeMB, writeStartTime, writeEndTime);
|
||||
double readSpeed = calculateDiskSpeedMBps(fileSizeMB, readStartTime, readEndTime);
|
||||
|
||||
writeSpeeds[run] = writeSpeed;
|
||||
readSpeeds[run] = readSpeed;
|
||||
|
||||
if (run == 0) {
|
||||
lowestWriteSpeedMBps = writeSpeed;
|
||||
highestWriteSpeedMBps = writeSpeed;
|
||||
lowestReadSpeedMBps = readSpeed;
|
||||
highestReadSpeedMBps = readSpeed;
|
||||
} else {
|
||||
if (writeSpeed < lowestWriteSpeedMBps) {
|
||||
lowestWriteSpeedMBps = writeSpeed;
|
||||
}
|
||||
if (writeSpeed > highestWriteSpeedMBps) {
|
||||
highestWriteSpeedMBps = writeSpeed;
|
||||
}
|
||||
if (readSpeed < lowestReadSpeedMBps) {
|
||||
lowestReadSpeedMBps = readSpeed;
|
||||
}
|
||||
if (readSpeed > highestReadSpeedMBps) {
|
||||
highestReadSpeedMBps = readSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
avgWriteSpeedMBps = calculateAverage(writeSpeeds);
|
||||
avgReadSpeedMBps = calculateAverage(readSpeeds);
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static void createTestFile(String filePath, int fileSizeMB) {
|
||||
try {
|
||||
File file = new File(filePath);
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
Arrays.fill(data, (byte) 0);
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
for (int i = 0; i < fileSizeMB; i++) {
|
||||
fos.write(data);
|
||||
}
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void readTestFile(String filePath) {
|
||||
try {
|
||||
File file = new File(filePath);
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
byte[] buffer = new byte[1024];
|
||||
while (fis.read(buffer) != -1) {
|
||||
}
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteTestFile(String filePath) {
|
||||
File file = new File(filePath);
|
||||
file.delete();
|
||||
}
|
||||
|
||||
public static double calculateDiskSpeedMBps(int fileSizeMB, long startTime, long endTime) {
|
||||
double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0;
|
||||
double writeSpeed = (fileSizeMB / elapsedSeconds);
|
||||
return writeSpeed;
|
||||
}
|
||||
|
||||
public static double calculateAverage(double[] values) {
|
||||
double sum = 0;
|
||||
for (double value : values) {
|
||||
sum += value;
|
||||
}
|
||||
return sum / values.length;
|
||||
}
|
||||
|
||||
public static void WindowsDiskSpeedTest() {
|
||||
try {
|
||||
String command = "winsat disk";
|
||||
Process process = Runtime.getRuntime().exec(command);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
Iris.debug(line);
|
||||
|
||||
if (line.contains("Disk Sequential 64.0 Read")) {
|
||||
avgReadSpeedMBps = extractSpeed(line);
|
||||
} else if (line.contains("Disk Sequential 64.0 Write")) {
|
||||
avgWriteSpeedMBps = extractSpeed(line);
|
||||
}
|
||||
}
|
||||
|
||||
process.waitFor();
|
||||
process.destroy();
|
||||
|
||||
Iris.debug("Sequential Read Speed: " + avgReadSpeedMBps + " MB/s");
|
||||
Iris.debug("Sequential Write Speed: " + avgWriteSpeedMBps + " MB/s");
|
||||
} catch (IOException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static double extractSpeed(String line) {
|
||||
String[] tokens = line.split("\\s+");
|
||||
for (int i = 0; i < tokens.length; i++) {
|
||||
if (tokens[i].endsWith("MB/s") && i > 0) {
|
||||
try {
|
||||
return Double.parseDouble(tokens[i - 1]);
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public static void WindowsCpuSpeedTest() {
|
||||
try {
|
||||
String command = "winsat cpuformal";
|
||||
Process process = Runtime.getRuntime().exec(command);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
Iris.debug(line);
|
||||
|
||||
if (line.contains("CPU AES256 Encryption")) {
|
||||
WindowsCPUEncryption = extractCpuInfo(line);
|
||||
}
|
||||
if (line.contains("CPU LZW Compression")) {
|
||||
WindowsCPUCompression = extractCpuInfo(line);
|
||||
}
|
||||
if (line.contains("CPU SHA1 Hash")) {
|
||||
WindowsCPUCSHA1 = extractCpuInfo(line);
|
||||
}
|
||||
}
|
||||
process.waitFor();
|
||||
process.destroy();
|
||||
|
||||
Iris.debug("Winsat Encryption: " + WindowsCPUEncryption + " MB/s");
|
||||
} catch (IOException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static double extractCpuInfo(String line) {
|
||||
String[] tokens = line.split("\\s+");
|
||||
for (int i = 0; i < tokens.length; i++) {
|
||||
if (tokens[i].endsWith("MB/s") && i > 0) {
|
||||
try {
|
||||
return Double.parseDouble(tokens[i - 1]);
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,9 +2,17 @@ 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;
|
||||
@@ -29,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();
|
||||
}
|
||||
@@ -48,7 +59,7 @@ public class IrisPackBenchmarking {
|
||||
benchmarkInProgress = true;
|
||||
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...");
|
||||
}
|
||||
@@ -65,8 +76,7 @@ public class IrisPackBenchmarking {
|
||||
|
||||
public void finishedBenchmark(KList<Integer> cps) {
|
||||
try {
|
||||
String time = Form.duration((long) stopwatch.getMilliseconds());
|
||||
Engine engine = IrisToolbelt.access(Bukkit.getWorld("benchmark")).getEngine();
|
||||
String time = Form.duration(stopwatch.getMillis());
|
||||
Iris.info("-----------------");
|
||||
Iris.info("Results:");
|
||||
Iris.info("- Total time: " + time);
|
||||
@@ -76,7 +86,11 @@ public class IrisPackBenchmarking {
|
||||
Iris.info(" - Lowest CPS: " + findLowest(cps));
|
||||
Iris.info("-----------------");
|
||||
Iris.info("Creating a report..");
|
||||
File results = Iris.instance.getDataFile("packbenchmarks", dimension.getName() + " " + LocalDateTime.now(Clock.systemDefaultZone()).toString().replace(':', '-') + ".txt");
|
||||
File profilers = new File("plugins" + File.separator + "Iris" + File.separator + "packbenchmarks");
|
||||
profilers.mkdir();
|
||||
|
||||
File results = new File(profilers, dimension.getName() + " " + LocalDateTime.now(Clock.systemDefaultZone()).toString().replace(':', '-') + ".txt");
|
||||
results.getParentFile().mkdirs();
|
||||
KMap<String, Double> metrics = engine.getMetrics().pull();
|
||||
try (FileWriter writer = new FileWriter(results)) {
|
||||
writer.write("-----------------\n");
|
||||
@@ -103,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) {
|
||||
@@ -119,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);
|
||||
}
|
||||
@@ -135,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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import com.volmit.iris.core.gui.PregeneratorJob;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
|
||||
import com.volmit.iris.core.pregenerator.methods.CachedPregenMethod;
|
||||
import com.volmit.iris.core.pregenerator.methods.HybridPregenMethod;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
@@ -142,18 +141,7 @@ public class IrisToolbelt {
|
||||
* @return the pregenerator job (already started)
|
||||
*/
|
||||
public static PregeneratorJob pregenerate(PregenTask task, PregeneratorMethod method, Engine engine) {
|
||||
return pregenerate(task, method, engine, IrisSettings.get().getPregen().useCacheByDefault);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a pregenerator task
|
||||
*
|
||||
* @param task the scheduled task
|
||||
* @param method the method to execute the task
|
||||
* @return the pregenerator job (already started)
|
||||
*/
|
||||
public static PregeneratorJob pregenerate(PregenTask task, PregeneratorMethod method, Engine engine, boolean cached) {
|
||||
return new PregeneratorJob(task, cached && engine != null ? new CachedPregenMethod(method, engine.getWorld().name()) : method, engine);
|
||||
return new PregeneratorJob(task, method, engine);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -180,10 +180,7 @@ public class IrisEngine implements Engine {
|
||||
File[] roots = getData().getLoaders()
|
||||
.values()
|
||||
.stream()
|
||||
.map(ResourceLoader::getFolderName)
|
||||
.map(n -> new File(getData().getDataFolder(), n))
|
||||
.filter(File::exists)
|
||||
.filter(File::isDirectory)
|
||||
.map(ResourceLoader::getRoot)
|
||||
.toArray(File[]::new);
|
||||
hash32.complete(IO.hashRecursive(roots));
|
||||
});
|
||||
@@ -307,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()) {
|
||||
|
||||
@@ -367,7 +367,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
|
||||
private void spawn(IrisPosition pos, IrisEntitySpawn i) {
|
||||
IrisSpawner ref = i.getReferenceSpawner();
|
||||
if (!ref.canSpawn(getEngine(), pos.getX() >> 4, pos.getZ() >> 4))
|
||||
if (!ref.canSpawn(getEngine(), pos.getX() >> 4, pos.getZ()))
|
||||
return;
|
||||
|
||||
int s = i.spawn(getEngine(), pos, RNG.r);
|
||||
|
||||
@@ -75,7 +75,6 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Arrays;
|
||||
@@ -611,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) {
|
||||
@@ -853,25 +855,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
return getBiomeOrMantle(l.getBlockX(), l.getBlockY(), l.getBlockZ());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@BlockCoordinates
|
||||
default Position2 getNearestStronghold(Position2 pos) {
|
||||
KList<Position2> p = getDimension().getStrongholds(getSeedManager().getMantle());
|
||||
if (p.isEmpty()) return null;
|
||||
|
||||
Position2 pr = null;
|
||||
double d = Double.MAX_VALUE;
|
||||
|
||||
for (Position2 i : p) {
|
||||
double dx = i.distance(pos);
|
||||
if (dx < d) {
|
||||
d = dx;
|
||||
pr = i;
|
||||
}
|
||||
}
|
||||
return pr;
|
||||
}
|
||||
|
||||
default void gotoBiome(IrisBiome biome, Player player, boolean teleport) {
|
||||
Set<String> regionKeys = getDimension()
|
||||
.getAllRegions(this).stream()
|
||||
@@ -892,10 +875,31 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
|
||||
default void gotoJigsaw(IrisJigsawStructure s, Player player, boolean teleport) {
|
||||
if (s.getLoadKey().equals(getDimension().getStronghold())) {
|
||||
Position2 pr = getNearestStronghold(new Position2(player.getLocation().getBlockX(), player.getLocation().getBlockZ()));
|
||||
if (pr == null) {
|
||||
KList<Position2> p = getDimension().getStrongholds(getSeedManager().getMantle());
|
||||
|
||||
if (p.isEmpty()) {
|
||||
player.sendMessage(C.GOLD + "No strongholds in world.");
|
||||
} else {
|
||||
}
|
||||
|
||||
Position2 px = new Position2(player.getLocation().getBlockX(), player.getLocation().getBlockZ());
|
||||
Position2 pr = null;
|
||||
double d = Double.MAX_VALUE;
|
||||
|
||||
Iris.debug("Ps: " + p.size());
|
||||
|
||||
for (Position2 i : p) {
|
||||
Iris.debug("- " + i.getX() + " " + i.getZ());
|
||||
}
|
||||
|
||||
for (Position2 i : p) {
|
||||
double dx = i.distance(px);
|
||||
if (dx < d) {
|
||||
d = dx;
|
||||
pr = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (pr != null) {
|
||||
Location ll = new Location(player.getWorld(), pr.getX(), 40, pr.getZ());
|
||||
J.s(() -> player.teleport(ll));
|
||||
}
|
||||
|
||||
@@ -97,6 +97,51 @@ public abstract class EngineAssignedWorldManager extends EngineAssignedComponent
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onItemUse(PlayerInteractEvent e) {
|
||||
if (e.getItem() == null || e.getHand() != EquipmentSlot.HAND) {
|
||||
return;
|
||||
}
|
||||
if (e.getAction() == Action.LEFT_CLICK_BLOCK || e.getAction() == Action.LEFT_CLICK_AIR) {
|
||||
return;
|
||||
}
|
||||
if (e.getPlayer().getWorld().equals(getTarget().getWorld().realWorld()) && e.getItem().getType() == Material.ENDER_EYE) {
|
||||
if (e.getClickedBlock() != null && e.getClickedBlock().getType() == Material.END_PORTAL_FRAME) {
|
||||
return;
|
||||
}
|
||||
|
||||
KList<Position2> positions = getEngine().getDimension().getStrongholds(getEngine().getSeedManager().getMantle());
|
||||
if (positions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Position2 playerPos = new Position2(e.getPlayer().getLocation().getBlockX(), e.getPlayer().getLocation().getBlockZ());
|
||||
Position2 pr = positions.get(0);
|
||||
double d = pr.distance(playerPos);
|
||||
|
||||
for (Position2 pos : positions) {
|
||||
double distance = pos.distance(playerPos);
|
||||
if (distance < d) {
|
||||
d = distance;
|
||||
pr = pos;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.getPlayer().getGameMode() != GameMode.CREATIVE) {
|
||||
if (e.getItem().getAmount() > 1) {
|
||||
e.getPlayer().getInventory().getItemInMainHand().setAmount(e.getItem().getAmount() - 1);
|
||||
} else {
|
||||
e.getPlayer().getInventory().setItemInMainHand(null);
|
||||
}
|
||||
}
|
||||
|
||||
EnderSignal eye = e.getPlayer().getWorld().spawn(e.getPlayer().getLocation().clone().add(0, 0.5F, 0), EnderSignal.class);
|
||||
eye.setTargetLocation(new Location(e.getPlayer().getWorld(), pr.getX(), 40, pr.getZ()));
|
||||
eye.getWorld().playSound(eye, Sound.ENTITY_ENDER_EYE_LAUNCH, 1, 1);
|
||||
Iris.debug("ESignal: " + eye.getTargetLocation().getBlockX() + " " + eye.getTargetLocation().getBlockX());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(WorldUnloadEvent e) {
|
||||
if (e.getWorld().equals(getTarget().getWorld().realWorld())) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -117,7 +117,7 @@ public class IrisEntitySpawn implements IRare {
|
||||
if (spawns > 0) {
|
||||
|
||||
if (referenceMarker != null) {
|
||||
gen.getMantle().getMantle().remove(c.getX(), c.getY() - gen.getWorld().minHeight(), c.getZ(), MatterMarker.class);
|
||||
gen.getMantle().getMantle().remove(c.getX(), c.getY(), c.getZ(), MatterMarker.class);
|
||||
}
|
||||
|
||||
for (int id = 0; id < spawns; id++) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,6 @@
|
||||
|
||||
package com.volmit.iris.util.io;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
|
||||
@@ -138,7 +134,8 @@ public class IO {
|
||||
continue;
|
||||
}
|
||||
|
||||
try (var din = new CheckedInputStream(readDeterministic(file), crc)) {
|
||||
try (var fin = new FileInputStream(file)) {
|
||||
var din = new CheckedInputStream(fin, crc);
|
||||
fullTransfer(din, new VoidOutputStream(), 8192);
|
||||
} catch (IOException e) {
|
||||
Iris.reportError(e);
|
||||
@@ -155,43 +152,10 @@ public class IO {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static InputStream readDeterministic(File file) throws IOException {
|
||||
if (!file.getName().endsWith(".json"))
|
||||
return new FileInputStream(file);
|
||||
|
||||
JsonElement json;
|
||||
try (FileReader reader = new FileReader(file)) {
|
||||
json = JsonParser.parseReader(reader);
|
||||
}
|
||||
|
||||
var queue = new LinkedList<JsonElement>();
|
||||
queue.add(json);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
var element = queue.pop();
|
||||
Collection<JsonElement> add = List.of();
|
||||
|
||||
if (element instanceof JsonObject obj) {
|
||||
var map = obj.asMap();
|
||||
var sorted = new TreeMap<>(map);
|
||||
map.clear();
|
||||
map.putAll(sorted);
|
||||
|
||||
add = sorted.values();
|
||||
} else if (element instanceof JsonArray array) {
|
||||
add = array.asList();
|
||||
}
|
||||
|
||||
add.stream().filter(e -> e.isJsonObject() || e.isJsonArray()).forEach(queue::add);
|
||||
}
|
||||
|
||||
return toInputStream(json.toString());
|
||||
}
|
||||
|
||||
public static String hash(File b) {
|
||||
try {
|
||||
MessageDigest d = MessageDigest.getInstance("SHA-256");
|
||||
DigestInputStream din = new DigestInputStream(readDeterministic(b), d);
|
||||
DigestInputStream din = new DigestInputStream(new FileInputStream(b), d);
|
||||
fullTransfer(din, new VoidOutputStream(), 8192);
|
||||
din.close();
|
||||
return bytesToHex(din.getMessageDigest().digest());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.volmit.iris.util.misc;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import oshi.SystemInfo;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
public class Hastebin {
|
||||
|
||||
public static void enviornment(CommandSender sender) {
|
||||
// Construct the server information
|
||||
StringBuilder sb = new StringBuilder();
|
||||
SystemInfo systemInfo = new SystemInfo();
|
||||
KList<String> disks = new KList<>(getHardware.getDisk());
|
||||
KList<String> interfaces = new KList<>(getHardware.getInterfaces());
|
||||
KList<String> displays = new KList<>(getHardware.getEDID());
|
||||
KList<String> sensors = new KList<>(getHardware.getSensors());
|
||||
KList<String> gpus = new KList<>(getHardware.getGraphicsCards());
|
||||
KList<String> powersources = new KList<>(getHardware.getPowerSources());
|
||||
|
||||
KList<World> IrisWorlds = new KList<>();
|
||||
KList<World> BukkitWorlds = new KList<>();
|
||||
|
||||
for (World w : Bukkit.getServer().getWorlds()) {
|
||||
try {
|
||||
Engine engine = IrisToolbelt.access(w).getEngine();
|
||||
if (engine != null) {
|
||||
IrisWorlds.add(w);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BukkitWorlds.add(w);
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(" -- == Iris Info == -- \n");
|
||||
sb.append("Iris Version Version: ").append(Iris.instance.getDescription().getVersion()).append("\n");
|
||||
sb.append("- Iris Worlds");
|
||||
for (World w : IrisWorlds.copy()) {
|
||||
sb.append(" - ").append(w.getName());
|
||||
}
|
||||
sb.append("- Bukkit Worlds");
|
||||
for (World w : BukkitWorlds.copy()) {
|
||||
sb.append(" - ").append(w.getName());
|
||||
}
|
||||
sb.append(" -- == Platform Overview == -- " + "\n");
|
||||
sb.append("Server Type: ").append(Bukkit.getVersion()).append("\n");
|
||||
sb.append("Server Uptime: ").append(Form.stampTime(systemInfo.getOperatingSystem().getSystemUptime())).append("\n");
|
||||
sb.append("Version: ").append(Platform.getVersion()).append(" - Platform: ").append(Platform.getName()).append("\n");
|
||||
sb.append("Java Vendor: ").append(Platform.ENVIRONMENT.getJavaVendor()).append(" - Java Version: ").append(Platform.ENVIRONMENT.getJavaVersion()).append("\n");
|
||||
sb.append(" -- == Processor Overview == -- " + "\n");
|
||||
sb.append("CPU Model: ").append(getHardware.getCPUModel());
|
||||
sb.append("CPU Architecture: ").append(Platform.CPU.getArchitecture()).append(" Available Processors: ").append(Platform.CPU.getAvailableProcessors()).append("\n");
|
||||
sb.append("CPU Load: ").append(Form.pc(Platform.CPU.getCPULoad())).append(" CPU Live Process Load: ").append(Form.pc(Platform.CPU.getLiveProcessCPULoad())).append("\n");
|
||||
sb.append("-=" + " Graphics " + "=- " + "\n");
|
||||
for (String gpu : gpus) {
|
||||
sb.append(" ").append(gpu).append("\n");
|
||||
}
|
||||
sb.append(" -- == Memory Information == -- " + "\n");
|
||||
sb.append("Physical Memory - Total: ").append(Form.memSize(Platform.MEMORY.PHYSICAL.getTotalMemory())).append(" Free: ").append(Form.memSize(Platform.MEMORY.PHYSICAL.getFreeMemory())).append(" Used: ").append(Form.memSize(Platform.MEMORY.PHYSICAL.getUsedMemory())).append("\n");
|
||||
sb.append("Virtual Memory - Total: ").append(Form.memSize(Platform.MEMORY.VIRTUAL.getTotalMemory())).append(" Free: ").append(Form.memSize(Platform.MEMORY.VIRTUAL.getFreeMemory())).append(" Used: ").append(Form.memSize(Platform.MEMORY.VIRTUAL.getUsedMemory())).append("\n");
|
||||
sb.append(" -- == Storage Information == -- " + "\n");
|
||||
for (String disk : disks) {
|
||||
sb.append(" ").append(sb.append(disk)).append("\n");
|
||||
}
|
||||
sb.append(" -- == Interface Information == -- "+ "\n" );
|
||||
for (String inter : interfaces) {
|
||||
sb.append(" ").append(inter).append("\n");
|
||||
}
|
||||
sb.append(" -- == Display Information == -- "+ "\n" );
|
||||
for (String display : displays) {
|
||||
sb.append(display).append("\n");
|
||||
}
|
||||
sb.append(" -- == Sensor Information == -- " + "\n");
|
||||
for (String sensor : sensors) {
|
||||
sb.append(" ").append(sensor).append("\n");
|
||||
}
|
||||
sb.append(" -- == Power Information == -- " + "\n");
|
||||
for (String power : powersources) {
|
||||
sb.append(" ").append(power).append("\n");
|
||||
}
|
||||
|
||||
try {
|
||||
String hastebinUrl = uploadToHastebin(sb.toString());
|
||||
|
||||
// Create the clickable message
|
||||
TextComponent message = new TextComponent("[Link]");
|
||||
TextComponent link = new TextComponent(hastebinUrl);
|
||||
link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, hastebinUrl));
|
||||
message.addExtra(link);
|
||||
|
||||
// Send the clickable message to the player
|
||||
sender.spigot().sendMessage(message);
|
||||
} catch (Exception e) {
|
||||
sender.sendMessage(C.DARK_RED + "Failed to upload server information to Hastebin.");
|
||||
}
|
||||
}
|
||||
|
||||
private static String uploadToHastebin(String content) throws Exception {
|
||||
URL url = new URL("https://paste.bytecode.ninja/documents");
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "text/plain");
|
||||
conn.setDoOutput(true);
|
||||
|
||||
DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
|
||||
wr.writeBytes(content);
|
||||
wr.flush();
|
||||
wr.close();
|
||||
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String response = br.readLine();
|
||||
br.close();
|
||||
|
||||
return "https://paste.bytecode.ninja/" + response.split("\"")[3];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.volmit.iris.util.misc;
|
||||
|
||||
import com.sun.management.OperatingSystemMXBean;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.management.ManagementFactory;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class Platform {
|
||||
public static String getVersion() {
|
||||
return getSystem().getVersion();
|
||||
}
|
||||
|
||||
public static String getName() {
|
||||
return getSystem().getName();
|
||||
}
|
||||
|
||||
private static OperatingSystemMXBean getSystem() {
|
||||
return (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
|
||||
}
|
||||
|
||||
public static class ENVIRONMENT {
|
||||
public static boolean canRunBatch() {
|
||||
return getSystem().getName().toLowerCase().contains("windows");
|
||||
}
|
||||
|
||||
public static String getJavaHome() {
|
||||
return System.getProperty("java.home");
|
||||
}
|
||||
|
||||
public static String getJavaVendor() {
|
||||
return System.getProperty("java.vendor");
|
||||
}
|
||||
|
||||
public static String getJavaVersion() {
|
||||
return System.getProperty("java.version");
|
||||
}
|
||||
}
|
||||
|
||||
public static class STORAGE {
|
||||
public static long getAbsoluteTotalSpace() {
|
||||
long t = 0;
|
||||
|
||||
for (File i : getRoots()) {
|
||||
t += getTotalSpace(i);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
public static long getTotalSpace() {
|
||||
return getTotalSpace(new File("."));
|
||||
}
|
||||
|
||||
public static long getTotalSpace(File root) {
|
||||
return root.getTotalSpace();
|
||||
}
|
||||
|
||||
public static long getAbsoluteFreeSpace() {
|
||||
long t = 0;
|
||||
|
||||
for (File i : getRoots()) {
|
||||
t += getFreeSpace(i);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
public static long getFreeSpace() {
|
||||
return getFreeSpace(new File("."));
|
||||
}
|
||||
|
||||
public static long getFreeSpace(File root) {
|
||||
return root.getFreeSpace();
|
||||
}
|
||||
|
||||
public static long getUsedSpace() {
|
||||
return getTotalSpace() - getFreeSpace();
|
||||
}
|
||||
|
||||
public static long getUsedSpace(File root) {
|
||||
return getTotalSpace(root) - getFreeSpace(root);
|
||||
}
|
||||
|
||||
public static long getAbsoluteUsedSpace() {
|
||||
return getAbsoluteTotalSpace() - getAbsoluteFreeSpace();
|
||||
}
|
||||
|
||||
public static File[] getRoots() {
|
||||
return File.listRoots();
|
||||
}
|
||||
}
|
||||
|
||||
public static class MEMORY {
|
||||
public static class PHYSICAL {
|
||||
public static long getTotalMemory() {
|
||||
return getSystem().getTotalPhysicalMemorySize();
|
||||
}
|
||||
|
||||
public static long getFreeMemory() {
|
||||
return getSystem().getFreePhysicalMemorySize();
|
||||
}
|
||||
|
||||
public static long getUsedMemory() {
|
||||
return getTotalMemory() - getFreeMemory();
|
||||
}
|
||||
}
|
||||
|
||||
public static class VIRTUAL {
|
||||
public static long getTotalMemory() {
|
||||
return getSystem().getTotalSwapSpaceSize();
|
||||
}
|
||||
|
||||
public static long getFreeMemory() {
|
||||
return getSystem().getFreeSwapSpaceSize();
|
||||
}
|
||||
|
||||
public static long getUsedMemory() {
|
||||
return getTotalMemory() - getFreeMemory();
|
||||
}
|
||||
|
||||
public static long getCommittedVirtualMemory() {
|
||||
return getSystem().getCommittedVirtualMemorySize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CPU {
|
||||
public static int getAvailableProcessors() {
|
||||
return getSystem().getAvailableProcessors();
|
||||
}
|
||||
|
||||
public static double getCPULoad() {
|
||||
return getSystem().getSystemCpuLoad();
|
||||
}
|
||||
|
||||
public static double getLiveProcessCPULoad() {
|
||||
return getSystem().getProcessCpuLoad();
|
||||
}
|
||||
|
||||
public static String getArchitecture() {
|
||||
return getSystem().getArch();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,12 @@ import com.volmit.iris.core.service.PreservationSVC;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class MultiBurst implements ExecutorService {
|
||||
public class MultiBurst {
|
||||
public static final MultiBurst burst = new MultiBurst();
|
||||
private final AtomicLong last;
|
||||
private final String name;
|
||||
@@ -146,106 +144,29 @@ public class MultiBurst implements ExecutorService {
|
||||
return getService().submit(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
close();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<Runnable> shutdownNow() {
|
||||
close();
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
return service == null || service.isShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
return service == null || service.isTerminated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException {
|
||||
return service == null || service.awaitTermination(timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull Runnable command) {
|
||||
getService().execute(command);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public <T> Future<T> submit(@NotNull Callable<T> task) {
|
||||
return getService().submit(task);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public <T> Future<T> submit(@NotNull Runnable task, T result) {
|
||||
return getService().submit(task, result);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Future<?> submit(@NotNull Runnable task) {
|
||||
return getService().submit(task);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public <T> List<Future<T>> invokeAll(@NotNull Collection<? extends Callable<T>> tasks) throws InterruptedException {
|
||||
return getService().invokeAll(tasks);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public <T> List<Future<T>> invokeAll(@NotNull Collection<? extends Callable<T>> tasks, long timeout, @NotNull TimeUnit unit) throws InterruptedException {
|
||||
return getService().invokeAll(tasks, timeout, unit);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public <T> T invokeAny(@NotNull Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
|
||||
return getService().invokeAny(tasks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T invokeAny(@NotNull Collection<? extends Callable<T>> tasks, long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return getService().invokeAny(tasks, timeout, unit);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (service != null) {
|
||||
close(service);
|
||||
}
|
||||
}
|
||||
service.shutdown();
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
try {
|
||||
while (!service.awaitTermination(1, TimeUnit.SECONDS)) {
|
||||
Iris.info("Still waiting to shutdown burster...");
|
||||
if (p.getMilliseconds() > 7000) {
|
||||
Iris.warn("Forcing Shutdown...");
|
||||
|
||||
public static void close(ExecutorService service) {
|
||||
service.shutdown();
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
try {
|
||||
while (!service.awaitTermination(1, TimeUnit.SECONDS)) {
|
||||
Iris.info("Still waiting to shutdown burster...");
|
||||
if (p.getMilliseconds() > 7000) {
|
||||
Iris.warn("Forcing Shutdown...");
|
||||
try {
|
||||
service.shutdownNow();
|
||||
} catch (Throwable e) {
|
||||
|
||||
try {
|
||||
service.shutdownNow();
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.tags.StructureTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.util.random.WeightedRandomList;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
@@ -123,11 +122,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
||||
if (holders.size() == 0) return null;
|
||||
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
|
||||
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
|
||||
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
|
||||
}
|
||||
if (engine.getDimension().isDisableExplorerMaps())
|
||||
return null;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+215
@@ -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);
|
||||
}
|
||||
}
|
||||
+312
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.tags.StructureTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.util.random.WeightedRandomList;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
@@ -123,11 +122,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
||||
if (holders.size() == 0) return null;
|
||||
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
|
||||
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
|
||||
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
|
||||
}
|
||||
if (engine.getDimension().isDisableExplorerMaps())
|
||||
return null;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+210
@@ -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);
|
||||
}
|
||||
}
|
||||
+311
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -125,11 +125,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
||||
if (holders.size() == 0) return null;
|
||||
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
|
||||
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
|
||||
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
|
||||
}
|
||||
if (engine.getDimension().isDisableExplorerMaps())
|
||||
return null;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+211
@@ -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);
|
||||
}
|
||||
}
|
||||
+311
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.tags.StructureTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.util.random.WeightedRandomList;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
@@ -121,11 +120,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
||||
if (holders.size() == 0) return null;
|
||||
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
|
||||
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
|
||||
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
|
||||
}
|
||||
if (engine.getDimension().isDisableExplorerMaps())
|
||||
return null;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+211
@@ -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);
|
||||
}
|
||||
}
|
||||
+311
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.tags.StructureTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.util.random.WeightedRandomList;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
@@ -122,11 +121,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
||||
if (holders.size() == 0) return null;
|
||||
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
|
||||
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
|
||||
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
|
||||
}
|
||||
if (engine.getDimension().isDisableExplorerMaps())
|
||||
return null;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+211
@@ -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);
|
||||
}
|
||||
}
|
||||
+307
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.tags.StructureTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.util.random.WeightedRandomList;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
@@ -121,11 +120,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
||||
if (holders.size() == 0) return null;
|
||||
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
|
||||
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
|
||||
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
|
||||
}
|
||||
if (engine.getDimension().isDisableExplorerMaps())
|
||||
return null;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+211
@@ -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);
|
||||
}
|
||||
}
|
||||
+244
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,9 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.tags.StructureTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.util.random.WeightedRandomList;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
import net.minecraft.world.item.EnderEyeItem;
|
||||
import net.minecraft.world.level.*;
|
||||
import net.minecraft.world.level.biome.*;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
@@ -33,7 +31,6 @@ import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||
import net.minecraft.world.level.levelgen.structure.BuiltinStructures;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureSet;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
@@ -117,11 +114,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
||||
if (holders.size() == 0) return null;
|
||||
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
|
||||
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
|
||||
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
|
||||
}
|
||||
if (engine.getDimension().isDisableExplorerMaps())
|
||||
return null;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+211
@@ -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);
|
||||
}
|
||||
}
|
||||
+244
@@ -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