mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-05-20 00:20:24 +00:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eff598d005 | |||
| d86ec7b1cd | |||
| 407e51378c | |||
| c468eb1ab1 | |||
| bdb7cc61e5 | |||
| e8f9e841c4 | |||
| 1b1b9d97b7 | |||
| 24355064ff | |||
| 06a45056d9 | |||
| dfe4894be7 | |||
| 8eb2287ec0 | |||
| c4f0722614 | |||
| 7fa1484b21 | |||
| 1c5eb8b910 | |||
| 94bf530d93 | |||
| 686ae57b5b | |||
| a911685aaf | |||
| 0d9a45dfd9 | |||
| 8a55bbfd20 | |||
| 6899761ca9 | |||
| a58958fd62 | |||
| 307f3c9158 | |||
| 4f275c2e06 | |||
| 7e4e3f3cd8 | |||
| 84e5add564 | |||
| 4d4adbb76f | |||
| ff2f285784 | |||
| 5934c43b70 | |||
| 11567b13d3 | |||
| 8b1636e78a | |||
| 3bdad10562 | |||
| ac03a977aa | |||
| 2087ba88b1 | |||
| e9d1b9f18e | |||
| 6e84d38680 | |||
| 22622f6e8a | |||
| 735203aa95 | |||
| 013bc365a9 | |||
| c2dfbac641 | |||
| 7d472c0b13 | |||
| d7270f66e1 | |||
| b220b1bffa | |||
| 4796fe98cb | |||
| ece905ec6e | |||
| 53c9e7c04c | |||
| 29f6f52443 | |||
| a778cc51a6 | |||
| c6963d0cd3 | |||
| 5b4ab0a3c1 | |||
| 489844f61b | |||
| 4d1b0246ca | |||
| 13f3511fa8 | |||
| f6f2766315 | |||
| e8dd81b014 | |||
| d32cc281e3 | |||
| 2ff6b59271 | |||
| 3ff87566f5 |
@@ -15,17 +15,17 @@ Consider supporting our development by buying Iris on spigot! We work hard to ma
|
|||||||
|
|
||||||
### Command Line Builds
|
### Command Line Builds
|
||||||
|
|
||||||
1. Install [Java JDK 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)
|
1. Install [Java JDK 21](https://www.oracle.com/java/technologies/javase/jdk21-archive-downloads.html)
|
||||||
2. Set the JDK installation path to `JAVA_HOME` as an environment variable.
|
2. Set the JDK installation path to `JAVA_HOME` as an environment variable.
|
||||||
* Windows
|
* Windows
|
||||||
1. Start > Type `env` and press Enter
|
1. Start > Type `env` and press Enter
|
||||||
2. Advanced > Environment Variables
|
2. Advanced > Environment Variables
|
||||||
3. Under System Variables, click `New...`
|
3. Under System Variables, click `New...`
|
||||||
4. Variable Name: `JAVA_HOME`
|
4. Variable Name: `JAVA_HOME`
|
||||||
5. Variable Value: `C:\Program Files\Java\jdk-17.0.1` (verify this exists after installing java don't just copy
|
5. Variable Value: `C:\Program Files\Java\jdk-21.0.1` (verify this exists after installing java don't just copy
|
||||||
the example text)
|
the example text)
|
||||||
* MacOS
|
* MacOS
|
||||||
1. Run `/usr/libexec/java_home -V` and look for Java 17
|
1. Run `/usr/libexec/java_home -V` and look for Java 21
|
||||||
2. Run `sudo nano ~/.zshenv`
|
2. Run `sudo nano ~/.zshenv`
|
||||||
3. Add `export JAVA_HOME=$(/usr/libexec/java_home)` as a new line
|
3. Add `export JAVA_HOME=$(/usr/libexec/java_home)` as a new line
|
||||||
4. Use `CTRL + X`, then Press `Y`, Then `ENTER`
|
4. Use `CTRL + X`, then Press `Y`, Then `ENTER`
|
||||||
@@ -35,7 +35,7 @@ Consider supporting our development by buying Iris on spigot! We work hard to ma
|
|||||||
|
|
||||||
### IDE Builds (for development)
|
### IDE Builds (for development)
|
||||||
|
|
||||||
* Configure ITJ Gradle to use JDK 17 (in settings, search for gradle)
|
* Configure ITJ Gradle to use JDK 21 (in settings, search for gradle)
|
||||||
* Add a build line in the build.gradle for your own build task to directly compile Iris into your plugins folder if you
|
* Add a build line in the build.gradle for your own build task to directly compile Iris into your plugins folder if you
|
||||||
prefer.
|
prefer.
|
||||||
* Resync the project & run your newly created task (under the development folder in gradle tasks!)
|
* Resync the project & run your newly created task (under the development folder in gradle tasks!)
|
||||||
|
|||||||
+7
-5
@@ -33,7 +33,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
version '3.5.8-1.19.2-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
|
// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED
|
||||||
// ======================== WINDOWS =============================
|
// ======================== WINDOWS =============================
|
||||||
@@ -61,9 +61,6 @@ def NMS_BINDINGS = Map.of(
|
|||||||
"v1_20_R3", "1.20.4-R0.1-SNAPSHOT",
|
"v1_20_R3", "1.20.4-R0.1-SNAPSHOT",
|
||||||
"v1_20_R2", "1.20.2-R0.1-SNAPSHOT",
|
"v1_20_R2", "1.20.2-R0.1-SNAPSHOT",
|
||||||
"v1_20_R1", "1.20.1-R0.1-SNAPSHOT",
|
"v1_20_R1", "1.20.1-R0.1-SNAPSHOT",
|
||||||
"v1_19_R3", "1.19.4-R0.1-SNAPSHOT",
|
|
||||||
"v1_19_R2", "1.19.3-R0.1-SNAPSHOT",
|
|
||||||
"v1_19_R1", "1.19.2-R0.1-SNAPSHOT"
|
|
||||||
)
|
)
|
||||||
def JVM_VERSION = Map.of()
|
def JVM_VERSION = Map.of()
|
||||||
NMS_BINDINGS.each { nms ->
|
NMS_BINDINGS.each { nms ->
|
||||||
@@ -95,6 +92,11 @@ shadowJar {
|
|||||||
relocate 'net.kyori', 'com.volmit.iris.util.kyori'
|
relocate 'net.kyori', 'com.volmit.iris.util.kyori'
|
||||||
relocate 'org.bstats', 'com.volmit.util.metrics'
|
relocate 'org.bstats', 'com.volmit.util.metrics'
|
||||||
archiveFileName.set("Iris-${project.version}.jar")
|
archiveFileName.set("Iris-${project.version}.jar")
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
exclude(dependency("org.ow2.asm:asm:"))
|
||||||
|
exclude(dependency("org.jetbrains:"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -131,7 +133,7 @@ allprojects {
|
|||||||
annotationProcessor 'org.projectlombok:lombok:1.18.36'
|
annotationProcessor 'org.projectlombok:lombok:1.18.36'
|
||||||
|
|
||||||
// Shaded
|
// Shaded
|
||||||
implementation 'com.dfsek:Paralithic:0.4.0'
|
implementation 'com.dfsek:paralithic:0.8.1'
|
||||||
implementation 'io.papermc:paperlib:1.0.5'
|
implementation 'io.papermc:paperlib:1.0.5'
|
||||||
implementation "net.kyori:adventure-text-minimessage:4.17.0"
|
implementation "net.kyori:adventure-text-minimessage:4.17.0"
|
||||||
implementation 'net.kyori:adventure-platform-bukkit:4.3.4'
|
implementation 'net.kyori:adventure-platform-bukkit:4.3.4'
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ dependencies {
|
|||||||
|
|
||||||
// Third Party Integrations
|
// Third Party Integrations
|
||||||
compileOnly 'com.ticxo.playeranimator:PlayerAnimator:R1.2.7'
|
compileOnly 'com.ticxo.playeranimator:PlayerAnimator:R1.2.7'
|
||||||
compileOnly 'com.nexomc:nexo:0.6.0-dev.0'
|
compileOnly 'com.nexomc:nexo:1.0.0-dev.38'
|
||||||
compileOnly 'com.github.LoneDev6:api-itemsadder:3.4.1-r4'
|
compileOnly 'com.github.LoneDev6:api-itemsadder:3.4.1-r4'
|
||||||
compileOnly 'com.github.PlaceholderAPI:placeholderapi:2.11.3'
|
compileOnly 'com.github.PlaceholderAPI:placeholderapi:2.11.3'
|
||||||
compileOnly 'com.github.Ssomar-Developement:SCore:4.23.10.8'
|
compileOnly 'com.github.Ssomar-Developement:SCore:4.23.10.8'
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ import com.volmit.iris.core.nms.v1X.NMSBinding1X;
|
|||||||
import com.volmit.iris.core.pregenerator.LazyPregenerator;
|
import com.volmit.iris.core.pregenerator.LazyPregenerator;
|
||||||
import com.volmit.iris.core.service.StudioSVC;
|
import com.volmit.iris.core.service.StudioSVC;
|
||||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||||
|
import com.volmit.iris.core.tools.IrisWorldCreator;
|
||||||
import com.volmit.iris.engine.EnginePanic;
|
import com.volmit.iris.engine.EnginePanic;
|
||||||
import com.volmit.iris.engine.object.IrisCompat;
|
import com.volmit.iris.engine.object.IrisCompat;
|
||||||
|
import com.volmit.iris.engine.object.IrisContextInjector;
|
||||||
import com.volmit.iris.engine.object.IrisDimension;
|
import com.volmit.iris.engine.object.IrisDimension;
|
||||||
import com.volmit.iris.engine.object.IrisWorld;
|
import com.volmit.iris.engine.object.IrisWorld;
|
||||||
import com.volmit.iris.engine.platform.BukkitChunkGenerator;
|
import com.volmit.iris.engine.platform.BukkitChunkGenerator;
|
||||||
@@ -101,8 +103,6 @@ import static com.volmit.iris.core.safeguard.ServerBootSFG.passedserversoftware;
|
|||||||
|
|
||||||
@SuppressWarnings("CanBeFinal")
|
@SuppressWarnings("CanBeFinal")
|
||||||
public class Iris extends VolmitPlugin implements Listener {
|
public class Iris extends VolmitPlugin implements Listener {
|
||||||
public static final String OVERWORLD_TAG = "31010";
|
|
||||||
|
|
||||||
private static final Queue<Runnable> syncJobs = new ShurikenQueue<>();
|
private static final Queue<Runnable> syncJobs = new ShurikenQueue<>();
|
||||||
|
|
||||||
public static Iris instance;
|
public static Iris instance;
|
||||||
@@ -459,9 +459,12 @@ public class Iris extends VolmitPlugin implements Listener {
|
|||||||
initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class<? extends IrisService>) i.getClass(), (IrisService) i));
|
initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class<? extends IrisService>) i.getClass(), (IrisService) i));
|
||||||
INMS.get();
|
INMS.get();
|
||||||
IO.delete(new File("iris"));
|
IO.delete(new File("iris"));
|
||||||
|
compat = IrisCompat.configured(getDataFile("compat.json"));
|
||||||
|
ServerConfigurator.configure();
|
||||||
|
new IrisContextInjector();
|
||||||
IrisSafeguard.IrisSafeguardSystem();
|
IrisSafeguard.IrisSafeguardSystem();
|
||||||
getSender().setTag(getTag());
|
getSender().setTag(getTag());
|
||||||
compat = IrisCompat.configured(getDataFile("compat.json"));
|
IrisSafeguard.earlySplash();
|
||||||
linkMultiverseCore = new MultiverseCoreLink();
|
linkMultiverseCore = new MultiverseCoreLink();
|
||||||
linkMythicMobs = new MythicMobsLink();
|
linkMythicMobs = new MythicMobsLink();
|
||||||
configWatcher = new FileWatcher(getDataFile("settings.json"));
|
configWatcher = new FileWatcher(getDataFile("settings.json"));
|
||||||
@@ -516,10 +519,10 @@ public class Iris extends VolmitPlugin implements Listener {
|
|||||||
Iris.info("Loading World: %s | Generator: %s", s, generator);
|
Iris.info("Loading World: %s | Generator: %s", s, generator);
|
||||||
|
|
||||||
Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "...");
|
Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "...");
|
||||||
new WorldCreator(s)
|
WorldCreator c = new WorldCreator(s)
|
||||||
.generator(getDefaultWorldGenerator(s, generator))
|
.generator(getDefaultWorldGenerator(s, generator))
|
||||||
.environment(IrisData.loadAnyDimension(generator).getEnvironment())
|
.environment(IrisData.loadAnyDimension(generator).getEnvironment());
|
||||||
.createWorld();
|
INMS.get().createWorld(c);
|
||||||
Iris.info(C.LIGHT_PURPLE + "Loaded " + s + "!");
|
Iris.info(C.LIGHT_PURPLE + "Loaded " + s + "!");
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ public class IrisSettings {
|
|||||||
public boolean splashLogoStartup = true;
|
public boolean splashLogoStartup = true;
|
||||||
public boolean useConsoleCustomColors = true;
|
public boolean useConsoleCustomColors = true;
|
||||||
public boolean useCustomColorsIngame = true;
|
public boolean useCustomColorsIngame = true;
|
||||||
|
public boolean adjustVanillaHeight = false;
|
||||||
public String forceMainWorld = "";
|
public String forceMainWorld = "";
|
||||||
public int spinh = -20;
|
public int spinh = -20;
|
||||||
public int spins = 7;
|
public int spins = 7;
|
||||||
|
|||||||
@@ -28,20 +28,28 @@ import com.volmit.iris.engine.object.IrisBiomeCustom;
|
|||||||
import com.volmit.iris.engine.object.IrisDimension;
|
import com.volmit.iris.engine.object.IrisDimension;
|
||||||
import com.volmit.iris.engine.object.IrisRange;
|
import com.volmit.iris.engine.object.IrisRange;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
|
import com.volmit.iris.util.collection.KMap;
|
||||||
import com.volmit.iris.util.collection.KSet;
|
import com.volmit.iris.util.collection.KSet;
|
||||||
import com.volmit.iris.util.format.C;
|
import com.volmit.iris.util.format.C;
|
||||||
|
import com.volmit.iris.util.misc.ServerProperties;
|
||||||
import com.volmit.iris.util.plugin.VolmitSender;
|
import com.volmit.iris.util.plugin.VolmitSender;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NonNull;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.configuration.InvalidConfigurationException;
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static com.volmit.iris.core.nms.datapack.IDataFixer.Dimension.*;
|
||||||
|
|
||||||
public class ServerConfigurator {
|
public class ServerConfigurator {
|
||||||
public static void configure() {
|
public static void configure() {
|
||||||
@@ -84,12 +92,13 @@ public class ServerConfigurator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<File> getDatapacksFolder() {
|
private static KList<File> getDatapacksFolder() {
|
||||||
if (!IrisSettings.get().getGeneral().forceMainWorld.isEmpty()) {
|
if (!IrisSettings.get().getGeneral().forceMainWorld.isEmpty()) {
|
||||||
return new KList<File>().qadd(new File(Bukkit.getWorldContainer(), IrisSettings.get().getGeneral().forceMainWorld + "/datapacks"));
|
return new KList<File>().qadd(new File(Bukkit.getWorldContainer(), IrisSettings.get().getGeneral().forceMainWorld + "/datapacks"));
|
||||||
}
|
}
|
||||||
KList<File> worlds = new KList<>();
|
KList<File> worlds = new KList<>();
|
||||||
Bukkit.getServer().getWorlds().forEach(w -> worlds.add(new File(w.getWorldFolder(), "datapacks")));
|
Bukkit.getServer().getWorlds().forEach(w -> worlds.add(new File(w.getWorldFolder(), "datapacks")));
|
||||||
|
if (worlds.isEmpty()) worlds.add(new File(Bukkit.getWorldContainer(), ServerProperties.LEVEL_NAME + "/datapacks"));
|
||||||
return worlds;
|
return worlds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,57 +108,17 @@ public class ServerConfigurator {
|
|||||||
|
|
||||||
public static void installDataPacks(IDataFixer fixer, boolean fullInstall) {
|
public static void installDataPacks(IDataFixer fixer, boolean fullInstall) {
|
||||||
Iris.info("Checking Data Packs...");
|
Iris.info("Checking Data Packs...");
|
||||||
File packs = new File("plugins/Iris/packs");
|
DimensionHeight height = new DimensionHeight(fixer);
|
||||||
double ultimateMaxHeight = 0;
|
KList<File> folders = getDatapacksFolder();
|
||||||
double ultimateMinHeight = 0;
|
KMap<String, KSet<String>> biomes = new KMap<>();
|
||||||
if (packs.exists() && packs.isDirectory()) {
|
|
||||||
for (File pack : packs.listFiles()) {
|
|
||||||
IrisData data = IrisData.get(pack);
|
|
||||||
if (pack.isDirectory()) {
|
|
||||||
File dimensionsFolder = new File(pack, "dimensions");
|
|
||||||
if (dimensionsFolder.exists() && dimensionsFolder.isDirectory()) {
|
|
||||||
for (File file : dimensionsFolder.listFiles()) {
|
|
||||||
if (file.isFile() && file.getName().endsWith(".json")) {
|
|
||||||
IrisDimension dim = data.getDimensionLoader().load(file.getName().split("\\Q.\\E")[0]);
|
|
||||||
if (ultimateMaxHeight < dim.getDimensionHeight().getMax()) {
|
|
||||||
ultimateMaxHeight = dim.getDimensionHeight().getMax();
|
|
||||||
}
|
|
||||||
if (ultimateMinHeight > dim.getDimensionHeight().getMin()) {
|
|
||||||
ultimateMinHeight = dim.getDimensionHeight().getMin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packs.exists()) {
|
allPacks().flatMap(height::merge)
|
||||||
for (File i : packs.listFiles()) {
|
.parallel()
|
||||||
if (i.isDirectory()) {
|
.forEach(dim -> {
|
||||||
Iris.verbose("Checking Pack: " + i.getPath());
|
Iris.verbose(" Checking Dimension " + dim.getLoadFile().getPath());
|
||||||
IrisData data = IrisData.get(i);
|
dim.installBiomes(fixer, dim::getLoader, folders, biomes.computeIfAbsent(dim.getLoadKey(), k -> new KSet<>()));
|
||||||
File dims = new File(i, "dimensions");
|
});
|
||||||
|
IrisDimension.writeShared(folders, height);
|
||||||
if (dims.exists()) {
|
|
||||||
for (File j : dims.listFiles()) {
|
|
||||||
if (j.getName().endsWith(".json")) {
|
|
||||||
IrisDimension dim = data.getDimensionLoader().load(j.getName().split("\\Q.\\E")[0]);
|
|
||||||
|
|
||||||
if (dim == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Iris.verbose(" Checking Dimension " + dim.getLoadFile().getPath());
|
|
||||||
for (File dpack : getDatapacksFolder()) {
|
|
||||||
dim.installDataPack(fixer, () -> data, dpack, ultimateMaxHeight, ultimateMinHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Iris.info("Data Packs Setup!");
|
Iris.info("Data Packs Setup!");
|
||||||
|
|
||||||
@@ -158,57 +127,40 @@ public class ServerConfigurator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void verifyDataPacksPost(boolean allowRestarting) {
|
private static void verifyDataPacksPost(boolean allowRestarting) {
|
||||||
File packs = new File("plugins/Iris/packs");
|
boolean bad = allPacks()
|
||||||
|
.map(data -> {
|
||||||
|
Iris.verbose("Checking Pack: " + data.getDataFolder().getPath());
|
||||||
|
var loader = data.getDimensionLoader();
|
||||||
|
return loader.loadAll(loader.getPossibleKeys())
|
||||||
|
.stream()
|
||||||
|
.map(ServerConfigurator::verifyDataPackInstalled)
|
||||||
|
.toList()
|
||||||
|
.contains(false);
|
||||||
|
})
|
||||||
|
.toList()
|
||||||
|
.contains(true);
|
||||||
|
if (!bad) return;
|
||||||
|
|
||||||
boolean bad = false;
|
|
||||||
if (packs.exists()) {
|
|
||||||
for (File i : packs.listFiles()) {
|
|
||||||
if (i.isDirectory()) {
|
|
||||||
Iris.verbose("Checking Pack: " + i.getPath());
|
|
||||||
IrisData data = IrisData.get(i);
|
|
||||||
File dims = new File(i, "dimensions");
|
|
||||||
|
|
||||||
if (dims.exists()) {
|
if (allowRestarting) {
|
||||||
for (File j : dims.listFiles()) {
|
restart();
|
||||||
if (j.getName().endsWith(".json")) {
|
} else if (INMS.get().supportsDataPacks()) {
|
||||||
IrisDimension dim = data.getDimensionLoader().load(j.getName().split("\\Q.\\E")[0]);
|
Iris.error("============================================================================");
|
||||||
|
Iris.error(C.ITALIC + "You need to restart your server to properly generate custom biomes.");
|
||||||
|
Iris.error(C.ITALIC + "By continuing, Iris will use backup biomes in place of the custom biomes.");
|
||||||
|
Iris.error("----------------------------------------------------------------------------");
|
||||||
|
Iris.error(C.UNDERLINE + "IT IS HIGHLY RECOMMENDED YOU RESTART THE SERVER BEFORE GENERATING!");
|
||||||
|
Iris.error("============================================================================");
|
||||||
|
|
||||||
if (dim == null) {
|
for (Player i : Bukkit.getOnlinePlayers()) {
|
||||||
Iris.error("Failed to load " + j.getPath() + " ");
|
if (i.isOp() || i.hasPermission("iris.all")) {
|
||||||
continue;
|
VolmitSender sender = new VolmitSender(i, Iris.instance.getTag("WARNING"));
|
||||||
}
|
sender.sendMessage("There are some Iris Packs that have custom biomes in them");
|
||||||
|
sender.sendMessage("You need to restart your server to use these packs.");
|
||||||
if (!verifyDataPackInstalled(dim)) {
|
|
||||||
bad = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (bad) {
|
J.sleep(3000);
|
||||||
if (allowRestarting) {
|
|
||||||
restart();
|
|
||||||
} else if (INMS.get().supportsDataPacks()) {
|
|
||||||
Iris.error("============================================================================");
|
|
||||||
Iris.error(C.ITALIC + "You need to restart your server to properly generate custom biomes.");
|
|
||||||
Iris.error(C.ITALIC + "By continuing, Iris will use backup biomes in place of the custom biomes.");
|
|
||||||
Iris.error("----------------------------------------------------------------------------");
|
|
||||||
Iris.error(C.UNDERLINE + "IT IS HIGHLY RECOMMENDED YOU RESTART THE SERVER BEFORE GENERATING!");
|
|
||||||
Iris.error("============================================================================");
|
|
||||||
|
|
||||||
for (Player i : Bukkit.getOnlinePlayers()) {
|
|
||||||
if (i.isOp() || i.hasPermission("iris.all")) {
|
|
||||||
VolmitSender sender = new VolmitSender(i, Iris.instance.getTag("WARNING"));
|
|
||||||
sender.sendMessage("There are some Iris Packs that have custom biomes in them");
|
|
||||||
sender.sendMessage("You need to restart your server to use these packs.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
J.sleep(3000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +170,7 @@ public class ServerConfigurator {
|
|||||||
Iris.warn("This will only happen when your pack changes (updates/first time setup)");
|
Iris.warn("This will only happen when your pack changes (updates/first time setup)");
|
||||||
Iris.warn("(You can disable this auto restart in iris settings)");
|
Iris.warn("(You can disable this auto restart in iris settings)");
|
||||||
J.s(() -> {
|
J.s(() -> {
|
||||||
Iris.warn("Looks like the restart command diddn't work. Stopping the server instead!");
|
Iris.warn("Looks like the restart command didn't work. Stopping the server instead!");
|
||||||
Bukkit.shutdown();
|
Bukkit.shutdown();
|
||||||
}, 100);
|
}, 100);
|
||||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "restart");
|
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "restart");
|
||||||
@@ -226,22 +178,24 @@ public class ServerConfigurator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean verifyDataPackInstalled(IrisDimension dimension) {
|
public static boolean verifyDataPackInstalled(IrisDimension dimension) {
|
||||||
IrisData idm = IrisData.get(Iris.instance.getDataFolder("packs", dimension.getLoadKey()));
|
|
||||||
KSet<String> keys = new KSet<>();
|
KSet<String> keys = new KSet<>();
|
||||||
boolean warn = false;
|
boolean warn = false;
|
||||||
|
|
||||||
for (IrisBiome i : dimension.getAllBiomes(() -> idm)) {
|
for (IrisBiome i : dimension.getAllBiomes(dimension::getLoader)) {
|
||||||
if (i.isCustom()) {
|
if (i.isCustom()) {
|
||||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||||
keys.add(dimension.getLoadKey() + ":" + j.getId());
|
keys.add(dimension.getLoadKey() + ":" + j.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
String key = getWorld(dimension.getLoader());
|
||||||
|
if (key == null) key = dimension.getLoadKey();
|
||||||
|
else key += "/" + dimension.getLoadKey();
|
||||||
|
|
||||||
if (!INMS.get().supportsDataPacks()) {
|
if (!INMS.get().supportsDataPacks()) {
|
||||||
if (!keys.isEmpty()) {
|
if (!keys.isEmpty()) {
|
||||||
Iris.warn("===================================================================================");
|
Iris.warn("===================================================================================");
|
||||||
Iris.warn("Pack " + dimension.getLoadKey() + " has " + keys.size() + " custom biome(s). ");
|
Iris.warn("Pack " + key + " has " + keys.size() + " custom biome(s). ");
|
||||||
Iris.warn("Your server version does not yet support datapacks for iris.");
|
Iris.warn("Your server version does not yet support datapacks for iris.");
|
||||||
Iris.warn("The world will generate these biomes as backup biomes.");
|
Iris.warn("The world will generate these biomes as backup biomes.");
|
||||||
Iris.warn("====================================================================================");
|
Iris.warn("====================================================================================");
|
||||||
@@ -260,10 +214,74 @@ public class ServerConfigurator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (warn) {
|
if (warn) {
|
||||||
Iris.error("The Pack " + dimension.getLoadKey() + " is INCAPABLE of generating custom biomes");
|
Iris.error("The Pack " + key + " is INCAPABLE of generating custom biomes");
|
||||||
Iris.error("If not done automatically, restart your server before generating with this pack!");
|
Iris.error("If not done automatically, restart your server before generating with this pack!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return !warn;
|
return !warn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Stream<IrisData> allPacks() {
|
||||||
|
return Stream.concat(listFiles(new File("plugins/Iris/packs")),
|
||||||
|
listFiles(Bukkit.getWorldContainer()).map(w -> new File(w, "iris/pack")))
|
||||||
|
.filter(File::isDirectory)
|
||||||
|
.map(IrisData::get);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String getWorld(@NonNull IrisData data) {
|
||||||
|
String worldContainer = Bukkit.getWorldContainer().getAbsolutePath();
|
||||||
|
if (!worldContainer.endsWith(File.separator)) worldContainer += File.separator;
|
||||||
|
|
||||||
|
String path = data.getDataFolder().getAbsolutePath();
|
||||||
|
if (!path.startsWith(worldContainer)) return null;
|
||||||
|
int l = path.endsWith(File.separator) ? 11 : 10;
|
||||||
|
return path.substring(worldContainer.length(), path.length() - l);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<File> listFiles(File parent) {
|
||||||
|
var files = parent.listFiles();
|
||||||
|
return files == null ? Stream.empty() : Arrays.stream(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DimensionHeight {
|
||||||
|
private final IDataFixer fixer;
|
||||||
|
private IrisRange overworld = new IrisRange();
|
||||||
|
private IrisRange nether = new IrisRange();
|
||||||
|
private IrisRange end = new IrisRange();
|
||||||
|
private int logicalOverworld = 0;
|
||||||
|
private int logicalNether = 0;
|
||||||
|
private int logicalEnd = 0;
|
||||||
|
|
||||||
|
public Stream<IrisDimension> merge(IrisData data) {
|
||||||
|
Iris.verbose("Checking Pack: " + data.getDataFolder().getPath());
|
||||||
|
var loader = data.getDimensionLoader();
|
||||||
|
return loader.loadAll(loader.getPossibleKeys())
|
||||||
|
.stream()
|
||||||
|
.peek(this::merge);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void merge(IrisDimension dimension) {
|
||||||
|
overworld.merge(dimension.getDimensionHeight());
|
||||||
|
nether.merge(dimension.getDimensionHeight());
|
||||||
|
end.merge(dimension.getDimensionHeight());
|
||||||
|
|
||||||
|
logicalOverworld = Math.max(logicalOverworld, dimension.getLogicalHeight());
|
||||||
|
logicalNether = Math.max(logicalNether, dimension.getLogicalHeightNether());
|
||||||
|
logicalEnd = Math.max(logicalEnd, dimension.getLogicalHeightEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String overworldType() {
|
||||||
|
return fixer.createDimension(OVERRWORLD, overworld, logicalOverworld).toString(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String netherType() {
|
||||||
|
return fixer.createDimension(NETHER, nether, logicalNether).toString(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String endType() {
|
||||||
|
return fixer.createDimension(THE_END, end, logicalEnd).toString(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,14 @@ import com.volmit.iris.Iris;
|
|||||||
import com.volmit.iris.core.ServerConfigurator;
|
import com.volmit.iris.core.ServerConfigurator;
|
||||||
import com.volmit.iris.core.loader.IrisData;
|
import com.volmit.iris.core.loader.IrisData;
|
||||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
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.service.IrisEngineSVC;
|
||||||
import com.volmit.iris.core.tools.IrisPackBenchmarking;
|
import com.volmit.iris.core.tools.IrisPackBenchmarking;
|
||||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
import com.volmit.iris.engine.object.IrisDimension;
|
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.DecreeExecutor;
|
||||||
import com.volmit.iris.util.decree.DecreeOrigin;
|
import com.volmit.iris.util.decree.DecreeOrigin;
|
||||||
import com.volmit.iris.util.decree.annotations.Decree;
|
import com.volmit.iris.util.decree.annotations.Decree;
|
||||||
@@ -36,6 +39,7 @@ import com.volmit.iris.util.format.Form;
|
|||||||
import com.volmit.iris.util.io.CountingDataInputStream;
|
import com.volmit.iris.util.io.CountingDataInputStream;
|
||||||
import com.volmit.iris.util.io.IO;
|
import com.volmit.iris.util.io.IO;
|
||||||
import com.volmit.iris.util.mantle.TectonicPlate;
|
import com.volmit.iris.util.mantle.TectonicPlate;
|
||||||
|
import com.volmit.iris.util.math.Position2;
|
||||||
import com.volmit.iris.util.nbt.mca.MCAFile;
|
import com.volmit.iris.util.nbt.mca.MCAFile;
|
||||||
import com.volmit.iris.util.nbt.mca.MCAUtil;
|
import com.volmit.iris.util.nbt.mca.MCAUtil;
|
||||||
import com.volmit.iris.util.parallel.MultiBurst;
|
import com.volmit.iris.util.parallel.MultiBurst;
|
||||||
@@ -48,6 +52,7 @@ import org.apache.commons.lang.RandomStringUtils;
|
|||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Chunk;
|
import org.bukkit.Chunk;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
@@ -140,12 +145,16 @@ public class CommandDeveloper implements DecreeExecutor {
|
|||||||
public void packBenchmark(
|
public void packBenchmark(
|
||||||
@Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld")
|
@Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld")
|
||||||
IrisDimension dimension,
|
IrisDimension dimension,
|
||||||
@Param(description = "Radius in regions", defaultValue = "5")
|
@Param(description = "Diameter in regions", defaultValue = "2048")
|
||||||
int radius,
|
int diameter,
|
||||||
|
@Param(description = "Headless", defaultValue = "true")
|
||||||
|
boolean headless,
|
||||||
@Param(description = "Open GUI while benchmarking", defaultValue = "false")
|
@Param(description = "Open GUI while benchmarking", defaultValue = "false")
|
||||||
boolean gui
|
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")
|
@Decree(description = "Upgrade to another Minecraft version")
|
||||||
@@ -213,6 +222,42 @@ public class CommandDeveloper implements DecreeExecutor {
|
|||||||
sender.sendMessage(C.RED + "Failed to load " + failed.get() + " of " + keys.length + " objects");
|
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"})
|
@Decree(description = "Test", aliases = {"ip"})
|
||||||
public void network() {
|
public void network() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -19,9 +19,7 @@
|
|||||||
package com.volmit.iris.core.commands;
|
package com.volmit.iris.core.commands;
|
||||||
|
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.IrisSettings;
|
|
||||||
import com.volmit.iris.core.gui.PregeneratorJob;
|
import com.volmit.iris.core.gui.PregeneratorJob;
|
||||||
import com.volmit.iris.core.pregenerator.LazyPregenerator;
|
|
||||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||||
@@ -29,12 +27,9 @@ import com.volmit.iris.util.decree.annotations.Decree;
|
|||||||
import com.volmit.iris.util.decree.annotations.Param;
|
import com.volmit.iris.util.decree.annotations.Param;
|
||||||
import com.volmit.iris.util.format.C;
|
import com.volmit.iris.util.format.C;
|
||||||
import com.volmit.iris.util.math.Position2;
|
import com.volmit.iris.util.math.Position2;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
@Decree(name = "pregen", aliases = "pregenerate", description = "Pregenerate your Iris worlds!")
|
@Decree(name = "pregen", aliases = "pregenerate", description = "Pregenerate your Iris worlds!")
|
||||||
public class CommandPregen implements DecreeExecutor {
|
public class CommandPregen implements DecreeExecutor {
|
||||||
@Decree(description = "Pregenerate a world")
|
@Decree(description = "Pregenerate a world")
|
||||||
@@ -52,13 +47,12 @@ public class CommandPregen implements DecreeExecutor {
|
|||||||
sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example.");
|
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);
|
radius = Math.max(radius, 1024);
|
||||||
int w = (radius >> 9 + 1) * 2;
|
|
||||||
IrisToolbelt.pregenerate(PregenTask
|
IrisToolbelt.pregenerate(PregenTask
|
||||||
.builder()
|
.builder()
|
||||||
.center(new Position2(center.getBlockX() >> 9, center.getBlockZ() >> 9))
|
.center(new Position2(center.getBlockX(), center.getBlockZ()))
|
||||||
.gui(true)
|
.gui(true)
|
||||||
.width(w)
|
.radiusX(radius)
|
||||||
.height(w)
|
.radiusZ(radius)
|
||||||
.build(), world);
|
.build(), world);
|
||||||
String msg = C.GREEN + "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();
|
String msg = C.GREEN + "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);
|
sender().sendMessage(msg);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import com.volmit.iris.core.pregenerator.IrisPregenerator;
|
|||||||
import com.volmit.iris.core.pregenerator.PregenListener;
|
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||||
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
|
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
|
||||||
import com.volmit.iris.core.tools.IrisPackBenchmarking;
|
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
import com.volmit.iris.util.format.Form;
|
import com.volmit.iris.util.format.Form;
|
||||||
@@ -45,8 +44,6 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static com.volmit.iris.core.tools.IrisPackBenchmarking.benchmarkInProgress;
|
|
||||||
|
|
||||||
public class PregeneratorJob implements PregenListener {
|
public class PregeneratorJob implements PregenListener {
|
||||||
private static final Color COLOR_EXISTS = parseColor("#4d7d5b");
|
private static final Color COLOR_EXISTS = parseColor("#4d7d5b");
|
||||||
private static final Color COLOR_BLACK = parseColor("#4d7d5b");
|
private static final Color COLOR_BLACK = parseColor("#4d7d5b");
|
||||||
@@ -81,12 +78,12 @@ public class PregeneratorJob implements PregenListener {
|
|||||||
this.task = task;
|
this.task = task;
|
||||||
this.pregenerator = new IrisPregenerator(task, method, this);
|
this.pregenerator = new IrisPregenerator(task, method, this);
|
||||||
max = new Position2(0, 0);
|
max = new Position2(0, 0);
|
||||||
min = new Position2(0, 0);
|
min = new Position2(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
task.iterateRegions((xx, zz) -> {
|
task.iterateAllChunks((xx, zz) -> {
|
||||||
min.setX(Math.min(xx << 5, min.getX()));
|
min.setX(Math.min(xx, min.getX()));
|
||||||
min.setZ(Math.min(zz << 5, min.getZ()));
|
min.setZ(Math.min(zz, min.getZ()));
|
||||||
max.setX(Math.max((xx << 5) + 31, max.getX()));
|
max.setX(Math.max(xx, max.getX()));
|
||||||
max.setZ(Math.max((zz << 5) + 31, max.getZ()));
|
max.setZ(Math.max(zz, max.getZ()));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (IrisSettings.get().getGui().isUseServerLaunchedGuis() && task.isGui()) {
|
if (IrisSettings.get().getGui().isUseServerLaunchedGuis() && task.isGui()) {
|
||||||
@@ -162,7 +159,7 @@ public class PregeneratorJob implements PregenListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void drawRegion(int x, int z, Color color) {
|
public void drawRegion(int x, int z, Color color) {
|
||||||
J.a(() -> PregenTask.iterateRegion(x, z, (xx, zz) -> {
|
J.a(() -> task.iterateChunks(x, z, (xx, zz) -> {
|
||||||
draw(xx, zz, color);
|
draw(xx, zz, color);
|
||||||
J.sleep(3);
|
J.sleep(3);
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ public class NexoDataProvider extends ExternalDataProvider {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Identifier[] getBlockTypes() {
|
public Identifier[] getBlockTypes() {
|
||||||
return Arrays.stream(NexoItems.itemNames())
|
return NexoItems.itemNames().stream()
|
||||||
.map(i -> new Identifier("nexo", i))
|
.map(i -> new Identifier("nexo", i))
|
||||||
.filter(i -> {
|
.filter(i -> {
|
||||||
try {
|
try {
|
||||||
@@ -140,7 +140,7 @@ public class NexoDataProvider extends ExternalDataProvider {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Identifier[] getItemTypes() {
|
public Identifier[] getItemTypes() {
|
||||||
return Arrays.stream(NexoItems.itemNames())
|
return NexoItems.itemNames().stream()
|
||||||
.map(i -> new Identifier("nexo", i))
|
.map(i -> new Identifier("nexo", i))
|
||||||
.filter(i -> {
|
.filter(i -> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.volmit.iris.core.IrisSettings;
|
|||||||
import com.volmit.iris.core.nms.v1X.NMSBinding1X;
|
import com.volmit.iris.core.nms.v1X.NMSBinding1X;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class INMS {
|
public class INMS {
|
||||||
@@ -35,8 +36,15 @@ public class INMS {
|
|||||||
"1.21.3", "v1_21_R2",
|
"1.21.3", "v1_21_R2",
|
||||||
"1.21.4", "v1_21_R3"
|
"1.21.4", "v1_21_R3"
|
||||||
);
|
);
|
||||||
|
private static final List<Version> PACKS = List.of(
|
||||||
|
new Version(21, 4, "31020"),
|
||||||
|
new Version(21, 2, "31000"),
|
||||||
|
new Version(20, 1, "3910")
|
||||||
|
);
|
||||||
|
|
||||||
//@done
|
//@done
|
||||||
private static final INMSBinding binding = bind();
|
private static final INMSBinding binding = bind();
|
||||||
|
public static final String OVERWORLD_TAG = getOverworldTag();
|
||||||
|
|
||||||
public static INMSBinding get() {
|
public static INMSBinding get() {
|
||||||
return binding;
|
return binding;
|
||||||
@@ -87,4 +95,26 @@ public class INMS {
|
|||||||
|
|
||||||
return new NMSBinding1X();
|
return new NMSBinding1X();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getOverworldTag() {
|
||||||
|
var version = Bukkit.getServer().getBukkitVersion().split("-")[0].split("\\.", 3);
|
||||||
|
int major = 0;
|
||||||
|
int minor = 0;
|
||||||
|
|
||||||
|
if (version.length > 2) {
|
||||||
|
major = Integer.parseInt(version[1]);
|
||||||
|
minor = Integer.parseInt(version[2]);
|
||||||
|
} else if (version.length == 2) {
|
||||||
|
major = Integer.parseInt(version[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var p : PACKS) {
|
||||||
|
if (p.major > major || p.minor > minor)
|
||||||
|
continue;
|
||||||
|
return p.tag;
|
||||||
|
}
|
||||||
|
return "3910";
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Version(int major, int minor, String tag) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,11 @@
|
|||||||
|
|
||||||
package com.volmit.iris.core.nms;
|
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.BiomeColor;
|
||||||
|
import com.volmit.iris.core.nms.container.Pair;
|
||||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
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.engine.framework.Engine;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
import com.volmit.iris.util.collection.KMap;
|
import com.volmit.iris.util.collection.KMap;
|
||||||
@@ -30,15 +33,12 @@ import com.volmit.iris.util.nbt.mca.palette.MCAPaletteAccess;
|
|||||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.entity.Dolphin;
|
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.EntityType;
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
import org.bukkit.generator.structure.Structure;
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
|
||||||
public interface INMSBinding {
|
public interface INMSBinding {
|
||||||
@@ -91,7 +91,12 @@ public interface INMSBinding {
|
|||||||
MCABiomeContainer newBiomeContainer(int min, int max);
|
MCABiomeContainer newBiomeContainer(int min, int max);
|
||||||
|
|
||||||
default World createWorld(WorldCreator c) {
|
default World createWorld(WorldCreator c) {
|
||||||
return c.createWorld();
|
if (missingDimensionTypes(true, true, true))
|
||||||
|
throw new IllegalStateException("Missing dimenstion types to create world");
|
||||||
|
|
||||||
|
try (var ignored = injectLevelStems()) {
|
||||||
|
return c.createWorld();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int countCustomBiomes();
|
int countCustomBiomes();
|
||||||
@@ -124,5 +129,13 @@ public interface INMSBinding {
|
|||||||
return 441;
|
return 441;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IRegionStorage createRegionStorage(Engine engine);
|
||||||
|
|
||||||
KList<String> getStructureKeys();
|
KList<String> getStructureKeys();
|
||||||
|
|
||||||
|
AutoClosing injectLevelStems();
|
||||||
|
|
||||||
|
Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end);
|
||||||
|
|
||||||
|
boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.volmit.iris.core.nms.container;
|
||||||
|
|
||||||
|
import com.volmit.iris.util.function.NastyRunnable;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AutoClosing implements AutoCloseable {
|
||||||
|
private final AtomicBoolean closed = new AtomicBoolean();
|
||||||
|
private final NastyRunnable action;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (closed.getAndSet(true)) return;
|
||||||
|
try {
|
||||||
|
action.run();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,28 @@
|
|||||||
package com.volmit.iris.core.nms.datapack;
|
package com.volmit.iris.core.nms.datapack;
|
||||||
|
|
||||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||||
|
import com.volmit.iris.engine.object.IrisRange;
|
||||||
import com.volmit.iris.util.json.JSONObject;
|
import com.volmit.iris.util.json.JSONObject;
|
||||||
|
|
||||||
public interface IDataFixer {
|
public interface IDataFixer {
|
||||||
|
|
||||||
JSONObject fixCustomBiome(IrisBiomeCustom biome, JSONObject json);
|
default JSONObject fixCustomBiome(IrisBiomeCustom biome, JSONObject json) {
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
JSONObject fixDimension(JSONObject json);
|
JSONObject rawDimension(Dimension dimension);
|
||||||
|
|
||||||
|
default JSONObject createDimension(Dimension dimension, IrisRange height, int logicalHeight) {
|
||||||
|
JSONObject obj = rawDimension(dimension);
|
||||||
|
obj.put("min_y", height.getMin());
|
||||||
|
obj.put("height", height.getMax() - height.getMin());
|
||||||
|
obj.put("logical_height", logicalHeight);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Dimension {
|
||||||
|
OVERRWORLD,
|
||||||
|
NETHER,
|
||||||
|
THE_END
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,81 @@
|
|||||||
package com.volmit.iris.core.nms.datapack.v1192;
|
package com.volmit.iris.core.nms.datapack.v1192;
|
||||||
|
|
||||||
import com.volmit.iris.core.nms.datapack.IDataFixer;
|
import com.volmit.iris.core.nms.datapack.IDataFixer;
|
||||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
|
||||||
import com.volmit.iris.util.json.JSONObject;
|
import com.volmit.iris.util.json.JSONObject;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class DataFixerV1192 implements IDataFixer {
|
public class DataFixerV1192 implements IDataFixer {
|
||||||
|
|
||||||
@Override
|
private static final Map<Dimension, String> DIMENSIONS = Map.of(
|
||||||
public JSONObject fixCustomBiome(IrisBiomeCustom biome, JSONObject json) {
|
Dimension.OVERRWORLD, """
|
||||||
return json;
|
{
|
||||||
}
|
"ambient_light": 0.0,
|
||||||
|
"bed_works": true,
|
||||||
|
"coordinate_scale": 1.0,
|
||||||
|
"effects": "minecraft:overworld",
|
||||||
|
"has_ceiling": false,
|
||||||
|
"has_raids": true,
|
||||||
|
"has_skylight": true,
|
||||||
|
"infiniburn": "#minecraft:infiniburn_overworld",
|
||||||
|
"monster_spawn_block_light_limit": 0,
|
||||||
|
"monster_spawn_light_level": {
|
||||||
|
"type": "minecraft:uniform",
|
||||||
|
"value": {
|
||||||
|
"max_inclusive": 7,
|
||||||
|
"min_inclusive": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"natural": true,
|
||||||
|
"piglin_safe": false,
|
||||||
|
"respawn_anchor_works": false,
|
||||||
|
"ultrawarm": false
|
||||||
|
}""",
|
||||||
|
Dimension.NETHER, """
|
||||||
|
{
|
||||||
|
"ambient_light": 0.1,
|
||||||
|
"bed_works": false,
|
||||||
|
"coordinate_scale": 8.0,
|
||||||
|
"effects": "minecraft:the_nether",
|
||||||
|
"fixed_time": 18000,
|
||||||
|
"has_ceiling": true,
|
||||||
|
"has_raids": false,
|
||||||
|
"has_skylight": false,
|
||||||
|
"infiniburn": "#minecraft:infiniburn_nether",
|
||||||
|
"monster_spawn_block_light_limit": 15,
|
||||||
|
"monster_spawn_light_level": 7,
|
||||||
|
"natural": false,
|
||||||
|
"piglin_safe": true,
|
||||||
|
"respawn_anchor_works": true,
|
||||||
|
"ultrawarm": true
|
||||||
|
}""",
|
||||||
|
Dimension.THE_END, """
|
||||||
|
{
|
||||||
|
"ambient_light": 0.0,
|
||||||
|
"bed_works": false,
|
||||||
|
"coordinate_scale": 1.0,
|
||||||
|
"effects": "minecraft:the_end",
|
||||||
|
"fixed_time": 6000,
|
||||||
|
"has_ceiling": false,
|
||||||
|
"has_raids": true,
|
||||||
|
"has_skylight": false,
|
||||||
|
"infiniburn": "#minecraft:infiniburn_end",
|
||||||
|
"monster_spawn_block_light_limit": 0,
|
||||||
|
"monster_spawn_light_level": {
|
||||||
|
"type": "minecraft:uniform",
|
||||||
|
"value": {
|
||||||
|
"max_inclusive": 7,
|
||||||
|
"min_inclusive": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"natural": false,
|
||||||
|
"piglin_safe": false,
|
||||||
|
"respawn_anchor_works": false,
|
||||||
|
"ultrawarm": false
|
||||||
|
}"""
|
||||||
|
);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JSONObject fixDimension(JSONObject json) {
|
public JSONObject rawDimension(Dimension dimension) {
|
||||||
return json;
|
return new JSONObject(DIMENSIONS.get(dimension));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.volmit.iris.core.nms.datapack.v1206;
|
package com.volmit.iris.core.nms.datapack.v1206;
|
||||||
|
|
||||||
import com.volmit.iris.core.nms.datapack.IDataFixer;
|
import com.volmit.iris.core.nms.datapack.v1192.DataFixerV1192;
|
||||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||||
import com.volmit.iris.engine.object.IrisBiomeCustomSpawn;
|
import com.volmit.iris.engine.object.IrisBiomeCustomSpawn;
|
||||||
import com.volmit.iris.engine.object.IrisBiomeCustomSpawnType;
|
import com.volmit.iris.engine.object.IrisBiomeCustomSpawnType;
|
||||||
@@ -10,7 +10,7 @@ import com.volmit.iris.util.json.JSONObject;
|
|||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class DataFixerV1206 implements IDataFixer {
|
public class DataFixerV1206 extends DataFixerV1192 {
|
||||||
@Override
|
@Override
|
||||||
public JSONObject fixCustomBiome(IrisBiomeCustom biome, JSONObject json) {
|
public JSONObject fixCustomBiome(IrisBiomeCustom biome, JSONObject json) {
|
||||||
int spawnRarity = biome.getSpawnRarity();
|
int spawnRarity = biome.getSpawnRarity();
|
||||||
@@ -45,7 +45,8 @@ public class DataFixerV1206 implements IDataFixer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JSONObject fixDimension(JSONObject json) {
|
public JSONObject rawDimension(Dimension dimension) {
|
||||||
|
JSONObject json = super.rawDimension(dimension);
|
||||||
if (!(json.get("monster_spawn_light_level") instanceof JSONObject lightLevel))
|
if (!(json.get("monster_spawn_light_level") instanceof JSONObject lightLevel))
|
||||||
return json;
|
return json;
|
||||||
var value = (JSONObject) lightLevel.remove("value");
|
var value = (JSONObject) lightLevel.remove("value");
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -20,7 +20,10 @@ package com.volmit.iris.core.nms.v1X;
|
|||||||
|
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.nms.INMSBinding;
|
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.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.engine.framework.Engine;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
import com.volmit.iris.util.collection.KMap;
|
import com.volmit.iris.util.collection.KMap;
|
||||||
@@ -109,6 +112,11 @@ public class NMSBinding1X implements INMSBinding {
|
|||||||
return Color.GREEN;
|
return Color.GREEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IRegionStorage createRegionStorage(Engine engine) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KList<String> getStructureKeys() {
|
public KList<String> getStructureKeys() {
|
||||||
var list = StreamSupport.stream(Registry.STRUCTURE.spliterator(), false)
|
var list = StreamSupport.stream(Registry.STRUCTURE.spliterator(), false)
|
||||||
@@ -118,6 +126,21 @@ public class NMSBinding1X implements INMSBinding {
|
|||||||
return new KList<>(list);
|
return new KList<>(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AutoClosing injectLevelStems() {
|
||||||
|
return new AutoClosing(() -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||||
|
return new Pair<>(0, new AutoClosing(() -> {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompoundTag serializeEntity(Entity location) {
|
public CompoundTag serializeEntity(Entity location) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -165,8 +165,11 @@ public class ChunkUpdater {
|
|||||||
if (rX < dimensions.min.getX() || rX > dimensions.max.getX() || rZ < dimensions.min.getZ() || rZ > dimensions.max.getZ()) {
|
if (rX < dimensions.min.getX() || rX > dimensions.max.getX() || rZ < dimensions.min.getZ() || rZ > dimensions.max.getZ()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!new File(world.getWorldFolder(), "region" + File.separator + rX + "." + rZ + ".mca").exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PregenTask.iterateRegion(rX, rZ, (x, z) -> {
|
task.iterateChunks(rX, rZ, (x, z) -> {
|
||||||
while (paused.get() && !cancelled.get()) {
|
while (paused.get() && !cancelled.get()) {
|
||||||
J.sleep(50);
|
J.sleep(50);
|
||||||
}
|
}
|
||||||
@@ -348,8 +351,8 @@ public class ChunkUpdater {
|
|||||||
int width = maxZ - minZ + 1;
|
int width = maxZ - minZ + 1;
|
||||||
|
|
||||||
return new Dimensions(new Position2(minX, minZ), new Position2(maxX, maxZ), height * width, PregenTask.builder()
|
return new Dimensions(new Position2(minX, minZ), new Position2(maxX, maxZ), height * width, PregenTask.builder()
|
||||||
.width((int) Math.ceil(width / 2d))
|
.radiusZ((int) Math.ceil(width / 2d * 512))
|
||||||
.height((int) Math.ceil(height / 2d))
|
.radiusX((int) Math.ceil(height / 2d * 512))
|
||||||
.center(new Position2(oX, oZ))
|
.center(new Position2(oX, oZ))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
package com.volmit.iris.core.pregenerator;
|
package com.volmit.iris.core.pregenerator;
|
||||||
|
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.pack.IrisPack;
|
|
||||||
import com.volmit.iris.core.tools.IrisPackBenchmarking;
|
import com.volmit.iris.core.tools.IrisPackBenchmarking;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
import com.volmit.iris.util.collection.KSet;
|
import com.volmit.iris.util.collection.KSet;
|
||||||
@@ -83,7 +82,7 @@ public class IrisPregenerator {
|
|||||||
generatedLast = new AtomicInteger(0);
|
generatedLast = new AtomicInteger(0);
|
||||||
generatedLastMinute = new AtomicInteger(0);
|
generatedLastMinute = new AtomicInteger(0);
|
||||||
totalChunks = new AtomicInteger(0);
|
totalChunks = new AtomicInteger(0);
|
||||||
task.iterateRegions((_a, _b) -> totalChunks.addAndGet(1024));
|
task.iterateAllChunks((_a, _b) -> totalChunks.incrementAndGet());
|
||||||
startTime = new AtomicLong(M.ms());
|
startTime = new AtomicLong(M.ms());
|
||||||
ticker = new Looper() {
|
ticker = new Looper() {
|
||||||
@Override
|
@Override
|
||||||
@@ -194,7 +193,7 @@ public class IrisPregenerator {
|
|||||||
} else if (!regions) {
|
} else if (!regions) {
|
||||||
hit = true;
|
hit = true;
|
||||||
listener.onRegionGenerating(x, z);
|
listener.onRegionGenerating(x, z);
|
||||||
PregenTask.iterateRegion(x, z, (xx, zz) -> {
|
task.iterateChunks(x, z, (xx, zz) -> {
|
||||||
while (paused.get() && !shutdown.get()) {
|
while (paused.get() && !shutdown.get()) {
|
||||||
J.sleep(50);
|
J.sleep(50);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,17 +32,26 @@ import java.util.Comparator;
|
|||||||
@Data
|
@Data
|
||||||
public class PregenTask {
|
public class PregenTask {
|
||||||
private static final Position2 ZERO = new Position2(0, 0);
|
private static final Position2 ZERO = new Position2(0, 0);
|
||||||
private static final KList<Position2> ORDER_CENTER = computeChunkOrder();
|
|
||||||
private static final KMap<Position2, KList<Position2>> ORDERS = new KMap<>();
|
private static final KMap<Position2, KList<Position2>> ORDERS = new KMap<>();
|
||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private boolean gui = false;
|
private final boolean gui = false;
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private Position2 center = new Position2(0, 0);
|
private final Position2 center = new Position2(0, 0);
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private int width = 1;
|
private final int radiusX = 1;
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private int height = 1;
|
private final int radiusZ = 1;
|
||||||
|
|
||||||
|
private final Bounds bounds = new Bounds();
|
||||||
|
|
||||||
|
protected PregenTask(boolean gui, Position2 center, int radiusX, int radiusZ) {
|
||||||
|
this.gui = gui;
|
||||||
|
this.center = new ProxiedPos(center);
|
||||||
|
this.radiusX = radiusX;
|
||||||
|
this.radiusZ = radiusZ;
|
||||||
|
bounds.update();
|
||||||
|
}
|
||||||
|
|
||||||
public static void iterateRegion(int xr, int zr, Spiraled s, Position2 pull) {
|
public static void iterateRegion(int xr, int zr, Spiraled s, Position2 pull) {
|
||||||
for (Position2 i : ORDERS.computeIfAbsent(pull, PregenTask::computeOrder)) {
|
for (Position2 i : ORDERS.computeIfAbsent(pull, PregenTask::computeOrder)) {
|
||||||
@@ -70,29 +79,72 @@ public class PregenTask {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KList<Position2> computeChunkOrder() {
|
public void iterateRegions(Spiraled s) {
|
||||||
Position2 center = new Position2(15, 15);
|
var bound = bounds.region();
|
||||||
KList<Position2> p = new KList<>();
|
new Spiraler(bound.sizeX, bound.sizeZ, ((x, z) -> {
|
||||||
new Spiraler(33, 33, (x, z) -> {
|
if (bound.check(x, z)) s.on(x, z);
|
||||||
int xx = x + 15;
|
})).setOffset(center.getX() >> 9, center.getZ() >> 9).drain();
|
||||||
int zz = z + 15;
|
|
||||||
if (xx < 0 || xx > 31 || zz < 0 || zz > 31) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.add(new Position2(xx, zz));
|
|
||||||
}).drain();
|
|
||||||
p.sort(Comparator.comparing((i) -> i.distance(center)));
|
|
||||||
return p;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void iterateRegions(Spiraled s) {
|
public void iterateChunks(int rX, int rZ, Spiraled s) {
|
||||||
new Spiraler(getWidth() * 2, getHeight() * 2, s)
|
var bound = bounds.chunk();
|
||||||
.setOffset(center.getX(), center.getZ()).drain();
|
iterateRegion(rX, rZ, ((x, z) -> {
|
||||||
|
if (bound.check(x, z)) s.on(x, z);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void iterateAllChunks(Spiraled s) {
|
public void iterateAllChunks(Spiraled s) {
|
||||||
new Spiraler(getWidth() * 2, getHeight() * 2, (x, z) -> iterateRegion(x, z, s))
|
iterateRegions(((rX, rZ) -> iterateChunks(rX, rZ, s)));
|
||||||
.setOffset(center.getX(), center.getZ()).drain();
|
}
|
||||||
|
|
||||||
|
private class Bounds {
|
||||||
|
private Bound chunk = null;
|
||||||
|
private Bound region = null;
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
int maxX = center.getX() + radiusX;
|
||||||
|
int maxZ = center.getZ() + radiusZ;
|
||||||
|
int minX = center.getX() - radiusX;
|
||||||
|
int minZ = center.getZ() - radiusZ;
|
||||||
|
|
||||||
|
chunk = new Bound(minX >> 4, minZ >> 4, Math.ceilDiv(maxX, 16), Math.ceilDiv(maxZ, 16));
|
||||||
|
region = new Bound(minX >> 9, minZ >> 9, Math.ceilDiv(maxX, 512), Math.ceilDiv(maxZ, 512));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bound chunk() {
|
||||||
|
if (chunk == null) update();
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bound region() {
|
||||||
|
if (region == null) update();
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Bound(int minX, int maxX, int minZ, int maxZ, int sizeX, int sizeZ) {
|
||||||
|
private Bound(int minX, int minZ, int maxX, int maxZ) {
|
||||||
|
this(minX, maxX, minZ, maxZ, maxZ - minZ + 1, maxZ - minZ + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean check(int x, int z) {
|
||||||
|
return x >= minX && x <= maxX && z >= minZ && z <= maxZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ProxiedPos extends Position2 {
|
||||||
|
public ProxiedPos(Position2 p) {
|
||||||
|
super(p.getX(), p.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setX(int x) {
|
||||||
|
throw new IllegalStateException("This Position2 may not be modified");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setZ(int z) {
|
||||||
|
throw new IllegalStateException("This Position2 may not be modified");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.volmit.iris.core.safeguard;
|
package com.volmit.iris.core.safeguard;
|
||||||
|
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.IrisSettings;
|
||||||
|
|
||||||
public class IrisSafeguard {
|
public class IrisSafeguard {
|
||||||
public static boolean unstablemode = false;
|
public static boolean unstablemode = false;
|
||||||
@@ -11,5 +12,13 @@ public class IrisSafeguard {
|
|||||||
Iris.info("Enabled Iris SafeGuard");
|
Iris.info("Enabled Iris SafeGuard");
|
||||||
ServerBootSFG.BootCheck();
|
ServerBootSFG.BootCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void earlySplash() {
|
||||||
|
if (ServerBootSFG.safeguardPassed || IrisSettings.get().getGeneral().DoomsdayAnnihilationSelfDestructMode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Iris.instance.splash();
|
||||||
|
UtilsSFG.splash();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.volmit.iris.core.safeguard;
|
|||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.nms.INMS;
|
import com.volmit.iris.core.nms.INMS;
|
||||||
import com.volmit.iris.core.nms.v1X.NMSBinding1X;
|
import com.volmit.iris.core.nms.v1X.NMSBinding1X;
|
||||||
|
import com.volmit.iris.engine.object.IrisContextInjector;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
@@ -29,6 +30,7 @@ public class ServerBootSFG {
|
|||||||
public static boolean isJRE = false;
|
public static boolean isJRE = false;
|
||||||
public static boolean hasPrivileges = true;
|
public static boolean hasPrivileges = true;
|
||||||
public static boolean unsuportedversion = false;
|
public static boolean unsuportedversion = false;
|
||||||
|
public static boolean missingDimensionTypes = false;
|
||||||
protected static boolean safeguardPassed;
|
protected static boolean safeguardPassed;
|
||||||
public static boolean passedserversoftware = true;
|
public static boolean passedserversoftware = true;
|
||||||
protected static int count;
|
protected static int count;
|
||||||
@@ -110,6 +112,12 @@ public class ServerBootSFG {
|
|||||||
severityMedium++;
|
severityMedium++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IrisContextInjector.isMissingDimensionTypes()) {
|
||||||
|
missingDimensionTypes = true;
|
||||||
|
joiner.add("Missing Dimension Types");
|
||||||
|
severityHigh++;
|
||||||
|
}
|
||||||
|
|
||||||
allIncompatibilities = joiner.toString();
|
allIncompatibilities = joiner.toString();
|
||||||
|
|
||||||
safeguardPassed = (severityHigh == 0 && severityMedium == 0 && severityLow == 0);
|
safeguardPassed = (severityHigh == 0 && severityMedium == 0 && severityLow == 0);
|
||||||
|
|||||||
@@ -37,7 +37,12 @@ public class UtilsSFG {
|
|||||||
}
|
}
|
||||||
if (ServerBootSFG.unsuportedversion) {
|
if (ServerBootSFG.unsuportedversion) {
|
||||||
Iris.safeguard(C.RED + "Server Version");
|
Iris.safeguard(C.RED + "Server Version");
|
||||||
Iris.safeguard(C.RED + "- Iris only supports 1.19.2 > 1.21.3");
|
Iris.safeguard(C.RED + "- Iris only supports 1.20.1 > 1.21.4");
|
||||||
|
}
|
||||||
|
if (ServerBootSFG.missingDimensionTypes) {
|
||||||
|
Iris.safeguard(C.RED + "Dimension Types");
|
||||||
|
Iris.safeguard(C.RED + "- Required Iris dimension types were not loaded.");
|
||||||
|
Iris.safeguard(C.RED + "- If this still happens after a restart please contact support.");
|
||||||
}
|
}
|
||||||
if (!ServerBootSFG.passedserversoftware) {
|
if (!ServerBootSFG.passedserversoftware) {
|
||||||
Iris.safeguard(C.YELLOW + "Unsupported Server Software");
|
Iris.safeguard(C.YELLOW + "Unsupported Server Software");
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.volmit.iris.Iris;
|
|||||||
import com.volmit.iris.core.IrisSettings;
|
import com.volmit.iris.core.IrisSettings;
|
||||||
import com.volmit.iris.core.ServerConfigurator;
|
import com.volmit.iris.core.ServerConfigurator;
|
||||||
import com.volmit.iris.core.loader.IrisData;
|
import com.volmit.iris.core.loader.IrisData;
|
||||||
|
import com.volmit.iris.core.nms.INMS;
|
||||||
import com.volmit.iris.core.pack.IrisPack;
|
import com.volmit.iris.core.pack.IrisPack;
|
||||||
import com.volmit.iris.core.project.IrisProject;
|
import com.volmit.iris.core.project.IrisProject;
|
||||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||||
@@ -64,7 +65,7 @@ public class StudioSVC implements IrisService {
|
|||||||
if (!f.exists()) {
|
if (!f.exists()) {
|
||||||
Iris.info("Downloading Default Pack " + pack);
|
Iris.info("Downloading Default Pack " + pack);
|
||||||
if (pack.equals("overworld")) {
|
if (pack.equals("overworld")) {
|
||||||
String url = "https://github.com/IrisDimensions/overworld/releases/download/" + Iris.OVERWORLD_TAG + "/overworld.zip";
|
String url = "https://github.com/IrisDimensions/overworld/releases/download/" + INMS.OVERWORLD_TAG + "/overworld.zip";
|
||||||
Iris.service(StudioSVC.class).downloadRelease(Iris.getSender(), url, false, false);
|
Iris.service(StudioSVC.class).downloadRelease(Iris.getSender(), url, false, false);
|
||||||
} else {
|
} else {
|
||||||
downloadSearch(Iris.getSender(), pack, false);
|
downloadSearch(Iris.getSender(), pack, false);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.google.common.util.concurrent.AtomicDouble;
|
|||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.IrisSettings;
|
import com.volmit.iris.core.IrisSettings;
|
||||||
import com.volmit.iris.core.ServerConfigurator;
|
import com.volmit.iris.core.ServerConfigurator;
|
||||||
|
import com.volmit.iris.core.nms.INMS;
|
||||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||||
import com.volmit.iris.core.service.StudioSVC;
|
import com.volmit.iris.core.service.StudioSVC;
|
||||||
import com.volmit.iris.engine.object.IrisDimension;
|
import com.volmit.iris.engine.object.IrisDimension;
|
||||||
@@ -83,6 +84,11 @@ public class IrisCreator {
|
|||||||
* Benchmark mode
|
* Benchmark mode
|
||||||
*/
|
*/
|
||||||
private boolean benchmark = false;
|
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 {
|
public static boolean removeFromBukkitYml(String name) throws IOException {
|
||||||
YamlConfiguration yml = YamlConfiguration.loadConfiguration(BUKKIT_YML);
|
YamlConfiguration yml = YamlConfiguration.loadConfiguration(BUKKIT_YML);
|
||||||
@@ -126,7 +132,6 @@ public class IrisCreator {
|
|||||||
Iris.service(StudioSVC.class).installIntoWorld(sender, d.getLoadKey(), new File(Bukkit.getWorldContainer(), name()));
|
Iris.service(StudioSVC.class).installIntoWorld(sender, d.getLoadKey(), new File(Bukkit.getWorldContainer(), name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
PlatformChunkGenerator access;
|
|
||||||
AtomicReference<World> world = new AtomicReference<>();
|
AtomicReference<World> world = new AtomicReference<>();
|
||||||
AtomicDouble pp = new AtomicDouble(0);
|
AtomicDouble pp = new AtomicDouble(0);
|
||||||
O<Boolean> done = new O<>();
|
O<Boolean> done = new O<>();
|
||||||
@@ -139,30 +144,56 @@ public class IrisCreator {
|
|||||||
.create();
|
.create();
|
||||||
ServerConfigurator.installDataPacks(false);
|
ServerConfigurator.installDataPacks(false);
|
||||||
|
|
||||||
access = (PlatformChunkGenerator) wc.generator();
|
PlatformChunkGenerator access = (PlatformChunkGenerator) wc.generator();
|
||||||
PlatformChunkGenerator finalAccess1 = access;
|
if (access == null) {
|
||||||
|
throw new IrisException("Access is null. Something bad happened.");
|
||||||
|
}
|
||||||
|
|
||||||
J.a(() ->
|
if (headlessRadius > 0 && !benchmark) {
|
||||||
{
|
AtomicBoolean failed = new AtomicBoolean(false);
|
||||||
Supplier<Integer> g = () -> {
|
J.a(() -> {
|
||||||
if (finalAccess1 == null || finalAccess1.getEngine() == null) {
|
int generated = access.getGenerated();
|
||||||
return 0;
|
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) {
|
try {
|
||||||
double v = (double) g.get() / (double) req;
|
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()) {
|
if (sender.isPlayer()) {
|
||||||
sender.sendProgress(v, "Generating");
|
sender.sendProgress(v, "Generating");
|
||||||
J.sleep(16);
|
J.sleep(16);
|
||||||
} else {
|
} 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);
|
J.sleep(1000);
|
||||||
}
|
}
|
||||||
|
generated = access.getGenerated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -170,7 +201,7 @@ public class IrisCreator {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
J.sfut(() -> {
|
J.sfut(() -> {
|
||||||
world.set(wc.createWorld());
|
world.set(INMS.get().createWorld(wc));
|
||||||
}).get();
|
}).get();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
@@ -2,13 +2,22 @@ package com.volmit.iris.core.tools;
|
|||||||
|
|
||||||
|
|
||||||
import com.volmit.iris.Iris;
|
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.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.Engine;
|
||||||
|
import com.volmit.iris.engine.framework.EngineTarget;
|
||||||
import com.volmit.iris.engine.object.IrisDimension;
|
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.KList;
|
||||||
import com.volmit.iris.util.collection.KMap;
|
import com.volmit.iris.util.collection.KMap;
|
||||||
import com.volmit.iris.util.exceptions.IrisException;
|
import com.volmit.iris.util.exceptions.IrisException;
|
||||||
import com.volmit.iris.util.format.Form;
|
import com.volmit.iris.util.format.Form;
|
||||||
|
import com.volmit.iris.util.io.IO;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -17,11 +26,6 @@ import org.bukkit.Bukkit;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.FileVisitResult;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.SimpleFileVisitor;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -33,13 +37,16 @@ public class IrisPackBenchmarking {
|
|||||||
public static boolean benchmarkInProgress = false;
|
public static boolean benchmarkInProgress = false;
|
||||||
private final PrecisionStopwatch stopwatch = new PrecisionStopwatch();
|
private final PrecisionStopwatch stopwatch = new PrecisionStopwatch();
|
||||||
private final IrisDimension dimension;
|
private final IrisDimension dimension;
|
||||||
private final int radius;
|
private final int diameter;
|
||||||
private final boolean gui;
|
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;
|
instance = this;
|
||||||
this.dimension = dimension;
|
this.dimension = dimension;
|
||||||
this.radius = radius;
|
this.diameter = diameter;
|
||||||
|
this.headless = headless;
|
||||||
this.gui = gui;
|
this.gui = gui;
|
||||||
runBenchmark();
|
runBenchmark();
|
||||||
}
|
}
|
||||||
@@ -50,12 +57,9 @@ public class IrisPackBenchmarking {
|
|||||||
.start(() -> {
|
.start(() -> {
|
||||||
Iris.info("Setting up benchmark environment ");
|
Iris.info("Setting up benchmark environment ");
|
||||||
benchmarkInProgress = true;
|
benchmarkInProgress = true;
|
||||||
File file = new File("benchmark");
|
IO.delete(new File(Bukkit.getWorldContainer(), "benchmark"));
|
||||||
if (file.exists()) {
|
|
||||||
deleteDirectory(file.toPath());
|
|
||||||
}
|
|
||||||
createBenchmark();
|
createBenchmark();
|
||||||
while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
|
while (!headless && !IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
|
||||||
J.sleep(1000);
|
J.sleep(1000);
|
||||||
Iris.debug("Iris PackBenchmark: Waiting...");
|
Iris.debug("Iris PackBenchmark: Waiting...");
|
||||||
}
|
}
|
||||||
@@ -73,7 +77,6 @@ public class IrisPackBenchmarking {
|
|||||||
public void finishedBenchmark(KList<Integer> cps) {
|
public void finishedBenchmark(KList<Integer> cps) {
|
||||||
try {
|
try {
|
||||||
String time = Form.duration(stopwatch.getMillis());
|
String time = Form.duration(stopwatch.getMillis());
|
||||||
Engine engine = IrisToolbelt.access(Bukkit.getWorld("benchmark")).getEngine();
|
|
||||||
Iris.info("-----------------");
|
Iris.info("-----------------");
|
||||||
Iris.info("Results:");
|
Iris.info("Results:");
|
||||||
Iris.info("- Total time: " + time);
|
Iris.info("- Total time: " + time);
|
||||||
@@ -114,12 +117,16 @@ public class IrisPackBenchmarking {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
J.s(() -> {
|
if (headless) {
|
||||||
var world = Bukkit.getWorld("benchmark");
|
engine.close();
|
||||||
if (world == null) return;
|
} else {
|
||||||
IrisToolbelt.evacuate(world);
|
J.s(() -> {
|
||||||
Bukkit.unloadWorld(world, true);
|
var world = Bukkit.getWorld("benchmark");
|
||||||
});
|
if (world == null) return;
|
||||||
|
IrisToolbelt.evacuate(world);
|
||||||
|
Bukkit.unloadWorld(world, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
stopwatch.end();
|
stopwatch.end();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -130,13 +137,34 @@ public class IrisPackBenchmarking {
|
|||||||
|
|
||||||
private void createBenchmark() {
|
private void createBenchmark() {
|
||||||
try {
|
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())
|
.dimension(dimension.getLoadKey())
|
||||||
.name("benchmark")
|
.name("benchmark")
|
||||||
.seed(1337)
|
.seed(1337)
|
||||||
.studio(false)
|
.studio(false)
|
||||||
.benchmark(true)
|
.benchmark(true)
|
||||||
.create();
|
.create())
|
||||||
|
.getEngine();
|
||||||
} catch (IrisException e) {
|
} catch (IrisException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -146,9 +174,15 @@ public class IrisPackBenchmarking {
|
|||||||
IrisToolbelt.pregenerate(PregenTask
|
IrisToolbelt.pregenerate(PregenTask
|
||||||
.builder()
|
.builder()
|
||||||
.gui(gui)
|
.gui(gui)
|
||||||
.width(radius)
|
.radiusX(diameter)
|
||||||
.height(radius)
|
.radiusZ(diameter)
|
||||||
.build(), Bukkit.getWorld("benchmark")
|
.build(), headless ?
|
||||||
|
new HeadlessPregenMethod(engine) :
|
||||||
|
new HybridPregenMethod(
|
||||||
|
engine.getWorld().realWorld(),
|
||||||
|
IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())
|
||||||
|
),
|
||||||
|
engine
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,26 +212,4 @@ public class IrisPackBenchmarking {
|
|||||||
private int findHighest(KList<Integer> list) {
|
private int findHighest(KList<Integer> list) {
|
||||||
return Collections.max(list);
|
return Collections.max(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean deleteDirectory(Path dir) {
|
|
||||||
try {
|
|
||||||
Files.walkFileTree(dir, new SimpleFileVisitor<>() {
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
||||||
Files.delete(file);
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
|
||||||
Files.delete(dir);
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -21,10 +21,13 @@ package com.volmit.iris.core.tools;
|
|||||||
import com.volmit.iris.core.loader.IrisData;
|
import com.volmit.iris.core.loader.IrisData;
|
||||||
import com.volmit.iris.engine.object.*;
|
import com.volmit.iris.engine.object.*;
|
||||||
import com.volmit.iris.engine.platform.BukkitChunkGenerator;
|
import com.volmit.iris.engine.platform.BukkitChunkGenerator;
|
||||||
|
import com.volmit.iris.util.reflect.WrappedField;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.WorldCreator;
|
import org.bukkit.WorldCreator;
|
||||||
|
import org.bukkit.WorldType;
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
|||||||
@@ -304,6 +304,11 @@ public class IrisEngine implements Engine {
|
|||||||
return generated.get();
|
return generated.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addGenerated(int x, int z) {
|
||||||
|
generated.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getGeneratedPerSecond() {
|
public double getGeneratedPerSecond() {
|
||||||
if (perSecondLatch.flip()) {
|
if (perSecondLatch.flip()) {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -299,28 +300,6 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
|||||||
energy += 1.2;
|
energy += 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
//@builder
|
|
||||||
IrisBiome biome = IrisSettings.get().getWorld().isAnbientEntitySpawningSystem()
|
|
||||||
? getEngine().getSurfaceBiome(c) : null;
|
|
||||||
IrisEntitySpawn v = IrisSettings.get().getWorld().isAnbientEntitySpawningSystem()
|
|
||||||
? spawnRandomly(Stream.concat(getData().getSpawnerLoader()
|
|
||||||
.loadAll(getDimension().getEntitySpawners())
|
|
||||||
.shuffleCopy(RNG.r).stream()
|
|
||||||
.filter(this::canSpawn)
|
|
||||||
.filter((i) -> i.isValid(biome))
|
|
||||||
.flatMap((i) -> stream(i, initial)),
|
|
||||||
Stream.concat(getData().getSpawnerLoader()
|
|
||||||
.loadAll(getEngine().getRegion(c.getX() << 4, c.getZ() << 4).getEntitySpawners())
|
|
||||||
.shuffleCopy(RNG.r).stream().filter(this::canSpawn)
|
|
||||||
.flatMap((i) -> stream(i, initial)),
|
|
||||||
getData().getSpawnerLoader()
|
|
||||||
.loadAll(getEngine().getSurfaceBiome(c.getX() << 4, c.getZ() << 4).getEntitySpawners())
|
|
||||||
.shuffleCopy(RNG.r).stream().filter(this::canSpawn)
|
|
||||||
.flatMap((i) -> stream(i, initial))))
|
|
||||||
.collect(Collectors.toList()))
|
|
||||||
.popRandom(RNG.r) : null;
|
|
||||||
//@done
|
|
||||||
|
|
||||||
if (IrisSettings.get().getWorld().isMarkerEntitySpawningSystem()) {
|
if (IrisSettings.get().getWorld().isMarkerEntitySpawningSystem()) {
|
||||||
getSpawnersFromMarkers(c).forEach((blockf, spawners) -> {
|
getSpawnersFromMarkers(c).forEach((blockf, spawners) -> {
|
||||||
if (spawners.isEmpty()) {
|
if (spawners.isEmpty()) {
|
||||||
@@ -335,94 +314,67 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v != null && v.getReferenceSpawner() != null) {
|
if (!IrisSettings.get().getWorld().isAnbientEntitySpawningSystem()) {
|
||||||
int maxEntCount = v.getReferenceSpawner().getMaxEntitiesPerChunk();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (Entity i : c.getEntities()) {
|
//@builder
|
||||||
if (i instanceof LivingEntity) {
|
Predicate<IrisSpawner> filter = i -> i.canSpawn(getEngine(), c.getX(), c.getZ());
|
||||||
if (-maxEntCount <= 0) {
|
ChunkCounter counter = new ChunkCounter(c.getEntities());
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
IrisBiome biome = getEngine().getSurfaceBiome(c);
|
||||||
spawn(c, v);
|
IrisEntitySpawn v = spawnRandomly(Stream.concat(getData().getSpawnerLoader()
|
||||||
} catch (Throwable e) {
|
.loadAll(getDimension().getEntitySpawners())
|
||||||
J.s(() -> spawn(c, v));
|
.shuffleCopy(RNG.r)
|
||||||
}
|
.stream()
|
||||||
|
.filter(filter)
|
||||||
|
.filter((i) -> i.isValid(biome)),
|
||||||
|
Stream.concat(getData()
|
||||||
|
.getSpawnerLoader()
|
||||||
|
.loadAll(getEngine().getRegion(c.getX() << 4, c.getZ() << 4).getEntitySpawners())
|
||||||
|
.shuffleCopy(RNG.r)
|
||||||
|
.stream()
|
||||||
|
.filter(filter),
|
||||||
|
getData().getSpawnerLoader()
|
||||||
|
.loadAll(getEngine().getSurfaceBiome(c.getX() << 4, c.getZ() << 4).getEntitySpawners())
|
||||||
|
.shuffleCopy(RNG.r)
|
||||||
|
.stream()
|
||||||
|
.filter(filter)))
|
||||||
|
.filter(counter)
|
||||||
|
.flatMap((i) -> stream(i, initial))
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.getRandom();
|
||||||
|
//@done
|
||||||
|
if (v == null || v.getReferenceSpawner() == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
spawn(c, v);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
J.s(() -> spawn(c, v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spawn(Chunk c, IrisEntitySpawn i) {
|
private void spawn(Chunk c, IrisEntitySpawn i) {
|
||||||
boolean allow = true;
|
IrisSpawner ref = i.getReferenceSpawner();
|
||||||
|
int s = i.spawn(getEngine(), c, RNG.r);
|
||||||
if (!i.getReferenceSpawner().getMaximumRatePerChunk().isInfinite()) {
|
actuallySpawned += s;
|
||||||
allow = false;
|
if (s > 0) {
|
||||||
IrisEngineChunkData cd = getEngine().getEngineData().getChunk(c.getX(), c.getZ());
|
ref.spawn(getEngine(), c.getX(), c.getZ());
|
||||||
IrisEngineSpawnerCooldown sc = null;
|
energy -= s * ((i.getEnergyMultiplier() * ref.getEnergyMultiplier() * 1));
|
||||||
for (IrisEngineSpawnerCooldown j : cd.getCooldowns()) {
|
|
||||||
if (j.getSpawner().equals(i.getReferenceSpawner().getLoadKey())) {
|
|
||||||
sc = j;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sc == null) {
|
|
||||||
sc = new IrisEngineSpawnerCooldown();
|
|
||||||
sc.setSpawner(i.getReferenceSpawner().getLoadKey());
|
|
||||||
cd.getCooldowns().add(sc);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sc.canSpawn(i.getReferenceSpawner().getMaximumRatePerChunk())) {
|
|
||||||
sc.spawn(getEngine());
|
|
||||||
allow = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allow) {
|
|
||||||
int s = i.spawn(getEngine(), c, RNG.r);
|
|
||||||
actuallySpawned += s;
|
|
||||||
if (s > 0) {
|
|
||||||
getCooldown(i.getReferenceSpawner()).spawn(getEngine());
|
|
||||||
energy -= s * ((i.getEnergyMultiplier() * i.getReferenceSpawner().getEnergyMultiplier() * 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spawn(IrisPosition c, IrisEntitySpawn i) {
|
private void spawn(IrisPosition pos, IrisEntitySpawn i) {
|
||||||
boolean allow = true;
|
IrisSpawner ref = i.getReferenceSpawner();
|
||||||
|
if (!ref.canSpawn(getEngine(), pos.getX() >> 4, pos.getZ()))
|
||||||
|
return;
|
||||||
|
|
||||||
if (!i.getReferenceSpawner().getMaximumRatePerChunk().isInfinite()) {
|
int s = i.spawn(getEngine(), pos, RNG.r);
|
||||||
allow = false;
|
actuallySpawned += s;
|
||||||
IrisEngineChunkData cd = getEngine().getEngineData().getChunk(c.getX() >> 4, c.getZ() >> 4);
|
if (s > 0) {
|
||||||
IrisEngineSpawnerCooldown sc = null;
|
ref.spawn(getEngine(), pos.getX() >> 4, pos.getZ() >> 4);
|
||||||
for (IrisEngineSpawnerCooldown j : cd.getCooldowns()) {
|
energy -= s * ((i.getEnergyMultiplier() * ref.getEnergyMultiplier() * 1));
|
||||||
if (j.getSpawner().equals(i.getReferenceSpawner().getLoadKey())) {
|
|
||||||
sc = j;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sc == null) {
|
|
||||||
sc = new IrisEngineSpawnerCooldown();
|
|
||||||
sc.setSpawner(i.getReferenceSpawner().getLoadKey());
|
|
||||||
cd.getCooldowns().add(sc);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sc.canSpawn(i.getReferenceSpawner().getMaximumRatePerChunk())) {
|
|
||||||
sc.spawn(getEngine());
|
|
||||||
allow = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allow) {
|
|
||||||
int s = i.spawn(getEngine(), c, RNG.r);
|
|
||||||
actuallySpawned += s;
|
|
||||||
if (s > 0) {
|
|
||||||
getCooldown(i.getReferenceSpawner()).spawn(getEngine());
|
|
||||||
energy -= s * ((i.getEnergyMultiplier() * i.getReferenceSpawner().getEnergyMultiplier() * 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,31 +402,6 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
|||||||
return rarityTypes;
|
return rarityTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canSpawn(IrisSpawner i) {
|
|
||||||
return i.isValid(getEngine().getWorld().realWorld())
|
|
||||||
&& getCooldown(i).canSpawn(i.getMaximumRate());
|
|
||||||
}
|
|
||||||
|
|
||||||
private IrisEngineSpawnerCooldown getCooldown(IrisSpawner i) {
|
|
||||||
IrisEngineData ed = getEngine().getEngineData();
|
|
||||||
IrisEngineSpawnerCooldown cd = null;
|
|
||||||
|
|
||||||
for (IrisEngineSpawnerCooldown j : ed.getSpawnerCooldowns().copy()) {
|
|
||||||
if (j.getSpawner().equals(i.getLoadKey())) {
|
|
||||||
cd = j;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cd == null) {
|
|
||||||
cd = new IrisEngineSpawnerCooldown();
|
|
||||||
cd.setSpawner(i.getLoadKey());
|
|
||||||
cd.setLastSpawn(M.ms() - i.getMaximumRate().getInterval());
|
|
||||||
ed.getSpawnerCooldowns().add(cd);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cd;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTick() {
|
public void onTick() {
|
||||||
|
|
||||||
@@ -708,4 +635,27 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
|||||||
|
|
||||||
return (double) entityCount / (getEngine().getWorld().realWorld().getLoadedChunks().length + 1) * 1.28;
|
return (double) entityCount / (getEngine().getWorld().realWorld().getLoadedChunks().length + 1) * 1.28;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class ChunkCounter implements Predicate<IrisSpawner> {
|
||||||
|
private final Entity[] entities;
|
||||||
|
private transient int index = 0;
|
||||||
|
private transient int count = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean test(IrisSpawner spawner) {
|
||||||
|
int max = spawner.getMaxEntitiesPerChunk();
|
||||||
|
if (max <= count)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (index < entities.length) {
|
||||||
|
if (entities[index++] instanceof LivingEntity) {
|
||||||
|
if (++count >= max)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -610,6 +610,9 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
|||||||
|
|
||||||
int getGenerated();
|
int getGenerated();
|
||||||
|
|
||||||
|
@ChunkCoordinates
|
||||||
|
void addGenerated(int x, int z);
|
||||||
|
|
||||||
CompletableFuture<Long> getHash32();
|
CompletableFuture<Long> getHash32();
|
||||||
|
|
||||||
default <T> IrisPosition lookForStreamResult(T find, ProceduralStream<T> stream, Function2<T, T, Boolean> matcher, long timeout) {
|
default <T> IrisPosition lookForStreamResult(T find, ProceduralStream<T> stream, Function2<T, T, Boolean> matcher, long timeout) {
|
||||||
|
|||||||
+5
-3
@@ -90,8 +90,10 @@ public class MantleJigsawComponent extends IrisMantleComponent {
|
|||||||
private boolean placeStructures(MantleWriter writer, long seed, int x, int z, KList<IrisJigsawStructurePlacement> structures,
|
private boolean placeStructures(MantleWriter writer, long seed, int x, int z, KList<IrisJigsawStructurePlacement> structures,
|
||||||
KSet<Position2> cachedRegions, KMap<String, KSet<Position2>> cache, KMap<Position2, Double> distanceCache) {
|
KSet<Position2> cachedRegions, KMap<String, KSet<Position2>> cache, KMap<Position2, Double> distanceCache) {
|
||||||
IrisJigsawStructurePlacement i = pick(structures, seed, x, z);
|
IrisJigsawStructurePlacement i = pick(structures, seed, x, z);
|
||||||
if (i == null || checkMinDistances(i.collectMinDistances(), x, z, cachedRegions, cache, distanceCache))
|
try {
|
||||||
return false;
|
if (i == null || checkMinDistances(i.collectMinDistances(), x, z, cachedRegions, cache, distanceCache))
|
||||||
|
return false;
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
RNG rng = new RNG(seed);
|
RNG rng = new RNG(seed);
|
||||||
IrisPosition position = new IrisPosition((x << 4) + rng.nextInt(15), 0, (z << 4) + rng.nextInt(15));
|
IrisPosition position = new IrisPosition((x << 4) + rng.nextInt(15), 0, (z << 4) + rng.nextInt(15));
|
||||||
IrisJigsawStructure structure = getData().getJigsawStructureLoader().load(i.getStructure());
|
IrisJigsawStructure structure = getData().getJigsawStructureLoader().load(i.getStructure());
|
||||||
@@ -159,7 +161,7 @@ public class MantleJigsawComponent extends IrisMantleComponent {
|
|||||||
@ChunkCoordinates
|
@ChunkCoordinates
|
||||||
private IrisJigsawStructurePlacement pick(List<IrisJigsawStructurePlacement> structures, long seed, int x, int z) {
|
private IrisJigsawStructurePlacement pick(List<IrisJigsawStructurePlacement> structures, long seed, int x, int z) {
|
||||||
return IRare.pick(structures.stream()
|
return IRare.pick(structures.stream()
|
||||||
.filter(p -> p.shouldPlace(getDimension().getJigsawStructureDivisor(), jigsaw(), x, z))
|
.filter(p -> p.shouldPlace(getData(), getDimension().getJigsawStructureDivisor(), jigsaw(), x, z))
|
||||||
.toList(), new RNG(seed).nextDouble());
|
.toList(), new RNG(seed).nextDouble());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
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.misc.ServerProperties;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.world.WorldInitEvent;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.volmit.iris.Iris.instance;
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
totalWorlds = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String levelName = ServerProperties.LEVEL_NAME;
|
||||||
|
List<String> irisWorlds = irisWorlds();
|
||||||
|
boolean overworld = irisWorlds.contains(levelName);
|
||||||
|
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) {
|
||||||
|
var pair = INMS.get().injectUncached(overworld, nether, end);
|
||||||
|
i += pair.getA() - 3;
|
||||||
|
autoClosing = pair.getB();
|
||||||
|
}
|
||||||
|
|
||||||
|
totalWorlds = i;
|
||||||
|
instance.registerListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void on(WorldInitEvent event) {
|
||||||
|
if (++worldCounter < totalWorlds) return;
|
||||||
|
if (autoClosing != null) {
|
||||||
|
autoClosing.close();
|
||||||
|
autoClosing = null;
|
||||||
|
}
|
||||||
|
instance.unregisterListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> irisWorlds() {
|
||||||
|
var config = YamlConfiguration.loadConfiguration(ServerProperties.BUKKIT_YML);
|
||||||
|
ConfigurationSection section = config.getConfigurationSection("worlds");
|
||||||
|
if (section == null) return List.of();
|
||||||
|
|
||||||
|
return section.getKeys(false)
|
||||||
|
.stream()
|
||||||
|
.filter(k -> section.getString(k + ".generator", "").startsWith("Iris"))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,8 @@
|
|||||||
package com.volmit.iris.engine.object;
|
package com.volmit.iris.engine.object;
|
||||||
|
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.IrisSettings;
|
||||||
|
import com.volmit.iris.core.ServerConfigurator.DimensionHeight;
|
||||||
import com.volmit.iris.core.loader.IrisData;
|
import com.volmit.iris.core.loader.IrisData;
|
||||||
import com.volmit.iris.core.loader.IrisRegistrant;
|
import com.volmit.iris.core.loader.IrisRegistrant;
|
||||||
import com.volmit.iris.core.nms.INMS;
|
import com.volmit.iris.core.nms.INMS;
|
||||||
@@ -26,6 +28,7 @@ import com.volmit.iris.core.nms.datapack.IDataFixer;
|
|||||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||||
import com.volmit.iris.engine.object.annotations.*;
|
import com.volmit.iris.engine.object.annotations.*;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
|
import com.volmit.iris.util.collection.KSet;
|
||||||
import com.volmit.iris.util.data.DataProvider;
|
import com.volmit.iris.util.data.DataProvider;
|
||||||
import com.volmit.iris.util.io.IO;
|
import com.volmit.iris.util.io.IO;
|
||||||
import com.volmit.iris.util.json.JSONObject;
|
import com.volmit.iris.util.json.JSONObject;
|
||||||
@@ -54,73 +57,6 @@ import java.io.IOException;
|
|||||||
public class IrisDimension extends IrisRegistrant {
|
public class IrisDimension extends IrisRegistrant {
|
||||||
public static final BlockData STONE = Material.STONE.createBlockData();
|
public static final BlockData STONE = Material.STONE.createBlockData();
|
||||||
public static final BlockData WATER = Material.WATER.createBlockData();
|
public static final BlockData WATER = Material.WATER.createBlockData();
|
||||||
private static final String DP_OVERWORLD_DEFAULT = """
|
|
||||||
{
|
|
||||||
"ambient_light": 0.0,
|
|
||||||
"bed_works": true,
|
|
||||||
"coordinate_scale": 1.0,
|
|
||||||
"effects": "minecraft:overworld",
|
|
||||||
"has_ceiling": false,
|
|
||||||
"has_raids": true,
|
|
||||||
"has_skylight": true,
|
|
||||||
"infiniburn": "#minecraft:infiniburn_overworld",
|
|
||||||
"monster_spawn_block_light_limit": 0,
|
|
||||||
"monster_spawn_light_level": {
|
|
||||||
"type": "minecraft:uniform",
|
|
||||||
"value": {
|
|
||||||
"max_inclusive": 7,
|
|
||||||
"min_inclusive": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"natural": true,
|
|
||||||
"piglin_safe": false,
|
|
||||||
"respawn_anchor_works": false,
|
|
||||||
"ultrawarm": false
|
|
||||||
}""";
|
|
||||||
|
|
||||||
private static final String DP_NETHER_DEFAULT = """
|
|
||||||
{
|
|
||||||
"ambient_light": 0.1,
|
|
||||||
"bed_works": false,
|
|
||||||
"coordinate_scale": 8.0,
|
|
||||||
"effects": "minecraft:the_nether",
|
|
||||||
"fixed_time": 18000,
|
|
||||||
"has_ceiling": true,
|
|
||||||
"has_raids": false,
|
|
||||||
"has_skylight": false,
|
|
||||||
"infiniburn": "#minecraft:infiniburn_nether",
|
|
||||||
"monster_spawn_block_light_limit": 15,
|
|
||||||
"monster_spawn_light_level": 7,
|
|
||||||
"natural": false,
|
|
||||||
"piglin_safe": true,
|
|
||||||
"respawn_anchor_works": true,
|
|
||||||
"ultrawarm": true
|
|
||||||
}""";
|
|
||||||
|
|
||||||
private static final String DP_END_DEFAULT = """
|
|
||||||
{
|
|
||||||
"ambient_light": 0.0,
|
|
||||||
"bed_works": false,
|
|
||||||
"coordinate_scale": 1.0,
|
|
||||||
"effects": "minecraft:the_end",
|
|
||||||
"fixed_time": 6000,
|
|
||||||
"has_ceiling": false,
|
|
||||||
"has_raids": true,
|
|
||||||
"has_skylight": false,
|
|
||||||
"infiniburn": "#minecraft:infiniburn_end",
|
|
||||||
"monster_spawn_block_light_limit": 0,
|
|
||||||
"monster_spawn_light_level": {
|
|
||||||
"type": "minecraft:uniform",
|
|
||||||
"value": {
|
|
||||||
"max_inclusive": 7,
|
|
||||||
"min_inclusive": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"natural": false,
|
|
||||||
"piglin_safe": false,
|
|
||||||
"respawn_anchor_works": false,
|
|
||||||
"ultrawarm": false
|
|
||||||
}""";
|
|
||||||
private final transient AtomicCache<Position2> parallaxSize = new AtomicCache<>();
|
private final transient AtomicCache<Position2> parallaxSize = new AtomicCache<>();
|
||||||
private final transient AtomicCache<CNG> rockLayerGenerator = new AtomicCache<>();
|
private final transient AtomicCache<CNG> rockLayerGenerator = new AtomicCache<>();
|
||||||
private final transient AtomicCache<CNG> fluidLayerGenerator = new AtomicCache<>();
|
private final transient AtomicCache<CNG> fluidLayerGenerator = new AtomicCache<>();
|
||||||
@@ -443,61 +379,35 @@ public class IrisDimension extends IrisRegistrant {
|
|||||||
return landBiomeStyle;
|
return landBiomeStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean installDataPack(IDataFixer fixer, DataProvider data, File datapacks, double ultimateMaxHeight, double ultimateMinHeight) {
|
public void installBiomes(IDataFixer fixer, DataProvider data, KList<File> folders, KSet<String> biomes) {
|
||||||
boolean write = false;
|
getAllBiomes(data)
|
||||||
boolean changed = false;
|
.stream()
|
||||||
|
.filter(IrisBiome::isCustom)
|
||||||
IO.delete(new File(datapacks, "iris/data/" + getLoadKey().toLowerCase()));
|
.map(IrisBiome::getCustomDerivitives)
|
||||||
|
.flatMap(KList::stream)
|
||||||
for (IrisBiome i : getAllBiomes(data)) {
|
.parallel()
|
||||||
if (i.isCustom()) {
|
.forEach(j -> {
|
||||||
write = true;
|
String json = j.generateJson(fixer);
|
||||||
|
synchronized (biomes) {
|
||||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
if (!biomes.add(j.getId())) {
|
||||||
File output = new File(datapacks, "iris/data/" + getLoadKey().toLowerCase() + "/worldgen/biome/" + j.getId() + ".json");
|
Iris.verbose("Duplicate Data Pack Biome: " + getLoadKey() + "/" + j.getId());
|
||||||
|
return;
|
||||||
if (!output.exists()) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Iris.verbose(" Installing Data Pack Biome: " + output.getPath());
|
|
||||||
output.getParentFile().mkdirs();
|
|
||||||
try {
|
|
||||||
IO.writeAll(output, j.generateJson(fixer));
|
|
||||||
} catch (IOException e) {
|
|
||||||
Iris.reportError(e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dimensionHeight.equals(new IrisRange(-64, 320)) && this.name.equalsIgnoreCase("overworld")) {
|
|
||||||
Iris.verbose(" Installing Data Pack Dimension Types: \"minecraft:overworld\", \"minecraft:the_nether\", \"minecraft:the_end\"");
|
|
||||||
dimensionHeight.setMax(ultimateMaxHeight);
|
|
||||||
dimensionHeight.setMin(ultimateMinHeight);
|
|
||||||
changed = writeDimensionType(fixer, changed, datapacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write) {
|
|
||||||
File mcm = new File(datapacks, "iris/pack.mcmeta");
|
|
||||||
try {
|
|
||||||
IO.writeAll(mcm, """
|
|
||||||
{
|
|
||||||
"pack": {
|
|
||||||
"description": "Iris Data Pack. This pack contains all installed Iris Packs' resources.",
|
|
||||||
"pack_format": {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
""".replace("{}", INMS.get().getDataVersion().getPackFormat() + ""));
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
Iris.reportError(e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
Iris.verbose(" Installing Data Pack MCMeta: " + mcm.getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed;
|
for (File datapacks : folders) {
|
||||||
|
File output = new File(datapacks, "iris/data/" + getLoadKey().toLowerCase() + "/worldgen/biome/" + j.getId() + ".json");
|
||||||
|
|
||||||
|
Iris.verbose(" Installing Data Pack Biome: " + output.getPath());
|
||||||
|
output.getParentFile().mkdirs();
|
||||||
|
try {
|
||||||
|
IO.writeAll(output, json);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -515,66 +425,55 @@ public class IrisDimension extends IrisRegistrant {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean writeDimensionType(IDataFixer fixer, boolean changed, File datapacks) {
|
public static void writeShared(KList<File> folders, DimensionHeight height) {
|
||||||
File dimTypeOverworld = new File(datapacks, "iris/data/minecraft/dimension_type/overworld.json");
|
Iris.verbose(" Installing Data Pack Dimension Types: \"iris:overworld\", \"iris:the_nether\", \"iris:the_end\"");
|
||||||
if (!dimTypeOverworld.exists())
|
for (File datapacks : folders) {
|
||||||
changed = true;
|
write(datapacks, "overworld", height.overworldType());
|
||||||
dimTypeOverworld.getParentFile().mkdirs();
|
write(datapacks, "the_nether", height.netherType());
|
||||||
|
write(datapacks, "the_end", height.endType());
|
||||||
|
}
|
||||||
|
|
||||||
|
String raw = """
|
||||||
|
{
|
||||||
|
"pack": {
|
||||||
|
"description": "Iris Data Pack. This pack contains all installed Iris Packs' resources.",
|
||||||
|
"pack_format": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".replace("{}", INMS.get().getDataVersion().getPackFormat() + "");
|
||||||
|
|
||||||
|
for (File datapacks : folders) {
|
||||||
|
File mcm = new File(datapacks, "iris/pack.mcmeta");
|
||||||
|
try {
|
||||||
|
IO.writeAll(mcm, raw);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
Iris.verbose(" Installing Data Pack MCMeta: " + mcm.getPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void write(File datapacks, String type, String json) {
|
||||||
|
File dimType = new File(datapacks, "iris/data/iris/dimension_type/" + type + ".json");
|
||||||
|
File dimTypeVanilla = new File(datapacks, "iris/data/minecraft/dimension_type/" + type + ".json");
|
||||||
|
|
||||||
|
dimType.getParentFile().mkdirs();
|
||||||
try {
|
try {
|
||||||
IO.writeAll(dimTypeOverworld, generateDatapackJsonOverworld(fixer));
|
IO.writeAll(dimType, json);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Iris.reportError(e);
|
Iris.reportError(e);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IrisSettings.get().getGeneral().adjustVanillaHeight || dimTypeVanilla.exists()) {
|
||||||
File dimTypeNether = new File(datapacks, "iris/data/minecraft/dimension_type/the_nether.json");
|
dimTypeVanilla.getParentFile().mkdirs();
|
||||||
if (!dimTypeNether.exists())
|
try {
|
||||||
changed = true;
|
IO.writeAll(dimTypeVanilla, json);
|
||||||
dimTypeNether.getParentFile().mkdirs();
|
} catch (IOException e) {
|
||||||
try {
|
Iris.reportError(e);
|
||||||
IO.writeAll(dimTypeNether, generateDatapackJsonNether(fixer));
|
e.printStackTrace();
|
||||||
} catch (IOException e) {
|
}
|
||||||
Iris.reportError(e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
File dimTypeEnd = new File(datapacks, "iris/data/minecraft/dimension_type/the_end.json");
|
|
||||||
if (!dimTypeEnd.exists())
|
|
||||||
changed = true;
|
|
||||||
dimTypeEnd.getParentFile().mkdirs();
|
|
||||||
try {
|
|
||||||
IO.writeAll(dimTypeEnd, generateDatapackJsonEnd(fixer));
|
|
||||||
} catch (IOException e) {
|
|
||||||
Iris.reportError(e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateDatapackJsonOverworld(IDataFixer fixer) {
|
|
||||||
JSONObject obj = new JSONObject(DP_OVERWORLD_DEFAULT);
|
|
||||||
obj.put("min_y", dimensionHeight.getMin());
|
|
||||||
obj.put("height", dimensionHeight.getMax() - dimensionHeight.getMin());
|
|
||||||
obj.put("logical_height", logicalHeight);
|
|
||||||
return fixer.fixDimension(obj).toString(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateDatapackJsonNether(IDataFixer fixer) {
|
|
||||||
JSONObject obj = new JSONObject(DP_NETHER_DEFAULT);
|
|
||||||
obj.put("min_y", dimensionHeightNether.getMin());
|
|
||||||
obj.put("height", dimensionHeightNether.getMax() - dimensionHeightNether.getMin());
|
|
||||||
obj.put("logical_height", logicalHeightNether);
|
|
||||||
return fixer.fixDimension(obj).toString(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateDatapackJsonEnd(IDataFixer fixer) {
|
|
||||||
JSONObject obj = new JSONObject(DP_END_DEFAULT);
|
|
||||||
obj.put("min_y", dimensionHeightEnd.getMin());
|
|
||||||
obj.put("height", dimensionHeightEnd.getMax() - dimensionHeightEnd.getMin());
|
|
||||||
obj.put("logical_height", logicalHeightEnd);
|
|
||||||
return fixer.fixDimension(obj).toString(4);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.engine.object;
|
|
||||||
|
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
|
||||||
import com.volmit.iris.util.collection.KList;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class IrisEngineChunkData {
|
|
||||||
private long chunk;
|
|
||||||
private KList<IrisEngineSpawnerCooldown> cooldowns = new KList<>();
|
|
||||||
|
|
||||||
public void cleanup(Engine engine) {
|
|
||||||
for (IrisEngineSpawnerCooldown i : getCooldowns().copy()) {
|
|
||||||
IrisSpawner sp = engine.getData().getSpawnerLoader().load(i.getSpawner());
|
|
||||||
|
|
||||||
if (sp == null || i.canSpawn(sp.getMaximumRate())) {
|
|
||||||
getCooldowns().remove(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return cooldowns.isEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,51 +20,31 @@ package com.volmit.iris.engine.object;
|
|||||||
|
|
||||||
import com.volmit.iris.engine.data.cache.Cache;
|
import com.volmit.iris.engine.data.cache.Cache;
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KMap;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class IrisEngineData {
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class IrisEngineData extends IrisSpawnerCooldowns {
|
||||||
private IrisEngineStatistics statistics = new IrisEngineStatistics();
|
private IrisEngineStatistics statistics = new IrisEngineStatistics();
|
||||||
private KList<IrisEngineSpawnerCooldown> spawnerCooldowns = new KList<>();
|
private KMap<Long, IrisSpawnerCooldowns> chunks = new KMap<>();
|
||||||
private KList<IrisEngineChunkData> chunks = new KList<>();
|
|
||||||
private Long seed = null;
|
private Long seed = null;
|
||||||
|
|
||||||
public void removeChunk(int x, int z) {
|
public void removeChunk(int x, int z) {
|
||||||
long k = Cache.key(x, z);
|
chunks.remove(Cache.key(x, z));
|
||||||
chunks.removeWhere((i) -> i.getChunk() == k);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IrisEngineChunkData getChunk(int x, int z) {
|
public IrisSpawnerCooldowns getChunk(int x, int z) {
|
||||||
long k = Cache.key(x, z);
|
return chunks.computeIfAbsent(Cache.key(x, z), k -> new IrisSpawnerCooldowns());
|
||||||
|
|
||||||
for (IrisEngineChunkData i : chunks) {
|
|
||||||
if (i.getChunk() == k) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IrisEngineChunkData c = new IrisEngineChunkData();
|
|
||||||
c.setChunk(k);
|
|
||||||
chunks.add(c);
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cleanup(Engine engine) {
|
public void cleanup(Engine engine) {
|
||||||
for (IrisEngineSpawnerCooldown i : getSpawnerCooldowns().copy()) {
|
super.cleanup(engine);
|
||||||
IrisSpawner sp = engine.getData().getSpawnerLoader().load(i.getSpawner());
|
|
||||||
|
|
||||||
if (sp == null || i.canSpawn(sp.getMaximumRate())) {
|
chunks.values().removeIf(chunk -> {
|
||||||
getSpawnerCooldowns().remove(i);
|
chunk.cleanup(engine);
|
||||||
}
|
return chunk.isEmpty();
|
||||||
}
|
});
|
||||||
|
|
||||||
for (IrisEngineChunkData i : chunks.copy()) {
|
|
||||||
i.cleanup(engine);
|
|
||||||
|
|
||||||
if (i.isEmpty()) {
|
|
||||||
getChunks().remove(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.dfsek.paralithic.eval.parser.Scope;
|
|||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.loader.IrisRegistrant;
|
import com.volmit.iris.core.loader.IrisRegistrant;
|
||||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||||
|
import com.volmit.iris.engine.object.IrisExpressionFunction.FunctionContext;
|
||||||
import com.volmit.iris.engine.object.annotations.ArrayType;
|
import com.volmit.iris.engine.object.annotations.ArrayType;
|
||||||
import com.volmit.iris.engine.object.annotations.Desc;
|
import com.volmit.iris.engine.object.annotations.Desc;
|
||||||
import com.volmit.iris.engine.object.annotations.Required;
|
import com.volmit.iris.engine.object.annotations.Required;
|
||||||
@@ -46,12 +47,14 @@ import lombok.experimental.Accessors;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = false)
|
@EqualsAndHashCode(callSuper = false)
|
||||||
public class IrisExpression extends IrisRegistrant {
|
public class IrisExpression extends IrisRegistrant {
|
||||||
private static final Parser parser = new Parser();
|
|
||||||
|
|
||||||
@ArrayType(type = IrisExpressionLoad.class, min = 1)
|
@ArrayType(type = IrisExpressionLoad.class, min = 1)
|
||||||
@Desc("Variables to use in this expression")
|
@Desc("Variables to use in this expression")
|
||||||
private KList<IrisExpressionLoad> variables = new KList<>();
|
private KList<IrisExpressionLoad> variables = new KList<>();
|
||||||
|
|
||||||
|
@ArrayType(type = IrisExpressionFunction.class, min = 1)
|
||||||
|
@Desc("Functions to use in this expression")
|
||||||
|
private KList<IrisExpressionFunction> functions = new KList<>();
|
||||||
|
|
||||||
@Required
|
@Required
|
||||||
@Desc("The expression. Inherited variables are x, y and z. Avoid using those variable names.")
|
@Desc("The expression. Inherited variables are x, y and z. Avoid using those variable names.")
|
||||||
private String expression;
|
private String expression;
|
||||||
@@ -62,6 +65,7 @@ public class IrisExpression extends IrisRegistrant {
|
|||||||
private Expression expression() {
|
private Expression expression() {
|
||||||
return expressionCache.aquire(() -> {
|
return expressionCache.aquire(() -> {
|
||||||
Scope scope = new Scope(); // Create variable scope. This scope can hold both constants and invocation variables.
|
Scope scope = new Scope(); // Create variable scope. This scope can hold both constants and invocation variables.
|
||||||
|
Parser parser = new Parser();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (IrisExpressionLoad i : variables) {
|
for (IrisExpressionLoad i : variables) {
|
||||||
@@ -76,6 +80,12 @@ public class IrisExpression extends IrisRegistrant {
|
|||||||
Iris.error("Script Variable load error in " + getLoadFile().getPath());
|
Iris.error("Script Variable load error in " + getLoadFile().getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (IrisExpressionFunction f : functions) {
|
||||||
|
if (!f.isValid()) continue;
|
||||||
|
f.setData(getLoader());
|
||||||
|
parser.registerFunction(f.getName(), f);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return parser.parse(getExpression(), scope);
|
return parser.parse(getExpression(), scope);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
@@ -103,7 +113,7 @@ public class IrisExpression extends IrisRegistrant {
|
|||||||
g[m++] = z;
|
g[m++] = z;
|
||||||
g[m] = -1;
|
g[m] = -1;
|
||||||
|
|
||||||
return expression().evaluate(g);
|
return expression().evaluate(new FunctionContext(rng), g);
|
||||||
}
|
}
|
||||||
|
|
||||||
public double evaluate(RNG rng, double x, double y, double z) {
|
public double evaluate(RNG rng, double x, double y, double z) {
|
||||||
@@ -117,7 +127,7 @@ public class IrisExpression extends IrisRegistrant {
|
|||||||
g[m++] = y;
|
g[m++] = y;
|
||||||
g[m] = z;
|
g[m] = z;
|
||||||
|
|
||||||
return expression().evaluate(g);
|
return expression().evaluate(new FunctionContext(rng), g);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package com.volmit.iris.engine.object;
|
||||||
|
|
||||||
|
import com.dfsek.paralithic.functions.dynamic.Context;
|
||||||
|
import com.dfsek.paralithic.functions.dynamic.DynamicFunction;
|
||||||
|
import com.dfsek.paralithic.node.Statefulness;
|
||||||
|
import com.volmit.iris.core.loader.IrisData;
|
||||||
|
import com.volmit.iris.engine.object.annotations.Desc;
|
||||||
|
import com.volmit.iris.engine.object.annotations.MinNumber;
|
||||||
|
import com.volmit.iris.engine.object.annotations.Required;
|
||||||
|
import com.volmit.iris.engine.object.annotations.Snippet;
|
||||||
|
import com.volmit.iris.util.collection.KMap;
|
||||||
|
import com.volmit.iris.util.math.RNG;
|
||||||
|
import lombok.*;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@Snippet("expression-function")
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Desc("Represents a function to use in your expression. Do not set the name to x, y, or z, also don't duplicate names.")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class IrisExpressionFunction implements DynamicFunction {
|
||||||
|
@Required
|
||||||
|
@Desc("The function to assign this value to. Do not set the name to x, y, or z")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Desc("If defined, this variable will use a generator style as it's value")
|
||||||
|
private IrisGeneratorStyle styleValue = null;
|
||||||
|
|
||||||
|
@Desc("If defined, iris will use an internal stream from the engine as it's value")
|
||||||
|
private IrisEngineStreamType engineStreamValue = null;
|
||||||
|
|
||||||
|
@MinNumber(2)
|
||||||
|
@Desc("Number of arguments for the function")
|
||||||
|
private int args = 2;
|
||||||
|
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
@Setter(AccessLevel.NONE)
|
||||||
|
private transient final KMap<FunctionContext, Provider> cache = new KMap<>();
|
||||||
|
private transient IrisData data;
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return styleValue != null || engineStreamValue != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getArgNumber() {
|
||||||
|
if (engineStreamValue != null) return 2;
|
||||||
|
return Math.max(args, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Statefulness statefulness() {
|
||||||
|
return Statefulness.STATEFUL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double eval(double... doubles) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double eval(@Nullable Context raw, double... args) {
|
||||||
|
return cache.computeIfAbsent((FunctionContext) raw, context -> {
|
||||||
|
assert context != null;
|
||||||
|
if (engineStreamValue != null) {
|
||||||
|
var stream = engineStreamValue.get(data.getEngine());
|
||||||
|
return d -> stream.get(d[0], d[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (styleValue != null) {
|
||||||
|
return styleValue.createNoCache(context.rng, data)::noise;
|
||||||
|
}
|
||||||
|
|
||||||
|
return d -> Double.NaN;
|
||||||
|
}).eval(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record FunctionContext(@NonNull RNG rng) implements Context {
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
FunctionContext that = (FunctionContext) o;
|
||||||
|
return rng.getSeed() == that.rng.getSeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Long.hashCode(rng.getSeed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
private interface Provider {
|
||||||
|
double eval(double... args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,12 +23,11 @@ import com.volmit.iris.engine.data.cache.AtomicCache;
|
|||||||
import com.volmit.iris.engine.object.annotations.Desc;
|
import com.volmit.iris.engine.object.annotations.Desc;
|
||||||
import com.volmit.iris.engine.object.annotations.Required;
|
import com.volmit.iris.engine.object.annotations.Required;
|
||||||
import com.volmit.iris.engine.object.annotations.Snippet;
|
import com.volmit.iris.engine.object.annotations.Snippet;
|
||||||
|
import com.volmit.iris.util.collection.KMap;
|
||||||
import com.volmit.iris.util.math.RNG;
|
import com.volmit.iris.util.math.RNG;
|
||||||
|
import com.volmit.iris.util.noise.CNG;
|
||||||
import com.volmit.iris.util.stream.ProceduralStream;
|
import com.volmit.iris.util.stream.ProceduralStream;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.*;
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
@Snippet("expression-load")
|
@Snippet("expression-load")
|
||||||
@@ -57,6 +56,9 @@ public class IrisExpressionLoad {
|
|||||||
|
|
||||||
private transient AtomicCache<ProceduralStream<Double>> streamCache = new AtomicCache<>();
|
private transient AtomicCache<ProceduralStream<Double>> streamCache = new AtomicCache<>();
|
||||||
private transient AtomicCache<Double> valueCache = new AtomicCache<>();
|
private transient AtomicCache<Double> valueCache = new AtomicCache<>();
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
@Setter(AccessLevel.NONE)
|
||||||
|
private transient final KMap<Long, CNG> styleCache = new KMap<>();
|
||||||
|
|
||||||
public double getValue(RNG rng, IrisData data, double x, double z) {
|
public double getValue(RNG rng, IrisData data, double x, double z) {
|
||||||
if (engineValue != null) {
|
if (engineValue != null) {
|
||||||
@@ -68,7 +70,8 @@ public class IrisExpressionLoad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (styleValue != null) {
|
if (styleValue != null) {
|
||||||
return styleValue.create(rng, data).noise(x, z);
|
return styleCache.computeIfAbsent(rng.getSeed(), k -> styleValue.createNoCache(new RNG(k), data))
|
||||||
|
.noise(x, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
return staticValue;
|
return staticValue;
|
||||||
@@ -84,7 +87,8 @@ public class IrisExpressionLoad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (styleValue != null) {
|
if (styleValue != null) {
|
||||||
return styleValue.create(rng, data).noise(x, y, z);
|
return styleCache.computeIfAbsent(rng.getSeed(), k -> styleValue.createNoCache(new RNG(k), data))
|
||||||
|
.noise(x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
return staticValue;
|
return staticValue;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
-13
@@ -19,13 +19,8 @@
|
|||||||
package com.volmit.iris.engine.object;
|
package com.volmit.iris.engine.object;
|
||||||
|
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.engine.object.annotations.ArrayType;
|
import com.volmit.iris.core.loader.IrisData;
|
||||||
import com.volmit.iris.engine.object.annotations.Desc;
|
import com.volmit.iris.engine.object.annotations.*;
|
||||||
import com.volmit.iris.engine.object.annotations.MaxNumber;
|
|
||||||
import com.volmit.iris.engine.object.annotations.MinNumber;
|
|
||||||
import com.volmit.iris.engine.object.annotations.RegistryListResource;
|
|
||||||
import com.volmit.iris.engine.object.annotations.Required;
|
|
||||||
import com.volmit.iris.engine.object.annotations.Snippet;
|
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
import com.volmit.iris.util.collection.KMap;
|
import com.volmit.iris.util.collection.KMap;
|
||||||
import com.volmit.iris.util.documentation.ChunkCoordinates;
|
import com.volmit.iris.util.documentation.ChunkCoordinates;
|
||||||
@@ -54,6 +49,7 @@ public class IrisJigsawStructurePlacement implements IRare {
|
|||||||
private int rarity = 100;
|
private int rarity = 100;
|
||||||
|
|
||||||
@Required
|
@Required
|
||||||
|
@DependsOn({"spacing", "separation"})
|
||||||
@Desc("The salt to use when generating the structure (to differentiate structures)")
|
@Desc("The salt to use when generating the structure (to differentiate structures)")
|
||||||
@MinNumber(Long.MIN_VALUE)
|
@MinNumber(Long.MIN_VALUE)
|
||||||
@MaxNumber(Long.MAX_VALUE)
|
@MaxNumber(Long.MAX_VALUE)
|
||||||
@@ -61,16 +57,26 @@ public class IrisJigsawStructurePlacement implements IRare {
|
|||||||
|
|
||||||
@Required
|
@Required
|
||||||
@MinNumber(0)
|
@MinNumber(0)
|
||||||
|
@DependsOn({"salt", "separation"})
|
||||||
@Desc("Average distance in chunks between two neighboring generation attempts")
|
@Desc("Average distance in chunks between two neighboring generation attempts")
|
||||||
private int spacing = -1;
|
private int spacing = -1;
|
||||||
|
|
||||||
@Required
|
@Required
|
||||||
@MinNumber(0)
|
@MinNumber(0)
|
||||||
|
@DependsOn({"salt", "spacing"})
|
||||||
@Desc("Minimum distance in chunks between two neighboring generation attempts\nThe maximum distance of two neighboring generation attempts is 2*spacing - separation")
|
@Desc("Minimum distance in chunks between two neighboring generation attempts\nThe maximum distance of two neighboring generation attempts is 2*spacing - separation")
|
||||||
private int separation = -1;
|
private int separation = -1;
|
||||||
|
|
||||||
@Desc("The method used to spread the structure")
|
@Desc("The method used to spread the structure")
|
||||||
private SpreadType spreadType = SpreadType.TRIANGULAR;
|
private SpreadType spreadType = SpreadType.LINEAR;
|
||||||
|
|
||||||
|
@DependsOn({"spreadType"})
|
||||||
|
@Desc("The noise style to use when spreadType is set to 'NOISE'\nThis ignores the spacing and separation parameters")
|
||||||
|
private IrisGeneratorStyle style = new IrisGeneratorStyle();
|
||||||
|
|
||||||
|
@DependsOn({"spreadType", "style"})
|
||||||
|
@Desc("Threshold for noise style")
|
||||||
|
private double threshold = 0.5;
|
||||||
|
|
||||||
@ArrayType(type = IrisJigsawMinDistance.class)
|
@ArrayType(type = IrisJigsawMinDistance.class)
|
||||||
@Desc("List of minimum distances to check for")
|
@Desc("List of minimum distances to check for")
|
||||||
@@ -89,20 +95,29 @@ public class IrisJigsawStructurePlacement implements IRare {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void calculateMissing(double divisor, long seed) {
|
private void calculateMissing(double divisor, long seed) {
|
||||||
seed = seed + hashCode();
|
if (salt != 0 && separation > 0 && spacing > 0)
|
||||||
|
return;
|
||||||
|
seed *= (long) structure.hashCode() * rarity;
|
||||||
if (salt == 0) {
|
if (salt == 0) {
|
||||||
salt = new RNG(seed).nextLong(Integer.MIN_VALUE, Integer.MAX_VALUE);
|
salt = new RNG(seed).l(Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (separation == -1 || spacing == -1) {
|
if (separation == -1 || spacing == -1) {
|
||||||
separation = (int) Math.round(rarity / divisor);
|
separation = (int) Math.round(rarity / divisor);
|
||||||
spacing = new RNG(seed).nextInt(separation, separation * 2);
|
spacing = new RNG(seed).i(separation, separation * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ChunkCoordinates
|
@ChunkCoordinates
|
||||||
public boolean shouldPlace(double divisor, long seed, int x, int z) {
|
public boolean shouldPlace(IrisData data, double divisor, long seed, int x, int z) {
|
||||||
calculateMissing(divisor, seed);
|
calculateMissing(divisor, seed);
|
||||||
|
if (spreadType != SpreadType.NOISE)
|
||||||
|
return shouldPlaceSpread(seed, x, z);
|
||||||
|
|
||||||
|
return style.create(new RNG(seed + salt), data).noise(x, z) > threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldPlaceSpread(long seed, int x, int z) {
|
||||||
if (separation > spacing) {
|
if (separation > spacing) {
|
||||||
separation = spacing;
|
separation = spacing;
|
||||||
Iris.warn("JigsawStructurePlacement: separation must be less than or equal to spacing");
|
Iris.warn("JigsawStructurePlacement: separation must be less than or equal to spacing");
|
||||||
@@ -123,7 +138,9 @@ public class IrisJigsawStructurePlacement implements IRare {
|
|||||||
@Desc("Linear spread")
|
@Desc("Linear spread")
|
||||||
LINEAR(RNG::i),
|
LINEAR(RNG::i),
|
||||||
@Desc("Triangular spread")
|
@Desc("Triangular spread")
|
||||||
TRIANGULAR((rng, bound) -> (rng.i(bound) + rng.i(bound)) / 2);
|
TRIANGULAR((rng, bound) -> (rng.i(bound) + rng.i(bound)) / 2),
|
||||||
|
@Desc("Noise based spread\nThis ignores the spacing and separation parameters")
|
||||||
|
NOISE((rng, bound) -> 0);
|
||||||
private final SpreadMethod method;
|
private final SpreadMethod method;
|
||||||
|
|
||||||
SpreadType(SpreadMethod method) {
|
SpreadType(SpreadMethod method) {
|
||||||
|
|||||||
@@ -1010,8 +1010,9 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean wouldReplace = B.isSolid(placer.get(xx, yy, zz)) && B.isVineBlock(data);
|
boolean wouldReplace = B.isSolid(placer.get(xx, yy, zz)) && B.isVineBlock(data);
|
||||||
|
boolean place = !data.getMaterial().equals(Material.AIR) && !data.getMaterial().equals(Material.CAVE_AIR) && !wouldReplace;
|
||||||
|
|
||||||
if (!data.getMaterial().equals(Material.AIR) && !data.getMaterial().equals(Material.CAVE_AIR) && !wouldReplace) {
|
if (data instanceof IrisCustomData || place) {
|
||||||
placer.set(xx, yy, zz, data);
|
placer.set(xx, yy, zz, data);
|
||||||
if (tile != null) {
|
if (tile != null) {
|
||||||
placer.setTile(xx, yy, zz, tile);
|
placer.setTile(xx, yy, zz, tile);
|
||||||
|
|||||||
@@ -50,4 +50,10 @@ public class IrisRange {
|
|||||||
public boolean contains(int v) {
|
public boolean contains(int v) {
|
||||||
return v >= min && v <= max;
|
return v >= min && v <= max;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IrisRange merge(IrisRange other) {
|
||||||
|
min = Math.min(min, other.min);
|
||||||
|
max = Math.max(max, other.max);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class IrisRate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public long getInterval() {
|
public long getInterval() {
|
||||||
long t = per.getMilliseconds() / (amount == 0 ? 1 : amount);
|
long t = per.toMilliseconds() / (amount == 0 ? 1 : amount);
|
||||||
return Math.abs(t <= 0 ? 1 : t);
|
return Math.abs(t <= 0 ? 1 : t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
package com.volmit.iris.engine.object;
|
package com.volmit.iris.engine.object;
|
||||||
|
|
||||||
import com.volmit.iris.core.loader.IrisRegistrant;
|
import com.volmit.iris.core.loader.IrisRegistrant;
|
||||||
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
import com.volmit.iris.engine.object.annotations.ArrayType;
|
import com.volmit.iris.engine.object.annotations.ArrayType;
|
||||||
import com.volmit.iris.engine.object.annotations.Desc;
|
import com.volmit.iris.engine.object.annotations.Desc;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
@@ -95,6 +96,37 @@ public class IrisSpawner extends IrisRegistrant {
|
|||||||
return timeBlock.isWithin(world) && weather.is(world);
|
return timeBlock.isWithin(world) && weather.is(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean canSpawn(Engine engine) {
|
||||||
|
if (!isValid(engine.getWorld().realWorld()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var rate = getMaximumRate();
|
||||||
|
return rate.isInfinite() || engine.getEngineData().getCooldown(this).canSpawn(rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canSpawn(Engine engine, int x, int z) {
|
||||||
|
if (!canSpawn(engine))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var rate = getMaximumRatePerChunk();
|
||||||
|
return rate.isInfinite() || engine.getEngineData().getChunk(x, z).getCooldown(this).canSpawn(rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void spawn(Engine engine) {
|
||||||
|
if (getMaximumRate().isInfinite())
|
||||||
|
return;
|
||||||
|
|
||||||
|
engine.getEngineData().getCooldown(this).spawn(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void spawn(Engine engine, int x, int z) {
|
||||||
|
spawn(engine);
|
||||||
|
if (getMaximumRatePerChunk().isInfinite())
|
||||||
|
return;
|
||||||
|
|
||||||
|
engine.getEngineData().getChunk(x, z).getCooldown(this).spawn(engine);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFolderName() {
|
public String getFolderName() {
|
||||||
return "spawners";
|
return "spawners";
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.volmit.iris.engine.object;
|
||||||
|
|
||||||
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
|
import com.volmit.iris.util.collection.KMap;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
@EqualsAndHashCode
|
||||||
|
public class IrisSpawnerCooldowns {
|
||||||
|
private final KMap<String, IrisEngineSpawnerCooldown> cooldowns = new KMap<>();
|
||||||
|
|
||||||
|
public IrisEngineSpawnerCooldown getCooldown(@NonNull IrisSpawner spawner) {
|
||||||
|
return getCooldown(spawner.getLoadKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public IrisEngineSpawnerCooldown getCooldown(@NonNull String loadKey) {
|
||||||
|
return cooldowns.computeIfAbsent(loadKey, k -> {
|
||||||
|
IrisEngineSpawnerCooldown cd = new IrisEngineSpawnerCooldown();
|
||||||
|
cd.setSpawner(loadKey);
|
||||||
|
return cd;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cleanup(Engine engine) {
|
||||||
|
cooldowns.values().removeIf(cd -> {
|
||||||
|
IrisSpawner sp = engine.getData().getSpawnerLoader().load(cd.getSpawner());
|
||||||
|
return sp == null || cd.canSpawn(sp.getMaximumRate());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return cooldowns.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,6 +48,7 @@ public class IrisWorld {
|
|||||||
private long seed;
|
private long seed;
|
||||||
private World.Environment environment;
|
private World.Environment environment;
|
||||||
private World realWorld;
|
private World realWorld;
|
||||||
|
private IrisHeadless headless;
|
||||||
private int minHeight;
|
private int minHeight;
|
||||||
private int maxHeight;
|
private int maxHeight;
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import java.io.IOException;
|
|||||||
|
|
||||||
@SuppressWarnings("ALL")
|
@SuppressWarnings("ALL")
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
@@ -137,4 +136,9 @@ public class TileData implements Cloneable {
|
|||||||
clone.properties = properties.copy(); //TODO make a deep copy
|
clone.properties = properties.copy(); //TODO make a deep copy
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return material.getKey() + gson.toJson(properties);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ package com.volmit.iris.engine.platform;
|
|||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.loader.IrisData;
|
import com.volmit.iris.core.loader.IrisData;
|
||||||
import com.volmit.iris.core.nms.INMS;
|
import com.volmit.iris.core.nms.INMS;
|
||||||
|
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.core.service.StudioSVC;
|
||||||
import com.volmit.iris.engine.IrisEngine;
|
import com.volmit.iris.engine.IrisEngine;
|
||||||
import com.volmit.iris.engine.data.chunk.TerrainChunk;
|
import com.volmit.iris.engine.data.chunk.TerrainChunk;
|
||||||
@@ -32,10 +34,13 @@ import com.volmit.iris.engine.object.StudioMode;
|
|||||||
import com.volmit.iris.engine.platform.studio.StudioGenerator;
|
import com.volmit.iris.engine.platform.studio.StudioGenerator;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
import com.volmit.iris.util.data.IrisBiomeStorage;
|
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.Hunk;
|
||||||
import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder;
|
import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder;
|
||||||
import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder;
|
import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder;
|
||||||
import com.volmit.iris.util.io.ReactiveFolder;
|
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.ChronoLatch;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
import com.volmit.iris.util.scheduling.Looper;
|
import com.volmit.iris.util.scheduling.Looper;
|
||||||
@@ -252,6 +257,10 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Engine getEngine(WorldInfo world) {
|
private Engine getEngine(WorldInfo world) {
|
||||||
|
return getEngine(world.getSeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Engine getEngine(long seed) {
|
||||||
if (setup.get()) {
|
if (setup.get()) {
|
||||||
return getEngine();
|
return getEngine();
|
||||||
}
|
}
|
||||||
@@ -264,7 +273,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getWorld().setRawWorldSeed(world.getSeed());
|
getWorld().setRawWorldSeed(seed);
|
||||||
setupEngine();
|
setupEngine();
|
||||||
setup.set(true);
|
setup.set(true);
|
||||||
this.hotloader = studio ? new Looper() {
|
this.hotloader = studio ? new Looper() {
|
||||||
@@ -335,6 +344,21 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
getEngine(world);
|
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
|
@Override
|
||||||
public void generateNoise(@NotNull WorldInfo world, @NotNull Random random, int x, int z, @NotNull ChunkGenerator.ChunkData d) {
|
public void generateNoise(@NotNull WorldInfo world, @NotNull Random random, int x, int z, @NotNull ChunkGenerator.ChunkData d) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -49,4 +49,12 @@ public interface PlatformChunkGenerator extends Hotloadable, DataProvider {
|
|||||||
void touch(World world);
|
void touch(World world);
|
||||||
|
|
||||||
CompletableFuture<Integer> getSpawnChunks();
|
CompletableFuture<Integer> getSpawnChunks();
|
||||||
|
|
||||||
|
void prepareSpawnChunks(long seed, int radius);
|
||||||
|
|
||||||
|
default int getGenerated() {
|
||||||
|
Engine engine = getEngine();
|
||||||
|
if (engine == null) return 0;
|
||||||
|
return engine.getGenerated();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
package com.volmit.iris.util.data.registry;
|
package com.volmit.iris.util.data.registry;
|
||||||
|
|
||||||
import com.volmit.iris.core.nms.container.Pair;
|
import com.volmit.iris.core.nms.container.Pair;
|
||||||
|
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Keyed;
|
import org.bukkit.Keyed;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.Registry;
|
import org.bukkit.Registry;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -20,6 +23,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public class RegistryUtil {
|
public class RegistryUtil {
|
||||||
|
private static final AtomicCache<RegistryLookup> registryLookup = new AtomicCache<>();
|
||||||
private static final Map<Class<?>, Map<NamespacedKey, Keyed>> KEYED_REGISTRY = new HashMap<>();
|
private static final Map<Class<?>, Map<NamespacedKey, Keyed>> KEYED_REGISTRY = new HashMap<>();
|
||||||
private static final Map<Class<?>, Map<NamespacedKey, Object>> ENUM_REGISTRY = new HashMap<>();
|
private static final Map<Class<?>, Map<NamespacedKey, Object>> ENUM_REGISTRY = new HashMap<>();
|
||||||
private static final Map<Class<?>, Registry<Keyed>> REGISTRY = new HashMap<>();
|
private static final Map<Class<?>, Registry<Keyed>> REGISTRY = new HashMap<>();
|
||||||
@@ -43,7 +47,7 @@ public class RegistryUtil {
|
|||||||
if (keys.length == 0) throw new IllegalArgumentException("Need at least one key");
|
if (keys.length == 0) throw new IllegalArgumentException("Need at least one key");
|
||||||
Registry<Keyed> registry = null;
|
Registry<Keyed> registry = null;
|
||||||
if (Keyed.class.isAssignableFrom(typeClass)) {
|
if (Keyed.class.isAssignableFrom(typeClass)) {
|
||||||
registry = Bukkit.getRegistry(typeClass.asSubclass(Keyed.class));
|
registry = getRegistry(typeClass.asSubclass(Keyed.class));
|
||||||
}
|
}
|
||||||
if (registry == null) {
|
if (registry == null) {
|
||||||
registry = REGISTRY.computeIfAbsent(typeClass, t -> Arrays.stream(Registry.class.getDeclaredFields())
|
registry = REGISTRY.computeIfAbsent(typeClass, t -> Arrays.stream(Registry.class.getDeclaredFields())
|
||||||
@@ -150,4 +154,56 @@ public class RegistryUtil {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static <T extends Keyed> Registry<T> getRegistry(@NotNull Class<T> type) {
|
||||||
|
RegistryLookup lookup = registryLookup.aquire(() -> {
|
||||||
|
RegistryLookup bukkit;
|
||||||
|
try {
|
||||||
|
bukkit = Bukkit::getRegistry;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
bukkit = null;
|
||||||
|
}
|
||||||
|
return new DefaultRegistryLookup(bukkit);
|
||||||
|
});
|
||||||
|
return lookup.find(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface RegistryLookup {
|
||||||
|
@Nullable
|
||||||
|
<T extends Keyed> Registry<T> find(@NonNull Class<T> type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefaultRegistryLookup implements RegistryLookup {
|
||||||
|
private final RegistryLookup bukkit;
|
||||||
|
private final Map<Type, Object> registries;
|
||||||
|
|
||||||
|
private DefaultRegistryLookup(RegistryLookup bukkit) {
|
||||||
|
this.bukkit = bukkit;
|
||||||
|
registries = Arrays.stream(Registry.class.getDeclaredFields())
|
||||||
|
.filter(field -> Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers()))
|
||||||
|
.filter(field -> Registry.class.isAssignableFrom(field.getType()))
|
||||||
|
.map(field -> {
|
||||||
|
var type = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
|
||||||
|
try {
|
||||||
|
return new Pair<>(type, field.get(null));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toMap(Pair::getA, Pair::getB, (a, b) -> a));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <T extends Keyed> Registry<T> find(@NonNull Class<T> type) {
|
||||||
|
if (bukkit == null) return (Registry<T>) registries.get(type);
|
||||||
|
try {
|
||||||
|
return bukkit.find(type);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return (Registry<T>) registries.get(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.volmit.iris.util.hunk.view;
|
||||||
|
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.util.hunk.storage.ArrayHunk;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
|
||||||
|
public class SyncChunkDataHunkHolder extends ArrayHunk<BlockData> {
|
||||||
|
private static final BlockData AIR = Material.AIR.createBlockData();
|
||||||
|
private final ChunkGenerator.ChunkData chunk;
|
||||||
|
private final Thread mainThread = Thread.currentThread();
|
||||||
|
|
||||||
|
public SyncChunkDataHunkHolder(ChunkGenerator.ChunkData chunk) {
|
||||||
|
super(16, chunk.getMaxHeight() - chunk.getMinHeight(), 16);
|
||||||
|
this.chunk = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRaw(int x, int y, int z, BlockData data) {
|
||||||
|
if (Thread.currentThread() != mainThread)
|
||||||
|
Iris.warn("SyncChunkDataHunkHolder is not on the main thread");
|
||||||
|
super.setRaw(x, y, z, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockData getRaw(int x, int y, int z) {
|
||||||
|
if (Thread.currentThread() != mainThread)
|
||||||
|
Iris.warn("SyncChunkDataHunkHolder is not on the main thread");
|
||||||
|
BlockData b = super.getRaw(x, y, z);
|
||||||
|
|
||||||
|
return b != null ? b : AIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void apply() {
|
||||||
|
for (int i = getHeight()-1; i >= 0; i--) {
|
||||||
|
for (int j = 0; j < getWidth(); j++) {
|
||||||
|
for (int k = 0; k < getDepth(); k++) {
|
||||||
|
BlockData b = super.getRaw(j, i, k);
|
||||||
|
|
||||||
|
if (b != null) {
|
||||||
|
chunk.setBlock(j, i + chunk.getMinHeight(), k, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,8 @@ package com.volmit.iris.util.math;
|
|||||||
import com.volmit.iris.engine.object.IrisPosition;
|
import com.volmit.iris.engine.object.IrisPosition;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
public class Position2 {
|
public class Position2 {
|
||||||
private int x;
|
private int x;
|
||||||
private int z;
|
private int z;
|
||||||
@@ -94,4 +96,8 @@ public class Position2 {
|
|||||||
public IrisPosition toIris() {
|
public IrisPosition toIris() {
|
||||||
return new IrisPosition(x, 23, z);
|
return new IrisPosition(x, 23, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T> T convert(BiFunction<Integer, Integer, T> constructor) {
|
||||||
|
return constructor.apply(x, z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class Spiraler {
|
|||||||
|
|
||||||
public void next() {
|
public void next() {
|
||||||
if ((-sizeX / 2 <= x) && (x <= sizeX / 2) && (-sizeZ / 2 <= z) && (z <= sizeZ / 2)) {
|
if ((-sizeX / 2 <= x) && (x <= sizeX / 2) && (-sizeZ / 2 <= z) && (z <= sizeZ / 2)) {
|
||||||
spiraled.on(x + ox, z + ox);
|
spiraled.on(x + ox, z + oz);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) {
|
if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) {
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.volmit.iris.util.misc;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class ServerProperties {
|
||||||
|
public static final Properties DATA = new Properties();
|
||||||
|
public static final File SERVER_PROPERTIES;
|
||||||
|
public static final File BUKKIT_YML;
|
||||||
|
|
||||||
|
public static final String LEVEL_NAME;
|
||||||
|
|
||||||
|
static {
|
||||||
|
String[] args = ProcessHandle.current()
|
||||||
|
.info()
|
||||||
|
.arguments()
|
||||||
|
.orElse(new String[0]);
|
||||||
|
|
||||||
|
String propertiesPath = "server.properties";
|
||||||
|
String bukkitYml = "bukkit.yml";
|
||||||
|
String levelName = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < args.length - 1; i++) {
|
||||||
|
switch (args[i]) {
|
||||||
|
case "-c", "--config" -> propertiesPath = args[i + 1];
|
||||||
|
case "-b", "--bukkit-settings" -> bukkitYml = args[i + 1];
|
||||||
|
case "-w", "--level-name", "--world" -> levelName = args[i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SERVER_PROPERTIES = new File(propertiesPath);
|
||||||
|
BUKKIT_YML = new File(bukkitYml);
|
||||||
|
try (FileInputStream in = new FileInputStream(SERVER_PROPERTIES)){
|
||||||
|
DATA.load(in);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (levelName != null) LEVEL_NAME = levelName;
|
||||||
|
else LEVEL_NAME = DATA.getProperty("level-name", "world");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
package com.volmit.iris.core.nms.v1_19_R1;
|
|
||||||
|
|
||||||
import com.mojang.serialization.Codec;
|
|
||||||
import com.volmit.iris.Iris;
|
|
||||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
|
||||||
import com.volmit.iris.engine.object.IrisBiome;
|
|
||||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
|
||||||
import com.volmit.iris.util.collection.KMap;
|
|
||||||
import com.volmit.iris.util.math.RNG;
|
|
||||||
import net.minecraft.core.Holder;
|
|
||||||
import net.minecraft.core.Registry;
|
|
||||||
import net.minecraft.core.RegistryAccess;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.world.level.biome.Biome;
|
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.biome.Climate;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.CraftServer;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlock;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class CustomBiomeSource extends BiomeSource {
|
|
||||||
private final long seed;
|
|
||||||
private final Engine engine;
|
|
||||||
private final Registry<Biome> biomeCustomRegistry;
|
|
||||||
private final Registry<Biome> biomeRegistry;
|
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
|
||||||
private final RNG rng;
|
|
||||||
private final KMap<String, Holder<Biome>> customBiomes;
|
|
||||||
|
|
||||||
public CustomBiomeSource(long seed, Engine engine, World world) {
|
|
||||||
super(getAllBiomes(
|
|
||||||
((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()))
|
|
||||||
.registry(Registry.BIOME_REGISTRY).orElse(null),
|
|
||||||
((CraftWorld) world).getHandle().registryAccess().registry(Registry.BIOME_REGISTRY).orElse(null),
|
|
||||||
engine));
|
|
||||||
this.engine = engine;
|
|
||||||
this.seed = seed;
|
|
||||||
this.biomeCustomRegistry = registry().registry(Registry.BIOME_REGISTRY).orElse(null);
|
|
||||||
this.biomeRegistry = ((CraftWorld) world).getHandle().registryAccess().registry(Registry.BIOME_REGISTRY).orElse(null);
|
|
||||||
this.rng = new RNG(engine.getSeedManager().getBiome());
|
|
||||||
this.customBiomes = fillCustomBiomes(biomeCustomRegistry, engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Holder<Biome>> getAllBiomes(Registry<Biome> customRegistry, Registry<Biome> registry, Engine engine) {
|
|
||||||
List<Holder<Biome>> b = new ArrayList<>();
|
|
||||||
|
|
||||||
for (IrisBiome i : engine.getAllBiomes()) {
|
|
||||||
if (i.isCustom()) {
|
|
||||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
|
||||||
b.add(customRegistry.getHolder(customRegistry.getResourceKey(customRegistry
|
|
||||||
.get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.add(CraftBlock.biomeToBiomeBase(registry, i.getVanillaDerivative()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object getFor(Class<?> type, Object source) {
|
|
||||||
Object o = fieldFor(type, source);
|
|
||||||
|
|
||||||
if (o != null) {
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
return invokeFor(type, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object fieldFor(Class<?> returns, Object in) {
|
|
||||||
return fieldForClass(returns, in.getClass(), in);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object invokeFor(Class<?> returns, Object in) {
|
|
||||||
for (Method i : in.getClass().getMethods()) {
|
|
||||||
if (i.getReturnType().equals(returns)) {
|
|
||||||
i.setAccessible(true);
|
|
||||||
try {
|
|
||||||
Iris.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()");
|
|
||||||
return i.invoke(in);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <T> T fieldForClass(Class<T> returnType, Class<?> sourceType, Object in) {
|
|
||||||
for (Field i : sourceType.getDeclaredFields()) {
|
|
||||||
if (i.getType().equals(returnType)) {
|
|
||||||
i.setAccessible(true);
|
|
||||||
try {
|
|
||||||
Iris.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName());
|
|
||||||
return (T) i.get(in);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private KMap<String, Holder<Biome>> fillCustomBiomes(Registry<Biome> customRegistry, Engine engine) {
|
|
||||||
KMap<String, Holder<Biome>> m = new KMap<>();
|
|
||||||
|
|
||||||
for (IrisBiome i : engine.getAllBiomes()) {
|
|
||||||
if (i.isCustom()) {
|
|
||||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
|
||||||
m.put(j.getId(), customRegistry.getHolder(customRegistry.getResourceKey(customRegistry
|
|
||||||
.get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RegistryAccess registry() {
|
|
||||||
return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Codec<? extends BiomeSource> codec() {
|
|
||||||
throw new UnsupportedOperationException("Not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
|
||||||
int m = (y - engine.getMinHeight()) << 2;
|
|
||||||
IrisBiome ib = engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2);
|
|
||||||
if (ib.isCustom()) {
|
|
||||||
return customBiomes.get(ib.getCustomBiome(rng, x << 2, m, z << 2).getId());
|
|
||||||
} else {
|
|
||||||
org.bukkit.block.Biome v = ib.getSkyBiome(rng, x << 2, m, z << 2);
|
|
||||||
return CraftBlock.biomeToBiomeBase(biomeRegistry, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
package com.volmit.iris.core.nms.v1_19_R1;
|
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
|
||||||
import com.mojang.serialization.Codec;
|
|
||||||
import com.volmit.iris.Iris;
|
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
|
||||||
import com.volmit.iris.engine.framework.ResultLocator;
|
|
||||||
import com.volmit.iris.engine.framework.WrongEngineBroException;
|
|
||||||
import com.volmit.iris.engine.object.IrisJigsawStructure;
|
|
||||||
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
|
|
||||||
import com.volmit.iris.util.collection.KList;
|
|
||||||
import com.volmit.iris.util.collection.KMap;
|
|
||||||
import com.volmit.iris.util.collection.KSet;
|
|
||||||
import com.volmit.iris.util.mantle.MantleFlag;
|
|
||||||
import com.volmit.iris.util.math.Position2;
|
|
||||||
import com.volmit.iris.util.reflect.WrappedField;
|
|
||||||
import net.minecraft.core.*;
|
|
||||||
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.TagKey;
|
|
||||||
import net.minecraft.util.random.WeightedRandomList;
|
|
||||||
import net.minecraft.world.entity.MobCategory;
|
|
||||||
import net.minecraft.world.level.LevelHeightAccessor;
|
|
||||||
import net.minecraft.world.level.NoiseColumn;
|
|
||||||
import net.minecraft.world.level.StructureManager;
|
|
||||||
import net.minecraft.world.level.WorldGenLevel;
|
|
||||||
import net.minecraft.world.level.biome.Biome;
|
|
||||||
import net.minecraft.world.level.biome.BiomeManager;
|
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.biome.MobSpawnSettings;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
|
||||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
|
||||||
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.Structure;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.generator.CustomChunkGenerator;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
public class IrisChunkGenerator extends CustomChunkGenerator {
|
|
||||||
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
|
|
||||||
private final ChunkGenerator delegate;
|
|
||||||
private final Engine engine;
|
|
||||||
private final KMap<ResourceKey<Structure>, KSet<String>> structures = new KMap<>();
|
|
||||||
|
|
||||||
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
|
|
||||||
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
|
|
||||||
this.delegate = delegate;
|
|
||||||
this.engine = engine;
|
|
||||||
var dimension = engine.getDimension();
|
|
||||||
|
|
||||||
KSet<IrisJigsawStructure> placements = new KSet<>();
|
|
||||||
addAll(dimension.getJigsawStructures(), placements);
|
|
||||||
for (var region : dimension.getAllRegions(engine)) {
|
|
||||||
addAll(region.getJigsawStructures(), placements);
|
|
||||||
for (var biome : region.getAllBiomes(engine))
|
|
||||||
addAll(biome.getJigsawStructures(), placements);
|
|
||||||
}
|
|
||||||
var stronghold = dimension.getStronghold();
|
|
||||||
if (stronghold != null)
|
|
||||||
placements.add(engine.getData().getJigsawStructureLoader().load(stronghold));
|
|
||||||
placements.removeIf(Objects::isNull);
|
|
||||||
|
|
||||||
var registry = ((CraftWorld) world).getHandle().registryAccess().registry(Registry.STRUCTURE_REGISTRY).orElseThrow();
|
|
||||||
for (var s : placements) {
|
|
||||||
try {
|
|
||||||
String raw = s.getStructureKey();
|
|
||||||
if (raw == null) continue;
|
|
||||||
boolean tag = raw.startsWith("#");
|
|
||||||
if (tag) raw = raw.substring(1);
|
|
||||||
|
|
||||||
var location = new ResourceLocation(raw);
|
|
||||||
if (!tag) {
|
|
||||||
structures.computeIfAbsent(ResourceKey.create(Registry.STRUCTURE_REGISTRY, location), k -> new KSet<>()).add(s.getLoadKey());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = TagKey.create(Registry.STRUCTURE_REGISTRY, location);
|
|
||||||
var set = registry.getTag(key).orElse(null);
|
|
||||||
if (set == null) {
|
|
||||||
Iris.error("Could not find structure tag: " + raw);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (var holder : set) {
|
|
||||||
var resourceKey = holder.unwrapKey().orElse(null);
|
|
||||||
if (resourceKey == null) continue;
|
|
||||||
structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey());
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.error("Failed to load structure: " + s.getLoadKey());
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAll(KList<IrisJigsawStructurePlacement> placements, KSet<IrisJigsawStructure> structures) {
|
|
||||||
if (placements == null) return;
|
|
||||||
placements.stream()
|
|
||||||
.map(IrisJigsawStructurePlacement::getStructure)
|
|
||||||
.map(engine.getData().getJigsawStructureLoader()::load)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.forEach(structures::add);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
|
||||||
if (engine.getDimension().isDisableExplorerMaps())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
KMap<String, Holder<Structure>> structures = new KMap<>();
|
|
||||||
for (var holder : holders) {
|
|
||||||
if (holder == null) continue;
|
|
||||||
var key = holder.unwrapKey().orElse(null);
|
|
||||||
var set = this.structures.get(key);
|
|
||||||
if (set == null) continue;
|
|
||||||
for (var structure : set) {
|
|
||||||
structures.put(structure, holder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (structures.isEmpty())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var locator = ResultLocator.locateStructure(structures.keySet())
|
|
||||||
.then((e, p , s) -> structures.get(s.getLoadKey()));
|
|
||||||
if (findUnexplored)
|
|
||||||
locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s);
|
|
||||||
|
|
||||||
try {
|
|
||||||
var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get();
|
|
||||||
if (result == null) return null;
|
|
||||||
var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ());
|
|
||||||
return Pair.of(blockPos, result.obj());
|
|
||||||
} catch (WrongEngineBroException | ExecutionException | InterruptedException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Codec<? extends ChunkGenerator> codec() {
|
|
||||||
return Codec.unit(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChunkGenerator getDelegate() {
|
|
||||||
if (delegate instanceof CustomChunkGenerator chunkGenerator)
|
|
||||||
return chunkGenerator.getDelegate();
|
|
||||||
return delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinY() {
|
|
||||||
return delegate.getMinY();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSeaLevel() {
|
|
||||||
return delegate.getSeaLevel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void createStructures(RegistryAccess iregistrycustom, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager, long i) {
|
|
||||||
delegate.createStructures(iregistrycustom, randomstate, structuremanager, ichunkaccess, structuretemplatemanager, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) {
|
|
||||||
delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess, GenerationStep.Carving worldgenstage_features) {
|
|
||||||
delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess, worldgenstage_features);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<ChunkAccess> fillFromNoise(Executor executor, Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) {
|
|
||||||
return delegate.fillFromNoise(executor, blender, randomstate, structuremanager, ichunkaccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
|
|
||||||
return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WeightedRandomList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
|
|
||||||
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
|
|
||||||
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addDebugScreenInfo(List<String> list, RandomState randomstate, BlockPos blockposition) {
|
|
||||||
delegate.addDebugScreenInfo(list, randomstate, blockposition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) {
|
|
||||||
delegate.spawnOriginalMobs(regionlimitedworldaccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) {
|
|
||||||
return delegate.getSpawnHeight(levelheightaccessor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getGenDepth() {
|
|
||||||
return delegate.getGenDepth();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
|
|
||||||
return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate);
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
Field biomeSource = null;
|
|
||||||
for (Field field : ChunkGenerator.class.getDeclaredFields()) {
|
|
||||||
if (!field.getType().equals(BiomeSource.class))
|
|
||||||
continue;
|
|
||||||
biomeSource = field;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (biomeSource == null)
|
|
||||||
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
|
|
||||||
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {
|
|
||||||
try {
|
|
||||||
BIOME_SOURCE.set(generator, source);
|
|
||||||
if (generator instanceof CustomChunkGenerator custom)
|
|
||||||
BIOME_SOURCE.set(custom.getDelegate(), source);
|
|
||||||
|
|
||||||
return generator;
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,626 +0,0 @@
|
|||||||
package com.volmit.iris.core.nms.v1_19_R1;
|
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Vector;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
|
||||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
|
||||||
import com.volmit.iris.util.scheduling.J;
|
|
||||||
import net.minecraft.nbt.*;
|
|
||||||
import net.minecraft.nbt.Tag;
|
|
||||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
|
||||||
import net.minecraft.tags.TagKey;
|
|
||||||
import net.minecraft.world.level.LevelReader;
|
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
|
||||||
import org.bukkit.*;
|
|
||||||
import org.bukkit.block.Biome;
|
|
||||||
import org.bukkit.block.data.BlockData;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.CraftChunk;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.CraftServer;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlock;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlockState;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlockStates;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.block.data.CraftBlockData;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftDolphin;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack;
|
|
||||||
import org.bukkit.entity.Dolphin;
|
|
||||||
import org.bukkit.entity.Entity;
|
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
|
||||||
import com.volmit.iris.Iris;
|
|
||||||
import com.volmit.iris.core.nms.INMSBinding;
|
|
||||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
|
||||||
import com.volmit.iris.util.collection.KList;
|
|
||||||
import com.volmit.iris.util.collection.KMap;
|
|
||||||
import com.volmit.iris.util.hunk.Hunk;
|
|
||||||
import com.volmit.iris.util.json.JSONObject;
|
|
||||||
import com.volmit.iris.util.mantle.Mantle;
|
|
||||||
import com.volmit.iris.util.math.Vector3d;
|
|
||||||
import com.volmit.iris.util.matter.MatterBiomeInject;
|
|
||||||
import com.volmit.iris.util.nbt.io.NBTUtil;
|
|
||||||
import com.volmit.iris.util.nbt.mca.NBTWorld;
|
|
||||||
import com.volmit.iris.util.nbt.mca.palette.*;
|
|
||||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|
||||||
import net.minecraft.core.BlockPos;
|
|
||||||
import net.minecraft.core.Holder;
|
|
||||||
import net.minecraft.core.Registry;
|
|
||||||
import net.minecraft.core.RegistryAccess;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.server.level.ServerLevel;
|
|
||||||
import net.minecraft.world.entity.EntityType;
|
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.block.Block;
|
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
|
||||||
import sun.misc.Unsafe;
|
|
||||||
|
|
||||||
public class NMSBinding implements INMSBinding {
|
|
||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
|
||||||
private final BlockData AIR = Material.AIR.createBlockData();
|
|
||||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
|
||||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
|
||||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
|
||||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
|
||||||
private Field biomeStorageCache = null;
|
|
||||||
|
|
||||||
private static Object getFor(Class<?> type, Object source) {
|
|
||||||
Object o = fieldFor(type, source);
|
|
||||||
|
|
||||||
if (o != null) {
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
return invokeFor(type, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object invokeFor(Class<?> returns, Object in) {
|
|
||||||
for (Method i : in.getClass().getMethods()) {
|
|
||||||
if (i.getReturnType().equals(returns)) {
|
|
||||||
i.setAccessible(true);
|
|
||||||
try {
|
|
||||||
Iris.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()");
|
|
||||||
return i.invoke(in);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object fieldFor(Class<?> returns, Object in) {
|
|
||||||
return fieldForClass(returns, in.getClass(), in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <T> T fieldForClass(Class<T> returnType, Class<?> sourceType, Object in) {
|
|
||||||
for (Field i : sourceType.getDeclaredFields()) {
|
|
||||||
if (i.getType().equals(returnType)) {
|
|
||||||
i.setAccessible(true);
|
|
||||||
try {
|
|
||||||
Iris.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName());
|
|
||||||
return (T) i.get(in);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<?> getClassType(Class<?> type, int ordinal) {
|
|
||||||
return type.getDeclaredClasses()[ordinal];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasTile(Material material) {
|
|
||||||
return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasTile(Location l) {
|
|
||||||
return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public KMap<String, Object> serializeTile(Location location) {
|
|
||||||
BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false);
|
|
||||||
|
|
||||||
if (e == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata();
|
|
||||||
return (KMap<String, Object>) convertFromTag(tag, 0, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Contract(value = "null, _, _ -> null", pure = true)
|
|
||||||
private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) {
|
|
||||||
if (tag == null || depth > maxDepth) return null;
|
|
||||||
if (tag instanceof CollectionTag<?> collection) {
|
|
||||||
KList<Object> list = new KList<>();
|
|
||||||
|
|
||||||
for (Object i : collection) {
|
|
||||||
if (i instanceof net.minecraft.nbt.Tag t)
|
|
||||||
list.add(convertFromTag(t, depth + 1, maxDepth));
|
|
||||||
else list.add(i);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
if (tag instanceof net.minecraft.nbt.CompoundTag compound) {
|
|
||||||
KMap<String, Object> map = new KMap<>();
|
|
||||||
|
|
||||||
for (String key : compound.getAllKeys()) {
|
|
||||||
var child = compound.get(key);
|
|
||||||
if (child == null) continue;
|
|
||||||
var value = convertFromTag(child, depth + 1, maxDepth);
|
|
||||||
if (value == null) continue;
|
|
||||||
map.put(key, value);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
if (tag instanceof NumericTag numeric)
|
|
||||||
return numeric.getAsNumber();
|
|
||||||
return tag.getAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deserializeTile(KMap<String, Object> map, Location pos) {
|
|
||||||
net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64);
|
|
||||||
var level = ((CraftWorld) pos.getWorld()).getHandle();
|
|
||||||
var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
|
|
||||||
J.s(() -> merge(level, blockPos, tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) {
|
|
||||||
var blockEntity = level.getBlockEntity(blockPos);
|
|
||||||
if (blockEntity == null) {
|
|
||||||
Iris.warn("[NMS] BlockEntity not found at " + blockPos);
|
|
||||||
var state = level.getBlockState(blockPos);
|
|
||||||
if (!state.hasBlockEntity())
|
|
||||||
return;
|
|
||||||
|
|
||||||
blockEntity = ((EntityBlock) state.getBlock())
|
|
||||||
.newBlockEntity(blockPos, state);
|
|
||||||
}
|
|
||||||
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
|
||||||
accessor.setData(tag.merge(accessor.getData()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
|
||||||
if (object == null || depth > maxDepth) return EndTag.INSTANCE;
|
|
||||||
if (object instanceof Map<?,?> map) {
|
|
||||||
var tag = new net.minecraft.nbt.CompoundTag();
|
|
||||||
for (var i : map.entrySet()) {
|
|
||||||
tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth));
|
|
||||||
}
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
if (object instanceof List<?> list) {
|
|
||||||
var tag = new net.minecraft.nbt.ListTag();
|
|
||||||
for (var i : list) {
|
|
||||||
tag.add(convertToTag(i, depth + 1, maxDepth));
|
|
||||||
}
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
if (object instanceof Byte number) return ByteTag.valueOf(number);
|
|
||||||
if (object instanceof Short number) return ShortTag.valueOf(number);
|
|
||||||
if (object instanceof Integer number) return IntTag.valueOf(number);
|
|
||||||
if (object instanceof Long number) return LongTag.valueOf(number);
|
|
||||||
if (object instanceof Float number) return FloatTag.valueOf(number);
|
|
||||||
if (object instanceof Double number) return DoubleTag.valueOf(number);
|
|
||||||
if (object instanceof String string) return StringTag.valueOf(string);
|
|
||||||
return EndTag.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompoundTag serializeEntity(Entity location) {
|
|
||||||
return null;// TODO:
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Entity deserializeEntity(CompoundTag s, Location newPosition) {
|
|
||||||
return null;// TODO:
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsCustomHeight() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RegistryAccess registry() {
|
|
||||||
return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Registry<net.minecraft.world.level.biome.Biome> getCustomBiomeRegistry() {
|
|
||||||
return registry().registry(Registry.BIOME_REGISTRY).orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Registry<Block> getBlockRegistry() {
|
|
||||||
return registry().registry(Registry.BLOCK_REGISTRY).orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getBiomeBaseFromId(int id) {
|
|
||||||
return getCustomBiomeRegistry().getHolder(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinHeight(World world) {
|
|
||||||
return world.getMinHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsCustomBiomes() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTrueBiomeBaseId(Object biomeBase) {
|
|
||||||
return getCustomBiomeRegistry().getId(((Holder<net.minecraft.world.level.biome.Biome>) biomeBase).value());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getTrueBiomeBase(Location location) {
|
|
||||||
return ((CraftWorld) location.getWorld()).getHandle().getBiome(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTrueBiomeBaseKey(Location location) {
|
|
||||||
return getKeyForBiomeBase(getTrueBiomeBase(location));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getCustomBiomeBaseFor(String mckey) {
|
|
||||||
return getCustomBiomeRegistry().get(new ResourceLocation(mckey));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getCustomBiomeBaseHolderFor(String mckey) {
|
|
||||||
return getCustomBiomeRegistry().getHolder(getTrueBiomeBaseId(getCustomBiomeRegistry().get(new ResourceLocation(mckey)))).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBiomeBaseIdForKey(String key) {
|
|
||||||
return getCustomBiomeRegistry().getId(getCustomBiomeRegistry().get(new ResourceLocation(key)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getKeyForBiomeBase(Object biomeBase) {
|
|
||||||
return getCustomBiomeRegistry().getKey((net.minecraft.world.level.biome.Biome) biomeBase).getPath(); // something, not something:something
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getBiomeBase(World world, Biome biome) {
|
|
||||||
return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle()
|
|
||||||
.registryAccess().registry(Registry.BIOME_REGISTRY).orElse(null), biome);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getBiomeBase(Object registry, Biome biome) {
|
|
||||||
Object v = baseBiomeCache.get(biome);
|
|
||||||
|
|
||||||
if (v != null) {
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
//noinspection unchecked
|
|
||||||
v = CraftBlock.biomeToBiomeBase((Registry<net.minecraft.world.level.biome.Biome>) registry, biome);
|
|
||||||
if (v == null) {
|
|
||||||
// Ok so there is this new biome name called "CUSTOM" in Paper's new releases.
|
|
||||||
// But, this does NOT exist within CraftBukkit which makes it return an error.
|
|
||||||
// So, we will just return the ID that the plains biome returns instead.
|
|
||||||
//noinspection unchecked
|
|
||||||
return CraftBlock.biomeToBiomeBase((Registry<net.minecraft.world.level.biome.Biome>) registry, Biome.PLAINS);
|
|
||||||
}
|
|
||||||
baseBiomeCache.put(biome, v);
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KList<Biome> getBiomes() {
|
|
||||||
return new KList<>(Biome.values()).qdel(Biome.CUSTOM);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isBukkit() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBiomeId(Biome biome) {
|
|
||||||
for (World i : Bukkit.getWorlds()) {
|
|
||||||
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
|
|
||||||
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registry.BIOME_REGISTRY).orElse(null);
|
|
||||||
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return biome.ordinal();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MCAIdMap<net.minecraft.world.level.biome.Biome> getBiomeMapping() {
|
|
||||||
return biomeMapCache.aquire(() -> new MCAIdMap<>() {
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Iterator<net.minecraft.world.level.biome.Biome> iterator() {
|
|
||||||
return getCustomBiomeRegistry().iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getId(net.minecraft.world.level.biome.Biome paramT) {
|
|
||||||
return getCustomBiomeRegistry().getId(paramT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public net.minecraft.world.level.biome.Biome byId(int paramInt) {
|
|
||||||
return (net.minecraft.world.level.biome.Biome) getBiomeBaseFromId(paramInt);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private MCABiomeContainer getBiomeContainerInterface(MCAIdMap<net.minecraft.world.level.biome.Biome> biomeMapping, MCAChunkBiomeContainer<net.minecraft.world.level.biome.Biome> base) {
|
|
||||||
return new MCABiomeContainer() {
|
|
||||||
@Override
|
|
||||||
public int[] getData() {
|
|
||||||
return base.writeBiomes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBiome(int x, int y, int z, int id) {
|
|
||||||
base.setBiome(x, y, z, biomeMapping.byId(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBiome(int x, int y, int z) {
|
|
||||||
return biomeMapping.getId(base.getBiome(x, y, z));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MCABiomeContainer newBiomeContainer(int min, int max) {
|
|
||||||
MCAChunkBiomeContainer<net.minecraft.world.level.biome.Biome> base = new MCAChunkBiomeContainer<>(getBiomeMapping(), min, max);
|
|
||||||
return getBiomeContainerInterface(getBiomeMapping(), base);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MCABiomeContainer newBiomeContainer(int min, int max, int[] data) {
|
|
||||||
MCAChunkBiomeContainer<net.minecraft.world.level.biome.Biome> base = new MCAChunkBiomeContainer<>(getBiomeMapping(), min, max, data);
|
|
||||||
return getBiomeContainerInterface(getBiomeMapping(), base);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int countCustomBiomes() {
|
|
||||||
AtomicInteger a = new AtomicInteger(0);
|
|
||||||
|
|
||||||
getCustomBiomeRegistry().keySet().forEach((i) -> {
|
|
||||||
if (i.getNamespace().equals("minecraft")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.incrementAndGet();
|
|
||||||
Iris.debug("Custom Biome: " + i);
|
|
||||||
});
|
|
||||||
|
|
||||||
return a.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean supportsDataPacks() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBiomes(int cx, int cz, World world, Hunk<Object> biomes) {
|
|
||||||
LevelChunk c = ((CraftWorld) world).getHandle().getChunk(cx, cz);
|
|
||||||
biomes.iterateSync((x, y, z, b) -> c.setBiome(x, y, z, (Holder<net.minecraft.world.level.biome.Biome>) b));
|
|
||||||
c.setUnsaved(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void forceBiomeInto(int x, int y, int z, Object somethingVeryDirty, ChunkGenerator.BiomeGrid chunk) {
|
|
||||||
try {
|
|
||||||
ChunkAccess s = (ChunkAccess) getFieldForBiomeStorage(chunk).get(chunk);
|
|
||||||
Holder<net.minecraft.world.level.biome.Biome> biome = (Holder<net.minecraft.world.level.biome.Biome>) somethingVeryDirty;
|
|
||||||
s.setBiome(x, y, z, biome);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Iris.reportError(e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Field getFieldForBiomeStorage(Object storage) {
|
|
||||||
Field f = biomeStorageCache;
|
|
||||||
|
|
||||||
if (f != null) {
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
f = storage.getClass().getDeclaredField("biome");
|
|
||||||
f.setAccessible(true);
|
|
||||||
return f;
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.reportError(e);
|
|
||||||
e.printStackTrace();
|
|
||||||
Iris.error(storage.getClass().getCanonicalName());
|
|
||||||
}
|
|
||||||
|
|
||||||
biomeStorageCache = f;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MCAPaletteAccess createPalette() {
|
|
||||||
MCAIdMapper<BlockState> registry = registryCache.aquireNasty(() -> {
|
|
||||||
Field cf = net.minecraft.core.IdMapper.class.getDeclaredField("tToId");
|
|
||||||
Field df = net.minecraft.core.IdMapper.class.getDeclaredField("idToT");
|
|
||||||
Field bf = net.minecraft.core.IdMapper.class.getDeclaredField("nextId");
|
|
||||||
cf.setAccessible(true);
|
|
||||||
df.setAccessible(true);
|
|
||||||
bf.setAccessible(true);
|
|
||||||
net.minecraft.core.IdMapper<BlockState> blockData = Block.BLOCK_STATE_REGISTRY;
|
|
||||||
int b = bf.getInt(blockData);
|
|
||||||
Object2IntMap<BlockState> c = (Object2IntMap<BlockState>) cf.get(blockData);
|
|
||||||
List<BlockState> d = (List<BlockState>) df.get(blockData);
|
|
||||||
return new MCAIdMapper<BlockState>(c, d, b);
|
|
||||||
});
|
|
||||||
MCAPalette<BlockState> global = globalCache.aquireNasty(() -> new MCAGlobalPalette<>(registry, ((CraftBlockData) AIR).getState()));
|
|
||||||
MCAPalettedContainer<BlockState> container = new MCAPalettedContainer<>(global, registry,
|
|
||||||
i -> ((CraftBlockData) NBTWorld.getBlockData(i)).getState(),
|
|
||||||
i -> NBTWorld.getCompound(CraftBlockData.fromData(i)),
|
|
||||||
((CraftBlockData) AIR).getState());
|
|
||||||
return new MCAWrappedPalettedContainer<>(container,
|
|
||||||
i -> NBTWorld.getCompound(CraftBlockData.fromData(i)),
|
|
||||||
i -> ((CraftBlockData) NBTWorld.getBlockData(i)).getState());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectBiomesFromMantle(Chunk e, Mantle mantle) {
|
|
||||||
ChunkAccess chunk = ((CraftChunk) e).getHandle();
|
|
||||||
AtomicInteger c = new AtomicInteger();
|
|
||||||
AtomicInteger r = new AtomicInteger();
|
|
||||||
mantle.iterateChunk(e.getX(), e.getZ(), MatterBiomeInject.class, (x, y, z, b) -> {
|
|
||||||
if (b != null) {
|
|
||||||
if (b.isCustom()) {
|
|
||||||
chunk.setBiome(x, y, z, getCustomBiomeRegistry().getHolder(b.getBiomeId()).get());
|
|
||||||
c.getAndIncrement();
|
|
||||||
} else {
|
|
||||||
chunk.setBiome(x, y, z, (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(e.getWorld(), b.getBiome()));
|
|
||||||
r.getAndIncrement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ItemStack applyCustomNbt(ItemStack itemStack, KMap<String, Object> customNbt) throws IllegalArgumentException {
|
|
||||||
if (customNbt != null && !customNbt.isEmpty()) {
|
|
||||||
net.minecraft.world.item.ItemStack s = CraftItemStack.asNMSCopy(itemStack);
|
|
||||||
|
|
||||||
try {
|
|
||||||
net.minecraft.nbt.CompoundTag tag = TagParser.parseTag((new JSONObject(customNbt)).toString());
|
|
||||||
tag.merge(s.getOrCreateTag());
|
|
||||||
s.setTag(tag);
|
|
||||||
} catch (CommandSyntaxException var5) {
|
|
||||||
throw new IllegalArgumentException(var5);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CraftItemStack.asBukkitCopy(s);
|
|
||||||
} else {
|
|
||||||
return itemStack;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void inject(long seed, Engine engine, World world) {
|
|
||||||
var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap;
|
|
||||||
chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) {
|
|
||||||
Field[] fields = EntityType.class.getDeclaredFields();
|
|
||||||
for (Field field : fields) {
|
|
||||||
if (Modifier.isStatic(field.getModifiers()) && field.getType().equals(EntityType.class)) {
|
|
||||||
try {
|
|
||||||
EntityType entityType = (EntityType) field.get(null);
|
|
||||||
if (entityType.getDescriptionId().equals("entity.minecraft." + entity.name().toLowerCase())) {
|
|
||||||
Vector<Float> v1 = new Vector<>();
|
|
||||||
v1.add(entityType.getHeight());
|
|
||||||
entityType.getDimensions();
|
|
||||||
Vector3d box = new Vector3d( entityType.getWidth(), entityType.getHeight(), entityType.getWidth());
|
|
||||||
//System.out.println("Entity Type: " + entityType.getDescriptionId() + ", " + "Height: " + height + ", Width: " + width);
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Iris.error("Unable to get entity dimensions!");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Entity spawnEntity(Location location, org.bukkit.entity.EntityType type, CreatureSpawnEvent.SpawnReason reason) {
|
|
||||||
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Color getBiomeColor(Location location, BiomeColor type) {
|
|
||||||
LevelReader reader = ((CraftWorld) location.getWorld()).getHandle();
|
|
||||||
var holder = reader.getBiome(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()));
|
|
||||||
var biome = holder.value();
|
|
||||||
if (biome == null) throw new IllegalArgumentException("Invalid biome: " + holder.unwrapKey().orElse(null));
|
|
||||||
|
|
||||||
int rgba = switch (type) {
|
|
||||||
case FOG -> biome.getFogColor();
|
|
||||||
case WATER -> biome.getWaterColor();
|
|
||||||
case WATER_FOG -> biome.getWaterFogColor();
|
|
||||||
case SKY -> biome.getSkyColor();
|
|
||||||
case FOLIAGE -> biome.getFoliageColor();
|
|
||||||
case GRASS -> biome.getGrassColor(location.getBlockX(), location.getBlockZ());
|
|
||||||
};
|
|
||||||
if (rgba == 0) {
|
|
||||||
if (BiomeColor.FOLIAGE == type && biome.getSpecialEffects().getFoliageColorOverride().isEmpty())
|
|
||||||
return null;
|
|
||||||
if (BiomeColor.GRASS == type && biome.getSpecialEffects().getGrassColorOverride().isEmpty())
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Color(rgba, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KList<String> getStructureKeys() {
|
|
||||||
KList<String> keys = new KList<>();
|
|
||||||
|
|
||||||
var registry = registry().registry(Registry.STRUCTURE_REGISTRY).orElse(null);
|
|
||||||
if (registry == null) return keys;
|
|
||||||
registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add);
|
|
||||||
registry.getTags()
|
|
||||||
.map(Pair::getFirst)
|
|
||||||
.map(TagKey::location)
|
|
||||||
.map(ResourceLocation::toString)
|
|
||||||
.map(s -> "#" + s)
|
|
||||||
.forEach(keys::add);
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
|
||||||
try {
|
|
||||||
for (Field f : clazz.getDeclaredFields()) {
|
|
||||||
if (f.getType().equals(fieldType))
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
throw new NoSuchFieldException(fieldType.getName());
|
|
||||||
} catch (NoSuchFieldException var4) {
|
|
||||||
Class<?> superClass = clazz.getSuperclass();
|
|
||||||
if (superClass == null) {
|
|
||||||
throw var4;
|
|
||||||
} else {
|
|
||||||
return getField(superClass, fieldType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
package com.volmit.iris.core.nms.v1_19_R2;
|
|
||||||
|
|
||||||
import com.mojang.serialization.Codec;
|
|
||||||
import com.volmit.iris.Iris;
|
|
||||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
|
||||||
import com.volmit.iris.engine.object.IrisBiome;
|
|
||||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
|
||||||
import com.volmit.iris.util.collection.KMap;
|
|
||||||
import com.volmit.iris.util.math.RNG;
|
|
||||||
import net.minecraft.core.Holder;
|
|
||||||
import net.minecraft.core.Registry;
|
|
||||||
import net.minecraft.core.RegistryAccess;
|
|
||||||
import net.minecraft.core.registries.Registries;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.world.level.biome.Biome;
|
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.biome.Climate;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.CraftServer;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.CraftWorld;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlock;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class CustomBiomeSource extends BiomeSource {
|
|
||||||
|
|
||||||
private final long seed;
|
|
||||||
private final Engine engine;
|
|
||||||
private final Registry<Biome> biomeCustomRegistry;
|
|
||||||
private final Registry<Biome> biomeRegistry;
|
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
|
||||||
private final RNG rng;
|
|
||||||
private final KMap<String, Holder<Biome>> customBiomes;
|
|
||||||
|
|
||||||
public CustomBiomeSource(long seed, Engine engine, World world) {
|
|
||||||
super(getAllBiomes(
|
|
||||||
((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()))
|
|
||||||
.registry(Registries.BIOME).orElse(null),
|
|
||||||
((CraftWorld) world).getHandle().registryAccess().registry(Registries.BIOME).orElse(null),
|
|
||||||
engine));
|
|
||||||
this.engine = engine;
|
|
||||||
this.seed = seed;
|
|
||||||
this.biomeCustomRegistry = registry().registry(Registries.BIOME).orElse(null);
|
|
||||||
this.biomeRegistry = ((CraftWorld) world).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
|
|
||||||
this.rng = new RNG(engine.getSeedManager().getBiome());
|
|
||||||
this.customBiomes = fillCustomBiomes(biomeCustomRegistry, engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Holder<Biome>> getAllBiomes(Registry<Biome> customRegistry, Registry<Biome> registry, Engine engine) {
|
|
||||||
List<Holder<Biome>> b = new ArrayList<>();
|
|
||||||
|
|
||||||
for (IrisBiome i : engine.getAllBiomes()) {
|
|
||||||
if (i.isCustom()) {
|
|
||||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
|
||||||
b.add(customRegistry.getHolder(customRegistry.getResourceKey(customRegistry
|
|
||||||
.get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.add(CraftBlock.biomeToBiomeBase(registry, i.getVanillaDerivative()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object getFor(Class<?> type, Object source) {
|
|
||||||
Object o = fieldFor(type, source);
|
|
||||||
|
|
||||||
if (o != null) {
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
return invokeFor(type, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object fieldFor(Class<?> returns, Object in) {
|
|
||||||
return fieldForClass(returns, in.getClass(), in);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object invokeFor(Class<?> returns, Object in) {
|
|
||||||
for (Method i : in.getClass().getMethods()) {
|
|
||||||
if (i.getReturnType().equals(returns)) {
|
|
||||||
i.setAccessible(true);
|
|
||||||
try {
|
|
||||||
Iris.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()");
|
|
||||||
return i.invoke(in);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <T> T fieldForClass(Class<T> returnType, Class<?> sourceType, Object in) {
|
|
||||||
for (Field i : sourceType.getDeclaredFields()) {
|
|
||||||
if (i.getType().equals(returnType)) {
|
|
||||||
i.setAccessible(true);
|
|
||||||
try {
|
|
||||||
Iris.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName());
|
|
||||||
return (T) i.get(in);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private KMap<String, Holder<Biome>> fillCustomBiomes(Registry<Biome> customRegistry, Engine engine) {
|
|
||||||
KMap<String, Holder<Biome>> m = new KMap<>();
|
|
||||||
|
|
||||||
for (IrisBiome i : engine.getAllBiomes()) {
|
|
||||||
if (i.isCustom()) {
|
|
||||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
|
||||||
m.put(j.getId(), customRegistry.getHolder(customRegistry.getResourceKey(customRegistry
|
|
||||||
.get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RegistryAccess registry() {
|
|
||||||
return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Codec<? extends BiomeSource> codec() {
|
|
||||||
throw new UnsupportedOperationException("Not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
|
||||||
int m = (y - engine.getMinHeight()) << 2;
|
|
||||||
IrisBiome ib = engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2);
|
|
||||||
if (ib.isCustom()) {
|
|
||||||
return customBiomes.get(ib.getCustomBiome(rng, x << 2, m, z << 2).getId());
|
|
||||||
} else {
|
|
||||||
org.bukkit.block.Biome v = ib.getSkyBiome(rng, x << 2, m, z << 2);
|
|
||||||
return CraftBlock.biomeToBiomeBase(biomeRegistry, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
package com.volmit.iris.core.nms.v1_19_R2;
|
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
|
||||||
import com.mojang.serialization.Codec;
|
|
||||||
import com.volmit.iris.Iris;
|
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
|
||||||
import com.volmit.iris.engine.framework.ResultLocator;
|
|
||||||
import com.volmit.iris.engine.framework.WrongEngineBroException;
|
|
||||||
import com.volmit.iris.engine.object.IrisJigsawStructure;
|
|
||||||
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
|
|
||||||
import com.volmit.iris.util.collection.KList;
|
|
||||||
import com.volmit.iris.util.collection.KMap;
|
|
||||||
import com.volmit.iris.util.collection.KSet;
|
|
||||||
import com.volmit.iris.util.mantle.MantleFlag;
|
|
||||||
import com.volmit.iris.util.math.Position2;
|
|
||||||
import com.volmit.iris.util.reflect.WrappedField;
|
|
||||||
import net.minecraft.core.BlockPos;
|
|
||||||
import net.minecraft.core.Holder;
|
|
||||||
import net.minecraft.core.HolderSet;
|
|
||||||
import net.minecraft.core.RegistryAccess;
|
|
||||||
import net.minecraft.core.registries.Registries;
|
|
||||||
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.TagKey;
|
|
||||||
import net.minecraft.util.random.WeightedRandomList;
|
|
||||||
import net.minecraft.world.entity.MobCategory;
|
|
||||||
import net.minecraft.world.level.LevelHeightAccessor;
|
|
||||||
import net.minecraft.world.level.NoiseColumn;
|
|
||||||
import net.minecraft.world.level.StructureManager;
|
|
||||||
import net.minecraft.world.level.WorldGenLevel;
|
|
||||||
import net.minecraft.world.level.biome.Biome;
|
|
||||||
import net.minecraft.world.level.biome.BiomeManager;
|
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.biome.MobSpawnSettings;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
|
||||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
|
||||||
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.Structure;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.CraftWorld;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.generator.CustomChunkGenerator;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
public class IrisChunkGenerator extends CustomChunkGenerator {
|
|
||||||
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
|
|
||||||
private final ChunkGenerator delegate;
|
|
||||||
private final Engine engine;
|
|
||||||
private final KMap<ResourceKey<Structure>, KSet<String>> structures = new KMap<>();
|
|
||||||
|
|
||||||
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
|
|
||||||
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
|
|
||||||
this.delegate = delegate;
|
|
||||||
this.engine = engine;
|
|
||||||
var dimension = engine.getDimension();
|
|
||||||
|
|
||||||
KSet<IrisJigsawStructure> placements = new KSet<>();
|
|
||||||
addAll(dimension.getJigsawStructures(), placements);
|
|
||||||
for (var region : dimension.getAllRegions(engine)) {
|
|
||||||
addAll(region.getJigsawStructures(), placements);
|
|
||||||
for (var biome : region.getAllBiomes(engine))
|
|
||||||
addAll(biome.getJigsawStructures(), placements);
|
|
||||||
}
|
|
||||||
var stronghold = dimension.getStronghold();
|
|
||||||
if (stronghold != null)
|
|
||||||
placements.add(engine.getData().getJigsawStructureLoader().load(stronghold));
|
|
||||||
placements.removeIf(Objects::isNull);
|
|
||||||
|
|
||||||
var registry = ((CraftWorld) world).getHandle().registryAccess().registry(Registries.STRUCTURE).orElseThrow();
|
|
||||||
for (var s : placements) {
|
|
||||||
try {
|
|
||||||
String raw = s.getStructureKey();
|
|
||||||
if (raw == null) continue;
|
|
||||||
boolean tag = raw.startsWith("#");
|
|
||||||
if (tag) raw = raw.substring(1);
|
|
||||||
|
|
||||||
var location = new ResourceLocation(raw);
|
|
||||||
if (!tag) {
|
|
||||||
structures.computeIfAbsent(ResourceKey.create(Registries.STRUCTURE, location), k -> new KSet<>()).add(s.getLoadKey());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = TagKey.create(Registries.STRUCTURE, location);
|
|
||||||
var set = registry.getTag(key).orElse(null);
|
|
||||||
if (set == null) {
|
|
||||||
Iris.error("Could not find structure tag: " + raw);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (var holder : set) {
|
|
||||||
var resourceKey = holder.unwrapKey().orElse(null);
|
|
||||||
if (resourceKey == null) continue;
|
|
||||||
structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey());
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.error("Failed to load structure: " + s.getLoadKey());
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAll(KList<IrisJigsawStructurePlacement> placements, KSet<IrisJigsawStructure> structures) {
|
|
||||||
if (placements == null) return;
|
|
||||||
placements.stream()
|
|
||||||
.map(IrisJigsawStructurePlacement::getStructure)
|
|
||||||
.map(engine.getData().getJigsawStructureLoader()::load)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.forEach(structures::add);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
|
||||||
if (engine.getDimension().isDisableExplorerMaps())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
KMap<String, Holder<Structure>> structures = new KMap<>();
|
|
||||||
for (var holder : holders) {
|
|
||||||
if (holder == null) continue;
|
|
||||||
var key = holder.unwrapKey().orElse(null);
|
|
||||||
var set = this.structures.get(key);
|
|
||||||
if (set == null) continue;
|
|
||||||
for (var structure : set) {
|
|
||||||
structures.put(structure, holder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (structures.isEmpty())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var locator = ResultLocator.locateStructure(structures.keySet())
|
|
||||||
.then((e, p , s) -> structures.get(s.getLoadKey()));
|
|
||||||
if (findUnexplored)
|
|
||||||
locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s);
|
|
||||||
|
|
||||||
try {
|
|
||||||
var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get();
|
|
||||||
if (result == null) return null;
|
|
||||||
var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ());
|
|
||||||
return Pair.of(blockPos, result.obj());
|
|
||||||
} catch (WrongEngineBroException | ExecutionException | InterruptedException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Codec<? extends ChunkGenerator> codec() {
|
|
||||||
return Codec.unit(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChunkGenerator getDelegate() {
|
|
||||||
if (delegate instanceof CustomChunkGenerator chunkGenerator)
|
|
||||||
return chunkGenerator.getDelegate();
|
|
||||||
return delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinY() {
|
|
||||||
return delegate.getMinY();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSeaLevel() {
|
|
||||||
return delegate.getSeaLevel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) {
|
|
||||||
delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) {
|
|
||||||
delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess, GenerationStep.Carving worldgenstage_features) {
|
|
||||||
delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess, worldgenstage_features);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<ChunkAccess> fillFromNoise(Executor executor, Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) {
|
|
||||||
return delegate.fillFromNoise(executor, blender, randomstate, structuremanager, ichunkaccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
|
|
||||||
return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WeightedRandomList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
|
|
||||||
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
|
|
||||||
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addDebugScreenInfo(List<String> list, RandomState randomstate, BlockPos blockposition) {
|
|
||||||
delegate.addDebugScreenInfo(list, randomstate, blockposition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) {
|
|
||||||
delegate.spawnOriginalMobs(regionlimitedworldaccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) {
|
|
||||||
return delegate.getSpawnHeight(levelheightaccessor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getGenDepth() {
|
|
||||||
return delegate.getGenDepth();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
|
|
||||||
return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate);
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
Field biomeSource = null;
|
|
||||||
for (Field field : ChunkGenerator.class.getDeclaredFields()) {
|
|
||||||
if (!field.getType().equals(BiomeSource.class))
|
|
||||||
continue;
|
|
||||||
biomeSource = field;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (biomeSource == null)
|
|
||||||
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
|
|
||||||
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {
|
|
||||||
try {
|
|
||||||
BIOME_SOURCE.set(generator, source);
|
|
||||||
if (generator instanceof CustomChunkGenerator custom)
|
|
||||||
BIOME_SOURCE.set(custom.getDelegate(), source);
|
|
||||||
|
|
||||||
return generator;
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,628 +0,0 @@
|
|||||||
package com.volmit.iris.core.nms.v1_19_R2;
|
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Vector;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
|
||||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
|
||||||
import com.volmit.iris.util.scheduling.J;
|
|
||||||
import net.minecraft.nbt.*;
|
|
||||||
import net.minecraft.nbt.Tag;
|
|
||||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
|
||||||
import net.minecraft.tags.TagKey;
|
|
||||||
import net.minecraft.world.level.LevelReader;
|
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
|
||||||
import org.bukkit.*;
|
|
||||||
import org.bukkit.block.Biome;
|
|
||||||
import org.bukkit.block.data.BlockData;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.CraftChunk;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.CraftServer;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.CraftWorld;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlock;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlockState;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlockStates;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.block.data.CraftBlockData;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftDolphin;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack;
|
|
||||||
import org.bukkit.entity.Dolphin;
|
|
||||||
import org.bukkit.entity.Entity;
|
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
|
||||||
import com.volmit.iris.Iris;
|
|
||||||
import com.volmit.iris.core.nms.INMSBinding;
|
|
||||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
|
||||||
import com.volmit.iris.util.collection.KList;
|
|
||||||
import com.volmit.iris.util.collection.KMap;
|
|
||||||
import com.volmit.iris.util.hunk.Hunk;
|
|
||||||
import com.volmit.iris.util.json.JSONObject;
|
|
||||||
import com.volmit.iris.util.mantle.Mantle;
|
|
||||||
import com.volmit.iris.util.math.Vector3d;
|
|
||||||
import com.volmit.iris.util.matter.MatterBiomeInject;
|
|
||||||
import com.volmit.iris.util.nbt.io.NBTUtil;
|
|
||||||
import com.volmit.iris.util.nbt.mca.NBTWorld;
|
|
||||||
import com.volmit.iris.util.nbt.mca.palette.*;
|
|
||||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|
||||||
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.Registries;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.server.level.ServerLevel;
|
|
||||||
import net.minecraft.world.entity.EntityType;
|
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.block.Block;
|
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
|
||||||
import sun.misc.Unsafe;
|
|
||||||
|
|
||||||
public class NMSBinding implements INMSBinding {
|
|
||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
|
||||||
private final BlockData AIR = Material.AIR.createBlockData();
|
|
||||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
|
||||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
|
||||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
|
||||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
|
||||||
private Field biomeStorageCache = null;
|
|
||||||
|
|
||||||
private static Object getFor(Class<?> type, Object source) {
|
|
||||||
Object o = fieldFor(type, source);
|
|
||||||
|
|
||||||
if (o != null) {
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
return invokeFor(type, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object invokeFor(Class<?> returns, Object in) {
|
|
||||||
for (Method i : in.getClass().getMethods()) {
|
|
||||||
if (i.getReturnType().equals(returns)) {
|
|
||||||
i.setAccessible(true);
|
|
||||||
try {
|
|
||||||
Iris.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()");
|
|
||||||
return i.invoke(in);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object fieldFor(Class<?> returns, Object in) {
|
|
||||||
return fieldForClass(returns, in.getClass(), in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <T> T fieldForClass(Class<T> returnType, Class<?> sourceType, Object in) {
|
|
||||||
for (Field i : sourceType.getDeclaredFields()) {
|
|
||||||
if (i.getType().equals(returnType)) {
|
|
||||||
i.setAccessible(true);
|
|
||||||
try {
|
|
||||||
Iris.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName());
|
|
||||||
return (T) i.get(in);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<?> getClassType(Class<?> type, int ordinal) {
|
|
||||||
return type.getDeclaredClasses()[ordinal];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasTile(Material material) {
|
|
||||||
return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasTile(Location l) {
|
|
||||||
return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public KMap<String, Object> serializeTile(Location location) {
|
|
||||||
BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false);
|
|
||||||
|
|
||||||
if (e == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata();
|
|
||||||
return (KMap<String, Object>) convertFromTag(tag, 0, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Contract(value = "null, _, _ -> null", pure = true)
|
|
||||||
private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) {
|
|
||||||
if (tag == null || depth > maxDepth) return null;
|
|
||||||
if (tag instanceof CollectionTag<?> collection) {
|
|
||||||
KList<Object> list = new KList<>();
|
|
||||||
|
|
||||||
for (Object i : collection) {
|
|
||||||
if (i instanceof net.minecraft.nbt.Tag t)
|
|
||||||
list.add(convertFromTag(t, depth + 1, maxDepth));
|
|
||||||
else list.add(i);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
if (tag instanceof net.minecraft.nbt.CompoundTag compound) {
|
|
||||||
KMap<String, Object> map = new KMap<>();
|
|
||||||
|
|
||||||
for (String key : compound.getAllKeys()) {
|
|
||||||
var child = compound.get(key);
|
|
||||||
if (child == null) continue;
|
|
||||||
var value = convertFromTag(child, depth + 1, maxDepth);
|
|
||||||
if (value == null) continue;
|
|
||||||
map.put(key, value);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
if (tag instanceof NumericTag numeric)
|
|
||||||
return numeric.getAsNumber();
|
|
||||||
return tag.getAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deserializeTile(KMap<String, Object> map, Location pos) {
|
|
||||||
net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64);
|
|
||||||
var level = ((CraftWorld) pos.getWorld()).getHandle();
|
|
||||||
var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
|
|
||||||
J.s(() -> merge(level, blockPos, tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) {
|
|
||||||
var blockEntity = level.getBlockEntity(blockPos);
|
|
||||||
if (blockEntity == null) {
|
|
||||||
Iris.warn("[NMS] BlockEntity not found at " + blockPos);
|
|
||||||
var state = level.getBlockState(blockPos);
|
|
||||||
if (!state.hasBlockEntity())
|
|
||||||
return;
|
|
||||||
|
|
||||||
blockEntity = ((EntityBlock) state.getBlock())
|
|
||||||
.newBlockEntity(blockPos, state);
|
|
||||||
}
|
|
||||||
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
|
||||||
accessor.setData(tag.merge(accessor.getData()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
|
||||||
if (object == null || depth > maxDepth) return EndTag.INSTANCE;
|
|
||||||
if (object instanceof Map<?,?> map) {
|
|
||||||
var tag = new net.minecraft.nbt.CompoundTag();
|
|
||||||
for (var i : map.entrySet()) {
|
|
||||||
tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth));
|
|
||||||
}
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
if (object instanceof List<?> list) {
|
|
||||||
var tag = new net.minecraft.nbt.ListTag();
|
|
||||||
for (var i : list) {
|
|
||||||
tag.add(convertToTag(i, depth + 1, maxDepth));
|
|
||||||
}
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
if (object instanceof Byte number) return ByteTag.valueOf(number);
|
|
||||||
if (object instanceof Short number) return ShortTag.valueOf(number);
|
|
||||||
if (object instanceof Integer number) return IntTag.valueOf(number);
|
|
||||||
if (object instanceof Long number) return LongTag.valueOf(number);
|
|
||||||
if (object instanceof Float number) return FloatTag.valueOf(number);
|
|
||||||
if (object instanceof Double number) return DoubleTag.valueOf(number);
|
|
||||||
if (object instanceof String string) return StringTag.valueOf(string);
|
|
||||||
return EndTag.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompoundTag serializeEntity(Entity location) {
|
|
||||||
return null;// TODO:
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Entity deserializeEntity(CompoundTag s, Location newPosition) {
|
|
||||||
return null;// TODO:
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsCustomHeight() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RegistryAccess registry() {
|
|
||||||
return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Registry<net.minecraft.world.level.biome.Biome> getCustomBiomeRegistry() {
|
|
||||||
return registry().registry(Registries.BIOME).orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Registry<Block> getBlockRegistry() {
|
|
||||||
return registry().registry(Registries.BLOCK).orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getBiomeBaseFromId(int id) {
|
|
||||||
return getCustomBiomeRegistry().getHolder(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinHeight(World world) {
|
|
||||||
return world.getMinHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsCustomBiomes() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTrueBiomeBaseId(Object biomeBase) {
|
|
||||||
return getCustomBiomeRegistry().getId(((Holder<net.minecraft.world.level.biome.Biome>) biomeBase).value());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getTrueBiomeBase(Location location) {
|
|
||||||
return ((CraftWorld) location.getWorld()).getHandle().getBiome(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTrueBiomeBaseKey(Location location) {
|
|
||||||
return getKeyForBiomeBase(getTrueBiomeBase(location));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getCustomBiomeBaseFor(String mckey) {
|
|
||||||
return getCustomBiomeRegistry().get(new ResourceLocation(mckey));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getCustomBiomeBaseHolderFor(String mckey) {
|
|
||||||
return getCustomBiomeRegistry().getHolder(getTrueBiomeBaseId(getCustomBiomeRegistry().get(new ResourceLocation(mckey)))).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBiomeBaseIdForKey(String key) {
|
|
||||||
return getCustomBiomeRegistry().getId(getCustomBiomeRegistry().get(new ResourceLocation(key)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getKeyForBiomeBase(Object biomeBase) {
|
|
||||||
return getCustomBiomeRegistry().getKey((net.minecraft.world.level.biome.Biome) biomeBase).getPath(); // something, not something:something
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getBiomeBase(World world, Biome biome) {
|
|
||||||
return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle()
|
|
||||||
.registryAccess().registry(Registries.BIOME).orElse(null), biome);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getBiomeBase(Object registry, Biome biome) {
|
|
||||||
Object v = baseBiomeCache.get(biome);
|
|
||||||
|
|
||||||
if (v != null) {
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
//noinspection unchecked
|
|
||||||
v = CraftBlock.biomeToBiomeBase((Registry<net.minecraft.world.level.biome.Biome>) registry, biome);
|
|
||||||
if (v == null) {
|
|
||||||
// Ok so there is this new biome name called "CUSTOM" in Paper's new releases.
|
|
||||||
// But, this does NOT exist within CraftBukkit which makes it return an error.
|
|
||||||
// So, we will just return the ID that the plains biome returns instead.
|
|
||||||
//noinspection unchecked
|
|
||||||
return CraftBlock.biomeToBiomeBase((Registry<net.minecraft.world.level.biome.Biome>) registry, Biome.PLAINS);
|
|
||||||
}
|
|
||||||
baseBiomeCache.put(biome, v);
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KList<Biome> getBiomes() {
|
|
||||||
return new KList<>(Biome.values()).qdel(Biome.CUSTOM);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isBukkit() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBiomeId(Biome biome) {
|
|
||||||
for (World i : Bukkit.getWorlds()) {
|
|
||||||
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
|
|
||||||
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
|
|
||||||
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return biome.ordinal();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MCAIdMap<net.minecraft.world.level.biome.Biome> getBiomeMapping() {
|
|
||||||
return biomeMapCache.aquire(() -> new MCAIdMap<>() {
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Iterator<net.minecraft.world.level.biome.Biome> iterator() {
|
|
||||||
return getCustomBiomeRegistry().iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getId(net.minecraft.world.level.biome.Biome paramT) {
|
|
||||||
return getCustomBiomeRegistry().getId(paramT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public net.minecraft.world.level.biome.Biome byId(int paramInt) {
|
|
||||||
return (net.minecraft.world.level.biome.Biome) getBiomeBaseFromId(paramInt);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private MCABiomeContainer getBiomeContainerInterface(MCAIdMap<net.minecraft.world.level.biome.Biome> biomeMapping, MCAChunkBiomeContainer<net.minecraft.world.level.biome.Biome> base) {
|
|
||||||
return new MCABiomeContainer() {
|
|
||||||
@Override
|
|
||||||
public int[] getData() {
|
|
||||||
return base.writeBiomes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBiome(int x, int y, int z, int id) {
|
|
||||||
base.setBiome(x, y, z, biomeMapping.byId(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBiome(int x, int y, int z) {
|
|
||||||
return biomeMapping.getId(base.getBiome(x, y, z));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MCABiomeContainer newBiomeContainer(int min, int max) {
|
|
||||||
MCAChunkBiomeContainer<net.minecraft.world.level.biome.Biome> base = new MCAChunkBiomeContainer<>(getBiomeMapping(), min, max);
|
|
||||||
return getBiomeContainerInterface(getBiomeMapping(), base);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MCABiomeContainer newBiomeContainer(int min, int max, int[] data) {
|
|
||||||
MCAChunkBiomeContainer<net.minecraft.world.level.biome.Biome> base = new MCAChunkBiomeContainer<>(getBiomeMapping(), min, max, data);
|
|
||||||
return getBiomeContainerInterface(getBiomeMapping(), base);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int countCustomBiomes() {
|
|
||||||
AtomicInteger a = new AtomicInteger(0);
|
|
||||||
|
|
||||||
getCustomBiomeRegistry().keySet().forEach((i) -> {
|
|
||||||
if (i.getNamespace().equals("minecraft")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.incrementAndGet();
|
|
||||||
Iris.debug("Custom Biome: " + i);
|
|
||||||
});
|
|
||||||
|
|
||||||
return a.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean supportsDataPacks() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBiomes(int cx, int cz, World world, Hunk<Object> biomes) {
|
|
||||||
LevelChunk c = ((CraftWorld) world).getHandle().getChunk(cx, cz);
|
|
||||||
biomes.iterateSync((x, y, z, b) -> c.setBiome(x, y, z, (Holder<net.minecraft.world.level.biome.Biome>) b));
|
|
||||||
c.setUnsaved(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void forceBiomeInto(int x, int y, int z, Object somethingVeryDirty, ChunkGenerator.BiomeGrid chunk) {
|
|
||||||
try {
|
|
||||||
ChunkAccess s = (ChunkAccess) getFieldForBiomeStorage(chunk).get(chunk);
|
|
||||||
Holder<net.minecraft.world.level.biome.Biome> biome = (Holder<net.minecraft.world.level.biome.Biome>) somethingVeryDirty;
|
|
||||||
s.setBiome(x, y, z, biome);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Iris.reportError(e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Field getFieldForBiomeStorage(Object storage) {
|
|
||||||
Field f = biomeStorageCache;
|
|
||||||
|
|
||||||
if (f != null) {
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
f = storage.getClass().getDeclaredField("biome");
|
|
||||||
f.setAccessible(true);
|
|
||||||
return f;
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.reportError(e);
|
|
||||||
e.printStackTrace();
|
|
||||||
Iris.error(storage.getClass().getCanonicalName());
|
|
||||||
}
|
|
||||||
|
|
||||||
biomeStorageCache = f;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MCAPaletteAccess createPalette() {
|
|
||||||
MCAIdMapper<BlockState> registry = registryCache.aquireNasty(() -> {
|
|
||||||
Field cf = net.minecraft.core.IdMapper.class.getDeclaredField("tToId");
|
|
||||||
Field df = net.minecraft.core.IdMapper.class.getDeclaredField("idToT");
|
|
||||||
Field bf = net.minecraft.core.IdMapper.class.getDeclaredField("nextId");
|
|
||||||
cf.setAccessible(true);
|
|
||||||
df.setAccessible(true);
|
|
||||||
bf.setAccessible(true);
|
|
||||||
net.minecraft.core.IdMapper<BlockState> blockData = Block.BLOCK_STATE_REGISTRY;
|
|
||||||
int b = bf.getInt(blockData);
|
|
||||||
Object2IntMap<BlockState> c = (Object2IntMap<BlockState>) cf.get(blockData);
|
|
||||||
List<BlockState> d = (List<BlockState>) df.get(blockData);
|
|
||||||
return new MCAIdMapper<BlockState>(c, d, b);
|
|
||||||
});
|
|
||||||
MCAPalette<BlockState> global = globalCache.aquireNasty(() -> new MCAGlobalPalette<>(registry, ((CraftBlockData) AIR).getState()));
|
|
||||||
MCAPalettedContainer<BlockState> container = new MCAPalettedContainer<>(global, registry,
|
|
||||||
i -> ((CraftBlockData) NBTWorld.getBlockData(i)).getState(),
|
|
||||||
i -> NBTWorld.getCompound(CraftBlockData.fromData(i)),
|
|
||||||
((CraftBlockData) AIR).getState());
|
|
||||||
return new MCAWrappedPalettedContainer<>(container,
|
|
||||||
i -> NBTWorld.getCompound(CraftBlockData.fromData(i)),
|
|
||||||
i -> ((CraftBlockData) NBTWorld.getBlockData(i)).getState());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectBiomesFromMantle(Chunk e, Mantle mantle) {
|
|
||||||
ChunkAccess chunk = ((CraftChunk) e).getHandle();
|
|
||||||
AtomicInteger c = new AtomicInteger();
|
|
||||||
AtomicInteger r = new AtomicInteger();
|
|
||||||
mantle.iterateChunk(e.getX(), e.getZ(), MatterBiomeInject.class, (x, y, z, b) -> {
|
|
||||||
if (b != null) {
|
|
||||||
if (b.isCustom()) {
|
|
||||||
chunk.setBiome(x, y, z, getCustomBiomeRegistry().getHolder(b.getBiomeId()).get());
|
|
||||||
c.getAndIncrement();
|
|
||||||
} else {
|
|
||||||
chunk.setBiome(x, y, z, (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(e.getWorld(), b.getBiome()));
|
|
||||||
r.getAndIncrement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ItemStack applyCustomNbt(ItemStack itemStack, KMap<String, Object> customNbt) throws IllegalArgumentException {
|
|
||||||
if (customNbt != null && !customNbt.isEmpty()) {
|
|
||||||
net.minecraft.world.item.ItemStack s = CraftItemStack.asNMSCopy(itemStack);
|
|
||||||
|
|
||||||
try {
|
|
||||||
net.minecraft.nbt.CompoundTag tag = TagParser.parseTag((new JSONObject(customNbt)).toString());
|
|
||||||
tag.merge(s.getOrCreateTag());
|
|
||||||
s.setTag(tag);
|
|
||||||
} catch (CommandSyntaxException var5) {
|
|
||||||
throw new IllegalArgumentException(var5);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CraftItemStack.asBukkitCopy(s);
|
|
||||||
} else {
|
|
||||||
return itemStack;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void inject(long seed, Engine engine, World world) {
|
|
||||||
var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap;
|
|
||||||
chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) {
|
|
||||||
Field[] fields = EntityType.class.getDeclaredFields();
|
|
||||||
for (Field field : fields) {
|
|
||||||
if (Modifier.isStatic(field.getModifiers()) && field.getType().equals(EntityType.class)) {
|
|
||||||
try {
|
|
||||||
EntityType entityType = (EntityType) field.get(null);
|
|
||||||
if (entityType.getDescriptionId().equals("entity.minecraft." + entity.name().toLowerCase())) {
|
|
||||||
Vector<Float> v1 = new Vector<>();
|
|
||||||
v1.add(entityType.getHeight());
|
|
||||||
entityType.getDimensions();
|
|
||||||
Vector3d box = new Vector3d( entityType.getWidth(), entityType.getHeight(), entityType.getWidth());
|
|
||||||
//System.out.println("Entity Type: " + entityType.getDescriptionId() + ", " + "Height: " + height + ", Width: " + width);
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Iris.error("Unable to get entity dimensions!");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Entity spawnEntity(Location location, org.bukkit.entity.EntityType type, CreatureSpawnEvent.SpawnReason reason) {
|
|
||||||
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Color getBiomeColor(Location location, BiomeColor type) {
|
|
||||||
LevelReader reader = ((CraftWorld) location.getWorld()).getHandle();
|
|
||||||
var holder = reader.getBiome(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()));
|
|
||||||
var biome = holder.value();
|
|
||||||
if (biome == null) throw new IllegalArgumentException("Invalid biome: " + holder.unwrapKey().orElse(null));
|
|
||||||
|
|
||||||
int rgba = switch (type) {
|
|
||||||
case FOG -> biome.getFogColor();
|
|
||||||
case WATER -> biome.getWaterColor();
|
|
||||||
case WATER_FOG -> biome.getWaterFogColor();
|
|
||||||
case SKY -> biome.getSkyColor();
|
|
||||||
case FOLIAGE -> biome.getFoliageColor();
|
|
||||||
case GRASS -> biome.getGrassColor(location.getBlockX(), location.getBlockZ());
|
|
||||||
};
|
|
||||||
if (rgba == 0) {
|
|
||||||
if (BiomeColor.FOLIAGE == type && biome.getSpecialEffects().getFoliageColorOverride().isEmpty())
|
|
||||||
return null;
|
|
||||||
if (BiomeColor.GRASS == type && biome.getSpecialEffects().getGrassColorOverride().isEmpty())
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Color(rgba, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KList<String> getStructureKeys() {
|
|
||||||
KList<String> keys = new KList<>();
|
|
||||||
|
|
||||||
var registry = registry().registry(Registries.STRUCTURE).orElse(null);
|
|
||||||
if (registry == null) return keys;
|
|
||||||
registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add);
|
|
||||||
registry.getTags()
|
|
||||||
.map(Pair::getFirst)
|
|
||||||
.map(TagKey::location)
|
|
||||||
.map(ResourceLocation::toString)
|
|
||||||
.map(s -> "#" + s)
|
|
||||||
.forEach(keys::add);
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
|
||||||
try {
|
|
||||||
for (Field f : clazz.getDeclaredFields()) {
|
|
||||||
if (f.getType().equals(fieldType))
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
throw new NoSuchFieldException(fieldType.getName());
|
|
||||||
} catch (NoSuchFieldException var4) {
|
|
||||||
Class<?> superClass = clazz.getSuperclass();
|
|
||||||
if (superClass == null) {
|
|
||||||
throw var4;
|
|
||||||
} else {
|
|
||||||
return getField(superClass, fieldType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
package com.volmit.iris.core.nms.v1_19_R3;
|
|
||||||
|
|
||||||
import com.mojang.serialization.Codec;
|
|
||||||
import com.volmit.iris.Iris;
|
|
||||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
|
||||||
import com.volmit.iris.engine.object.IrisBiome;
|
|
||||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
|
||||||
import com.volmit.iris.util.collection.KMap;
|
|
||||||
import com.volmit.iris.util.math.RNG;
|
|
||||||
import net.minecraft.core.Holder;
|
|
||||||
import net.minecraft.core.Registry;
|
|
||||||
import net.minecraft.core.RegistryAccess;
|
|
||||||
import net.minecraft.core.registries.Registries;
|
|
||||||
import net.minecraft.resources.ResourceKey;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.world.level.biome.Biome;
|
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.biome.Climate;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.CraftServer;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlock;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public class CustomBiomeSource extends BiomeSource {
|
|
||||||
|
|
||||||
private final long seed;
|
|
||||||
private final Engine engine;
|
|
||||||
private final Registry<Biome> biomeCustomRegistry;
|
|
||||||
private final Registry<Biome> biomeRegistry;
|
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
|
||||||
private final RNG rng;
|
|
||||||
private final KMap<String, Holder<Biome>> customBiomes;
|
|
||||||
|
|
||||||
public CustomBiomeSource(long seed, Engine engine, World world) {
|
|
||||||
this.engine = engine;
|
|
||||||
this.seed = seed;
|
|
||||||
this.biomeCustomRegistry = registry().registry(Registries.BIOME).orElse(null);
|
|
||||||
this.biomeRegistry = ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())).registry(Registries.BIOME).orElse(null);
|
|
||||||
this.rng = new RNG(engine.getSeedManager().getBiome());
|
|
||||||
this.customBiomes = fillCustomBiomes(biomeCustomRegistry, engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Holder<Biome>> getAllBiomes(Registry<Biome> customRegistry, Registry<Biome> registry, Engine engine) {
|
|
||||||
List<Holder<Biome>> b = new ArrayList<>();
|
|
||||||
|
|
||||||
for (IrisBiome i : engine.getAllBiomes()) {
|
|
||||||
if (i.isCustom()) {
|
|
||||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
|
||||||
b.add(customRegistry.getHolder(customRegistry.getResourceKey(customRegistry
|
|
||||||
.get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.add(CraftBlock.biomeToBiomeBase(registry, i.getVanillaDerivative()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object getFor(Class<?> type, Object source) {
|
|
||||||
Object o = fieldFor(type, source);
|
|
||||||
|
|
||||||
if (o != null) {
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
return invokeFor(type, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object fieldFor(Class<?> returns, Object in) {
|
|
||||||
return fieldForClass(returns, in.getClass(), in);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object invokeFor(Class<?> returns, Object in) {
|
|
||||||
for (Method i : in.getClass().getMethods()) {
|
|
||||||
if (i.getReturnType().equals(returns)) {
|
|
||||||
i.setAccessible(true);
|
|
||||||
try {
|
|
||||||
Iris.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()");
|
|
||||||
return i.invoke(in);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <T> T fieldForClass(Class<T> returnType, Class<?> sourceType, Object in) {
|
|
||||||
for (Field i : sourceType.getDeclaredFields()) {
|
|
||||||
if (i.getType().equals(returnType)) {
|
|
||||||
i.setAccessible(true);
|
|
||||||
try {
|
|
||||||
Iris.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName());
|
|
||||||
return (T) i.get(in);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Stream<Holder<Biome>> collectPossibleBiomes() {
|
|
||||||
return getAllBiomes(
|
|
||||||
((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()))
|
|
||||||
.registry(Registries.BIOME).orElse(null),
|
|
||||||
((CraftWorld) engine.getWorld().realWorld()).getHandle().registryAccess().registry(Registries.BIOME).orElse(null),
|
|
||||||
engine).stream();
|
|
||||||
}
|
|
||||||
private KMap<String, Holder<Biome>> fillCustomBiomes(Registry<Biome> customRegistry, Engine engine) {
|
|
||||||
KMap<String, Holder<Biome>> m = new KMap<>();
|
|
||||||
|
|
||||||
for (IrisBiome i : engine.getAllBiomes()) {
|
|
||||||
if (i.isCustom()) {
|
|
||||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
|
||||||
ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
|
||||||
Biome biome = customRegistry.get(resourceLocation);
|
|
||||||
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
|
|
||||||
if (optionalBiomeKey.isEmpty()) {
|
|
||||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ResourceKey<Biome> biomeKey = optionalBiomeKey.get();
|
|
||||||
Optional<Holder.Reference<Biome>> optionalReferenceHolder = customRegistry.getHolder(biomeKey);
|
|
||||||
if (optionalReferenceHolder.isEmpty()) {
|
|
||||||
Iris.error("Cannot find reference to biome " + biomeKey + " for engine " + engine.getName());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
m.put(j.getId(), optionalReferenceHolder.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RegistryAccess registry() {
|
|
||||||
return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Codec<? extends BiomeSource> codec() {
|
|
||||||
throw new UnsupportedOperationException("Not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
|
||||||
int m = (y - engine.getMinHeight()) << 2;
|
|
||||||
IrisBiome ib = engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2);
|
|
||||||
if (ib.isCustom()) {
|
|
||||||
return customBiomes.get(ib.getCustomBiome(rng, x << 2, m, z << 2).getId());
|
|
||||||
} else {
|
|
||||||
org.bukkit.block.Biome v = ib.getSkyBiome(rng, x << 2, m, z << 2);
|
|
||||||
return CraftBlock.biomeToBiomeBase(biomeRegistry, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
package com.volmit.iris.core.nms.v1_19_R3;
|
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
|
||||||
import com.mojang.serialization.Codec;
|
|
||||||
import com.volmit.iris.Iris;
|
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
|
||||||
import com.volmit.iris.engine.framework.ResultLocator;
|
|
||||||
import com.volmit.iris.engine.framework.WrongEngineBroException;
|
|
||||||
import com.volmit.iris.engine.object.IrisJigsawStructure;
|
|
||||||
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
|
|
||||||
import com.volmit.iris.util.collection.KList;
|
|
||||||
import com.volmit.iris.util.collection.KMap;
|
|
||||||
import com.volmit.iris.util.collection.KSet;
|
|
||||||
import com.volmit.iris.util.mantle.MantleFlag;
|
|
||||||
import com.volmit.iris.util.math.Position2;
|
|
||||||
import com.volmit.iris.util.reflect.WrappedField;
|
|
||||||
import net.minecraft.core.BlockPos;
|
|
||||||
import net.minecraft.core.Holder;
|
|
||||||
import net.minecraft.core.HolderSet;
|
|
||||||
import net.minecraft.core.RegistryAccess;
|
|
||||||
import net.minecraft.core.registries.Registries;
|
|
||||||
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.TagKey;
|
|
||||||
import net.minecraft.util.random.WeightedRandomList;
|
|
||||||
import net.minecraft.world.entity.MobCategory;
|
|
||||||
import net.minecraft.world.level.LevelHeightAccessor;
|
|
||||||
import net.minecraft.world.level.NoiseColumn;
|
|
||||||
import net.minecraft.world.level.StructureManager;
|
|
||||||
import net.minecraft.world.level.WorldGenLevel;
|
|
||||||
import net.minecraft.world.level.biome.Biome;
|
|
||||||
import net.minecraft.world.level.biome.BiomeManager;
|
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.biome.MobSpawnSettings;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
|
||||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
|
||||||
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.Structure;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.generator.CustomChunkGenerator;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
public class IrisChunkGenerator extends CustomChunkGenerator {
|
|
||||||
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
|
|
||||||
private final ChunkGenerator delegate;
|
|
||||||
private final Engine engine;
|
|
||||||
private final KMap<ResourceKey<Structure>, KSet<String>> structures = new KMap<>();
|
|
||||||
|
|
||||||
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
|
|
||||||
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
|
|
||||||
this.delegate = delegate;
|
|
||||||
this.engine = engine;
|
|
||||||
var dimension = engine.getDimension();
|
|
||||||
|
|
||||||
KSet<IrisJigsawStructure> placements = new KSet<>();
|
|
||||||
addAll(dimension.getJigsawStructures(), placements);
|
|
||||||
for (var region : dimension.getAllRegions(engine)) {
|
|
||||||
addAll(region.getJigsawStructures(), placements);
|
|
||||||
for (var biome : region.getAllBiomes(engine))
|
|
||||||
addAll(biome.getJigsawStructures(), placements);
|
|
||||||
}
|
|
||||||
var stronghold = dimension.getStronghold();
|
|
||||||
if (stronghold != null)
|
|
||||||
placements.add(engine.getData().getJigsawStructureLoader().load(stronghold));
|
|
||||||
placements.removeIf(Objects::isNull);
|
|
||||||
|
|
||||||
var registry = ((CraftWorld) world).getHandle().registryAccess().registry(Registries.STRUCTURE).orElseThrow();
|
|
||||||
for (var s : placements) {
|
|
||||||
try {
|
|
||||||
String raw = s.getStructureKey();
|
|
||||||
if (raw == null) continue;
|
|
||||||
boolean tag = raw.startsWith("#");
|
|
||||||
if (tag) raw = raw.substring(1);
|
|
||||||
|
|
||||||
var location = new ResourceLocation(raw);
|
|
||||||
if (!tag) {
|
|
||||||
structures.computeIfAbsent(ResourceKey.create(Registries.STRUCTURE, location), k -> new KSet<>()).add(s.getLoadKey());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = TagKey.create(Registries.STRUCTURE, location);
|
|
||||||
var set = registry.getTag(key).orElse(null);
|
|
||||||
if (set == null) {
|
|
||||||
Iris.error("Could not find structure tag: " + raw);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (var holder : set) {
|
|
||||||
var resourceKey = holder.unwrapKey().orElse(null);
|
|
||||||
if (resourceKey == null) continue;
|
|
||||||
structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey());
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.error("Failed to load structure: " + s.getLoadKey());
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAll(KList<IrisJigsawStructurePlacement> placements, KSet<IrisJigsawStructure> structures) {
|
|
||||||
if (placements == null) return;
|
|
||||||
placements.stream()
|
|
||||||
.map(IrisJigsawStructurePlacement::getStructure)
|
|
||||||
.map(engine.getData().getJigsawStructureLoader()::load)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.forEach(structures::add);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
|
||||||
if (engine.getDimension().isDisableExplorerMaps())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
KMap<String, Holder<Structure>> structures = new KMap<>();
|
|
||||||
for (var holder : holders) {
|
|
||||||
if (holder == null) continue;
|
|
||||||
var key = holder.unwrapKey().orElse(null);
|
|
||||||
var set = this.structures.get(key);
|
|
||||||
if (set == null) continue;
|
|
||||||
for (var structure : set) {
|
|
||||||
structures.put(structure, holder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (structures.isEmpty())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var locator = ResultLocator.locateStructure(structures.keySet())
|
|
||||||
.then((e, p , s) -> structures.get(s.getLoadKey()));
|
|
||||||
if (findUnexplored)
|
|
||||||
locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s);
|
|
||||||
|
|
||||||
try {
|
|
||||||
var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get();
|
|
||||||
if (result == null) return null;
|
|
||||||
var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ());
|
|
||||||
return Pair.of(blockPos, result.obj());
|
|
||||||
} catch (WrongEngineBroException | ExecutionException | InterruptedException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Codec<? extends ChunkGenerator> codec() {
|
|
||||||
return Codec.unit(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChunkGenerator getDelegate() {
|
|
||||||
if (delegate instanceof CustomChunkGenerator chunkGenerator)
|
|
||||||
return chunkGenerator.getDelegate();
|
|
||||||
return delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinY() {
|
|
||||||
return delegate.getMinY();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSeaLevel() {
|
|
||||||
return delegate.getSeaLevel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) {
|
|
||||||
delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) {
|
|
||||||
delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess, GenerationStep.Carving worldgenstage_features) {
|
|
||||||
delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess, worldgenstage_features);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<ChunkAccess> fillFromNoise(Executor executor, Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) {
|
|
||||||
return delegate.fillFromNoise(executor, blender, randomstate, structuremanager, ichunkaccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
|
|
||||||
return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WeightedRandomList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
|
|
||||||
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
|
|
||||||
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addDebugScreenInfo(List<String> list, RandomState randomstate, BlockPos blockposition) {
|
|
||||||
delegate.addDebugScreenInfo(list, randomstate, blockposition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) {
|
|
||||||
delegate.spawnOriginalMobs(regionlimitedworldaccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) {
|
|
||||||
return delegate.getSpawnHeight(levelheightaccessor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getGenDepth() {
|
|
||||||
return delegate.getGenDepth();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
|
|
||||||
return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate);
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
Field biomeSource = null;
|
|
||||||
for (Field field : ChunkGenerator.class.getDeclaredFields()) {
|
|
||||||
if (!field.getType().equals(BiomeSource.class))
|
|
||||||
continue;
|
|
||||||
biomeSource = field;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (biomeSource == null)
|
|
||||||
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
|
|
||||||
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {
|
|
||||||
try {
|
|
||||||
BIOME_SOURCE.set(generator, source);
|
|
||||||
if (generator instanceof CustomChunkGenerator custom)
|
|
||||||
BIOME_SOURCE.set(custom.getDelegate(), source);
|
|
||||||
|
|
||||||
return generator;
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,638 +0,0 @@
|
|||||||
package com.volmit.iris.core.nms.v1_19_R3;
|
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Vector;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
|
||||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
|
||||||
import com.volmit.iris.util.scheduling.J;
|
|
||||||
import net.minecraft.nbt.*;
|
|
||||||
import net.minecraft.nbt.Tag;
|
|
||||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
|
||||||
import net.minecraft.tags.TagKey;
|
|
||||||
import net.minecraft.world.level.LevelReader;
|
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
|
||||||
import org.bukkit.*;
|
|
||||||
import org.bukkit.block.Biome;
|
|
||||||
import org.bukkit.block.data.BlockData;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.CraftChunk;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.CraftServer;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlock;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlockState;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlockStates;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.block.data.CraftBlockData;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftDolphin;
|
|
||||||
import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;
|
|
||||||
import org.bukkit.entity.Dolphin;
|
|
||||||
import org.bukkit.entity.Entity;
|
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
|
||||||
import com.volmit.iris.Iris;
|
|
||||||
import com.volmit.iris.core.nms.INMSBinding;
|
|
||||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
|
||||||
import com.volmit.iris.util.collection.KList;
|
|
||||||
import com.volmit.iris.util.collection.KMap;
|
|
||||||
import com.volmit.iris.util.hunk.Hunk;
|
|
||||||
import com.volmit.iris.util.json.JSONObject;
|
|
||||||
import com.volmit.iris.util.mantle.Mantle;
|
|
||||||
import com.volmit.iris.util.math.Vector3d;
|
|
||||||
import com.volmit.iris.util.matter.MatterBiomeInject;
|
|
||||||
import com.volmit.iris.util.nbt.io.NBTUtil;
|
|
||||||
import com.volmit.iris.util.nbt.mca.NBTWorld;
|
|
||||||
import com.volmit.iris.util.nbt.mca.palette.*;
|
|
||||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|
||||||
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.Registries;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.server.level.ServerLevel;
|
|
||||||
import net.minecraft.world.entity.EntityType;
|
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.block.Block;
|
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
|
||||||
import sun.misc.Unsafe;
|
|
||||||
|
|
||||||
public class NMSBinding implements INMSBinding {
|
|
||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
|
||||||
private final BlockData AIR = Material.AIR.createBlockData();
|
|
||||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
|
||||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
|
||||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
|
||||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
|
||||||
private Field biomeStorageCache = null;
|
|
||||||
|
|
||||||
private static Object getFor(Class<?> type, Object source) {
|
|
||||||
Object o = fieldFor(type, source);
|
|
||||||
|
|
||||||
if (o != null) {
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
return invokeFor(type, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object invokeFor(Class<?> returns, Object in) {
|
|
||||||
for (Method i : in.getClass().getMethods()) {
|
|
||||||
if (i.getReturnType().equals(returns)) {
|
|
||||||
i.setAccessible(true);
|
|
||||||
try {
|
|
||||||
Iris.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()");
|
|
||||||
return i.invoke(in);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object fieldFor(Class<?> returns, Object in) {
|
|
||||||
return fieldForClass(returns, in.getClass(), in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <T> T fieldForClass(Class<T> returnType, Class<?> sourceType, Object in) {
|
|
||||||
for (Field i : sourceType.getDeclaredFields()) {
|
|
||||||
if (i.getType().equals(returnType)) {
|
|
||||||
i.setAccessible(true);
|
|
||||||
try {
|
|
||||||
Iris.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName());
|
|
||||||
return (T) i.get(in);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<?> getClassType(Class<?> type, int ordinal) {
|
|
||||||
return type.getDeclaredClasses()[ordinal];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasTile(Material material) {
|
|
||||||
return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasTile(Location l) {
|
|
||||||
return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public KMap<String, Object> serializeTile(Location location) {
|
|
||||||
BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false);
|
|
||||||
|
|
||||||
if (e == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata();
|
|
||||||
return (KMap<String, Object>) convertFromTag(tag, 0, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Contract(value = "null, _, _ -> null", pure = true)
|
|
||||||
private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) {
|
|
||||||
if (tag == null || depth > maxDepth) return null;
|
|
||||||
if (tag instanceof CollectionTag<?> collection) {
|
|
||||||
KList<Object> list = new KList<>();
|
|
||||||
|
|
||||||
for (Object i : collection) {
|
|
||||||
if (i instanceof net.minecraft.nbt.Tag t)
|
|
||||||
list.add(convertFromTag(t, depth + 1, maxDepth));
|
|
||||||
else list.add(i);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
if (tag instanceof net.minecraft.nbt.CompoundTag compound) {
|
|
||||||
KMap<String, Object> map = new KMap<>();
|
|
||||||
|
|
||||||
for (String key : compound.getAllKeys()) {
|
|
||||||
var child = compound.get(key);
|
|
||||||
if (child == null) continue;
|
|
||||||
var value = convertFromTag(child, depth + 1, maxDepth);
|
|
||||||
if (value == null) continue;
|
|
||||||
map.put(key, value);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
if (tag instanceof NumericTag numeric)
|
|
||||||
return numeric.getAsNumber();
|
|
||||||
return tag.getAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deserializeTile(KMap<String, Object> map, Location pos) {
|
|
||||||
net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64);
|
|
||||||
var level = ((CraftWorld) pos.getWorld()).getHandle();
|
|
||||||
var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
|
|
||||||
J.s(() -> merge(level, blockPos, tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) {
|
|
||||||
var blockEntity = level.getBlockEntity(blockPos);
|
|
||||||
if (blockEntity == null) {
|
|
||||||
Iris.warn("[NMS] BlockEntity not found at " + blockPos);
|
|
||||||
var state = level.getBlockState(blockPos);
|
|
||||||
if (!state.hasBlockEntity())
|
|
||||||
return;
|
|
||||||
|
|
||||||
blockEntity = ((EntityBlock) state.getBlock())
|
|
||||||
.newBlockEntity(blockPos, state);
|
|
||||||
}
|
|
||||||
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
|
||||||
accessor.setData(tag.merge(accessor.getData()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
|
||||||
if (object == null || depth > maxDepth) return EndTag.INSTANCE;
|
|
||||||
if (object instanceof Map<?,?> map) {
|
|
||||||
var tag = new net.minecraft.nbt.CompoundTag();
|
|
||||||
for (var i : map.entrySet()) {
|
|
||||||
tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth));
|
|
||||||
}
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
if (object instanceof List<?> list) {
|
|
||||||
var tag = new net.minecraft.nbt.ListTag();
|
|
||||||
for (var i : list) {
|
|
||||||
tag.add(convertToTag(i, depth + 1, maxDepth));
|
|
||||||
}
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
if (object instanceof Byte number) return ByteTag.valueOf(number);
|
|
||||||
if (object instanceof Short number) return ShortTag.valueOf(number);
|
|
||||||
if (object instanceof Integer number) return IntTag.valueOf(number);
|
|
||||||
if (object instanceof Long number) return LongTag.valueOf(number);
|
|
||||||
if (object instanceof Float number) return FloatTag.valueOf(number);
|
|
||||||
if (object instanceof Double number) return DoubleTag.valueOf(number);
|
|
||||||
if (object instanceof String string) return StringTag.valueOf(string);
|
|
||||||
return EndTag.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompoundTag serializeEntity(Entity location) {
|
|
||||||
return null;// TODO:
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Entity deserializeEntity(CompoundTag s, Location newPosition) {
|
|
||||||
return null;// TODO:
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsCustomHeight() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RegistryAccess registry() {
|
|
||||||
return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Registry<net.minecraft.world.level.biome.Biome> getCustomBiomeRegistry() {
|
|
||||||
return registry().registry(Registries.BIOME).orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Registry<Block> getBlockRegistry() {
|
|
||||||
return registry().registry(Registries.BLOCK).orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getBiomeBaseFromId(int id) {
|
|
||||||
return getCustomBiomeRegistry().getHolder(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinHeight(World world) {
|
|
||||||
return world.getMinHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsCustomBiomes() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTrueBiomeBaseId(Object biomeBase) {
|
|
||||||
return getCustomBiomeRegistry().getId(((Holder<net.minecraft.world.level.biome.Biome>) biomeBase).value());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getTrueBiomeBase(Location location) {
|
|
||||||
return ((CraftWorld) location.getWorld()).getHandle().getBiome(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTrueBiomeBaseKey(Location location) {
|
|
||||||
return getKeyForBiomeBase(getTrueBiomeBase(location));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getCustomBiomeBaseFor(String mckey) {
|
|
||||||
return getCustomBiomeRegistry().get(new ResourceLocation(mckey));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getCustomBiomeBaseHolderFor(String mckey) {
|
|
||||||
return getCustomBiomeRegistry().getHolder(getTrueBiomeBaseId(getCustomBiomeRegistry().get(new ResourceLocation(mckey)))).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBiomeBaseIdForKey(String key) {
|
|
||||||
return getCustomBiomeRegistry().getId(getCustomBiomeRegistry().get(new ResourceLocation(key)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getKeyForBiomeBase(Object biomeBase) {
|
|
||||||
return getCustomBiomeRegistry().getKey((net.minecraft.world.level.biome.Biome) biomeBase).getPath(); // something, not something:something
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getBiomeBase(World world, Biome biome) {
|
|
||||||
return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle()
|
|
||||||
.registryAccess().registry(Registries.BIOME).orElse(null), biome);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getBiomeBase(Object registry, Biome biome) {
|
|
||||||
Object v = baseBiomeCache.get(biome);
|
|
||||||
|
|
||||||
if (v != null) {
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
//noinspection unchecked
|
|
||||||
v = CraftBlock.biomeToBiomeBase((Registry<net.minecraft.world.level.biome.Biome>) registry, biome);
|
|
||||||
if (v == null) {
|
|
||||||
// Ok so there is this new biome name called "CUSTOM" in Paper's new releases.
|
|
||||||
// But, this does NOT exist within CraftBukkit which makes it return an error.
|
|
||||||
// So, we will just return the ID that the plains biome returns instead.
|
|
||||||
//noinspection unchecked
|
|
||||||
return CraftBlock.biomeToBiomeBase((Registry<net.minecraft.world.level.biome.Biome>) registry, Biome.PLAINS);
|
|
||||||
}
|
|
||||||
baseBiomeCache.put(biome, v);
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KList<Biome> getBiomes() {
|
|
||||||
return new KList<>(Biome.values()).qdel(Biome.CHERRY_GROVE).qdel(Biome.CUSTOM);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isBukkit() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBiomeId(Biome biome) {
|
|
||||||
for (World i : Bukkit.getWorlds()) {
|
|
||||||
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
|
|
||||||
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
|
|
||||||
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return biome.ordinal();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MCAIdMap<net.minecraft.world.level.biome.Biome> getBiomeMapping() {
|
|
||||||
return biomeMapCache.aquire(() -> new MCAIdMap<>() {
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Iterator<net.minecraft.world.level.biome.Biome> iterator() {
|
|
||||||
return getCustomBiomeRegistry().iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getId(net.minecraft.world.level.biome.Biome paramT) {
|
|
||||||
return getCustomBiomeRegistry().getId(paramT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public net.minecraft.world.level.biome.Biome byId(int paramInt) {
|
|
||||||
return (net.minecraft.world.level.biome.Biome) getBiomeBaseFromId(paramInt);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private MCABiomeContainer getBiomeContainerInterface(MCAIdMap<net.minecraft.world.level.biome.Biome> biomeMapping, MCAChunkBiomeContainer<net.minecraft.world.level.biome.Biome> base) {
|
|
||||||
return new MCABiomeContainer() {
|
|
||||||
@Override
|
|
||||||
public int[] getData() {
|
|
||||||
return base.writeBiomes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBiome(int x, int y, int z, int id) {
|
|
||||||
base.setBiome(x, y, z, biomeMapping.byId(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBiome(int x, int y, int z) {
|
|
||||||
return biomeMapping.getId(base.getBiome(x, y, z));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MCABiomeContainer newBiomeContainer(int min, int max) {
|
|
||||||
MCAChunkBiomeContainer<net.minecraft.world.level.biome.Biome> base = new MCAChunkBiomeContainer<>(getBiomeMapping(), min, max);
|
|
||||||
return getBiomeContainerInterface(getBiomeMapping(), base);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MCABiomeContainer newBiomeContainer(int min, int max, int[] data) {
|
|
||||||
MCAChunkBiomeContainer<net.minecraft.world.level.biome.Biome> base = new MCAChunkBiomeContainer<>(getBiomeMapping(), min, max, data);
|
|
||||||
return getBiomeContainerInterface(getBiomeMapping(), base);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int countCustomBiomes() {
|
|
||||||
AtomicInteger a = new AtomicInteger(0);
|
|
||||||
|
|
||||||
getCustomBiomeRegistry().keySet().forEach((i) -> {
|
|
||||||
if (i.getNamespace().equals("minecraft")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.incrementAndGet();
|
|
||||||
Iris.debug("Custom Biome: " + i);
|
|
||||||
});
|
|
||||||
|
|
||||||
return a.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean supportsDataPacks() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBiomes(int cx, int cz, World world, Hunk<Object> biomes) {
|
|
||||||
LevelChunk c = ((CraftWorld) world).getHandle().getChunk(cx, cz);
|
|
||||||
biomes.iterateSync((x, y, z, b) -> c.setBiome(x, y, z, (Holder<net.minecraft.world.level.biome.Biome>) b));
|
|
||||||
c.setUnsaved(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void forceBiomeInto(int x, int y, int z, Object somethingVeryDirty, ChunkGenerator.BiomeGrid chunk) {
|
|
||||||
try {
|
|
||||||
ChunkAccess s = (ChunkAccess) getFieldForBiomeStorage(chunk).get(chunk);
|
|
||||||
Holder<net.minecraft.world.level.biome.Biome> biome = (Holder<net.minecraft.world.level.biome.Biome>) somethingVeryDirty;
|
|
||||||
s.setBiome(x, y, z, biome);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Iris.reportError(e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Field getFieldForBiomeStorage(Object storage) {
|
|
||||||
Field f = biomeStorageCache;
|
|
||||||
|
|
||||||
if (f != null) {
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
f = storage.getClass().getDeclaredField("biome");
|
|
||||||
f.setAccessible(true);
|
|
||||||
return f;
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.reportError(e);
|
|
||||||
e.printStackTrace();
|
|
||||||
Iris.error(storage.getClass().getCanonicalName());
|
|
||||||
}
|
|
||||||
|
|
||||||
biomeStorageCache = f;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MCAPaletteAccess createPalette() {
|
|
||||||
MCAIdMapper<BlockState> registry = registryCache.aquireNasty(() -> {
|
|
||||||
Field cf = net.minecraft.core.IdMapper.class.getDeclaredField("tToId");
|
|
||||||
Field df = net.minecraft.core.IdMapper.class.getDeclaredField("idToT");
|
|
||||||
Field bf = net.minecraft.core.IdMapper.class.getDeclaredField("nextId");
|
|
||||||
cf.setAccessible(true);
|
|
||||||
df.setAccessible(true);
|
|
||||||
bf.setAccessible(true);
|
|
||||||
net.minecraft.core.IdMapper<BlockState> blockData = Block.BLOCK_STATE_REGISTRY;
|
|
||||||
int b = bf.getInt(blockData);
|
|
||||||
Object2IntMap<BlockState> c = (Object2IntMap<BlockState>) cf.get(blockData);
|
|
||||||
List<BlockState> d = (List<BlockState>) df.get(blockData);
|
|
||||||
return new MCAIdMapper<BlockState>(c, d, b);
|
|
||||||
});
|
|
||||||
MCAPalette<BlockState> global = globalCache.aquireNasty(() -> new MCAGlobalPalette<>(registry, ((CraftBlockData) AIR).getState()));
|
|
||||||
MCAPalettedContainer<BlockState> container = new MCAPalettedContainer<>(global, registry,
|
|
||||||
i -> ((CraftBlockData) NBTWorld.getBlockData(i)).getState(),
|
|
||||||
i -> NBTWorld.getCompound(CraftBlockData.fromData(i)),
|
|
||||||
((CraftBlockData) AIR).getState());
|
|
||||||
return new MCAWrappedPalettedContainer<>(container,
|
|
||||||
i -> NBTWorld.getCompound(CraftBlockData.fromData(i)),
|
|
||||||
i -> ((CraftBlockData) NBTWorld.getBlockData(i)).getState());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectBiomesFromMantle(Chunk e, Mantle mantle) {
|
|
||||||
ChunkAccess chunk = ((CraftChunk) e).getHandle(ChunkStatus.FULL);
|
|
||||||
AtomicInteger c = new AtomicInteger();
|
|
||||||
AtomicInteger r = new AtomicInteger();
|
|
||||||
mantle.iterateChunk(e.getX(), e.getZ(), MatterBiomeInject.class, (x, y, z, b) -> {
|
|
||||||
if (b != null) {
|
|
||||||
if (b.isCustom()) {
|
|
||||||
chunk.setBiome(x, y, z, getCustomBiomeRegistry().getHolder(b.getBiomeId()).get());
|
|
||||||
c.getAndIncrement();
|
|
||||||
} else {
|
|
||||||
chunk.setBiome(x, y, z, (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(e.getWorld(), b.getBiome()));
|
|
||||||
r.getAndIncrement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ItemStack applyCustomNbt(ItemStack itemStack, KMap<String, Object> customNbt) throws IllegalArgumentException {
|
|
||||||
if (customNbt != null && !customNbt.isEmpty()) {
|
|
||||||
net.minecraft.world.item.ItemStack s = CraftItemStack.asNMSCopy(itemStack);
|
|
||||||
|
|
||||||
try {
|
|
||||||
net.minecraft.nbt.CompoundTag tag = TagParser.parseTag((new JSONObject(customNbt)).toString());
|
|
||||||
tag.merge(s.getOrCreateTag());
|
|
||||||
s.setTag(tag);
|
|
||||||
} catch (CommandSyntaxException var5) {
|
|
||||||
throw new IllegalArgumentException(var5);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CraftItemStack.asBukkitCopy(s);
|
|
||||||
} else {
|
|
||||||
return itemStack;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTreasurePos(Dolphin dolphin, com.volmit.iris.core.nms.container.BlockPos pos) {
|
|
||||||
CraftDolphin cd = (CraftDolphin)dolphin;
|
|
||||||
cd.getHandle().setTreasurePos(new BlockPos(pos.getX(), pos.getY(), pos.getZ()));
|
|
||||||
cd.getHandle().setGotFish(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void inject(long seed, Engine engine, World world) {
|
|
||||||
var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap;
|
|
||||||
chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) {
|
|
||||||
Field[] fields = EntityType.class.getDeclaredFields();
|
|
||||||
for (Field field : fields) {
|
|
||||||
if (Modifier.isStatic(field.getModifiers()) && field.getType().equals(EntityType.class)) {
|
|
||||||
try {
|
|
||||||
EntityType entityType = (EntityType) field.get(null);
|
|
||||||
if (entityType.getDescriptionId().equals("entity.minecraft." + entity.name().toLowerCase())) {
|
|
||||||
Vector<Float> v1 = new Vector<>();
|
|
||||||
v1.add(entityType.getHeight());
|
|
||||||
entityType.getDimensions();
|
|
||||||
Vector3d box = new Vector3d( entityType.getWidth(), entityType.getHeight(), entityType.getWidth());
|
|
||||||
//System.out.println("Entity Type: " + entityType.getDescriptionId() + ", " + "Height: " + height + ", Width: " + width);
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Iris.error("Unable to get entity dimensions!");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Entity spawnEntity(Location location, org.bukkit.entity.EntityType type, CreatureSpawnEvent.SpawnReason reason) {
|
|
||||||
if (type == org.bukkit.entity.EntityType.CAMEL) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Color getBiomeColor(Location location, BiomeColor type) {
|
|
||||||
LevelReader reader = ((CraftWorld) location.getWorld()).getHandle();
|
|
||||||
var holder = reader.getBiome(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()));
|
|
||||||
var biome = holder.value();
|
|
||||||
if (biome == null) throw new IllegalArgumentException("Invalid biome: " + holder.unwrapKey().orElse(null));
|
|
||||||
|
|
||||||
int rgba = switch (type) {
|
|
||||||
case FOG -> biome.getFogColor();
|
|
||||||
case WATER -> biome.getWaterColor();
|
|
||||||
case WATER_FOG -> biome.getWaterFogColor();
|
|
||||||
case SKY -> biome.getSkyColor();
|
|
||||||
case FOLIAGE -> biome.getFoliageColor();
|
|
||||||
case GRASS -> biome.getGrassColor(location.getBlockX(), location.getBlockZ());
|
|
||||||
};
|
|
||||||
if (rgba == 0) {
|
|
||||||
if (BiomeColor.FOLIAGE == type && biome.getSpecialEffects().getFoliageColorOverride().isEmpty())
|
|
||||||
return null;
|
|
||||||
if (BiomeColor.GRASS == type && biome.getSpecialEffects().getGrassColorOverride().isEmpty())
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Color(rgba, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KList<String> getStructureKeys() {
|
|
||||||
KList<String> keys = new KList<>();
|
|
||||||
|
|
||||||
var registry = registry().registry(Registries.STRUCTURE).orElse(null);
|
|
||||||
if (registry == null) return keys;
|
|
||||||
registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add);
|
|
||||||
registry.getTags()
|
|
||||||
.map(Pair::getFirst)
|
|
||||||
.map(TagKey::location)
|
|
||||||
.map(ResourceLocation::toString)
|
|
||||||
.map(s -> "#" + s)
|
|
||||||
.forEach(keys::add);
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
|
||||||
try {
|
|
||||||
for (Field f : clazz.getDeclaredFields()) {
|
|
||||||
if (f.getType().equals(fieldType))
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
throw new NoSuchFieldException(fieldType.getName());
|
|
||||||
} catch (NoSuchFieldException var4) {
|
|
||||||
Class<?> superClass = clazz.getSuperclass();
|
|
||||||
if (superClass == null) {
|
|
||||||
throw var4;
|
|
||||||
} else {
|
|
||||||
return getField(superClass, fieldType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,13 @@ package com.volmit.iris.core.nms.v1_20_R1;
|
|||||||
|
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
import com.mojang.datafixers.util.Pair;
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import com.mojang.serialization.Lifecycle;
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.nms.INMSBinding;
|
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.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.data.cache.AtomicCache;
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
@@ -20,27 +24,36 @@ import com.volmit.iris.util.nbt.mca.palette.*;
|
|||||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
import net.minecraft.core.BlockPos;
|
import lombok.SneakyThrows;
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.core.*;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.core.RegistryAccess;
|
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.nbt.*;
|
import net.minecraft.nbt.*;
|
||||||
import net.minecraft.nbt.Tag;
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.WorldLoader;
|
||||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.world.entity.EntityDimensions;
|
import net.minecraft.world.entity.EntityDimensions;
|
||||||
import net.minecraft.world.level.LevelReader;
|
import net.minecraft.world.level.LevelReader;
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
import net.minecraft.world.level.biome.BiomeSource;
|
||||||
|
import net.minecraft.world.level.biome.Biomes;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import net.minecraft.world.level.dimension.DimensionType;
|
||||||
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -73,11 +86,10 @@ import java.io.DataOutputStream;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.Iterator;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Vector;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class NMSBinding implements INMSBinding {
|
public class NMSBinding implements INMSBinding {
|
||||||
|
|
||||||
@@ -85,9 +97,11 @@ public class NMSBinding implements INMSBinding {
|
|||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||||
private final BlockData AIR = Material.AIR.createBlockData();
|
private final BlockData AIR = Material.AIR.createBlockData();
|
||||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||||
|
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||||
|
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||||
private Field biomeStorageCache = null;
|
private Field biomeStorageCache = null;
|
||||||
|
|
||||||
@@ -214,7 +228,7 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.newBlockEntity(blockPos, state);
|
.newBlockEntity(blockPos, state);
|
||||||
}
|
}
|
||||||
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
||||||
accessor.setData(tag.merge(accessor.getData()));
|
accessor.setData(accessor.getData().merge(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
||||||
@@ -568,6 +582,9 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
public void inject(long seed, Engine engine, World world) {
|
public void inject(long seed, Engine engine, World world) {
|
||||||
var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap;
|
var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap;
|
||||||
|
var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null);
|
||||||
|
if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris"))
|
||||||
|
Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString());
|
||||||
chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world);
|
chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,4 +645,125 @@ public class NMSBinding implements INMSBinding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IRegionStorage createRegionStorage(Engine engine) {
|
||||||
|
return new RegionStorage(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AutoClosing injectLevelStems() {
|
||||||
|
return inject(this::supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 com.volmit.iris.core.nms.container.Pair<>(
|
||||||
|
injected.size(),
|
||||||
|
new AutoClosing(() -> {
|
||||||
|
closing.close();
|
||||||
|
field.set(reg, old);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
|
||||||
|
var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
|
||||||
|
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
|
||||||
|
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
|
||||||
|
return overworld || nether || end;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
var access = registry();
|
||||||
|
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
|
||||||
|
var settings = new FlatLevelGeneratorSettings(
|
||||||
|
Optional.empty(),
|
||||||
|
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||||
|
settings.updateLayers();
|
||||||
|
|
||||||
|
var source = new FlatLevelSource(settings);
|
||||||
|
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||||
|
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||||
|
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||||
|
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||||
|
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
|
||||||
|
|
||||||
|
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||||
|
|
||||||
|
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||||
|
var loc = createIrisKey(key);
|
||||||
|
target.register(key, new LevelStem(
|
||||||
|
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||||
|
source
|
||||||
|
), Lifecycle.stable());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||||
|
if (source == null) return;
|
||||||
|
source.registryKeySet().forEach(key -> {
|
||||||
|
var value = source.get(key);
|
||||||
|
var info = source.lifecycle(value);
|
||||||
|
if (value != null && info != null && !target.containsKey(key))
|
||||||
|
target.register(key, value, info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||||
|
return new ResourceLocation("iris", key.location().getPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,21 +8,36 @@ import java.io.DataOutputStream;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.Iterator;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Vector;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
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.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 com.volmit.iris.util.scheduling.J;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import net.minecraft.core.*;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.nbt.*;
|
import net.minecraft.nbt.*;
|
||||||
import net.minecraft.nbt.Tag;
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.WorldLoader;
|
||||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.world.level.LevelReader;
|
import net.minecraft.world.level.LevelReader;
|
||||||
|
import net.minecraft.world.level.biome.Biomes;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
import net.minecraft.world.level.dimension.DimensionType;
|
||||||
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -61,10 +76,6 @@ import com.volmit.iris.util.nbt.mca.palette.*;
|
|||||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
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.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
@@ -83,9 +94,11 @@ public class NMSBinding implements INMSBinding {
|
|||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||||
private final BlockData AIR = Material.AIR.createBlockData();
|
private final BlockData AIR = Material.AIR.createBlockData();
|
||||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||||
|
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||||
|
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||||
private Field biomeStorageCache = null;
|
private Field biomeStorageCache = null;
|
||||||
|
|
||||||
@@ -212,7 +225,7 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.newBlockEntity(blockPos, state);
|
.newBlockEntity(blockPos, state);
|
||||||
}
|
}
|
||||||
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
||||||
accessor.setData(tag.merge(accessor.getData()));
|
accessor.setData(accessor.getData().merge(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
||||||
@@ -538,6 +551,9 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
public void inject(long seed, Engine engine, World world) {
|
public void inject(long seed, Engine engine, World world) {
|
||||||
var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap;
|
var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap;
|
||||||
|
var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null);
|
||||||
|
if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris"))
|
||||||
|
Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString());
|
||||||
chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world);
|
chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,4 +646,125 @@ public class NMSBinding implements INMSBinding {
|
|||||||
public static Holder<net.minecraft.world.level.biome.Biome> biomeToBiomeBase(Registry<net.minecraft.world.level.biome.Biome> registry, Biome biome) {
|
public static Holder<net.minecraft.world.level.biome.Biome> biomeToBiomeBase(Registry<net.minecraft.world.level.biome.Biome> registry, Biome biome) {
|
||||||
return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey())));
|
return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IRegionStorage createRegionStorage(Engine engine) {
|
||||||
|
return new RegionStorage(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AutoClosing injectLevelStems() {
|
||||||
|
return inject(this::supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 com.volmit.iris.core.nms.container.Pair<>(
|
||||||
|
injected.size(),
|
||||||
|
new AutoClosing(() -> {
|
||||||
|
closing.close();
|
||||||
|
field.set(reg, old);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
|
||||||
|
var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
|
||||||
|
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
|
||||||
|
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
|
||||||
|
return overworld || nether || end;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
var access = registry();
|
||||||
|
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
|
||||||
|
var settings = new FlatLevelGeneratorSettings(
|
||||||
|
Optional.empty(),
|
||||||
|
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||||
|
settings.updateLayers();
|
||||||
|
|
||||||
|
var source = new FlatLevelSource(settings);
|
||||||
|
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||||
|
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||||
|
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||||
|
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||||
|
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
|
||||||
|
|
||||||
|
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||||
|
|
||||||
|
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||||
|
var loc = createIrisKey(key);
|
||||||
|
target.register(key, new LevelStem(
|
||||||
|
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||||
|
source
|
||||||
|
), Lifecycle.stable());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||||
|
if (source == null) return;
|
||||||
|
source.registryKeySet().forEach(key -> {
|
||||||
|
var value = source.get(key);
|
||||||
|
var info = source.lifecycle(value);
|
||||||
|
if (value != null && info != null && !target.containsKey(key))
|
||||||
|
target.register(key, value, info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||||
|
return new ResourceLocation("iris", key.location().getPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,21 +8,36 @@ import java.io.DataOutputStream;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.Iterator;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Vector;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
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.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 com.volmit.iris.util.scheduling.J;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import net.minecraft.core.*;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.nbt.*;
|
import net.minecraft.nbt.*;
|
||||||
import net.minecraft.nbt.Tag;
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.WorldLoader;
|
||||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.world.level.LevelReader;
|
import net.minecraft.world.level.LevelReader;
|
||||||
|
import net.minecraft.world.level.biome.Biomes;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
import net.minecraft.world.level.dimension.DimensionType;
|
||||||
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -61,10 +76,6 @@ import com.volmit.iris.util.nbt.mca.palette.*;
|
|||||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
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.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
@@ -83,9 +94,11 @@ public class NMSBinding implements INMSBinding {
|
|||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||||
private final BlockData AIR = Material.AIR.createBlockData();
|
private final BlockData AIR = Material.AIR.createBlockData();
|
||||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||||
|
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||||
|
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||||
private Field biomeStorageCache = null;
|
private Field biomeStorageCache = null;
|
||||||
|
|
||||||
@@ -212,7 +225,7 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.newBlockEntity(blockPos, state);
|
.newBlockEntity(blockPos, state);
|
||||||
}
|
}
|
||||||
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
||||||
accessor.setData(tag.merge(accessor.getData()));
|
accessor.setData(accessor.getData().merge(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
||||||
@@ -538,6 +551,9 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
public void inject(long seed, Engine engine, World world) {
|
public void inject(long seed, Engine engine, World world) {
|
||||||
var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap;
|
var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap;
|
||||||
|
var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null);
|
||||||
|
if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris"))
|
||||||
|
Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString());
|
||||||
chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world);
|
chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,4 +647,125 @@ public class NMSBinding implements INMSBinding {
|
|||||||
public static Holder<net.minecraft.world.level.biome.Biome> biomeToBiomeBase(Registry<net.minecraft.world.level.biome.Biome> registry, Biome biome) {
|
public static Holder<net.minecraft.world.level.biome.Biome> biomeToBiomeBase(Registry<net.minecraft.world.level.biome.Biome> registry, Biome biome) {
|
||||||
return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey())));
|
return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IRegionStorage createRegionStorage(Engine engine) {
|
||||||
|
return new RegionStorage(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AutoClosing injectLevelStems() {
|
||||||
|
return inject(this::supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 com.volmit.iris.core.nms.container.Pair<>(
|
||||||
|
injected.size(),
|
||||||
|
new AutoClosing(() -> {
|
||||||
|
closing.close();
|
||||||
|
field.set(reg, old);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
|
||||||
|
var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
|
||||||
|
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
|
||||||
|
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
|
||||||
|
return overworld || nether || end;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
var access = registry();
|
||||||
|
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
|
||||||
|
var settings = new FlatLevelGeneratorSettings(
|
||||||
|
Optional.empty(),
|
||||||
|
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||||
|
settings.updateLayers();
|
||||||
|
|
||||||
|
var source = new FlatLevelSource(settings);
|
||||||
|
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||||
|
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||||
|
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||||
|
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||||
|
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
|
||||||
|
|
||||||
|
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||||
|
|
||||||
|
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||||
|
var loc = createIrisKey(key);
|
||||||
|
target.register(key, new LevelStem(
|
||||||
|
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||||
|
source
|
||||||
|
), Lifecycle.stable());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||||
|
if (source == null) return;
|
||||||
|
source.registryKeySet().forEach(key -> {
|
||||||
|
var value = source.get(key);
|
||||||
|
var info = source.lifecycle(value);
|
||||||
|
if (value != null && info != null && !target.containsKey(key))
|
||||||
|
target.register(key, value, info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||||
|
return new ResourceLocation("iris", key.location().getPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,12 +6,19 @@ import java.lang.reflect.Method;
|
|||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
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.container.BiomeColor;
|
||||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
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.nbt.tag.CompoundTag;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import net.minecraft.core.*;
|
import net.minecraft.core.*;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.core.component.DataComponents;
|
import net.minecraft.core.component.DataComponents;
|
||||||
@@ -25,15 +32,24 @@ import net.minecraft.nbt.LongTag;
|
|||||||
import net.minecraft.nbt.ShortTag;
|
import net.minecraft.nbt.ShortTag;
|
||||||
import net.minecraft.nbt.StringTag;
|
import net.minecraft.nbt.StringTag;
|
||||||
import net.minecraft.nbt.Tag;
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.WorldLoader;
|
||||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||||
import net.minecraft.server.commands.data.DataCommands;
|
import net.minecraft.server.commands.data.DataCommands;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.world.item.component.CustomData;
|
import net.minecraft.world.item.component.CustomData;
|
||||||
import net.minecraft.world.level.LevelReader;
|
import net.minecraft.world.level.LevelReader;
|
||||||
|
import net.minecraft.world.level.biome.Biomes;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||||
|
import net.minecraft.world.level.dimension.DimensionType;
|
||||||
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -86,9 +102,11 @@ public class NMSBinding implements INMSBinding {
|
|||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||||
private final BlockData AIR = Material.AIR.createBlockData();
|
private final BlockData AIR = Material.AIR.createBlockData();
|
||||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||||
|
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||||
|
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||||
private Field biomeStorageCache = null;
|
private Field biomeStorageCache = null;
|
||||||
|
|
||||||
@@ -216,7 +234,7 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.newBlockEntity(blockPos, state);
|
.newBlockEntity(blockPos, state);
|
||||||
}
|
}
|
||||||
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
||||||
accessor.setData(tag.merge(accessor.getData()));
|
accessor.setData(accessor.getData().merge(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
||||||
@@ -544,6 +562,10 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
public void inject(long seed, Engine engine, World world) {
|
public void inject(long seed, Engine engine, World world) {
|
||||||
var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap;
|
var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap;
|
||||||
|
var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null);
|
||||||
|
if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris"))
|
||||||
|
Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString());
|
||||||
|
|
||||||
chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world);
|
chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,4 +672,125 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IRegionStorage createRegionStorage(Engine engine) {
|
||||||
|
return new RegionStorage(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AutoClosing injectLevelStems() {
|
||||||
|
return inject(this::supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 com.volmit.iris.core.nms.container.Pair<>(
|
||||||
|
injected.size(),
|
||||||
|
new AutoClosing(() -> {
|
||||||
|
closing.close();
|
||||||
|
field.set(reg, old);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
|
||||||
|
var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
|
||||||
|
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
|
||||||
|
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
|
||||||
|
return overworld || nether || end;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
var access = registry();
|
||||||
|
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
|
||||||
|
var settings = new FlatLevelGeneratorSettings(
|
||||||
|
Optional.empty(),
|
||||||
|
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||||
|
settings.updateLayers();
|
||||||
|
|
||||||
|
var source = new FlatLevelSource(settings);
|
||||||
|
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||||
|
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||||
|
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||||
|
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||||
|
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
|
||||||
|
|
||||||
|
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||||
|
|
||||||
|
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||||
|
var loc = createIrisKey(key);
|
||||||
|
target.register(key, new LevelStem(
|
||||||
|
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||||
|
source
|
||||||
|
), RegistrationInfo.BUILT_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||||
|
if (source == null) return;
|
||||||
|
source.registryKeySet().forEach(key -> {
|
||||||
|
var value = source.get(key);
|
||||||
|
var info = source.registrationInfo(key).orElse(null);
|
||||||
|
if (value != null && info != null && !target.containsKey(key))
|
||||||
|
target.register(key, value, info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||||
|
return new ResourceLocation("iris", key.location().getPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,22 +10,40 @@ import java.lang.reflect.Method;
|
|||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
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.container.BiomeColor;
|
||||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
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 com.volmit.iris.util.scheduling.J;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import net.minecraft.core.*;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.core.component.DataComponents;
|
import net.minecraft.core.component.DataComponents;
|
||||||
import net.minecraft.nbt.*;
|
import net.minecraft.nbt.*;
|
||||||
import net.minecraft.nbt.Tag;
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.WorldLoader;
|
||||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||||
import net.minecraft.server.level.ChunkMap;
|
import net.minecraft.server.level.ChunkMap;
|
||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.world.item.component.CustomData;
|
import net.minecraft.world.item.component.CustomData;
|
||||||
import net.minecraft.world.level.LevelReader;
|
import net.minecraft.world.level.LevelReader;
|
||||||
|
import net.minecraft.world.level.biome.Biomes;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||||
import net.minecraft.world.level.chunk.status.WorldGenContext;
|
import net.minecraft.world.level.chunk.status.WorldGenContext;
|
||||||
|
import net.minecraft.world.level.dimension.DimensionType;
|
||||||
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -64,10 +82,6 @@ import com.volmit.iris.util.nbt.mca.palette.*;
|
|||||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
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.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
@@ -85,9 +99,11 @@ public class NMSBinding implements INMSBinding {
|
|||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||||
private final BlockData AIR = Material.AIR.createBlockData();
|
private final BlockData AIR = Material.AIR.createBlockData();
|
||||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||||
|
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||||
|
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||||
private Field biomeStorageCache = null;
|
private Field biomeStorageCache = null;
|
||||||
|
|
||||||
@@ -215,7 +231,7 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.newBlockEntity(blockPos, state);
|
.newBlockEntity(blockPos, state);
|
||||||
}
|
}
|
||||||
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
||||||
accessor.setData(tag.merge(accessor.getData()));
|
accessor.setData(accessor.getData().merge(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
||||||
@@ -546,6 +562,9 @@ public class NMSBinding implements INMSBinding {
|
|||||||
var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class);
|
var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class);
|
||||||
worldGenContextField.setAccessible(true);
|
worldGenContextField.setAccessible(true);
|
||||||
var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap);
|
var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap);
|
||||||
|
var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null);
|
||||||
|
if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris"))
|
||||||
|
Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString());
|
||||||
|
|
||||||
var newContext = new WorldGenContext(
|
var newContext = new WorldGenContext(
|
||||||
worldGenContext.level(), new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world),
|
worldGenContext.level(), new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world),
|
||||||
@@ -657,4 +676,125 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IRegionStorage createRegionStorage(Engine engine) {
|
||||||
|
return new RegionStorage(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AutoClosing injectLevelStems() {
|
||||||
|
return inject(this::supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 com.volmit.iris.core.nms.container.Pair<>(
|
||||||
|
injected.size(),
|
||||||
|
new AutoClosing(() -> {
|
||||||
|
closing.close();
|
||||||
|
field.set(reg, old);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
|
||||||
|
var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
|
||||||
|
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
|
||||||
|
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
|
||||||
|
return overworld || nether || end;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
var access = registry();
|
||||||
|
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
|
||||||
|
var settings = new FlatLevelGeneratorSettings(
|
||||||
|
Optional.empty(),
|
||||||
|
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||||
|
settings.updateLayers();
|
||||||
|
|
||||||
|
var source = new FlatLevelSource(settings);
|
||||||
|
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||||
|
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||||
|
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||||
|
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||||
|
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
|
||||||
|
|
||||||
|
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||||
|
|
||||||
|
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||||
|
var loc = createIrisKey(key);
|
||||||
|
target.register(key, new LevelStem(
|
||||||
|
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||||
|
source
|
||||||
|
), RegistrationInfo.BUILT_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||||
|
if (source == null) return;
|
||||||
|
source.registryKeySet().forEach(key -> {
|
||||||
|
var value = source.get(key);
|
||||||
|
var info = source.registrationInfo(key).orElse(null);
|
||||||
|
if (value != null && info != null && !target.containsKey(key))
|
||||||
|
target.register(key, value, info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||||
|
return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,22 +6,39 @@ import java.lang.reflect.Method;
|
|||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
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.container.BiomeColor;
|
||||||
|
import com.volmit.iris.core.nms.container.Pair;
|
||||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
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 com.volmit.iris.util.scheduling.J;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import net.minecraft.core.*;
|
import net.minecraft.core.*;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.core.component.DataComponents;
|
import net.minecraft.core.component.DataComponents;
|
||||||
import net.minecraft.nbt.*;
|
import net.minecraft.nbt.*;
|
||||||
import net.minecraft.nbt.Tag;
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.WorldLoader;
|
||||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.world.item.component.CustomData;
|
import net.minecraft.world.item.component.CustomData;
|
||||||
import net.minecraft.world.level.LevelReader;
|
import net.minecraft.world.level.LevelReader;
|
||||||
|
import net.minecraft.world.level.biome.Biomes;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||||
import net.minecraft.world.level.chunk.status.WorldGenContext;
|
import net.minecraft.world.level.chunk.status.WorldGenContext;
|
||||||
|
import net.minecraft.world.level.dimension.DimensionType;
|
||||||
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -72,9 +89,11 @@ public class NMSBinding implements INMSBinding {
|
|||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||||
private final BlockData AIR = Material.AIR.createBlockData();
|
private final BlockData AIR = Material.AIR.createBlockData();
|
||||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||||
|
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||||
|
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||||
private Field biomeStorageCache = null;
|
private Field biomeStorageCache = null;
|
||||||
|
|
||||||
@@ -202,7 +221,7 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.newBlockEntity(blockPos, state);
|
.newBlockEntity(blockPos, state);
|
||||||
}
|
}
|
||||||
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
||||||
accessor.setData(tag.merge(accessor.getData()));
|
accessor.setData(accessor.getData().merge(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
||||||
@@ -533,6 +552,9 @@ public class NMSBinding implements INMSBinding {
|
|||||||
var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class);
|
var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class);
|
||||||
worldGenContextField.setAccessible(true);
|
worldGenContextField.setAccessible(true);
|
||||||
var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap);
|
var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap);
|
||||||
|
var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null);
|
||||||
|
if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris"))
|
||||||
|
Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString());
|
||||||
|
|
||||||
var newContext = new WorldGenContext(
|
var newContext = new WorldGenContext(
|
||||||
worldGenContext.level(), new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world),
|
worldGenContext.level(), new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world),
|
||||||
@@ -644,4 +666,125 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IRegionStorage createRegionStorage(Engine engine) {
|
||||||
|
return new RegionStorage(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AutoClosing injectLevelStems() {
|
||||||
|
return inject(this::supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 Pair<>(
|
||||||
|
injected.size(),
|
||||||
|
new AutoClosing(() -> {
|
||||||
|
closing.close();
|
||||||
|
field.set(reg, old);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
|
||||||
|
var registry = registry().lookupOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
|
||||||
|
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
|
||||||
|
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
|
||||||
|
return overworld || nether || end;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
var access = registry();
|
||||||
|
var dimensions = access.lookupOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
|
||||||
|
var settings = new FlatLevelGeneratorSettings(
|
||||||
|
Optional.empty(),
|
||||||
|
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||||
|
settings.updateLayers();
|
||||||
|
|
||||||
|
var source = new FlatLevelSource(settings);
|
||||||
|
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||||
|
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||||
|
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||||
|
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||||
|
copy(fake, datapack.lookup(Registries.LEVEL_STEM).orElse(null));
|
||||||
|
|
||||||
|
if (copy) copy(fake, access.lookupOrThrow(Registries.LEVEL_STEM));
|
||||||
|
|
||||||
|
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||||
|
var loc = createIrisKey(key);
|
||||||
|
target.register(key, new LevelStem(
|
||||||
|
dimensions.get(loc).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||||
|
source
|
||||||
|
), RegistrationInfo.BUILT_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||||
|
if (source == null) return;
|
||||||
|
source.listElementIds().forEach(key -> {
|
||||||
|
var value = source.getValue(key);
|
||||||
|
var info = source.registrationInfo(key).orElse(null);
|
||||||
|
if (value != null && info != null && !target.containsKey(key))
|
||||||
|
target.register(key, value, info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||||
|
return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,15 @@
|
|||||||
package com.volmit.iris.core.nms.v1_21_R3;
|
package com.volmit.iris.core.nms.v1_21_R3;
|
||||||
|
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.serialization.Lifecycle;
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.nms.INMSBinding;
|
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.container.BiomeColor;
|
||||||
|
import com.volmit.iris.core.nms.container.Pair;
|
||||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
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.data.cache.AtomicCache;
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
@@ -19,6 +24,7 @@ import com.volmit.iris.util.nbt.mca.palette.*;
|
|||||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.core.*;
|
import net.minecraft.core.*;
|
||||||
import net.minecraft.core.component.DataComponents;
|
import net.minecraft.core.component.DataComponents;
|
||||||
@@ -27,13 +33,17 @@ import net.minecraft.nbt.Tag;
|
|||||||
import net.minecraft.nbt.*;
|
import net.minecraft.nbt.*;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.WorldLoader;
|
||||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.world.entity.EntityType;
|
import net.minecraft.world.entity.EntityType;
|
||||||
import net.minecraft.world.item.component.CustomData;
|
import net.minecraft.world.item.component.CustomData;
|
||||||
import net.minecraft.world.level.LevelReader;
|
import net.minecraft.world.level.LevelReader;
|
||||||
|
import net.minecraft.world.level.biome.Biomes;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
@@ -41,6 +51,11 @@ import net.minecraft.world.level.chunk.ChunkAccess;
|
|||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||||
import net.minecraft.world.level.chunk.status.WorldGenContext;
|
import net.minecraft.world.level.chunk.status.WorldGenContext;
|
||||||
|
import net.minecraft.world.level.dimension.DimensionType;
|
||||||
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -66,14 +81,18 @@ import java.lang.reflect.Modifier;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class NMSBinding implements INMSBinding {
|
public class NMSBinding implements INMSBinding {
|
||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||||
private final BlockData AIR = Material.AIR.createBlockData();
|
private final BlockData AIR = Material.AIR.createBlockData();
|
||||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||||
|
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||||
|
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||||
private Field biomeStorageCache = null;
|
private Field biomeStorageCache = null;
|
||||||
|
|
||||||
@@ -201,7 +220,7 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.newBlockEntity(blockPos, state);
|
.newBlockEntity(blockPos, state);
|
||||||
}
|
}
|
||||||
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
var accessor = new BlockDataAccessor(blockEntity, blockPos);
|
||||||
accessor.setData(tag.merge(accessor.getData()));
|
accessor.setData(accessor.getData().merge(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
private Tag convertToTag(Object object, int depth, int maxDepth) {
|
||||||
@@ -532,6 +551,9 @@ public class NMSBinding implements INMSBinding {
|
|||||||
var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class);
|
var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class);
|
||||||
worldGenContextField.setAccessible(true);
|
worldGenContextField.setAccessible(true);
|
||||||
var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap);
|
var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap);
|
||||||
|
var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null);
|
||||||
|
if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris"))
|
||||||
|
Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString());
|
||||||
|
|
||||||
var newContext = new WorldGenContext(
|
var newContext = new WorldGenContext(
|
||||||
worldGenContext.level(), new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world),
|
worldGenContext.level(), new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world),
|
||||||
@@ -643,4 +665,125 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IRegionStorage createRegionStorage(Engine engine) {
|
||||||
|
return new RegionStorage(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AutoClosing injectLevelStems() {
|
||||||
|
return inject(this::supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 Pair<>(
|
||||||
|
injected.size(),
|
||||||
|
new AutoClosing(() -> {
|
||||||
|
closing.close();
|
||||||
|
field.set(reg, old);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
|
||||||
|
var registry = registry().lookupOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
|
||||||
|
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
|
||||||
|
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
|
||||||
|
return overworld || nether || end;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
var access = registry();
|
||||||
|
var dimensions = access.lookupOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
|
||||||
|
var settings = new FlatLevelGeneratorSettings(
|
||||||
|
Optional.empty(),
|
||||||
|
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||||
|
settings.updateLayers();
|
||||||
|
|
||||||
|
var source = new FlatLevelSource(settings);
|
||||||
|
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||||
|
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||||
|
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||||
|
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||||
|
copy(fake, datapack.lookup(Registries.LEVEL_STEM).orElse(null));
|
||||||
|
|
||||||
|
if (copy) copy(fake, access.lookupOrThrow(Registries.LEVEL_STEM));
|
||||||
|
|
||||||
|
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||||
|
var loc = createIrisKey(key);
|
||||||
|
target.register(key, new LevelStem(
|
||||||
|
dimensions.get(loc).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||||
|
source
|
||||||
|
), RegistrationInfo.BUILT_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||||
|
if (source == null) return;
|
||||||
|
source.listElementIds().forEach(key -> {
|
||||||
|
var value = source.getValue(key);
|
||||||
|
var info = source.registrationInfo(key).orElse(null);
|
||||||
|
if (value != null && info != null && !target.containsKey(key))
|
||||||
|
target.register(key, value, info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||||
|
return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,4 @@ include(
|
|||||||
':nms:v1_20_R3',
|
':nms:v1_20_R3',
|
||||||
':nms:v1_20_R2',
|
':nms:v1_20_R2',
|
||||||
':nms:v1_20_R1',
|
':nms:v1_20_R1',
|
||||||
':nms:v1_19_R3',
|
|
||||||
':nms:v1_19_R2',
|
|
||||||
':nms:v1_19_R1'
|
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user