diff --git a/core/src/main/java/art/arcane/iris/core/IrisSettings.java b/core/src/main/java/art/arcane/iris/core/IrisSettings.java index 467dd811d..fda47968c 100644 --- a/core/src/main/java/art/arcane/iris/core/IrisSettings.java +++ b/core/src/main/java/art/arcane/iris/core/IrisSettings.java @@ -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; diff --git a/core/src/main/java/art/arcane/iris/core/commands/CommandStudio.java b/core/src/main/java/art/arcane/iris/core/commands/CommandStudio.java index 57fa908ba..ae1ff0bd5 100644 --- a/core/src/main/java/art/arcane/iris/core/commands/CommandStudio.java +++ b/core/src/main/java/art/arcane/iris/core/commands/CommandStudio.java @@ -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") diff --git a/core/src/main/java/art/arcane/iris/core/nms/INMSBinding.java b/core/src/main/java/art/arcane/iris/core/nms/INMSBinding.java index 1c12ee6f0..51694f0ca 100644 --- a/core/src/main/java/art/arcane/iris/core/nms/INMSBinding.java +++ b/core/src/main/java/art/arcane/iris/core/nms/INMSBinding.java @@ -151,6 +151,10 @@ public interface INMSBinding { KList getStructureKeys(); + default KMap> getVanillaStructureBiomeTags() { + return new KMap<>(); + } + boolean missingDimensionTypes(String... keys); default boolean injectBukkit() { diff --git a/core/src/main/java/art/arcane/iris/core/service/CommandSVC.java b/core/src/main/java/art/arcane/iris/core/service/CommandSVC.java index 7ce06c13f..740db7ff3 100644 --- a/core/src/main/java/art/arcane/iris/core/service/CommandSVC.java +++ b/core/src/main/java/art/arcane/iris/core/service/CommandSVC.java @@ -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 "); - e.setCancelled(true); - } - } - public DirectorRuntimeEngine getDirector() { return directorCache.aquireNastyPrint(() -> DirectorEngineFactory.create( new CommandIris(), diff --git a/core/src/main/java/art/arcane/iris/core/service/ConversionSVC.java b/core/src/main/java/art/arcane/iris/core/service/ConversionSVC.java deleted file mode 100644 index 03ed5ce4a..000000000 --- a/core/src/main/java/art/arcane/iris/core/service/ConversionSVC.java +++ /dev/null @@ -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 . - */ - -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 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 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 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")); - } -} diff --git a/core/src/main/java/art/arcane/iris/core/service/PackValidationSVC.java b/core/src/main/java/art/arcane/iris/core/service/PackValidationSVC.java deleted file mode 100644 index 1a969d0ba..000000000 --- a/core/src/main/java/art/arcane/iris/core/service/PackValidationSVC.java +++ /dev/null @@ -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, Map> fieldCache = new ConcurrentHashMap<>(); - private final Map, 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> loaders = data.getLoaders().values(); - - for (ResourceLoader loader : loaders) { - Class rootType = loader.getObjectClass(); - if (rootType == null) { - continue; - } - - List 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> 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> 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 jsonFiles = new ArrayList<>(); - collectJsonFiles(folder, jsonFiles); - - for (File jsonFile : jsonFiles) { - report.filesChecked++; - validateJsonFile(data, packFolder, jsonFile, rootType, report); - } - } - - private void collectJsonFiles(File folder, List 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 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) 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 registry; - try { - registry = RegistryUtil.lookup((Class) 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 getSerializableFields(Class type) { - return fieldCache.computeIfAbsent(type, this::buildSerializableFields); - } - - private Map buildSerializableFields(Class type) { - Map 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 registry = RegistryUtil.lookup((Class) 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 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); - } - } -} diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java index 7748ce4d7..7c13d882d 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java @@ -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 folders, KSet 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 folders, KSet biomes) { + KMap 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 folders, KMap customBiomeToVanillaBiome) { + if (customBiomeToVanillaBiome.isEmpty()) { + return; + } + + KMap> vanillaTags = INMS.get().getVanillaStructureBiomeTags(); + if (vanillaTags == null || vanillaTags.isEmpty()) { + return; + } + + KMap> customTagValues = new KMap<>(); + for (Map.Entry customBiomeEntry : customBiomeToVanillaBiome.entrySet()) { + String customBiomeKey = customBiomeEntry.getKey(); + String vanillaBiomeKey = customBiomeEntry.getValue(); + if (vanillaBiomeKey == null) { + continue; + } + + for (Map.Entry> tagEntry : vanillaTags.entrySet()) { + KList 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> tagEntry : customTagValues.entrySet()) { + String tagPath = tagEntry.getKey(); + KSet 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 customValues) throws IOException { + synchronized (IrisDimension.class) { + KSet mergedValues = readExistingStructureBiomeTagValues(output); + mergedValues.addAll(customValues); + + JSONArray values = new JSONArray(); + KList 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 readExistingStructureBiomeTagValues(File output) { + KSet 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()) { diff --git a/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java index bedfd94a7..605d7ea4a 100644 --- a/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java @@ -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 diff --git a/nms/v1_21_R7/src/main/java/art/arcane/iris/core/nms/v1_21_R7/CustomBiomeSource.java b/nms/v1_21_R7/src/main/java/art/arcane/iris/core/nms/v1_21_R7/CustomBiomeSource.java index 8dd96e0cd..fd6596832 100644 --- a/nms/v1_21_R7/src/main/java/art/arcane/iris/core/nms/v1_21_R7/CustomBiomeSource.java +++ b/nms/v1_21_R7/src/main/java/art/arcane/iris/core/nms/v1_21_R7/CustomBiomeSource.java @@ -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 biomeCustomRegistry; private final Registry biomeRegistry; private final AtomicCache registryAccess = new AtomicCache<>(); - private final RNG rng; private final KMap> customBiomes; private final Holder fallbackBiome; + private final ConcurrentHashMap> 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 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 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 holder = customBiomes.get(custom.getId()); + Holder resolvedHolder = resolveNoiseBiomeHolder(x, y, z); + Holder existingHolder = noiseBiomeCache.putIfAbsent(cacheKey, resolvedHolder); + if (existingHolder != null) { + return existingHolder; + } + + if (noiseBiomeCache.size() > NOISE_BIOME_CACHE_MAX) { + noiseBiomeCache.clear(); + } + + return resolvedHolder; + } + + private Holder 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 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 holder = NMSBinding.biomeToBiomeBase(biomeRegistry, v); + org.bukkit.block.Biome vanillaBiome = irisBiome.getSkyBiome(noiseRng, blockX, blockY, blockZ); + Holder holder = NMSBinding.biomeToBiomeBase(biomeRegistry, vanillaBiome); if (holder != null) { return holder; } - return resolveFallbackBiome(biomeRegistry, biomeCustomRegistry); + return getFallbackBiome(); + } + + private Holder getFallbackBiome() { + if (fallbackBiome != null) { + return fallbackBiome; + } + + Holder 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 resolveCustomBiomeHolder(Registry customRegistry, Engine engine, String customBiomeId) { diff --git a/nms/v1_21_R7/src/main/java/art/arcane/iris/core/nms/v1_21_R7/IrisChunkGenerator.java b/nms/v1_21_R7/src/main/java/art/arcane/iris/core/nms/v1_21_R7/IrisChunkGenerator.java index 194e02404..a98f78bac 100644 --- a/nms/v1_21_R7/src/main/java/art/arcane/iris/core/nms/v1_21_R7/IrisChunkGenerator.java +++ b/nms/v1_21_R7/src/main/java/art/arcane/iris/core/nms/v1_21_R7/IrisChunkGenerator.java @@ -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 SET_HEIGHT; private final ChunkGenerator delegate; private final Engine engine; + private volatile Registry cachedStructureRegistry; + private volatile Map 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 structureRegistry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE); + Map 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 supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); + List 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 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 getStructureOrder(Registry structureRegistry) { + Map localOrder = cachedStructureOrder; + Registry localRegistry = cachedStructureRegistry; + if (localRegistry == structureRegistry && localOrder != null) { + return localOrder; + } + + synchronized (this) { + Map synchronizedOrder = cachedStructureOrder; + Registry synchronizedRegistry = cachedStructureRegistry; + if (synchronizedRegistry == structureRegistry && synchronizedOrder != null) { + return synchronizedOrder; + } + + List sortedStructures = structureRegistry.stream() + .sorted(Comparator.comparingInt(structure -> structure.step().ordinal())) + .toList(); + Map 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); diff --git a/nms/v1_21_R7/src/main/java/art/arcane/iris/core/nms/v1_21_R7/NMSBinding.java b/nms/v1_21_R7/src/main/java/art/arcane/iris/core/nms/v1_21_R7/NMSBinding.java index 9eeeff250..fe6cc5a46 100644 --- a/nms/v1_21_R7/src/main/java/art/arcane/iris/core/nms/v1_21_R7/NMSBinding.java +++ b/nms/v1_21_R7/src/main/java/art/arcane/iris/core/nms/v1_21_R7/NMSBinding.java @@ -727,6 +727,48 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public KMap> getVanillaStructureBiomeTags() { + KMap> tags = new KMap<>(); + + Registry registry = registry().lookup(Registries.BIOME).orElse(null); + if (registry == null) { + return tags; + } + + registry.getTags().forEach(named -> { + TagKey tagKey = named.key(); + Identifier location = tagKey.location(); + if (!"minecraft".equals(location.getNamespace())) { + return; + } + + String path = location.getPath(); + if (!path.startsWith("has_structure/")) { + return; + } + + KList 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 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);