mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-04-03 06:16:19 +00:00
JIGGY!
This commit is contained in:
@@ -215,9 +215,6 @@ public class IrisSettings {
|
||||
public boolean commandSounds = true;
|
||||
public boolean debug = false;
|
||||
public boolean dumpMantleOnError = false;
|
||||
public boolean validatePacksOnStartup = true;
|
||||
public boolean stopStartupOnPackValidationFailure = false;
|
||||
public int maxPackValidationErrorsPerPack = 200;
|
||||
public boolean disableNMS = false;
|
||||
public boolean pluginMetrics = true;
|
||||
public boolean splashLogoStartup = true;
|
||||
|
||||
@@ -24,7 +24,6 @@ import art.arcane.iris.core.gui.NoiseExplorerGUI;
|
||||
import art.arcane.iris.core.gui.VisionGUI;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.project.IrisProject;
|
||||
import art.arcane.iris.core.service.ConversionSVC;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.IrisNoisemapPrebakePipeline;
|
||||
@@ -268,12 +267,6 @@ public class CommandStudio implements DirectorExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Convert objects in the \"convert\" folder")
|
||||
public void convert() {
|
||||
Iris.service(ConversionSVC.class).check(sender());
|
||||
//IrisConverter.convertSchematics(sender());
|
||||
}
|
||||
|
||||
@Director(description = "Execute a script", aliases = "run", origin = DirectorOrigin.PLAYER)
|
||||
public void execute(
|
||||
@Param(description = "The script to run")
|
||||
|
||||
@@ -151,6 +151,10 @@ public interface INMSBinding {
|
||||
|
||||
KList<String> getStructureKeys();
|
||||
|
||||
default KMap<String, KList<String>> getVanillaStructureBiomeTags() {
|
||||
return new KMap<>();
|
||||
}
|
||||
|
||||
boolean missingDimensionTypes(String... keys);
|
||||
|
||||
default boolean injectBukkit() {
|
||||
|
||||
@@ -21,7 +21,6 @@ package art.arcane.iris.core.service;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.commands.CommandIris;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
import art.arcane.iris.util.common.director.DirectorContext;
|
||||
import art.arcane.iris.util.common.director.DirectorContextHandler;
|
||||
@@ -48,8 +47,6 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -83,16 +80,6 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
|
||||
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(PlayerCommandPreprocessEvent e) {
|
||||
String msg = e.getMessage().startsWith("/") ? e.getMessage().substring(1) : e.getMessage();
|
||||
|
||||
if ((msg.startsWith("locate ") || msg.startsWith("locatebiome ")) && IrisToolbelt.isIrisWorld(e.getPlayer().getWorld())) {
|
||||
new VolmitSender(e.getPlayer()).sendMessage(C.RED + "Locating biomes & objects is disabled in Iris Worlds. Use /iris studio goto <biome>");
|
||||
e.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public DirectorRuntimeEngine getDirector() {
|
||||
return directorCache.aquireNastyPrint(() -> DirectorEngineFactory.create(
|
||||
new CommandIris(),
|
||||
|
||||
@@ -1,160 +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 art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.io.Converter;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.iris.util.common.plugin.IrisService;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
public class ConversionSVC implements IrisService {
|
||||
private KList<Converter> converters;
|
||||
private File folder;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
folder = Iris.instance.getDataFolder("convert");
|
||||
converters = new KList<>();
|
||||
|
||||
J.s(() ->
|
||||
J.attemptAsync(() ->
|
||||
{
|
||||
|
||||
}), 5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
|
||||
}
|
||||
|
||||
private void findAllNBT(File path, KList<File> found) {
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.isFile() && path.getName().endsWith(".nbt")) {
|
||||
found.add(path);
|
||||
return;
|
||||
}
|
||||
|
||||
File[] children = path.listFiles();
|
||||
if (children == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (File i : children) {
|
||||
if (i.isDirectory()) {
|
||||
findAllNBT(i, found);
|
||||
} else if (i.isFile() && i.getName().endsWith(".nbt")) {
|
||||
found.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensurePackMetadata(File datapackRoot) throws IOException {
|
||||
File mcmeta = new File(datapackRoot, "pack.mcmeta");
|
||||
if (mcmeta.exists()) {
|
||||
return;
|
||||
}
|
||||
int format = INMS.get().getDataVersion().getPackFormat();
|
||||
String meta = """
|
||||
{
|
||||
"pack": {
|
||||
"description": "Iris loose structure ingestion pack",
|
||||
"pack_format": {}
|
||||
}
|
||||
}
|
||||
""".replace("{}", String.valueOf(format));
|
||||
IO.writeAll(mcmeta, meta);
|
||||
}
|
||||
|
||||
private int ingestLooseNbtStructures(File source, File datapackRoot) {
|
||||
KList<File> nbtFiles = new KList<>();
|
||||
findAllNBT(source, nbtFiles);
|
||||
if (nbtFiles.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
File structureRoot = new File(datapackRoot, "data/iris_loose/structures");
|
||||
structureRoot.mkdirs();
|
||||
|
||||
int copied = 0;
|
||||
try {
|
||||
ensurePackMetadata(datapackRoot);
|
||||
for (File nbt : nbtFiles) {
|
||||
String relative = source.toURI().relativize(nbt.toURI()).getPath();
|
||||
if (relative == null || relative.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
File output = new File(structureRoot, relative);
|
||||
File parent = output.getParentFile();
|
||||
if (parent != null) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
Files.copy(nbt.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
copied++;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
public void check(VolmitSender s) {
|
||||
int m = 0;
|
||||
Iris.instance.getDataFolder("convert");
|
||||
|
||||
File[] files = folder.listFiles();
|
||||
if (files == null) {
|
||||
s.sendMessage("Converted 0 Files");
|
||||
return;
|
||||
}
|
||||
|
||||
for (File i : files) {
|
||||
for (Converter j : converters) {
|
||||
if (i.getName().endsWith("." + j.getInExtension())) {
|
||||
File out = new File(folder, i.getName().replaceAll("\\Q." + j.getInExtension() + "\\E", "." + j.getOutExtension()));
|
||||
m++;
|
||||
j.convert(i, out);
|
||||
s.sendMessage("Converted " + i.getName() + " -> " + out.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File loose = new File(folder, "structures");
|
||||
if (loose.isDirectory()) {
|
||||
File datapackRoot = Iris.instance.getDataFolder("datapacks", "iris-loose-structures");
|
||||
int ingested = ingestLooseNbtStructures(loose, datapackRoot);
|
||||
s.sendMessage("Ingested " + ingested + " loose NBT structure" + (ingested == 1 ? "" : "s") + " into " + datapackRoot.getName());
|
||||
}
|
||||
|
||||
s.sendMessage("Converted " + m + " File" + (m == 1 ? "" : "s"));
|
||||
}
|
||||
}
|
||||
@@ -1,576 +0,0 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.loader.IrisRegistrant;
|
||||
import art.arcane.iris.core.loader.ResourceLoader;
|
||||
import art.arcane.iris.engine.object.annotations.ArrayType;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import art.arcane.iris.util.common.data.registry.KeyedRegistry;
|
||||
import art.arcane.iris.util.common.data.registry.RegistryUtil;
|
||||
import art.arcane.iris.util.common.plugin.IrisService;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import org.bukkit.NamespacedKey;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class PackValidationSVC implements IrisService {
|
||||
private final Map<Class<?>, Map<String, Field>> fieldCache = new ConcurrentHashMap<>();
|
||||
private final Map<Class<?>, Boolean> keyedTypeCache = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
IrisSettings.IrisSettingsGeneral general = IrisSettings.get().getGeneral();
|
||||
if (!general.isValidatePacksOnStartup()) {
|
||||
return;
|
||||
}
|
||||
|
||||
File packsFolder = Iris.instance.getDataFolder("packs");
|
||||
File[] packFolders = packsFolder.listFiles(File::isDirectory);
|
||||
if (packFolders == null || packFolders.length == 0) {
|
||||
Iris.info("Startup pack validation skipped: no pack folders found.");
|
||||
return;
|
||||
}
|
||||
|
||||
int maxLoggedIssues = Math.max(1, general.getMaxPackValidationErrorsPerPack());
|
||||
int totalPacks = 0;
|
||||
int totalFiles = 0;
|
||||
int totalErrors = 0;
|
||||
int packsWithErrors = 0;
|
||||
long started = System.currentTimeMillis();
|
||||
|
||||
Iris.info("Startup pack validation started for %d pack(s).", packFolders.length);
|
||||
|
||||
for (File packFolder : packFolders) {
|
||||
totalPacks++;
|
||||
PackValidationReport report = validatePack(packFolder, maxLoggedIssues);
|
||||
totalFiles += report.filesChecked;
|
||||
totalErrors += report.errorCount;
|
||||
|
||||
if (report.errorCount > 0) {
|
||||
packsWithErrors++;
|
||||
Iris.error("Pack \"%s\" has %d validation issue(s) across %d file(s).", report.packName, report.errorCount, report.filesChecked);
|
||||
for (String issue : report.sampleIssues) {
|
||||
Iris.error(" - %s", issue);
|
||||
}
|
||||
|
||||
int hiddenIssues = report.errorCount - report.sampleIssues.size();
|
||||
if (hiddenIssues > 0) {
|
||||
Iris.error(" - ... %d additional issue(s) not shown", hiddenIssues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long elapsed = System.currentTimeMillis() - started;
|
||||
if (totalErrors == 0) {
|
||||
Iris.info("Startup pack validation finished: %d pack(s), %d file(s), no issues (%dms).", totalPacks, totalFiles, elapsed);
|
||||
return;
|
||||
}
|
||||
|
||||
Iris.error("Startup pack validation finished: %d issue(s) in %d pack(s), %d file(s) checked (%dms).", totalErrors, packsWithErrors, totalFiles, elapsed);
|
||||
if (general.isStopStartupOnPackValidationFailure()) {
|
||||
throw new IllegalStateException("Pack validation failed with " + totalErrors + " issue(s).");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
|
||||
}
|
||||
|
||||
private PackValidationReport validatePack(File packFolder, int maxLoggedIssues) {
|
||||
PackValidationReport report = new PackValidationReport(packFolder.getName(), maxLoggedIssues);
|
||||
IrisData data = IrisData.get(packFolder);
|
||||
Collection<ResourceLoader<? extends IrisRegistrant>> loaders = data.getLoaders().values();
|
||||
|
||||
for (ResourceLoader<? extends IrisRegistrant> loader : loaders) {
|
||||
Class<?> rootType = loader.getObjectClass();
|
||||
if (rootType == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<File> folders = loader.getFolders();
|
||||
for (File folder : folders) {
|
||||
validateFolder(data, packFolder, folder, rootType, report);
|
||||
}
|
||||
}
|
||||
|
||||
validateSnippetFolders(data, packFolder, report);
|
||||
return report;
|
||||
}
|
||||
|
||||
private void validateSnippetFolders(IrisData data, File packFolder, PackValidationReport report) {
|
||||
File snippetRoot = new File(packFolder, "snippet");
|
||||
if (!snippetRoot.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Class<?>> snippetTypes = new HashMap<>();
|
||||
for (Class<?> snippetType : data.resolveSnippets()) {
|
||||
Snippet snippet = snippetType.getDeclaredAnnotation(Snippet.class);
|
||||
if (snippet == null) {
|
||||
continue;
|
||||
}
|
||||
snippetTypes.put(snippet.value(), snippetType);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Class<?>> entry : snippetTypes.entrySet()) {
|
||||
File typeFolder = new File(snippetRoot, entry.getKey());
|
||||
if (!typeFolder.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
validateFolder(data, packFolder, typeFolder, entry.getValue(), report);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateFolder(IrisData data, File packFolder, File folder, Class<?> rootType, PackValidationReport report) {
|
||||
List<File> jsonFiles = new ArrayList<>();
|
||||
collectJsonFiles(folder, jsonFiles);
|
||||
|
||||
for (File jsonFile : jsonFiles) {
|
||||
report.filesChecked++;
|
||||
validateJsonFile(data, packFolder, jsonFile, rootType, report);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectJsonFiles(File folder, List<File> output) {
|
||||
File[] files = folder.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
collectJsonFiles(file, output);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file.isFile() && file.getName().endsWith(".json")) {
|
||||
output.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateJsonFile(IrisData data, File packFolder, File file, Class<?> rootType, PackValidationReport report) {
|
||||
String content;
|
||||
try {
|
||||
content = IO.readAll(file);
|
||||
} catch (Throwable e) {
|
||||
report.addIssue(packFolder, file, "$", "Unable to read file: " + simpleMessage(e));
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject object;
|
||||
try {
|
||||
object = new JSONObject(content);
|
||||
} catch (Throwable e) {
|
||||
report.addIssue(packFolder, file, "$", "Invalid JSON syntax: " + simpleMessage(e));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Object decoded = data.getGson().fromJson(content, rootType);
|
||||
if (decoded == null) {
|
||||
report.addIssue(packFolder, file, "$", "Decoded value is null for root type " + rootType.getSimpleName());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
report.addIssue(packFolder, file, "$", "Deserializer rejected file for " + rootType.getSimpleName() + ": " + simpleMessage(e));
|
||||
}
|
||||
|
||||
validateObject(packFolder, file, "$", object, rootType, report);
|
||||
}
|
||||
|
||||
private void validateObject(File packFolder, File file, String path, JSONObject object, Class<?> type, PackValidationReport report) {
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Field> fields = getSerializableFields(type);
|
||||
for (String key : object.keySet()) {
|
||||
Field field = fields.get(key);
|
||||
String keyPath = path + "." + key;
|
||||
|
||||
if (field == null) {
|
||||
report.addIssue(packFolder, file, keyPath, "Unknown or misplaced key for type " + type.getSimpleName());
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = object.get(key);
|
||||
validateValue(packFolder, file, keyPath, value, field.getType(), field.getGenericType(), field, report);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateValue(File packFolder, File file, String path, Object value, Class<?> expectedType, Type genericType, Field sourceField, PackValidationReport report) {
|
||||
Class<?> normalizedType = normalizeType(expectedType);
|
||||
if (value == JSONObject.NULL) {
|
||||
if (normalizedType.isPrimitive()) {
|
||||
report.addIssue(packFolder, file, path, "Null value is not allowed for primitive type " + normalizedType.getSimpleName());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (Collection.class.isAssignableFrom(normalizedType)) {
|
||||
validateCollection(packFolder, file, path, value, genericType, sourceField, report);
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalizedType.isArray()) {
|
||||
validateArray(packFolder, file, path, value, normalizedType, report);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Map.class.isAssignableFrom(normalizedType)) {
|
||||
validateMap(packFolder, file, path, value, genericType, report);
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalizedType.isEnum()) {
|
||||
validateEnum(packFolder, file, path, value, normalizedType, report);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isKeyedType(normalizedType)) {
|
||||
validateKeyed(packFolder, file, path, value, normalizedType, report);
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalizedType == String.class) {
|
||||
if (!(value instanceof String)) {
|
||||
report.addIssue(packFolder, file, path, "Expected string value");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalizedType == Boolean.class) {
|
||||
if (!(value instanceof Boolean)) {
|
||||
report.addIssue(packFolder, file, path, "Expected boolean value");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (Number.class.isAssignableFrom(normalizedType)) {
|
||||
if (!(value instanceof Number)) {
|
||||
report.addIssue(packFolder, file, path, "Expected numeric value");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalizedType == Character.class) {
|
||||
if (!(value instanceof String text) || text.length() != 1) {
|
||||
report.addIssue(packFolder, file, path, "Expected single-character string value");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Snippet snippet = normalizedType.getDeclaredAnnotation(Snippet.class);
|
||||
if (snippet != null) {
|
||||
if (value instanceof String reference) {
|
||||
if (!reference.startsWith("snippet/")) {
|
||||
report.addIssue(packFolder, file, path, "Snippet reference must start with snippet/");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof JSONObject jsonObject) {
|
||||
validateObject(packFolder, file, path, jsonObject, normalizedType, report);
|
||||
return;
|
||||
}
|
||||
|
||||
report.addIssue(packFolder, file, path, "Snippet value must be an object or snippet reference string");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof JSONObject jsonObject) {
|
||||
if (shouldValidateNestedObject(normalizedType)) {
|
||||
validateObject(packFolder, file, path, jsonObject, normalizedType, report);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof JSONArray) {
|
||||
report.addIssue(packFolder, file, path, "Unexpected array value for type " + normalizedType.getSimpleName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldValidateNestedObject(normalizedType)) {
|
||||
report.addIssue(packFolder, file, path, "Expected object value for type " + normalizedType.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateCollection(File packFolder, File file, String path, Object value, Type genericType, Field sourceField, PackValidationReport report) {
|
||||
if (!(value instanceof JSONArray array)) {
|
||||
report.addIssue(packFolder, file, path, "Expected array value");
|
||||
return;
|
||||
}
|
||||
|
||||
Class<?> elementType = resolveCollectionElementType(genericType, sourceField);
|
||||
if (elementType == null || elementType == Object.class) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
Object element = array.get(i);
|
||||
validateValue(packFolder, file, path + "[" + i + "]", element, elementType, null, null, report);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateArray(File packFolder, File file, String path, Object value, Class<?> expectedType, PackValidationReport report) {
|
||||
if (!(value instanceof JSONArray array)) {
|
||||
report.addIssue(packFolder, file, path, "Expected array value");
|
||||
return;
|
||||
}
|
||||
|
||||
Class<?> componentType = normalizeType(expectedType.getComponentType());
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
Object element = array.get(i);
|
||||
validateValue(packFolder, file, path + "[" + i + "]", element, componentType, null, null, report);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateMap(File packFolder, File file, String path, Object value, Type genericType, PackValidationReport report) {
|
||||
if (!(value instanceof JSONObject object)) {
|
||||
report.addIssue(packFolder, file, path, "Expected object value");
|
||||
return;
|
||||
}
|
||||
|
||||
Class<?> valueType = resolveMapValueType(genericType);
|
||||
if (valueType == null || valueType == Object.class) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String key : object.keySet()) {
|
||||
Object child = object.get(key);
|
||||
validateValue(packFolder, file, path + "." + key, child, valueType, null, null, report);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
private void validateEnum(File packFolder, File file, String path, Object value, Class<?> expectedType, PackValidationReport report) {
|
||||
if (!(value instanceof String text)) {
|
||||
report.addIssue(packFolder, file, path, "Expected enum string for " + expectedType.getSimpleName());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Enum.valueOf((Class<? extends Enum>) expectedType, text);
|
||||
} catch (Throwable e) {
|
||||
report.addIssue(packFolder, file, path, "Unknown enum value \"" + text + "\" for " + expectedType.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void validateKeyed(File packFolder, File file, String path, Object value, Class<?> expectedType, PackValidationReport report) {
|
||||
if (!(value instanceof String text)) {
|
||||
report.addIssue(packFolder, file, path, "Expected namespaced key string for " + expectedType.getSimpleName());
|
||||
return;
|
||||
}
|
||||
|
||||
NamespacedKey key = NamespacedKey.fromString(text);
|
||||
if (key == null) {
|
||||
report.addIssue(packFolder, file, path, "Invalid namespaced key format \"" + text + "\"");
|
||||
return;
|
||||
}
|
||||
|
||||
KeyedRegistry<Object> registry;
|
||||
try {
|
||||
registry = RegistryUtil.lookup((Class<Object>) expectedType);
|
||||
} catch (Throwable e) {
|
||||
report.addIssue(packFolder, file, path, "Unable to resolve keyed registry for " + expectedType.getSimpleName() + ": " + simpleMessage(e));
|
||||
return;
|
||||
}
|
||||
|
||||
if (registry.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (registry.get(key) == null) {
|
||||
report.addIssue(packFolder, file, path, "Unknown registry key \"" + text + "\" for " + expectedType.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Field> getSerializableFields(Class<?> type) {
|
||||
return fieldCache.computeIfAbsent(type, this::buildSerializableFields);
|
||||
}
|
||||
|
||||
private Map<String, Field> buildSerializableFields(Class<?> type) {
|
||||
Map<String, Field> fields = new LinkedHashMap<>();
|
||||
Class<?> cursor = type;
|
||||
|
||||
while (cursor != null && cursor != Object.class) {
|
||||
Field[] declared = cursor.getDeclaredFields();
|
||||
for (Field field : declared) {
|
||||
int modifiers = field.getModifiers();
|
||||
if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers) || field.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fields.putIfAbsent(field.getName(), field);
|
||||
}
|
||||
cursor = cursor.getSuperclass();
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private Class<?> resolveCollectionElementType(Type genericType, Field sourceField) {
|
||||
if (sourceField != null) {
|
||||
ArrayType arrayType = sourceField.getDeclaredAnnotation(ArrayType.class);
|
||||
if (arrayType != null && arrayType.type() != Object.class) {
|
||||
return arrayType.type();
|
||||
}
|
||||
}
|
||||
|
||||
if (genericType instanceof ParameterizedType parameterizedType) {
|
||||
Type[] arguments = parameterizedType.getActualTypeArguments();
|
||||
if (arguments.length > 0) {
|
||||
Class<?> resolved = resolveType(arguments[0]);
|
||||
if (resolved != null) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
private Class<?> resolveMapValueType(Type genericType) {
|
||||
if (genericType instanceof ParameterizedType parameterizedType) {
|
||||
Type[] arguments = parameterizedType.getActualTypeArguments();
|
||||
if (arguments.length > 1) {
|
||||
Class<?> resolved = resolveType(arguments[1]);
|
||||
if (resolved != null) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
private Class<?> resolveType(Type type) {
|
||||
if (type instanceof Class<?> clazz) {
|
||||
return normalizeType(clazz);
|
||||
}
|
||||
|
||||
if (type instanceof ParameterizedType parameterizedType && parameterizedType.getRawType() instanceof Class<?> clazz) {
|
||||
return normalizeType(clazz);
|
||||
}
|
||||
|
||||
if (type instanceof WildcardType wildcardType) {
|
||||
Type[] upperBounds = wildcardType.getUpperBounds();
|
||||
if (upperBounds.length > 0) {
|
||||
return resolveType(upperBounds[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (type instanceof GenericArrayType genericArrayType) {
|
||||
Class<?> component = resolveType(genericArrayType.getGenericComponentType());
|
||||
if (component != null) {
|
||||
return normalizeType(Array.newInstance(component, 0).getClass());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Class<?> normalizeType(Class<?> type) {
|
||||
if (type == null) {
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
if (!type.isPrimitive()) {
|
||||
return type;
|
||||
}
|
||||
|
||||
if (type == int.class) return Integer.class;
|
||||
if (type == long.class) return Long.class;
|
||||
if (type == double.class) return Double.class;
|
||||
if (type == float.class) return Float.class;
|
||||
if (type == short.class) return Short.class;
|
||||
if (type == byte.class) return Byte.class;
|
||||
if (type == boolean.class) return Boolean.class;
|
||||
if (type == char.class) return Character.class;
|
||||
return type;
|
||||
}
|
||||
|
||||
private boolean shouldValidateNestedObject(Class<?> type) {
|
||||
String name = type.getName();
|
||||
return name.startsWith("art.arcane.iris.");
|
||||
}
|
||||
|
||||
private boolean isKeyedType(Class<?> type) {
|
||||
return keyedTypeCache.computeIfAbsent(type, this::resolveKeyedType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean resolveKeyedType(Class<?> type) {
|
||||
try {
|
||||
KeyedRegistry<Object> registry = RegistryUtil.lookup((Class<Object>) type);
|
||||
return !registry.isEmpty();
|
||||
} catch (Throwable ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String simpleMessage(Throwable throwable) {
|
||||
String message = throwable.getMessage();
|
||||
if (message == null || message.trim().isEmpty()) {
|
||||
return throwable.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
return throwable.getClass().getSimpleName() + ": " + message;
|
||||
}
|
||||
|
||||
private String relativePath(File packFolder, File file) {
|
||||
try {
|
||||
Path packPath = packFolder.toPath();
|
||||
Path filePath = file.toPath();
|
||||
return packPath.relativize(filePath).toString().replace(File.separatorChar, '/');
|
||||
} catch (Throwable ignored) {
|
||||
return file.getPath();
|
||||
}
|
||||
}
|
||||
|
||||
private final class PackValidationReport {
|
||||
private final String packName;
|
||||
private final int maxIssues;
|
||||
private final List<String> sampleIssues;
|
||||
private int filesChecked;
|
||||
private int errorCount;
|
||||
|
||||
private PackValidationReport(String packName, int maxIssues) {
|
||||
this.packName = packName;
|
||||
this.maxIssues = maxIssues;
|
||||
this.sampleIssues = new ArrayList<>();
|
||||
}
|
||||
|
||||
private void addIssue(File packFolder, File file, String path, String message) {
|
||||
errorCount++;
|
||||
if (sampleIssues.size() >= maxIssues) {
|
||||
return;
|
||||
}
|
||||
|
||||
String relative = relativePath(packFolder, file);
|
||||
sampleIssues.add(relative + " " + path + " -> " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,27 +29,36 @@ import art.arcane.iris.core.nms.datapack.IDataFixer.Dimension;
|
||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
import art.arcane.iris.engine.object.annotations.*;
|
||||
import art.arcane.iris.engine.object.annotations.functions.ComponentFlagFunction;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import art.arcane.iris.util.common.data.DataProvider;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
import java.io.*;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import art.arcane.iris.util.common.data.DataProvider;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@AllArgsConstructor
|
||||
@@ -379,36 +388,170 @@ public class IrisDimension extends IrisRegistrant {
|
||||
return landBiomeStyle;
|
||||
}
|
||||
|
||||
public void installBiomes(IDataFixer fixer, DataProvider data, KList<File> folders, KSet<String> biomes) {
|
||||
getAllBiomes(data)
|
||||
.stream()
|
||||
.filter(IrisBiome::isCustom)
|
||||
.map(IrisBiome::getCustomDerivitives)
|
||||
.flatMap(KList::stream)
|
||||
.parallel()
|
||||
.forEach(j -> {
|
||||
String json = j.generateJson(fixer);
|
||||
synchronized (biomes) {
|
||||
if (!biomes.add(j.getId())) {
|
||||
Iris.verbose("Duplicate Data Pack Biome: " + getLoadKey() + "/" + j.getId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
public void installBiomes(IDataFixer fixer, DataProvider data, KList<File> folders, KSet<String> biomes) {
|
||||
KMap<String, String> customBiomeToVanillaBiome = new KMap<>();
|
||||
String namespace = getLoadKey().toLowerCase(Locale.ROOT);
|
||||
|
||||
for (IrisBiome irisBiome : getAllBiomes(data)) {
|
||||
if (!irisBiome.isCustom()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Biome vanillaDerivative = irisBiome.getVanillaDerivative();
|
||||
NamespacedKey vanillaDerivativeKey = vanillaDerivative == null ? null : vanillaDerivative.getKey();
|
||||
String vanillaBiomeKey = vanillaDerivativeKey == null ? null : vanillaDerivativeKey.toString();
|
||||
|
||||
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
|
||||
String customBiomeId = customBiome.getId();
|
||||
String customBiomeKey = namespace + ":" + customBiomeId.toLowerCase(Locale.ROOT);
|
||||
String json = customBiome.generateJson(fixer);
|
||||
|
||||
synchronized (biomes) {
|
||||
if (!biomes.add(customBiomeId)) {
|
||||
Iris.verbose("Duplicate Data Pack Biome: " + getLoadKey() + "/" + customBiomeId);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (vanillaBiomeKey != null) {
|
||||
customBiomeToVanillaBiome.put(customBiomeKey, vanillaBiomeKey);
|
||||
}
|
||||
|
||||
for (File datapacks : folders) {
|
||||
File output = new File(datapacks, "iris/data/" + namespace + "/worldgen/biome/" + customBiomeId + ".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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
installStructureBiomeTags(folders, customBiomeToVanillaBiome);
|
||||
}
|
||||
|
||||
private void installStructureBiomeTags(KList<File> folders, KMap<String, String> customBiomeToVanillaBiome) {
|
||||
if (customBiomeToVanillaBiome.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
KMap<String, KList<String>> vanillaTags = INMS.get().getVanillaStructureBiomeTags();
|
||||
if (vanillaTags == null || vanillaTags.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
KMap<String, KSet<String>> customTagValues = new KMap<>();
|
||||
for (Map.Entry<String, String> customBiomeEntry : customBiomeToVanillaBiome.entrySet()) {
|
||||
String customBiomeKey = customBiomeEntry.getKey();
|
||||
String vanillaBiomeKey = customBiomeEntry.getValue();
|
||||
if (vanillaBiomeKey == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, KList<String>> tagEntry : vanillaTags.entrySet()) {
|
||||
KList<String> values = tagEntry.getValue();
|
||||
if (values == null || !values.contains(vanillaBiomeKey)) {
|
||||
continue;
|
||||
}
|
||||
customTagValues.computeIfAbsent(tagEntry.getKey(), key -> new KSet<>()).add(customBiomeKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (customTagValues.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (File datapacks : folders) {
|
||||
for (Map.Entry<String, KSet<String>> tagEntry : customTagValues.entrySet()) {
|
||||
String tagPath = tagEntry.getKey();
|
||||
KSet<String> customValues = tagEntry.getValue();
|
||||
if (customValues == null || customValues.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
File output = new File(datapacks, "iris/data/minecraft/tags/worldgen/biome/" + tagPath + ".json");
|
||||
try {
|
||||
writeMergedStructureBiomeTag(output, customValues);
|
||||
} catch (IOException e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeMergedStructureBiomeTag(File output, KSet<String> customValues) throws IOException {
|
||||
synchronized (IrisDimension.class) {
|
||||
KSet<String> mergedValues = readExistingStructureBiomeTagValues(output);
|
||||
mergedValues.addAll(customValues);
|
||||
|
||||
JSONArray values = new JSONArray();
|
||||
KList<String> sortedValues = new KList<>(mergedValues).sort();
|
||||
for (String value : sortedValues) {
|
||||
values.put(value);
|
||||
}
|
||||
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("replace", false);
|
||||
json.put("values", values);
|
||||
|
||||
writeAtomicFile(output, json.toString(4));
|
||||
}
|
||||
}
|
||||
|
||||
private KSet<String> readExistingStructureBiomeTagValues(File output) {
|
||||
KSet<String> values = new KSet<>();
|
||||
if (output == null || !output.exists()) {
|
||||
return values;
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject json = new JSONObject(IO.readAll(output));
|
||||
if (!json.has("values")) {
|
||||
return values;
|
||||
}
|
||||
|
||||
JSONArray existingValues = json.getJSONArray("values");
|
||||
for (int index = 0; index < existingValues.length(); index++) {
|
||||
Object rawValue = existingValues.get(index);
|
||||
if (rawValue == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String value = String.valueOf(rawValue).trim();
|
||||
if (!value.isEmpty()) {
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.warn("Skipping malformed existing structure biome tag file: " + output.getPath());
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
private void writeAtomicFile(File output, String contents) throws IOException {
|
||||
File parent = output.getParentFile();
|
||||
if (parent != null && !parent.exists()) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
|
||||
File temp = new File(parent, output.getName() + ".tmp-" + System.nanoTime());
|
||||
IO.writeAll(temp, contents);
|
||||
|
||||
Path tempPath = temp.toPath();
|
||||
Path outputPath = output.toPath();
|
||||
try {
|
||||
Files.move(tempPath, outputPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||
} catch (AtomicMoveNotSupportedException e) {
|
||||
Files.move(tempPath, outputPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
public Dimension getBaseDimension() {
|
||||
return switch (getEnvironment()) {
|
||||
|
||||
@@ -508,7 +508,17 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
|
||||
@Override
|
||||
public int getBaseHeight(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull HeightMap heightMap) {
|
||||
return 4;
|
||||
Engine currentEngine = engine;
|
||||
if (currentEngine == null || !setup.get()) {
|
||||
currentEngine = getEngine(worldInfo);
|
||||
}
|
||||
|
||||
boolean ignoreFluid = switch (heightMap) {
|
||||
case OCEAN_FLOOR, OCEAN_FLOOR_WG -> true;
|
||||
default -> false;
|
||||
};
|
||||
|
||||
return currentEngine.getMinHeight() + currentEngine.getHeight(x, z, ignoreFluid) + 1;
|
||||
}
|
||||
|
||||
private void computeStudioGenerator() {
|
||||
@@ -541,7 +551,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
|
||||
@Override
|
||||
public boolean shouldGenerateStructures() {
|
||||
return false;
|
||||
return IrisSettings.get().getGeneral().isAutoGenerateIntrinsicStructures();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,28 +25,29 @@ import org.bukkit.craftbukkit.v1_21_R7.CraftWorld;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class CustomBiomeSource extends BiomeSource {
|
||||
private static final int NOISE_BIOME_CACHE_MAX = 262144;
|
||||
|
||||
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;
|
||||
private final Holder<Biome> fallbackBiome;
|
||||
private final ConcurrentHashMap<Long, Holder<Biome>> noiseBiomeCache = new ConcurrentHashMap<>();
|
||||
|
||||
public CustomBiomeSource(long seed, Engine engine, World world) {
|
||||
this.engine = engine;
|
||||
this.seed = seed;
|
||||
this.biomeCustomRegistry = registry().lookup(Registries.BIOME).orElse(null);
|
||||
this.biomeRegistry = ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())).lookup(Registries.BIOME).orElse(null);
|
||||
this.rng = new RNG(engine.getSeedManager().getBiome());
|
||||
this.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry);
|
||||
this.customBiomes = fillCustomBiomes(this.biomeCustomRegistry, engine, this.fallbackBiome);
|
||||
}
|
||||
@@ -172,31 +173,85 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
|
||||
@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 == null) {
|
||||
return resolveFallbackBiome(biomeRegistry, biomeCustomRegistry);
|
||||
long cacheKey = packNoiseKey(x, y, z);
|
||||
Holder<Biome> cachedHolder = noiseBiomeCache.get(cacheKey);
|
||||
if (cachedHolder != null) {
|
||||
return cachedHolder;
|
||||
}
|
||||
|
||||
if (ib.isCustom()) {
|
||||
IrisBiomeCustom custom = ib.getCustomBiome(rng, x << 2, m, z << 2);
|
||||
if (custom != null) {
|
||||
Holder<Biome> holder = customBiomes.get(custom.getId());
|
||||
Holder<Biome> resolvedHolder = resolveNoiseBiomeHolder(x, y, z);
|
||||
Holder<Biome> existingHolder = noiseBiomeCache.putIfAbsent(cacheKey, resolvedHolder);
|
||||
if (existingHolder != null) {
|
||||
return existingHolder;
|
||||
}
|
||||
|
||||
if (noiseBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
|
||||
noiseBiomeCache.clear();
|
||||
}
|
||||
|
||||
return resolvedHolder;
|
||||
}
|
||||
|
||||
private Holder<Biome> resolveNoiseBiomeHolder(int x, int y, int z) {
|
||||
if (engine == null || engine.isClosed()) {
|
||||
return getFallbackBiome();
|
||||
}
|
||||
|
||||
if (engine.getComplex() == null) {
|
||||
return getFallbackBiome();
|
||||
}
|
||||
|
||||
int blockX = x << 2;
|
||||
int blockZ = z << 2;
|
||||
int blockY = (y - engine.getMinHeight()) << 2;
|
||||
IrisBiome irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||
if (irisBiome == null) {
|
||||
return getFallbackBiome();
|
||||
}
|
||||
|
||||
RNG noiseRng = new RNG(seed
|
||||
^ (((long) blockX) * 341873128712L)
|
||||
^ (((long) blockY) * 132897987541L)
|
||||
^ (((long) blockZ) * 42317861L));
|
||||
|
||||
if (irisBiome.isCustom()) {
|
||||
IrisBiomeCustom customBiome = irisBiome.getCustomBiome(noiseRng, blockX, blockY, blockZ);
|
||||
if (customBiome != null) {
|
||||
Holder<Biome> holder = customBiomes.get(customBiome.getId());
|
||||
if (holder != null) {
|
||||
return holder;
|
||||
}
|
||||
}
|
||||
|
||||
return resolveFallbackBiome(biomeRegistry, biomeCustomRegistry);
|
||||
return getFallbackBiome();
|
||||
}
|
||||
|
||||
org.bukkit.block.Biome v = ib.getSkyBiome(rng, x << 2, m, z << 2);
|
||||
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, v);
|
||||
org.bukkit.block.Biome vanillaBiome = irisBiome.getSkyBiome(noiseRng, blockX, blockY, blockZ);
|
||||
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, vanillaBiome);
|
||||
if (holder != null) {
|
||||
return holder;
|
||||
}
|
||||
|
||||
return resolveFallbackBiome(biomeRegistry, biomeCustomRegistry);
|
||||
return getFallbackBiome();
|
||||
}
|
||||
|
||||
private Holder<Biome> getFallbackBiome() {
|
||||
if (fallbackBiome != null) {
|
||||
return fallbackBiome;
|
||||
}
|
||||
|
||||
Holder<Biome> holder = resolveFallbackBiome(biomeRegistry, biomeCustomRegistry);
|
||||
if (holder != null) {
|
||||
return holder;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Unable to resolve any biome holder fallback for Iris biome source");
|
||||
}
|
||||
|
||||
private static long packNoiseKey(int x, int y, int z) {
|
||||
return (((long) x & 67108863L) << 38)
|
||||
| (((long) z & 67108863L) << 12)
|
||||
| ((long) y & 4095L);
|
||||
}
|
||||
|
||||
private static Holder<Biome> resolveCustomBiomeHolder(Registry<Biome> customRegistry, Engine engine, String customBiomeId) {
|
||||
|
||||
@@ -30,6 +30,7 @@ import net.minecraft.world.level.levelgen.*;
|
||||
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureSet;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -50,6 +51,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
|
||||
private final ChunkGenerator delegate;
|
||||
private final Engine engine;
|
||||
private volatile Registry<Structure> cachedStructureRegistry;
|
||||
private volatile Map<Structure, Integer> cachedStructureOrder;
|
||||
|
||||
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
|
||||
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
|
||||
@@ -152,20 +155,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
|
||||
if (!structureManager.shouldGenerateStructures())
|
||||
return;
|
||||
if (!IrisSettings.get().getGeneral().isAutoGenerateIntrinsicStructures()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSectionY());
|
||||
BlockPos blockPos = sectionPos.origin();
|
||||
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
|
||||
long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ());
|
||||
var structures = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
var list = structures.stream()
|
||||
.sorted(Comparator.comparingInt(s -> s.step().ordinal()))
|
||||
.toList();
|
||||
Registry<Structure> structureRegistry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
Map<Structure, Integer> structureOrder = getStructureOrder(structureRegistry);
|
||||
|
||||
var surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG);
|
||||
var ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG);
|
||||
var motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING);
|
||||
var motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES);
|
||||
Heightmap surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG);
|
||||
Heightmap ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG);
|
||||
Heightmap motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING);
|
||||
Heightmap motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES);
|
||||
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
@@ -174,21 +178,42 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
int noAir = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1;
|
||||
int noFluid = engine.getHeight(wX, wZ, true) + engine.getMinHeight() + 1;
|
||||
SET_HEIGHT.invoke(ocean, x, z, Math.min(noFluid, ocean.getFirstAvailable(x, z)));
|
||||
SET_HEIGHT.invoke(surface, x, z, Math.min(noAir, surface.getFirstAvailable(x, z)));
|
||||
SET_HEIGHT.invoke(motion, x, z, Math.min(noAir, motion.getFirstAvailable(x, z)));
|
||||
SET_HEIGHT.invoke(motionNoLeaves, x, z, Math.min(noAir, motionNoLeaves.getFirstAvailable(x, z)));
|
||||
int oceanHeight = ocean.getFirstAvailable(x, z);
|
||||
int surfaceHeight = surface.getFirstAvailable(x, z);
|
||||
int motionHeight = motion.getFirstAvailable(x, z);
|
||||
int motionNoLeavesHeight = motionNoLeaves.getFirstAvailable(x, z);
|
||||
if (noFluid > oceanHeight) {
|
||||
SET_HEIGHT.invoke(ocean, x, z, noFluid);
|
||||
}
|
||||
if (noAir > surfaceHeight) {
|
||||
SET_HEIGHT.invoke(surface, x, z, noAir);
|
||||
}
|
||||
if (noAir > motionHeight) {
|
||||
SET_HEIGHT.invoke(motion, x, z, noAir);
|
||||
}
|
||||
if (noAir > motionNoLeavesHeight) {
|
||||
SET_HEIGHT.invoke(motionNoLeaves, x, z, noAir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < list.size(); j++) {
|
||||
Structure structure = list.get(j);
|
||||
random.setFeatureSeed(i, j, structure.step().ordinal());
|
||||
Supplier<String> supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString);
|
||||
List<StructureStart> starts = new ArrayList<>(structureManager.startsForStructure(chunkAccess.getPos(), structure -> true));
|
||||
starts.sort(Comparator.comparingInt(start -> structureOrder.getOrDefault(start.getStructure(), Integer.MAX_VALUE)));
|
||||
|
||||
int seededStructureIndex = Integer.MIN_VALUE;
|
||||
for (int j = 0; j < starts.size(); j++) {
|
||||
StructureStart start = starts.get(j);
|
||||
Structure structure = start.getStructure();
|
||||
int structureIndex = structureOrder.getOrDefault(structure, j);
|
||||
if (structureIndex != seededStructureIndex) {
|
||||
random.setFeatureSeed(i, structureIndex, structure.step().ordinal());
|
||||
seededStructureIndex = structureIndex;
|
||||
}
|
||||
Supplier<String> supplier = () -> structureRegistry.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString);
|
||||
|
||||
try {
|
||||
level.setCurrentlyGenerating(supplier);
|
||||
structureManager.startsForStructure(sectionPos, structure).forEach((start) -> start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos()));
|
||||
start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos());
|
||||
} catch (Exception exception) {
|
||||
CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement");
|
||||
CrashReportCategory category = crashReport.addCategory("Feature");
|
||||
@@ -210,6 +235,35 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
|
||||
}
|
||||
|
||||
private Map<Structure, Integer> getStructureOrder(Registry<Structure> structureRegistry) {
|
||||
Map<Structure, Integer> localOrder = cachedStructureOrder;
|
||||
Registry<Structure> localRegistry = cachedStructureRegistry;
|
||||
if (localRegistry == structureRegistry && localOrder != null) {
|
||||
return localOrder;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
Map<Structure, Integer> synchronizedOrder = cachedStructureOrder;
|
||||
Registry<Structure> synchronizedRegistry = cachedStructureRegistry;
|
||||
if (synchronizedRegistry == structureRegistry && synchronizedOrder != null) {
|
||||
return synchronizedOrder;
|
||||
}
|
||||
|
||||
List<Structure> sortedStructures = structureRegistry.stream()
|
||||
.sorted(Comparator.comparingInt(structure -> structure.step().ordinal()))
|
||||
.toList();
|
||||
Map<Structure, Integer> builtOrder = new IdentityHashMap<>(sortedStructures.size());
|
||||
for (int index = 0; index < sortedStructures.size(); index++) {
|
||||
Structure structure = sortedStructures.get(index);
|
||||
builtOrder.put(structure, index);
|
||||
}
|
||||
|
||||
cachedStructureRegistry = structureRegistry;
|
||||
cachedStructureOrder = builtOrder;
|
||||
return builtOrder;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) {
|
||||
delegate.spawnOriginalMobs(regionlimitedworldaccess);
|
||||
|
||||
@@ -727,6 +727,48 @@ public class NMSBinding implements INMSBinding {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KMap<String, KList<String>> getVanillaStructureBiomeTags() {
|
||||
KMap<String, KList<String>> tags = new KMap<>();
|
||||
|
||||
Registry<net.minecraft.world.level.biome.Biome> registry = registry().lookup(Registries.BIOME).orElse(null);
|
||||
if (registry == null) {
|
||||
return tags;
|
||||
}
|
||||
|
||||
registry.getTags().forEach(named -> {
|
||||
TagKey<net.minecraft.world.level.biome.Biome> tagKey = named.key();
|
||||
Identifier location = tagKey.location();
|
||||
if (!"minecraft".equals(location.getNamespace())) {
|
||||
return;
|
||||
}
|
||||
|
||||
String path = location.getPath();
|
||||
if (!path.startsWith("has_structure/")) {
|
||||
return;
|
||||
}
|
||||
|
||||
KList<String> values = new KList<>();
|
||||
named.stream().forEach(holder -> {
|
||||
net.minecraft.world.level.biome.Biome biome = holder.value();
|
||||
Identifier biomeLocation = registry.getKey(biome);
|
||||
if (biomeLocation == null) {
|
||||
return;
|
||||
}
|
||||
if ("minecraft".equals(biomeLocation.getNamespace())) {
|
||||
values.add(biomeLocation.toString());
|
||||
}
|
||||
});
|
||||
|
||||
KList<String> uniqueValues = values.removeDuplicates();
|
||||
if (!uniqueValues.isEmpty()) {
|
||||
tags.put(path, uniqueValues);
|
||||
}
|
||||
});
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean missingDimensionTypes(String... keys) {
|
||||
var type = registry().lookupOrThrow(Registries.DIMENSION_TYPE);
|
||||
|
||||
Reference in New Issue
Block a user