mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-06-18 23:01:07 +00:00
JIGGY!
This commit is contained in:
@@ -215,9 +215,6 @@ public class IrisSettings {
|
|||||||
public boolean commandSounds = true;
|
public boolean commandSounds = true;
|
||||||
public boolean debug = false;
|
public boolean debug = false;
|
||||||
public boolean dumpMantleOnError = false;
|
public boolean dumpMantleOnError = false;
|
||||||
public boolean validatePacksOnStartup = true;
|
|
||||||
public boolean stopStartupOnPackValidationFailure = false;
|
|
||||||
public int maxPackValidationErrorsPerPack = 200;
|
|
||||||
public boolean disableNMS = false;
|
public boolean disableNMS = false;
|
||||||
public boolean pluginMetrics = true;
|
public boolean pluginMetrics = true;
|
||||||
public boolean splashLogoStartup = 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.gui.VisionGUI;
|
||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.core.project.IrisProject;
|
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.service.StudioSVC;
|
||||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||||
import art.arcane.iris.engine.IrisNoisemapPrebakePipeline;
|
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)
|
@Director(description = "Execute a script", aliases = "run", origin = DirectorOrigin.PLAYER)
|
||||||
public void execute(
|
public void execute(
|
||||||
@Param(description = "The script to run")
|
@Param(description = "The script to run")
|
||||||
|
|||||||
@@ -151,6 +151,10 @@ public interface INMSBinding {
|
|||||||
|
|
||||||
KList<String> getStructureKeys();
|
KList<String> getStructureKeys();
|
||||||
|
|
||||||
|
default KMap<String, KList<String>> getVanillaStructureBiomeTags() {
|
||||||
|
return new KMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
boolean missingDimensionTypes(String... keys);
|
boolean missingDimensionTypes(String... keys);
|
||||||
|
|
||||||
default boolean injectBukkit() {
|
default boolean injectBukkit() {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ package art.arcane.iris.core.service;
|
|||||||
import art.arcane.iris.Iris;
|
import art.arcane.iris.Iris;
|
||||||
import art.arcane.iris.core.IrisSettings;
|
import art.arcane.iris.core.IrisSettings;
|
||||||
import art.arcane.iris.core.commands.CommandIris;
|
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.engine.data.cache.AtomicCache;
|
||||||
import art.arcane.iris.util.common.director.DirectorContext;
|
import art.arcane.iris.util.common.director.DirectorContext;
|
||||||
import art.arcane.iris.util.common.director.DirectorContextHandler;
|
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.PluginCommand;
|
||||||
import org.bukkit.command.TabCompleter;
|
import org.bukkit.command.TabCompleter;
|
||||||
import org.bukkit.entity.Player;
|
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.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
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() {
|
public DirectorRuntimeEngine getDirector() {
|
||||||
return directorCache.aquireNastyPrint(() -> DirectorEngineFactory.create(
|
return directorCache.aquireNastyPrint(() -> DirectorEngineFactory.create(
|
||||||
new CommandIris(),
|
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.data.cache.AtomicCache;
|
||||||
import art.arcane.iris.engine.object.annotations.*;
|
import art.arcane.iris.engine.object.annotations.*;
|
||||||
import art.arcane.iris.engine.object.annotations.functions.ComponentFlagFunction;
|
import art.arcane.iris.engine.object.annotations.functions.ComponentFlagFunction;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
import art.arcane.volmlib.util.collection.KMap;
|
import art.arcane.volmlib.util.collection.KMap;
|
||||||
import art.arcane.volmlib.util.collection.KSet;
|
import art.arcane.volmlib.util.collection.KSet;
|
||||||
import art.arcane.iris.util.common.data.DataProvider;
|
import art.arcane.iris.util.common.data.DataProvider;
|
||||||
import art.arcane.volmlib.util.io.IO;
|
import art.arcane.volmlib.util.io.IO;
|
||||||
import art.arcane.volmlib.util.json.JSONObject;
|
import art.arcane.volmlib.util.json.JSONArray;
|
||||||
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
import art.arcane.volmlib.util.json.JSONObject;
|
||||||
import art.arcane.volmlib.util.math.Position2;
|
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.Position2;
|
||||||
import art.arcane.iris.util.project.noise.CNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
import art.arcane.iris.util.project.noise.CNG;
|
||||||
import lombok.AllArgsConstructor;
|
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||||
import lombok.Data;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.NoArgsConstructor;
|
||||||
import org.bukkit.Material;
|
import lombok.experimental.Accessors;
|
||||||
import org.bukkit.World.Environment;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.World.Environment;
|
||||||
import java.io.*;
|
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)
|
@Accessors(chain = true)
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@@ -379,36 +388,170 @@ public class IrisDimension extends IrisRegistrant {
|
|||||||
return landBiomeStyle;
|
return landBiomeStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void installBiomes(IDataFixer fixer, DataProvider data, KList<File> folders, KSet<String> biomes) {
|
public void installBiomes(IDataFixer fixer, DataProvider data, KList<File> folders, KSet<String> biomes) {
|
||||||
getAllBiomes(data)
|
KMap<String, String> customBiomeToVanillaBiome = new KMap<>();
|
||||||
.stream()
|
String namespace = getLoadKey().toLowerCase(Locale.ROOT);
|
||||||
.filter(IrisBiome::isCustom)
|
|
||||||
.map(IrisBiome::getCustomDerivitives)
|
for (IrisBiome irisBiome : getAllBiomes(data)) {
|
||||||
.flatMap(KList::stream)
|
if (!irisBiome.isCustom()) {
|
||||||
.parallel()
|
continue;
|
||||||
.forEach(j -> {
|
}
|
||||||
String json = j.generateJson(fixer);
|
|
||||||
synchronized (biomes) {
|
Biome vanillaDerivative = irisBiome.getVanillaDerivative();
|
||||||
if (!biomes.add(j.getId())) {
|
NamespacedKey vanillaDerivativeKey = vanillaDerivative == null ? null : vanillaDerivative.getKey();
|
||||||
Iris.verbose("Duplicate Data Pack Biome: " + getLoadKey() + "/" + j.getId());
|
String vanillaBiomeKey = vanillaDerivativeKey == null ? null : vanillaDerivativeKey.toString();
|
||||||
return;
|
|
||||||
}
|
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
|
||||||
}
|
String customBiomeId = customBiome.getId();
|
||||||
|
String customBiomeKey = namespace + ":" + customBiomeId.toLowerCase(Locale.ROOT);
|
||||||
for (File datapacks : folders) {
|
String json = customBiome.generateJson(fixer);
|
||||||
File output = new File(datapacks, "iris/data/" + getLoadKey().toLowerCase() + "/worldgen/biome/" + j.getId() + ".json");
|
|
||||||
|
synchronized (biomes) {
|
||||||
Iris.verbose(" Installing Data Pack Biome: " + output.getPath());
|
if (!biomes.add(customBiomeId)) {
|
||||||
output.getParentFile().mkdirs();
|
Iris.verbose("Duplicate Data Pack Biome: " + getLoadKey() + "/" + customBiomeId);
|
||||||
try {
|
continue;
|
||||||
IO.writeAll(output, json);
|
}
|
||||||
} catch (IOException e) {
|
}
|
||||||
Iris.reportError(e);
|
|
||||||
e.printStackTrace();
|
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() {
|
public Dimension getBaseDimension() {
|
||||||
return switch (getEnvironment()) {
|
return switch (getEnvironment()) {
|
||||||
|
|||||||
@@ -508,7 +508,17 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBaseHeight(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull HeightMap heightMap) {
|
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() {
|
private void computeStudioGenerator() {
|
||||||
@@ -541,7 +551,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldGenerateStructures() {
|
public boolean shouldGenerateStructures() {
|
||||||
return false;
|
return IrisSettings.get().getGeneral().isAutoGenerateIntrinsicStructures();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+69
-14
@@ -25,28 +25,29 @@ import org.bukkit.craftbukkit.v1_21_R7.CraftWorld;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class CustomBiomeSource extends BiomeSource {
|
public class CustomBiomeSource extends BiomeSource {
|
||||||
|
private static final int NOISE_BIOME_CACHE_MAX = 262144;
|
||||||
|
|
||||||
private final long seed;
|
private final long seed;
|
||||||
private final Engine engine;
|
private final Engine engine;
|
||||||
private final Registry<Biome> biomeCustomRegistry;
|
private final Registry<Biome> biomeCustomRegistry;
|
||||||
private final Registry<Biome> biomeRegistry;
|
private final Registry<Biome> biomeRegistry;
|
||||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||||
private final RNG rng;
|
|
||||||
private final KMap<String, Holder<Biome>> customBiomes;
|
private final KMap<String, Holder<Biome>> customBiomes;
|
||||||
private final Holder<Biome> fallbackBiome;
|
private final Holder<Biome> fallbackBiome;
|
||||||
|
private final ConcurrentHashMap<Long, Holder<Biome>> noiseBiomeCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public CustomBiomeSource(long seed, Engine engine, World world) {
|
public CustomBiomeSource(long seed, Engine engine, World world) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.seed = seed;
|
this.seed = seed;
|
||||||
this.biomeCustomRegistry = registry().lookup(Registries.BIOME).orElse(null);
|
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.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.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry);
|
||||||
this.customBiomes = fillCustomBiomes(this.biomeCustomRegistry, engine, this.fallbackBiome);
|
this.customBiomes = fillCustomBiomes(this.biomeCustomRegistry, engine, this.fallbackBiome);
|
||||||
}
|
}
|
||||||
@@ -172,31 +173,85 @@ public class CustomBiomeSource extends BiomeSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
||||||
int m = (y - engine.getMinHeight()) << 2;
|
long cacheKey = packNoiseKey(x, y, z);
|
||||||
IrisBiome ib = engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2);
|
Holder<Biome> cachedHolder = noiseBiomeCache.get(cacheKey);
|
||||||
if (ib == null) {
|
if (cachedHolder != null) {
|
||||||
return resolveFallbackBiome(biomeRegistry, biomeCustomRegistry);
|
return cachedHolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ib.isCustom()) {
|
Holder<Biome> resolvedHolder = resolveNoiseBiomeHolder(x, y, z);
|
||||||
IrisBiomeCustom custom = ib.getCustomBiome(rng, x << 2, m, z << 2);
|
Holder<Biome> existingHolder = noiseBiomeCache.putIfAbsent(cacheKey, resolvedHolder);
|
||||||
if (custom != null) {
|
if (existingHolder != null) {
|
||||||
Holder<Biome> holder = customBiomes.get(custom.getId());
|
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) {
|
if (holder != null) {
|
||||||
return holder;
|
return holder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolveFallbackBiome(biomeRegistry, biomeCustomRegistry);
|
return getFallbackBiome();
|
||||||
}
|
}
|
||||||
|
|
||||||
org.bukkit.block.Biome v = ib.getSkyBiome(rng, x << 2, m, z << 2);
|
org.bukkit.block.Biome vanillaBiome = irisBiome.getSkyBiome(noiseRng, blockX, blockY, blockZ);
|
||||||
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, v);
|
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, vanillaBiome);
|
||||||
if (holder != null) {
|
if (holder != null) {
|
||||||
return holder;
|
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) {
|
private static Holder<Biome> resolveCustomBiomeHolder(Registry<Biome> customRegistry, Engine engine, String customBiomeId) {
|
||||||
|
|||||||
+71
-17
@@ -30,6 +30,7 @@ import net.minecraft.world.level.levelgen.*;
|
|||||||
import net.minecraft.world.level.levelgen.blending.Blender;
|
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
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.StructureSet;
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@@ -50,6 +51,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
|||||||
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
|
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
|
||||||
private final ChunkGenerator delegate;
|
private final ChunkGenerator delegate;
|
||||||
private final Engine engine;
|
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) {
|
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
|
||||||
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
|
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) {
|
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
|
||||||
if (!structureManager.shouldGenerateStructures())
|
if (!structureManager.shouldGenerateStructures())
|
||||||
return;
|
return;
|
||||||
|
if (!IrisSettings.get().getGeneral().isAutoGenerateIntrinsicStructures()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSectionY());
|
SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSectionY());
|
||||||
BlockPos blockPos = sectionPos.origin();
|
BlockPos blockPos = sectionPos.origin();
|
||||||
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
|
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
|
||||||
long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ());
|
long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ());
|
||||||
var structures = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
Registry<Structure> structureRegistry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||||
var list = structures.stream()
|
Map<Structure, Integer> structureOrder = getStructureOrder(structureRegistry);
|
||||||
.sorted(Comparator.comparingInt(s -> s.step().ordinal()))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
var surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG);
|
Heightmap surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG);
|
||||||
var ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG);
|
Heightmap ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG);
|
||||||
var motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING);
|
Heightmap motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING);
|
||||||
var motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES);
|
Heightmap motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES);
|
||||||
|
|
||||||
for (int x = 0; x < 16; x++) {
|
for (int x = 0; x < 16; x++) {
|
||||||
for (int z = 0; z < 16; z++) {
|
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 noAir = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1;
|
||||||
int noFluid = engine.getHeight(wX, wZ, true) + 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)));
|
int oceanHeight = ocean.getFirstAvailable(x, z);
|
||||||
SET_HEIGHT.invoke(surface, x, z, Math.min(noAir, surface.getFirstAvailable(x, z)));
|
int surfaceHeight = surface.getFirstAvailable(x, z);
|
||||||
SET_HEIGHT.invoke(motion, x, z, Math.min(noAir, motion.getFirstAvailable(x, z)));
|
int motionHeight = motion.getFirstAvailable(x, z);
|
||||||
SET_HEIGHT.invoke(motionNoLeaves, x, z, Math.min(noAir, motionNoLeaves.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++) {
|
List<StructureStart> starts = new ArrayList<>(structureManager.startsForStructure(chunkAccess.getPos(), structure -> true));
|
||||||
Structure structure = list.get(j);
|
starts.sort(Comparator.comparingInt(start -> structureOrder.getOrDefault(start.getStructure(), Integer.MAX_VALUE)));
|
||||||
random.setFeatureSeed(i, j, structure.step().ordinal());
|
|
||||||
Supplier<String> supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString);
|
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 {
|
try {
|
||||||
level.setCurrentlyGenerating(supplier);
|
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) {
|
} catch (Exception exception) {
|
||||||
CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement");
|
CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement");
|
||||||
CrashReportCategory category = crashReport.addCategory("Feature");
|
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);
|
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
|
@Override
|
||||||
public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) {
|
public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) {
|
||||||
delegate.spawnOriginalMobs(regionlimitedworldaccess);
|
delegate.spawnOriginalMobs(regionlimitedworldaccess);
|
||||||
|
|||||||
@@ -727,6 +727,48 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return keys;
|
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
|
@Override
|
||||||
public boolean missingDimensionTypes(String... keys) {
|
public boolean missingDimensionTypes(String... keys) {
|
||||||
var type = registry().lookupOrThrow(Registries.DIMENSION_TYPE);
|
var type = registry().lookupOrThrow(Registries.DIMENSION_TYPE);
|
||||||
|
|||||||
Reference in New Issue
Block a user