From 2ca8cc7ad326c4f1d5a61c52d79efa6a8a7933f5 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Sat, 21 Feb 2026 18:56:10 -0500 Subject: [PATCH] f --- .../iris/core/ExternalDataPackPipeline.java | 351 +++++++++++++++++- .../arcane/iris/core/ServerConfigurator.java | 57 ++- .../iris/core/commands/CommandWhat.java | 3 +- .../arcane/iris/core/edit/BlockSignal.java | 98 +++-- .../arcane/iris/core/edit/DustRevealer.java | 14 +- .../arcane/iris/core/tools/IrisCreator.java | 21 +- .../engine/IrisNoisemapPrebakePipeline.java | 66 +++- .../arcane/iris/engine/IrisWorldManager.java | 142 ++++--- .../arcane/iris/engine/framework/Locator.java | 41 +- .../mantle/components/IrisCaveCarver3D.java | 52 +-- .../components/MantleObjectComponent.java | 232 +++++++++++- .../arcane/iris/engine/object/IrisEntity.java | 39 +- .../engine/object/IrisExternalDatapack.java | 3 + .../arcane/iris/engine/object/IrisObject.java | 60 ++- .../core/nms/v1_21_R7/IrisChunkGenerator.java | 30 +- 15 files changed, 1014 insertions(+), 195 deletions(-) diff --git a/core/src/main/java/art/arcane/iris/core/ExternalDataPackPipeline.java b/core/src/main/java/art/arcane/iris/core/ExternalDataPackPipeline.java index 4236a84a5..b6873adf6 100644 --- a/core/src/main/java/art/arcane/iris/core/ExternalDataPackPipeline.java +++ b/core/src/main/java/art/arcane/iris/core/ExternalDataPackPipeline.java @@ -91,6 +91,8 @@ public final class ExternalDataPackPipeline { private static final String IMPORT_PREFIX = "imports"; private static final String LOCATE_MANIFEST_PATH = "cache/external-datapack-locate-manifest.json"; private static final String OBJECT_LOCATE_MANIFEST_PATH = "cache/external-datapack-object-locate-manifest.json"; + private static final String SMARTBORE_STRUCTURE_MANIFEST_PATH = "cache/external-datapack-smartbore-manifest.json"; + private static final String SUPPRESSED_VANILLA_STRUCTURE_MANIFEST_PATH = "cache/external-datapack-suppressed-vanilla-structures.json"; private static final int CONNECT_TIMEOUT_MS = 4000; private static final int READ_TIMEOUT_MS = 8000; private static final int IMPORT_PARALLELISM = Math.max(1, Math.min(8, Runtime.getRuntime().availableProcessors())); @@ -99,6 +101,8 @@ public final class ExternalDataPackPipeline { private static final Map PACK_ENVIRONMENT_CACHE = new ConcurrentHashMap<>(); private static final Map> RESOLVED_LOCATE_STRUCTURES_BY_ID = new ConcurrentHashMap<>(); private static final Map> RESOLVED_LOCATE_STRUCTURES_BY_OBJECT_KEY = new ConcurrentHashMap<>(); + private static final Map> RESOLVED_SMARTBORE_STRUCTURES_BY_ID = new ConcurrentHashMap<>(); + private static final Set SUPPRESSED_VANILLA_STRUCTURE_KEYS = ConcurrentHashMap.newKeySet(); private static final AtomicCache> VANILLA_STRUCTURE_PLACEMENTS = new AtomicCache<>(); private static final BlockData AIR = B.getAir(); @@ -203,11 +207,48 @@ public final class ExternalDataPackPipeline { return Set.copyOf(structures); } + public static Set snapshotSmartBoreStructureKeys() { + if (RESOLVED_SMARTBORE_STRUCTURES_BY_ID.isEmpty()) { + Map> manifest = readSmartBoreManifest(); + if (!manifest.isEmpty()) { + RESOLVED_SMARTBORE_STRUCTURES_BY_ID.putAll(manifest); + } + } + + LinkedHashSet structures = new LinkedHashSet<>(); + for (Set values : RESOLVED_SMARTBORE_STRUCTURES_BY_ID.values()) { + if (values == null || values.isEmpty()) { + continue; + } + + for (String value : values) { + String normalized = normalizeLocateStructure(value); + if (!normalized.isBlank()) { + structures.add(normalized); + } + } + } + + return Set.copyOf(structures); + } + + public static Set snapshotSuppressedVanillaStructureKeys() { + if (SUPPRESSED_VANILLA_STRUCTURE_KEYS.isEmpty()) { + Set manifest = readSuppressedVanillaStructureManifest(); + if (!manifest.isEmpty()) { + SUPPRESSED_VANILLA_STRUCTURE_KEYS.addAll(manifest); + } + } + return Set.copyOf(SUPPRESSED_VANILLA_STRUCTURE_KEYS); + } + public static PipelineSummary processDatapacks(List requests, Map> worldDatapackFoldersByPack) { PipelineSummary summary = new PipelineSummary(); PACK_ENVIRONMENT_CACHE.clear(); RESOLVED_LOCATE_STRUCTURES_BY_ID.clear(); RESOLVED_LOCATE_STRUCTURES_BY_OBJECT_KEY.clear(); + RESOLVED_SMARTBORE_STRUCTURES_BY_ID.clear(); + SUPPRESSED_VANILLA_STRUCTURE_KEYS.clear(); Set knownWorldDatapackFolders = new LinkedHashSet<>(); if (worldDatapackFoldersByPack != null) { @@ -233,6 +274,8 @@ public final class ExternalDataPackPipeline { Iris.info("Downloading datapacks [0/0] Downloading/Done!"); writeLocateManifest(Map.of()); writeObjectLocateManifest(Map.of()); + writeSmartBoreManifest(Map.of()); + writeSuppressedVanillaStructureManifest(Set.of()); summary.legacyWorldCopyRemovals += pruneManagedWorldDatapacks(knownWorldDatapackFolders, Set.of()); return summary; } @@ -240,6 +283,8 @@ public final class ExternalDataPackPipeline { List sourceInputs = new ArrayList<>(); LinkedHashMap> resolvedLocateStructuresById = new LinkedHashMap<>(); LinkedHashMap> resolvedLocateStructuresByObjectKey = new LinkedHashMap<>(); + LinkedHashMap> resolvedSmartBoreStructuresById = new LinkedHashMap<>(); + LinkedHashSet suppressedVanillaStructures = new LinkedHashSet<>(); for (int requestIndex = 0; requestIndex < normalizedRequests.size(); requestIndex++) { DatapackRequest request = normalizedRequests.get(requestIndex); if (request == null) { @@ -285,8 +330,12 @@ public final class ExternalDataPackPipeline { } writeLocateManifest(resolvedLocateStructuresById); writeObjectLocateManifest(resolvedLocateStructuresByObjectKey); + writeSmartBoreManifest(resolvedSmartBoreStructuresById); + writeSuppressedVanillaStructureManifest(suppressedVanillaStructures); RESOLVED_LOCATE_STRUCTURES_BY_ID.putAll(resolvedLocateStructuresById); RESOLVED_LOCATE_STRUCTURES_BY_OBJECT_KEY.putAll(resolvedLocateStructuresByObjectKey); + RESOLVED_SMARTBORE_STRUCTURES_BY_ID.putAll(resolvedSmartBoreStructuresById); + SUPPRESSED_VANILLA_STRUCTURE_KEYS.addAll(suppressedVanillaStructures); return summary; } @@ -376,6 +425,13 @@ public final class ExternalDataPackPipeline { summary.worldDatapacksInstalled += projectionResult.installedDatapacks(); summary.worldAssetsInstalled += projectionResult.installedAssets(); mergeResolvedLocateStructures(resolvedLocateStructuresById, request.id(), projectionResult.resolvedLocateStructures()); + suppressedVanillaStructures.addAll(determineSuppressedVanillaStructures(request, projectionResult.projectedStructureKeys())); + if (request.supportSmartBore()) { + LinkedHashSet smartBoreTargets = new LinkedHashSet<>(); + smartBoreTargets.addAll(request.resolvedLocateStructures()); + smartBoreTargets.addAll(projectionResult.resolvedLocateStructures()); + mergeResolvedLocateStructures(resolvedSmartBoreStructuresById, request.id(), smartBoreTargets); + } LinkedHashSet objectLocateTargets = new LinkedHashSet<>(); objectLocateTargets.addAll(request.resolvedLocateStructures()); objectLocateTargets.addAll(projectionResult.resolvedLocateStructures()); @@ -425,8 +481,12 @@ public final class ExternalDataPackPipeline { writeLocateManifest(resolvedLocateStructuresById); writeObjectLocateManifest(resolvedLocateStructuresByObjectKey); + writeSmartBoreManifest(resolvedSmartBoreStructuresById); + writeSuppressedVanillaStructureManifest(suppressedVanillaStructures); RESOLVED_LOCATE_STRUCTURES_BY_ID.putAll(resolvedLocateStructuresById); RESOLVED_LOCATE_STRUCTURES_BY_OBJECT_KEY.putAll(resolvedLocateStructuresByObjectKey); + RESOLVED_SMARTBORE_STRUCTURES_BY_ID.putAll(resolvedSmartBoreStructuresById); + SUPPRESSED_VANILLA_STRUCTURE_KEYS.addAll(suppressedVanillaStructures); return summary; } @@ -438,6 +498,14 @@ public final class ExternalDataPackPipeline { return Iris.instance.getDataFile(OBJECT_LOCATE_MANIFEST_PATH); } + private static File getSmartBoreManifestFile() { + return Iris.instance.getDataFile(SMARTBORE_STRUCTURE_MANIFEST_PATH); + } + + private static File getSuppressedVanillaStructureManifestFile() { + return Iris.instance.getDataFile(SUPPRESSED_VANILLA_STRUCTURE_MANIFEST_PATH); + } + private static String normalizeLocateId(String id) { if (id == null) { return ""; @@ -464,6 +532,26 @@ public final class ExternalDataPackPipeline { return normalized; } + private static Set determineSuppressedVanillaStructures(DatapackRequest request, Set projectedStructureKeys) { + LinkedHashSet suppressed = new LinkedHashSet<>(); + if (request == null || !request.replaceVanilla() || request.alongsideMode()) { + return suppressed; + } + + Set projected = projectedStructureKeys == null ? Set.of() : projectedStructureKeys; + for (String structureTarget : request.structures()) { + String normalizedTarget = normalizeLocateStructure(structureTarget); + if (normalizedTarget.isBlank() || !normalizedTarget.startsWith("minecraft:")) { + continue; + } + if (!projected.contains(normalizedTarget)) { + suppressed.add(normalizedTarget); + } + } + + return suppressed; + } + private static String normalizeObjectLoadKey(String objectKey) { if (objectKey == null) { return ""; @@ -678,6 +766,93 @@ public final class ExternalDataPackPipeline { } } + private static void writeSmartBoreManifest(Map> resolvedSmartBoreStructuresById) { + File output = getSmartBoreManifestFile(); + LinkedHashMap> normalized = new LinkedHashMap<>(); + if (resolvedSmartBoreStructuresById != null) { + for (Map.Entry> entry : resolvedSmartBoreStructuresById.entrySet()) { + String normalizedId = normalizeLocateId(entry.getKey()); + if (normalizedId.isBlank()) { + continue; + } + + LinkedHashSet structures = new LinkedHashSet<>(); + Set values = entry.getValue(); + if (values != null) { + for (String structure : values) { + String normalizedStructure = normalizeLocateStructure(structure); + if (!normalizedStructure.isBlank()) { + structures.add(normalizedStructure); + } + } + } + + if (!structures.isEmpty()) { + normalized.put(normalizedId, Set.copyOf(structures)); + } + } + } + + JSONObject root = new JSONObject(); + root.put("generatedAt", Instant.now().toString()); + JSONObject mappings = new JSONObject(); + ArrayList ids = new ArrayList<>(normalized.keySet()); + ids.sort(String::compareTo); + for (String id : ids) { + Set structures = normalized.get(id); + if (structures == null || structures.isEmpty()) { + continue; + } + + ArrayList sortedStructures = new ArrayList<>(structures); + sortedStructures.sort(String::compareTo); + JSONArray values = new JSONArray(); + for (String structure : sortedStructures) { + values.put(structure); + } + mappings.put(id, values); + } + root.put("ids", mappings); + + try { + writeBytesToFile(root.toString(4).getBytes(StandardCharsets.UTF_8), output); + } catch (Throwable e) { + Iris.warn("Failed to write external datapack smartbore manifest " + output.getPath()); + Iris.reportError(e); + } + } + + private static void writeSuppressedVanillaStructureManifest(Set suppressedStructures) { + File output = getSuppressedVanillaStructureManifestFile(); + LinkedHashSet normalized = new LinkedHashSet<>(); + if (suppressedStructures != null) { + for (String value : suppressedStructures) { + String normalizedStructure = normalizeLocateStructure(value); + if (normalizedStructure.isBlank() || !normalizedStructure.startsWith("minecraft:")) { + continue; + } + normalized.add(normalizedStructure); + } + } + + JSONObject root = new JSONObject(); + root.put("generatedAt", Instant.now().toString()); + JSONArray values = new JSONArray(); + ArrayList sorted = new ArrayList<>(normalized); + sorted.sort(String::compareTo); + for (String value : sorted) { + values.put(value); + } + root.put("structures", values); + + try { + writeBytesToFile(root.toString(4).getBytes(StandardCharsets.UTF_8), output); + } catch (Throwable e) { + Iris.warn("Failed to write external datapack suppressed-vanilla structure manifest " + output.getPath()); + Iris.reportError(e); + } + } + private static Map> readLocateManifest() { LinkedHashMap> mapped = new LinkedHashMap<>(); File input = getLocateManifestFile(); @@ -778,6 +953,88 @@ public final class ExternalDataPackPipeline { return mapped; } + private static Map> readSmartBoreManifest() { + LinkedHashMap> mapped = new LinkedHashMap<>(); + File input = getSmartBoreManifestFile(); + if (!input.exists() || !input.isFile()) { + return mapped; + } + + try { + JSONObject root = new JSONObject(Files.readString(input.toPath(), StandardCharsets.UTF_8)); + JSONObject ids = root.optJSONObject("ids"); + if (ids == null) { + return mapped; + } + + ArrayList keys = new ArrayList<>(ids.keySet()); + keys.sort(String::compareTo); + for (String key : keys) { + String normalizedId = normalizeLocateId(key); + if (normalizedId.isBlank()) { + continue; + } + + LinkedHashSet structures = new LinkedHashSet<>(); + JSONArray values = ids.optJSONArray(key); + if (values != null) { + for (int i = 0; i < values.length(); i++) { + Object rawValue = values.opt(i); + if (rawValue == null) { + continue; + } + + String normalizedStructure = normalizeLocateStructure(String.valueOf(rawValue)); + if (!normalizedStructure.isBlank()) { + structures.add(normalizedStructure); + } + } + } + + if (!structures.isEmpty()) { + mapped.put(normalizedId, Set.copyOf(structures)); + } + } + } catch (Throwable e) { + Iris.warn("Failed to read external datapack smartbore manifest " + input.getPath()); + Iris.reportError(e); + } + + return mapped; + } + + private static Set readSuppressedVanillaStructureManifest() { + LinkedHashSet mapped = new LinkedHashSet<>(); + File input = getSuppressedVanillaStructureManifestFile(); + if (!input.exists() || !input.isFile()) { + return mapped; + } + + try { + JSONObject root = new JSONObject(Files.readString(input.toPath(), StandardCharsets.UTF_8)); + JSONArray values = root.optJSONArray("structures"); + if (values == null) { + return mapped; + } + + for (int i = 0; i < values.length(); i++) { + Object rawValue = values.opt(i); + if (rawValue == null) { + continue; + } + String normalizedStructure = normalizeLocateStructure(String.valueOf(rawValue)); + if (!normalizedStructure.isBlank() && normalizedStructure.startsWith("minecraft:")) { + mapped.add(normalizedStructure); + } + } + } catch (Throwable e) { + Iris.warn("Failed to read external datapack suppressed-vanilla structure manifest " + input.getPath()); + Iris.reportError(e); + } + + return mapped; + } + private static List normalizeRequests(List requests) { Map deduplicated = new HashMap<>(); if (requests == null) { @@ -1028,7 +1285,10 @@ public final class ExternalDataPackPipeline { String managedName = buildManagedWorldDatapackName(sourceDescriptor.targetPack(), sourceDescriptor.sourceKey()); if (worldDatapackFolders == null || worldDatapackFolders.isEmpty()) { - return ProjectionResult.success(managedName, 0, 0, Set.copyOf(request.resolvedLocateStructures()), 0); + if (request.required()) { + return ProjectionResult.failure(managedName, "no target world datapack folder is available for required external datapack request"); + } + return ProjectionResult.success(managedName, 0, 0, Set.copyOf(request.resolvedLocateStructures()), 0, Set.of()); } ProjectionAssetSummary projectionAssetSummary; @@ -1041,7 +1301,14 @@ public final class ExternalDataPackPipeline { } if (projectionAssetSummary.assets().isEmpty()) { - return ProjectionResult.success(managedName, 0, 0, projectionAssetSummary.resolvedLocateStructures(), projectionAssetSummary.syntheticStructureSets()); + return ProjectionResult.success( + managedName, + 0, + 0, + projectionAssetSummary.resolvedLocateStructures(), + projectionAssetSummary.syntheticStructureSets(), + projectionAssetSummary.projectedStructureKeys() + ); } int installedDatapacks = 0; @@ -1070,7 +1337,14 @@ public final class ExternalDataPackPipeline { } } - return ProjectionResult.success(managedName, installedDatapacks, installedAssets, projectionAssetSummary.resolvedLocateStructures(), projectionAssetSummary.syntheticStructureSets()); + return ProjectionResult.success( + managedName, + installedDatapacks, + installedAssets, + projectionAssetSummary.resolvedLocateStructures(), + projectionAssetSummary.syntheticStructureSets(), + projectionAssetSummary.projectedStructureKeys() + ); } private static ProjectionAssetSummary buildProjectedAssets(File source, SourceDescriptor sourceDescriptor, DatapackRequest request) throws IOException { @@ -1081,7 +1355,16 @@ public final class ExternalDataPackPipeline { List inputAssets = projectionSelection.assets(); if (inputAssets.isEmpty()) { - return new ProjectionAssetSummary(List.of(), Set.copyOf(request.resolvedLocateStructures()), 0); + return new ProjectionAssetSummary(List.of(), Set.copyOf(request.resolvedLocateStructures()), 0, Set.of()); + } + int selectedStructureNbtCount = 0; + for (ProjectionInputAsset inputAsset : inputAssets) { + if (inputAsset == null || inputAsset.entry() == null) { + continue; + } + if (inputAsset.entry().type() == ProjectedEntryType.STRUCTURE_NBT) { + selectedStructureNbtCount++; + } } String scopeNamespace = buildScopeNamespace(sourceDescriptor, request); @@ -1103,9 +1386,11 @@ public final class ExternalDataPackPipeline { LinkedHashSet resolvedLocateStructures = new LinkedHashSet<>(); resolvedLocateStructures.addAll(request.resolvedLocateStructures()); LinkedHashSet remappedStructureKeys = new LinkedHashSet<>(); + LinkedHashSet projectedStructureKeys = new LinkedHashSet<>(); LinkedHashSet structureSetReferences = new LinkedHashSet<>(); LinkedHashSet writtenPaths = new LinkedHashSet<>(); ArrayList outputAssets = new ArrayList<>(); + int projectedCanonicalStructureNbtCount = 0; for (ProjectionInputAsset inputAsset : inputAssets) { ProjectedEntry projectedEntry = inputAsset.entry(); @@ -1152,6 +1437,10 @@ public final class ExternalDataPackPipeline { remappedStructureKeys.add(effectiveEntry.key()); resolvedLocateStructures.add(effectiveEntry.key()); + String normalizedProjectedStructure = normalizeLocateStructure(effectiveEntry.key()); + if (!normalizedProjectedStructure.isBlank()) { + projectedStructureKeys.add(normalizedProjectedStructure); + } } else if (projectedEntry.type() == ProjectedEntryType.STRUCTURE_SET) { structureSetReferences.addAll(readStructureSetReferences(root)); } @@ -1160,6 +1449,11 @@ public final class ExternalDataPackPipeline { } outputAssets.add(new ProjectionOutputAsset(outputRelativePath, outputBytes)); + if (projectedEntry.type() == ProjectedEntryType.STRUCTURE_NBT + && outputRelativePath.endsWith(".nbt") + && outputRelativePath.contains("/structure/")) { + projectedCanonicalStructureNbtCount++; + } } int syntheticStructureSets = 0; @@ -1195,7 +1489,11 @@ public final class ExternalDataPackPipeline { outputAssets.add(new ProjectionOutputAsset(tagPath, root.toString(4).getBytes(StandardCharsets.UTF_8))); } - return new ProjectionAssetSummary(outputAssets, Set.copyOf(resolvedLocateStructures), syntheticStructureSets); + if (request.required() && selectedStructureNbtCount > 0 && projectedCanonicalStructureNbtCount <= 0) { + throw new IOException("Required external datapack projection produced no canonical structure template outputs (data/*/structure/*.nbt)."); + } + + return new ProjectionAssetSummary(outputAssets, Set.copyOf(resolvedLocateStructures), syntheticStructureSets, Set.copyOf(projectedStructureKeys)); } private static ProjectionSelection readProjectedEntries(File source, DatapackRequest request) throws IOException { @@ -1775,7 +2073,7 @@ public final class ExternalDataPackPipeline { case TEMPLATE_POOL -> "data/" + namespace + "/worldgen/template_pool/" + path + ".json"; case PROCESSOR_LIST -> "data/" + namespace + "/worldgen/processor_list/" + path + ".json"; case BIOME_HAS_STRUCTURE_TAG -> "data/" + namespace + "/tags/worldgen/biome/has_structure/" + path + ".json"; - case STRUCTURE_NBT -> "data/" + namespace + "/structures/" + path + ".nbt"; + case STRUCTURE_NBT -> "data/" + namespace + "/structure/" + path + ".nbt"; }; } @@ -3090,6 +3388,7 @@ public final class ExternalDataPackPipeline { String requiredEnvironment, boolean required, boolean replaceVanilla, + boolean supportSmartBore, Set structures, Set structureSets, Set configuredFeatures, @@ -3110,6 +3409,7 @@ public final class ExternalDataPackPipeline { String requiredEnvironment, boolean required, boolean replaceVanilla, + boolean supportSmartBore, IrisExternalDatapackReplaceTargets replaceTargets, KList structurePatches ) { @@ -3120,6 +3420,7 @@ public final class ExternalDataPackPipeline { requiredEnvironment, required, replaceVanilla, + supportSmartBore, replaceTargets, structurePatches, Set.of(), @@ -3136,6 +3437,7 @@ public final class ExternalDataPackPipeline { String requiredEnvironment, boolean required, boolean replaceVanilla, + boolean supportSmartBore, IrisExternalDatapackReplaceTargets replaceTargets, KList structurePatches, Set forcedBiomeKeys, @@ -3150,6 +3452,7 @@ public final class ExternalDataPackPipeline { normalizeEnvironment(requiredEnvironment), required, replaceVanilla, + supportSmartBore, normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructures(), "worldgen/structure/"), normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructureSets(), "worldgen/structure_set/"), normalizeTargets(replaceTargets == null ? null : replaceTargets.getConfiguredFeatures(), "worldgen/configured_feature/"), @@ -3216,6 +3519,7 @@ public final class ExternalDataPackPipeline { environment, required || other.required, replaceVanilla || other.replaceVanilla, + supportSmartBore || other.supportSmartBore, union(structures, other.structures), union(structureSets, other.structureSets), union(configuredFeatures, other.configuredFeatures), @@ -3573,6 +3877,7 @@ public final class ExternalDataPackPipeline { int installedAssets, Set resolvedLocateStructures, int syntheticStructureSets, + Set projectedStructureKeys, String managedName, String error ) { @@ -3587,6 +3892,16 @@ public final class ExternalDataPackPipeline { } } resolvedLocateStructures = Set.copyOf(normalized); + LinkedHashSet normalizedProjected = new LinkedHashSet<>(); + if (projectedStructureKeys != null) { + for (String structure : projectedStructureKeys) { + String normalizedStructure = normalizeLocateStructure(structure); + if (!normalizedStructure.isBlank()) { + normalizedProjected.add(normalizedStructure); + } + } + } + projectedStructureKeys = Set.copyOf(normalizedProjected); syntheticStructureSets = Math.max(0, syntheticStructureSets); if (error == null) { error = ""; @@ -3598,14 +3913,15 @@ public final class ExternalDataPackPipeline { int installedDatapacks, int installedAssets, Set resolvedLocateStructures, - int syntheticStructureSets + int syntheticStructureSets, + Set projectedStructureKeys ) { - return new ProjectionResult(true, installedDatapacks, installedAssets, resolvedLocateStructures, syntheticStructureSets, managedName, ""); + return new ProjectionResult(true, installedDatapacks, installedAssets, resolvedLocateStructures, syntheticStructureSets, projectedStructureKeys, managedName, ""); } private static ProjectionResult failure(String managedName, String error) { String message = error == null || error.isBlank() ? "projection failed" : error; - return new ProjectionResult(false, 0, 0, Set.of(), 0, managedName, message); + return new ProjectionResult(false, 0, 0, Set.of(), 0, Set.of(), managedName, message); } } @@ -3621,7 +3937,12 @@ public final class ExternalDataPackPipeline { } } - private record ProjectionAssetSummary(List assets, Set resolvedLocateStructures, int syntheticStructureSets) { + private record ProjectionAssetSummary( + List assets, + Set resolvedLocateStructures, + int syntheticStructureSets, + Set projectedStructureKeys + ) { private ProjectionAssetSummary { assets = assets == null ? List.of() : List.copyOf(assets); LinkedHashSet normalized = new LinkedHashSet<>(); @@ -3634,6 +3955,16 @@ public final class ExternalDataPackPipeline { } } resolvedLocateStructures = Set.copyOf(normalized); + LinkedHashSet normalizedProjected = new LinkedHashSet<>(); + if (projectedStructureKeys != null) { + for (String structure : projectedStructureKeys) { + String normalizedStructure = normalizeLocateStructure(structure); + if (!normalizedStructure.isBlank()) { + normalizedProjected.add(normalizedStructure); + } + } + } + projectedStructureKeys = Set.copyOf(normalizedProjected); syntheticStructureSets = Math.max(0, syntheticStructureSets); } } diff --git a/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java b/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java index 6ce8d5cd6..82e72b031 100644 --- a/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java +++ b/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java @@ -24,6 +24,7 @@ import art.arcane.iris.core.loader.ResourceLoader; import art.arcane.iris.core.nms.INMS; import art.arcane.iris.core.nms.datapack.DataVersion; import art.arcane.iris.core.nms.datapack.IDataFixer; +import art.arcane.iris.engine.IrisNoisemapPrebakePipeline; import art.arcane.iris.engine.object.*; import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KMap; @@ -76,7 +77,10 @@ public class ServerConfigurator { } deferredInstallPending = false; - installDataPacks(true); + boolean datapacksMissing = installDataPacks(true); + if (!datapacksMissing) { + IrisNoisemapPrebakePipeline.scheduleInstalledPacksPrebakeAsync(); + } } public static void configureIfDeferred() { @@ -135,13 +139,21 @@ public class ServerConfigurator { } public static boolean installDataPacks(boolean fullInstall, boolean includeExternal) { + return installDataPacks(fullInstall, includeExternal, null); + } + + public static boolean installDataPacks( + boolean fullInstall, + boolean includeExternal, + KMap> extraWorldDatapackFoldersByPack + ) { IDataFixer fixer = DataVersion.getDefault(); if (fixer == null) { DataVersion fallback = DataVersion.getLatest(); Iris.warn("Primary datapack fixer was null, forcing latest fixer: " + fallback.getVersion()); fixer = fallback.get(); } - return installDataPacks(fixer, fullInstall, includeExternal); + return installDataPacks(fixer, fullInstall, includeExternal, extraWorldDatapackFoldersByPack); } public static boolean installDataPacks(IDataFixer fixer, boolean fullInstall) { @@ -149,6 +161,15 @@ public class ServerConfigurator { } public static boolean installDataPacks(IDataFixer fixer, boolean fullInstall, boolean includeExternal) { + return installDataPacks(fixer, fullInstall, includeExternal, null); + } + + public static boolean installDataPacks( + IDataFixer fixer, + boolean fullInstall, + boolean includeExternal, + KMap> extraWorldDatapackFoldersByPack + ) { if (fixer == null) { Iris.error("Unable to install datapacks, fixer is null!"); return false; @@ -161,7 +182,7 @@ public class ServerConfigurator { DimensionHeight height = new DimensionHeight(fixer); KList folders = getDatapacksFolder(); if (includeExternal) { - installExternalDataPacks(folders); + installExternalDataPacks(folders, extraWorldDatapackFoldersByPack); } KMap> biomes = new KMap<>(); @@ -184,13 +205,16 @@ public class ServerConfigurator { return fullInstall && verifyDataPacksPost(IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall()); } - private static void installExternalDataPacks(KList folders) { + private static void installExternalDataPacks( + KList folders, + KMap> extraWorldDatapackFoldersByPack + ) { if (!IrisSettings.get().getGeneral().isImportExternalDatapacks()) { return; } KList requests = collectExternalDatapackRequests(); - KMap> worldDatapackFoldersByPack = collectWorldDatapackFoldersByPack(folders); + KMap> worldDatapackFoldersByPack = collectWorldDatapackFoldersByPack(folders, extraWorldDatapackFoldersByPack); ExternalDataPackPipeline.PipelineSummary summary = ExternalDataPackPipeline.processDatapacks(requests, worldDatapackFoldersByPack); if (summary.getLegacyDownloadRemovals() > 0) { Iris.verbose("Removed " + summary.getLegacyDownloadRemovals() + " legacy global datapack downloads."); @@ -315,6 +339,7 @@ public class ServerConfigurator { environment, definition.isRequired(), definition.isReplaceVanilla(), + definition.isSupportSmartBore(), definition.getReplaceTargets(), definition.getStructurePatches(), Set.of(), @@ -343,6 +368,7 @@ public class ServerConfigurator { environment, group.required(), group.replaceVanilla(), + definition.isSupportSmartBore(), definition.getReplaceTargets(), definition.getStructurePatches(), group.forcedBiomeKeys(), @@ -871,7 +897,10 @@ public class ServerConfigurator { ) { } - private static KMap> collectWorldDatapackFoldersByPack(KList fallbackFolders) { + private static KMap> collectWorldDatapackFoldersByPack( + KList fallbackFolders, + KMap> extraWorldDatapackFoldersByPack + ) { KMap> foldersByPack = new KMap<>(); KMap mappedWorlds = IrisWorlds.get().getWorlds(); @@ -905,6 +934,22 @@ public class ServerConfigurator { } } + if (extraWorldDatapackFoldersByPack != null && !extraWorldDatapackFoldersByPack.isEmpty()) { + for (Map.Entry> entry : extraWorldDatapackFoldersByPack.entrySet()) { + String packName = sanitizePackName(entry.getKey()); + if (packName.isBlank()) { + continue; + } + KList folders = entry.getValue(); + if (folders == null || folders.isEmpty()) { + continue; + } + for (File folder : folders) { + addWorldDatapackFolder(foldersByPack, packName, folder); + } + } + } + return foldersByPack; } diff --git a/core/src/main/java/art/arcane/iris/core/commands/CommandWhat.java b/core/src/main/java/art/arcane/iris/core/commands/CommandWhat.java index 18692d148..4cc8dcce8 100644 --- a/core/src/main/java/art/arcane/iris/core/commands/CommandWhat.java +++ b/core/src/main/java/art/arcane/iris/core/commands/CommandWhat.java @@ -32,7 +32,6 @@ import art.arcane.volmlib.util.director.annotations.Director; import art.arcane.volmlib.util.director.annotations.Param; import art.arcane.iris.util.common.format.C; import art.arcane.volmlib.util.matter.MatterMarker; -import art.arcane.iris.util.common.scheduling.J; import org.bukkit.Chunk; import org.bukkit.FluidCollisionMode; import org.bukkit.Material; @@ -161,7 +160,7 @@ public class CommandWhat implements DirectorExecutor { for (int zzz = c.getZ() - 4; zzz <= c.getZ() + 4; zzz++) { IrisToolbelt.access(c.getWorld()).getEngine().getMantle().findMarkers(xxx, zzz, new MatterMarker(marker)) .convert((i) -> i.toLocation(c.getWorld())).forEach((i) -> { - J.s(() -> BlockSignal.of(i.getBlock(), 100)); + BlockSignal.of(i.getWorld(), i.getBlockX(), i.getBlockY(), i.getBlockZ(), 100); v.incrementAndGet(); }); } diff --git a/core/src/main/java/art/arcane/iris/core/edit/BlockSignal.java b/core/src/main/java/art/arcane/iris/core/edit/BlockSignal.java index 678311e46..bc1958ec2 100644 --- a/core/src/main/java/art/arcane/iris/core/edit/BlockSignal.java +++ b/core/src/main/java/art/arcane/iris/core/edit/BlockSignal.java @@ -18,10 +18,11 @@ package art.arcane.iris.core.edit; -import art.arcane.iris.util.common.parallel.MultiBurst; import art.arcane.iris.util.common.scheduling.J; import art.arcane.volmlib.util.scheduling.SR; +import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.entity.FallingBlock; @@ -46,26 +47,51 @@ public class BlockSignal { e.setSilent(true); e.setTicksLived(1); e.setVelocity(new Vector(0, 0, 0)); - J.s(() -> { - e.remove(); + Location blockLocation = block.getLocation(); + Runnable removeTask = () -> { + if (!J.runEntity(e, e::remove) && !e.isDead()) { + e.remove(); + } active.decrementAndGet(); - BlockData type = block.getBlockData(); - MultiBurst.burst.lazy(() -> { - for (Player i : block.getWorld().getPlayers()) { - i.sendBlockChange(block.getLocation(), block.getBlockData()); - } - }); - }, ticks); + sendBlockRefresh(block); + }; + if (!J.runAt(blockLocation, removeTask, ticks)) { + if (!J.isFolia()) { + J.s(removeTask, ticks); + } + } } public static void of(Block block, int ticks) { - new BlockSignal(block, ticks); + if (block == null) { + return; + } + + of(block.getWorld(), block.getX(), block.getY(), block.getZ(), ticks); } public static void of(Block block) { of(block, 100); } + public static void of(World world, int x, int y, int z, int ticks) { + if (world == null) { + return; + } + + Location location = new Location(world, x, y, z); + Runnable createTask = () -> new BlockSignal(world.getBlockAt(x, y, z), ticks); + if (!J.runAt(location, createTask)) { + if (!J.isFolia()) { + J.s(createTask); + } + } + } + + public static void of(World world, int x, int y, int z) { + of(world, x, y, z, 100); + } + public static Runnable forever(Block block) { Location tg = block.getLocation().clone().add(0.5, 0, 0.5).clone(); FallingBlock e = block.getWorld().spawnFallingBlock(tg.clone(), block.getBlockData()); @@ -82,26 +108,46 @@ public class BlockSignal { new SR(20) { @Override public void run() { - if (e.isDead()) { - cancel(); - return; - } + if (!J.runEntity(e, () -> { + if (e.isDead()) { + cancel(); + return; + } - e.setTicksLived(1); - e.teleport(tg.clone()); - e.setVelocity(new Vector(0, 0, 0)); + e.setTicksLived(1); + e.teleport(tg.clone()); + e.setVelocity(new Vector(0, 0, 0)); + })) { + cancel(); + } } }; return () -> { - e.remove(); - BlockData type = block.getBlockData(); - - MultiBurst.burst.lazy(() -> { - for (Player i : block.getWorld().getPlayers()) { - i.sendBlockChange(block.getLocation(), block.getBlockData()); - } - }); + if (!J.runEntity(e, e::remove) && !e.isDead()) { + e.remove(); + } + Location blockLocation = block.getLocation(); + Runnable refreshTask = () -> sendBlockRefresh(block); + if (!J.runAt(blockLocation, refreshTask)) { + refreshTask.run(); + } }; } + + private static void sendBlockRefresh(Block block) { + if (block == null) { + return; + } + + Location location = block.getLocation(); + BlockData blockData = block.getBlockData(); + for (Player player : Bukkit.getOnlinePlayers()) { + if (!player.getWorld().equals(location.getWorld())) { + continue; + } + + J.runEntity(player, () -> player.sendBlockChange(location, blockData)); + } + } } diff --git a/core/src/main/java/art/arcane/iris/core/edit/DustRevealer.java b/core/src/main/java/art/arcane/iris/core/edit/DustRevealer.java index a38fe6581..b559b5b95 100644 --- a/core/src/main/java/art/arcane/iris/core/edit/DustRevealer.java +++ b/core/src/main/java/art/arcane/iris/core/edit/DustRevealer.java @@ -28,6 +28,7 @@ import art.arcane.volmlib.util.math.RNG; import art.arcane.iris.util.common.plugin.VolmitSender; import art.arcane.iris.util.common.scheduling.J; import lombok.Data; +import org.bukkit.Location; import org.bukkit.Sound; import org.bukkit.World; import org.bukkit.block.Block; @@ -48,8 +49,9 @@ public class DustRevealer { this.key = key; this.hits = hits; - J.s(() -> { - new BlockSignal(world.getBlockAt(block.getX(), block.getY(), block.getZ()), 10); + Location blockLocation = block.toBlock(world).getLocation(); + Runnable revealTask = () -> { + BlockSignal.of(world, block.getX(), block.getY(), block.getZ(), 10); if (M.r(0.25)) { world.playSound(block.toBlock(world).getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 1f, RNG.r.f(0.2f, 2f)); } @@ -90,7 +92,13 @@ public class DustRevealer { e.printStackTrace(); } }); - }, RNG.r.i(2, 8)); + }; + int delay = RNG.r.i(2, 8); + if (!J.runAt(blockLocation, revealTask, delay)) { + if (!J.isFolia()) { + J.s(revealTask, delay); + } + } } public static void spawn(Block block, VolmitSender sender) { diff --git a/core/src/main/java/art/arcane/iris/core/tools/IrisCreator.java b/core/src/main/java/art/arcane/iris/core/tools/IrisCreator.java index 9045ae490..99bcd1378 100644 --- a/core/src/main/java/art/arcane/iris/core/tools/IrisCreator.java +++ b/core/src/main/java/art/arcane/iris/core/tools/IrisCreator.java @@ -33,6 +33,8 @@ import art.arcane.iris.engine.IrisNoisemapPrebakePipeline; import art.arcane.iris.engine.framework.SeedManager; import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.platform.PlatformChunkGenerator; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.exceptions.IrisException; import art.arcane.iris.util.common.format.C; import art.arcane.volmlib.util.format.Form; @@ -166,8 +168,16 @@ public class IrisCreator { IrisWorlds.get().put(name(), dimension()); } boolean verifyDataPacks = !studio(); - boolean includeExternalDataPacks = !studio(); - if (ServerConfigurator.installDataPacks(verifyDataPacks, includeExternalDataPacks)) { + boolean includeExternalDataPacks = true; + KMap> extraWorldDatapackFoldersByPack = null; + if (studio()) { + File studioDatapackFolder = new File(new File(Bukkit.getWorldContainer(), name()), "datapacks"); + KList studioDatapackFolders = new KList<>(); + studioDatapackFolders.add(studioDatapackFolder); + extraWorldDatapackFoldersByPack = new KMap<>(); + extraWorldDatapackFoldersByPack.put(d.getLoadKey(), studioDatapackFolders); + } + if (ServerConfigurator.installDataPacks(verifyDataPacks, includeExternalDataPacks, extraWorldDatapackFoldersByPack)) { throw new IrisException("Datapacks were missing!"); } @@ -295,6 +305,13 @@ public class IrisCreator { return; } + if (studio() && !benchmark) { + boolean startupPrebakeReady = IrisNoisemapPrebakePipeline.awaitInstalledPacksPrebakeForStudio(); + if (startupPrebakeReady) { + return; + } + } + try { File targetDataFolder = new File(Bukkit.getWorldContainer(), name()); if (studio() && !benchmark) { diff --git a/core/src/main/java/art/arcane/iris/engine/IrisNoisemapPrebakePipeline.java b/core/src/main/java/art/arcane/iris/engine/IrisNoisemapPrebakePipeline.java index c6b5ed936..629d94746 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisNoisemapPrebakePipeline.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisNoisemapPrebakePipeline.java @@ -38,6 +38,8 @@ import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.CompletionService; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; @@ -45,7 +47,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BooleanSupplier; public final class IrisNoisemapPrebakePipeline { @@ -53,7 +57,10 @@ public final class IrisNoisemapPrebakePipeline { private static final long STARTUP_PROGRESS_INTERVAL_MS = Long.getLong("iris.prebake.progress.interval", 30000L); private static final int STATE_VERSION = 1; private static final String STATE_FILE = "noisemap-prebake.state"; + private static final AtomicBoolean STARTUP_PREBAKE_SCHEDULED = new AtomicBoolean(false); + private static final AtomicBoolean STARTUP_PREBAKE_FAILURE_REPORTED = new AtomicBoolean(false); private static final AtomicInteger STARTUP_WORKER_SEQUENCE = new AtomicInteger(); + private static final AtomicReference> STARTUP_PREBAKE_COMPLETION = new AtomicReference<>(); private static final ConcurrentHashMap, Field[]> FIELD_CACHE = new ConcurrentHashMap<>(); private static final ConcurrentHashMap SKIP_ONCE = new ConcurrentHashMap<>(); private static final Set PREBAKE_LOADERS = Set.of( @@ -83,9 +90,64 @@ public final class IrisNoisemapPrebakePipeline { private IrisNoisemapPrebakePipeline() { } - public static void prebakeInstalledPacksAtStartup() { - IrisSettings.IrisSettingsPregen settings = IrisSettings.get().getPregen(); + public static void scheduleInstalledPacksPrebakeAsync() { + if (!IrisSettings.get().getPregen().isStartupNoisemapPrebake()) { + return; + } + if (!STARTUP_PREBAKE_SCHEDULED.compareAndSet(false, true)) { + return; + } + + CompletableFuture completion = new CompletableFuture<>(); + STARTUP_PREBAKE_COMPLETION.set(completion); + Thread thread = new Thread(() -> { + try { + prebakeInstalledPacksAtStartup(); + completion.complete(null); + } catch (Throwable throwable) { + completion.completeExceptionally(throwable); + if (STARTUP_PREBAKE_FAILURE_REPORTED.compareAndSet(false, true)) { + Iris.warn("Startup noisemap pre-bake failed."); + Iris.reportError(throwable); + } + } + }, "Iris-StartupNoisemapPrebake"); + thread.setDaemon(true); + thread.start(); + } + + public static boolean awaitInstalledPacksPrebakeForStudio() { + if (!IrisSettings.get().getPregen().isStartupNoisemapPrebake()) { + return false; + } + + scheduleInstalledPacksPrebakeAsync(); + CompletableFuture completion = STARTUP_PREBAKE_COMPLETION.get(); + if (completion == null) { + return false; + } + + try { + completion.join(); + return true; + } catch (CompletionException e) { + Throwable cause = e.getCause() == null ? e : e.getCause(); + if (STARTUP_PREBAKE_FAILURE_REPORTED.compareAndSet(false, true)) { + Iris.warn("Startup noisemap pre-bake failed."); + Iris.reportError(cause); + } + return false; + } catch (Throwable throwable) { + if (STARTUP_PREBAKE_FAILURE_REPORTED.compareAndSet(false, true)) { + Iris.warn("Startup noisemap pre-bake failed."); + Iris.reportError(throwable); + } + return false; + } + } + + public static void prebakeInstalledPacksAtStartup() { List targets = collectStartupTargets(); if (targets.isEmpty()) { Iris.info("Startup noisemap pre-bake skipped (no installed or self-contained packs found)."); diff --git a/core/src/main/java/art/arcane/iris/engine/IrisWorldManager.java b/core/src/main/java/art/arcane/iris/engine/IrisWorldManager.java index 2a334cd29..3e5a86845 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisWorldManager.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisWorldManager.java @@ -213,26 +213,24 @@ public class IrisWorldManager extends EngineAssignedWorldManager { return; } - J.s(() -> { - for (Player player : world.getPlayers()) { - if (player == null || !player.isOnline()) { - continue; - } - - J.runEntity(player, () -> { - int centerX = player.getLocation().getBlockX() >> 4; - int centerZ = player.getLocation().getBlockZ() >> 4; - int radius = 1; - for (int x = -radius; x <= radius; x++) { - for (int z = -radius; z <= radius; z++) { - int chunkX = centerX + x; - int chunkZ = centerZ + z; - raiseDiscoveredChunkFlag(world, chunkX, chunkZ); - } - } - }); + for (Player player : getEngine().getWorld().getPlayers()) { + if (player == null || !player.isOnline()) { + continue; } - }); + + J.runEntity(player, () -> { + int centerX = player.getLocation().getBlockX() >> 4; + int centerZ = player.getLocation().getBlockZ() >> 4; + int radius = 1; + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + int chunkX = centerX + x; + int chunkZ = centerZ + z; + raiseDiscoveredChunkFlag(world, chunkX, chunkZ); + } + } + }); + } } private void raiseDiscoveredChunkFlag(World world, int chunkX, int chunkZ) { @@ -274,27 +272,25 @@ public class IrisWorldManager extends EngineAssignedWorldManager { return; } - J.s(() -> { - for (Player player : world.getPlayers()) { - if (player == null || !player.isOnline()) { - continue; - } - - J.runEntity(player, () -> { - int centerX = player.getLocation().getBlockX() >> 4; - int centerZ = player.getLocation().getBlockZ() >> 4; - int radius = 1; - - for (int x = -radius; x <= radius; x++) { - for (int z = -radius; z <= radius; z++) { - int targetX = centerX + x; - int targetZ = centerZ + z; - J.runRegion(world, targetX, targetZ, () -> updateChunkRegion(world, targetX, targetZ)); - } - } - }); + for (Player player : getEngine().getWorld().getPlayers()) { + if (player == null || !player.isOnline()) { + continue; } - }); + + J.runEntity(player, () -> { + int centerX = player.getLocation().getBlockX() >> 4; + int centerZ = player.getLocation().getBlockZ() >> 4; + int radius = 1; + + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + int targetX = centerX + x; + int targetZ = centerZ + z; + J.runRegion(world, targetX, targetZ, () -> updateChunkRegion(world, targetX, targetZ)); + } + } + }); + } } private void updateChunkRegion(World world, int chunkX, int chunkZ) { @@ -432,7 +428,22 @@ public class IrisWorldManager extends EngineAssignedWorldManager { if (cl.flip()) { try { - J.s(() -> precount = getEngine().getWorld().realWorld().getEntities()); + World realWorld = getEngine().getWorld().realWorld(); + if (realWorld == null) { + precount = new KList<>(); + } else if (J.isFolia()) { + precount = getFoliaEntitySnapshot(realWorld); + } else { + CompletableFuture> future = new CompletableFuture<>(); + J.s(() -> { + try { + future.complete(realWorld.getEntities()); + } catch (Throwable ex) { + future.completeExceptionally(ex); + } + }); + precount = future.get(2, TimeUnit.SECONDS); + } } catch (Throwable e) { close(); } @@ -499,6 +510,47 @@ public class IrisWorldManager extends EngineAssignedWorldManager { } } + private List getFoliaEntitySnapshot(World world) { + Map snapshot = new ConcurrentHashMap<>(); + List players = getEngine().getWorld().getPlayers(); + if (players == null || players.isEmpty()) { + return new KList<>(); + } + + CountDownLatch latch = new CountDownLatch(players.size()); + for (Player player : players) { + if (player == null || !player.isOnline() || !world.equals(player.getWorld())) { + latch.countDown(); + continue; + } + + if (!J.runEntity(player, () -> { + try { + snapshot.put(player.getUniqueId().toString(), player); + for (Entity nearby : player.getNearbyEntities(64, 64, 64)) { + if (nearby != null && world.equals(nearby.getWorld())) { + snapshot.put(nearby.getUniqueId().toString(), nearby); + } + } + } finally { + latch.countDown(); + } + })) { + latch.countDown(); + } + } + + try { + latch.await(2, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + KList entities = new KList<>(); + entities.addAll(snapshot.values()); + return entities; + } + private void spawnChunkSafely(World world, int chunkX, int chunkZ, boolean initial) { if (world == null) { return; @@ -756,7 +808,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { public void teleportAsync(PlayerTeleportEvent e) { if (IrisSettings.get().getWorld().getAsyncTeleport().isEnabled()) { e.setCancelled(true); - warmupAreaAsync(e.getPlayer(), e.getTo(), () -> J.s(() -> { + warmupAreaAsync(e.getPlayer(), e.getTo(), () -> J.runEntity(e.getPlayer(), () -> { ignoreTP.set(true); e.getPlayer().teleport(e.getTo(), e.getCause()); ignoreTP.set(false); @@ -1018,7 +1070,13 @@ public class IrisWorldManager extends EngineAssignedWorldManager { if (d.isNotEmpty()) { World w = e.getBlock().getWorld(); - J.s(() -> d.forEach(item -> w.dropItemNaturally(e.getBlock().getLocation().clone().add(.5, .5, .5), item))); + Location dropLocation = e.getBlock().getLocation().clone().add(.5, .5, .5); + Runnable dropTask = () -> d.forEach(item -> w.dropItemNaturally(dropLocation, item)); + if (!J.runAt(dropLocation, dropTask)) { + if (!J.isFolia()) { + J.s(dropTask); + } + } } } } diff --git a/core/src/main/java/art/arcane/iris/engine/framework/Locator.java b/core/src/main/java/art/arcane/iris/engine/framework/Locator.java index 0b26420fa..a1189e2dd 100644 --- a/core/src/main/java/art/arcane/iris/engine/framework/Locator.java +++ b/core/src/main/java/art/arcane/iris/engine/framework/Locator.java @@ -18,6 +18,7 @@ package art.arcane.iris.engine.framework; +import art.arcane.iris.Iris; import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.nms.container.BlockPos; import art.arcane.iris.core.nms.container.Pair; @@ -38,11 +39,14 @@ import art.arcane.iris.util.common.plugin.VolmitSender; import art.arcane.iris.util.common.scheduling.J; import art.arcane.volmlib.util.scheduling.PrecisionStopwatch; import art.arcane.iris.util.common.scheduling.jobs.SingleJob; +import io.papermc.lib.PaperLib; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; +import java.lang.reflect.Method; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; @@ -106,7 +110,7 @@ public interface Locator { default void find(Player player, boolean teleport, String message) { find(player, location -> { if (teleport) { - J.s(() -> player.teleport(location)); + J.runEntity(player, () -> teleportAsyncSafely(player, location)); } else { player.sendMessage(C.GREEN + message + " at: " + location.getBlockX() + " " + location.getBlockY() + " " + location.getBlockZ()); } @@ -206,4 +210,39 @@ public interface Locator { return null; }); } + + static void teleportAsyncSafely(Player player, Location location) { + if (player == null || location == null) { + return; + } + + if (invokeNativeTeleportAsync(player, location)) { + return; + } + + try { + CompletableFuture teleportFuture = PaperLib.teleportAsync(player, location); + if (teleportFuture != null) { + teleportFuture.exceptionally(throwable -> { + Iris.reportError(throwable); + return false; + }); + } + } catch (Throwable throwable) { + Iris.reportError(throwable); + } + } + + static boolean invokeNativeTeleportAsync(Player player, Location location) { + try { + Method teleportAsyncMethod = player.getClass().getMethod("teleportAsync", Location.class); + teleportAsyncMethod.invoke(player, location); + return true; + } catch (NoSuchMethodException ignored) { + return false; + } catch (Throwable throwable) { + Iris.reportError(throwable); + return false; + } + } } diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3D.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3D.java index d035c2e9f..78dd0d116 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3D.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3D.java @@ -33,7 +33,6 @@ import java.util.Arrays; public class IrisCaveCarver3D { private static final byte LIQUID_AIR = 0; - private static final byte LIQUID_WATER = 1; private static final byte LIQUID_LAVA = 2; private static final byte LIQUID_FORCED_AIR = 3; @@ -48,7 +47,6 @@ public class IrisCaveCarver3D { private final KList modules; private final double normalization; private final MatterCavern carveAir; - private final MatterCavern carveWater; private final MatterCavern carveLava; private final MatterCavern carveForcedAir; @@ -57,7 +55,6 @@ public class IrisCaveCarver3D { this.data = engine.getData(); this.profile = profile; this.carveAir = new MatterCavern(true, "", LIQUID_AIR); - this.carveWater = new MatterCavern(true, "", LIQUID_WATER); this.carveLava = new MatterCavern(true, "", LIQUID_LAVA); this.carveForcedAir = new MatterCavern(true, "", LIQUID_FORCED_AIR); this.modules = new KList<>(); @@ -352,22 +349,7 @@ public class IrisCaveCarver3D { } if (profile.isAllowWater() && y <= fluidHeight) { - if (surfaceY - y < waterMinDepthBelowSurface) { - return carveAir; - } - - double depthFactor = Math.max(0, Math.min(1.5, (fluidHeight - y) / 48D)); - double cutoff = 0.35 + (depthFactor * 0.2); - double aquifer = signed(detailDensity.noise(x, y * 0.5D, z)); - if (aquifer <= cutoff) { - return carveAir; - } - - if (waterRequiresFloor && !hasAquiferCupSupport(x, y, z, localThreshold)) { - return carveAir; - } - - return carveWater; + return carveAir; } if (!profile.isAllowLava() && y <= lavaHeight) { @@ -377,38 +359,6 @@ public class IrisCaveCarver3D { return carveAir; } - private boolean hasAquiferCupSupport(int x, int y, int z, double threshold) { - int floorY = Math.max(0, y - 1); - int deepFloorY = Math.max(0, y - 2); - if (!isDensitySolid(x, floorY, z, threshold)) { - return false; - } - - if (!isDensitySolid(x, deepFloorY, z, threshold - 0.05D)) { - return false; - } - - int support = 0; - if (isDensitySolid(x + 1, y, z, threshold)) { - support++; - } - if (isDensitySolid(x - 1, y, z, threshold)) { - support++; - } - if (isDensitySolid(x, y, z + 1, threshold)) { - support++; - } - if (isDensitySolid(x, y, z - 1, threshold)) { - support++; - } - - return support >= 3; - } - - private boolean isDensitySolid(int x, int y, int z, double threshold) { - return sampleDensity(x, y, z) > threshold; - } - private double clampColumnWeight(double weight) { if (Double.isNaN(weight) || Double.isInfinite(weight)) { return 0D; diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java index 78e1760b8..90c3c2df2 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java @@ -48,10 +48,15 @@ import java.io.IOException; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; @ComponentFlag(ReservedFlag.OBJECT) public class MantleObjectComponent extends IrisMantleComponent { + private static final long CAVE_REJECT_LOG_THROTTLE_MS = 5000L; + private static final Map CAVE_REJECT_LOG_STATE = new ConcurrentHashMap<>(); public MantleObjectComponent(EngineMantle engineMantle) { super(engineMantle, ReservedFlag.OBJECT, 1); @@ -66,11 +71,7 @@ public class MantleObjectComponent extends IrisMantleComponent { IrisRegion region = getComplex().getRegionStream().get(xxx, zzz); IrisBiome surfaceBiome = getComplex().getTrueBiomeStream().get(xxx, zzz); int surfaceY = getEngineMantle().getEngine().getHeight(xxx, zzz, true); - int sampleY = Math.max(1, surfaceY - 48); - IrisBiome caveBiome = getEngineMantle().getEngine().getCaveBiome(xxx, sampleY, zzz); - if (caveBiome == null) { - caveBiome = surfaceBiome; - } + IrisBiome caveBiome = resolveCaveObjectBiome(xxx, zzz, surfaceY, surfaceBiome); if (traceRegen) { Iris.info("Regen object layer start: chunk=" + x + "," + z + " surfaceBiome=" + surfaceBiome.getLoadKey() @@ -105,6 +106,41 @@ public class MantleObjectComponent extends IrisMantleComponent { return new RNG((long) (seed * noise.noise(x, z))); } + private IrisBiome resolveCaveObjectBiome(int x, int z, int surfaceY, IrisBiome surfaceBiome) { + int legacySampleY = Math.max(1, surfaceY - 48); + IrisBiome legacyCaveBiome = getEngineMantle().getEngine().getCaveBiome(x, legacySampleY, z); + if (legacyCaveBiome == null) { + legacyCaveBiome = surfaceBiome; + } + + int[] sampleDepths = new int[]{48, 80, 112}; + IrisBiome ladderChoice = null; + for (int sampleDepth : sampleDepths) { + int sampleY = Math.max(1, surfaceY - sampleDepth); + IrisBiome sampled = getEngineMantle().getEngine().getCaveBiome(x, sampleY, z); + boolean sameSurface = sampled == surfaceBiome; + if (!sameSurface && sampled != null && surfaceBiome != null) { + String sampledKey = sampled.getLoadKey(); + String surfaceKey = surfaceBiome.getLoadKey(); + sameSurface = sampledKey != null && sampledKey.equals(surfaceKey); + } + + if (sampled == null || sameSurface) { + continue; + } + + if (!sampled.getCarvingObjects().isEmpty()) { + ladderChoice = sampled; + } + } + + if (ladderChoice != null) { + return ladderChoice; + } + + return legacyCaveBiome; + } + @ChunkCoordinates private ObjectPlacementSummary placeObjects(MantleWriter writer, RNG rng, int x, int z, IrisBiome surfaceBiome, IrisBiome caveBiome, IrisRegion region, boolean traceRegen) { int biomeSurfaceChecked = 0; @@ -393,6 +429,22 @@ public class MantleObjectComponent extends IrisMantleComponent { + " density=" + density + " placementKeys=" + objectPlacement.getPlace().toString(",")); } + logCaveReject( + scope, + "NULL_OBJECT", + metricChunkX, + metricChunkZ, + objectPlacement, + null, + i, + density, + anchorMode, + anchorSearchAttempts, + anchorScanStep, + objectMinDepthBelowSurface, + null, + null + ); continue; } @@ -415,24 +467,59 @@ public class MantleObjectComponent extends IrisMantleComponent { if (y < 0) { rejected++; + logCaveReject( + scope, + "NO_ANCHOR", + metricChunkX, + metricChunkZ, + objectPlacement, + object, + i, + density, + anchorMode, + anchorSearchAttempts, + anchorScanStep, + objectMinDepthBelowSurface, + null, + null + ); continue; } int id = rng.i(0, Integer.MAX_VALUE); IrisObjectPlacement effectivePlacement = resolveEffectivePlacement(objectPlacement, object); + AtomicBoolean wrotePlacementData = new AtomicBoolean(false); try { int result = object.place(x, y, z, writer, effectivePlacement, rng, (b, data) -> { + wrotePlacementData.set(true); writer.setData(b.getX(), b.getY(), b.getZ(), object.getLoadKey() + "@" + id); if (effectivePlacement.isDolphinTarget() && effectivePlacement.isUnderwater() && B.isStorageChest(data)) { writer.setData(b.getX(), b.getY(), b.getZ(), MatterStructurePOI.BURIED_TREASURE); } }, null, getData()); - if (result >= 0) { + boolean wroteBlocks = wrotePlacementData.get(); + if (wroteBlocks) { placed++; - } else { + } else if (result < 0) { rejected++; + logCaveReject( + scope, + "PLACE_NEGATIVE", + metricChunkX, + metricChunkZ, + objectPlacement, + object, + i, + density, + anchorMode, + anchorSearchAttempts, + anchorScanStep, + objectMinDepthBelowSurface, + y, + null + ); } if (traceRegen) { @@ -443,6 +530,7 @@ public class MantleObjectComponent extends IrisMantleComponent { + " anchorY=" + y + " px=" + x + " pz=" + z + + " wroteBlocks=" + wroteBlocks + " densityIndex=" + i + " density=" + density); } @@ -455,13 +543,87 @@ public class MantleObjectComponent extends IrisMantleComponent { + " densityIndex=" + i + " density=" + density + " error=" + e.getClass().getSimpleName() + ":" + e.getMessage()); + logCaveReject( + scope, + "EXCEPTION", + metricChunkX, + metricChunkZ, + objectPlacement, + object, + i, + density, + anchorMode, + anchorSearchAttempts, + anchorScanStep, + objectMinDepthBelowSurface, + y, + e + ); } } return new ObjectPlacementResult(attempts, placed, rejected, nullObjects, errors); } - private static IrisObjectPlacement resolveEffectivePlacement(IrisObjectPlacement objectPlacement, IrisObject object) { + private void logCaveReject( + String scope, + String reason, + int chunkX, + int chunkZ, + IrisObjectPlacement objectPlacement, + IrisObject object, + int densityIndex, + int density, + IrisCaveAnchorMode anchorMode, + int anchorSearchAttempts, + int anchorScanStep, + int objectMinDepthBelowSurface, + Integer anchorY, + Throwable error + ) { + if (!IrisSettings.get().getGeneral().isDebug()) { + return; + } + + String placementKeys = objectPlacement == null ? "none" : objectPlacement.getPlace().toString(","); + String objectKey = object == null ? "null" : object.getLoadKey(); + String throttleKey = scope + "|" + reason + "|" + placementKeys + "|" + objectKey; + CaveRejectLogState state = CAVE_REJECT_LOG_STATE.computeIfAbsent(throttleKey, (k) -> new CaveRejectLogState()); + long now = System.currentTimeMillis(); + long last = state.lastLogMs.get(); + if ((now - last) < CAVE_REJECT_LOG_THROTTLE_MS) { + state.suppressed.incrementAndGet(); + return; + } + + if (!state.lastLogMs.compareAndSet(last, now)) { + state.suppressed.incrementAndGet(); + return; + } + + int suppressed = state.suppressed.getAndSet(0); + String anchorYText = anchorY == null ? "none" : String.valueOf(anchorY); + String errorText = error == null ? "none" : error.getClass().getSimpleName() + ":" + String.valueOf(error.getMessage()); + Iris.warn("Cave object reject: scope=" + scope + + " reason=" + reason + + " chunk=" + chunkX + "," + chunkZ + + " object=" + objectKey + + " placements=" + placementKeys + + " densityIndex=" + densityIndex + + " density=" + density + + " anchorMode=" + anchorMode + + " anchorSearchAttempts=" + anchorSearchAttempts + + " anchorScanStep=" + anchorScanStep + + " minDepthBelowSurface=" + objectMinDepthBelowSurface + + " anchorY=" + anchorYText + + " forcePlace=" + (objectPlacement != null && objectPlacement.isForcePlace()) + + " carvingSupport=" + (objectPlacement == null ? "none" : objectPlacement.getCarvingSupport()) + + " bottom=" + (objectPlacement != null && objectPlacement.isBottom()) + + " suppressed=" + suppressed + + " error=" + errorText); + } + + private IrisObjectPlacement resolveEffectivePlacement(IrisObjectPlacement objectPlacement, IrisObject object) { if (objectPlacement == null || object == null) { return objectPlacement; } @@ -472,19 +634,20 @@ public class MantleObjectComponent extends IrisMantleComponent { } String normalized = loadKey.toLowerCase(Locale.ROOT); - boolean imported = normalized.startsWith("imports/") + boolean legacyImported = normalized.startsWith("imports/") || normalized.contains("/imports/") || normalized.contains("imports/"); + IrisExternalDatapack externalDatapack = resolveExternalDatapackForObjectKey(normalized); + boolean externalImported = externalDatapack != null; + boolean imported = legacyImported || externalImported; + if (!imported) { return objectPlacement; } ObjectPlaceMode mode = objectPlacement.getMode(); - if (mode == ObjectPlaceMode.STILT - || mode == ObjectPlaceMode.FAST_STILT - || mode == ObjectPlaceMode.MIN_STILT - || mode == ObjectPlaceMode.FAST_MIN_STILT - || mode == ObjectPlaceMode.CENTER_STILT) { + boolean needsModeChange = mode != ObjectPlaceMode.FAST_MIN_STILT; + if (!needsModeChange) { return objectPlacement; } @@ -493,6 +656,42 @@ public class MantleObjectComponent extends IrisMantleComponent { return effectivePlacement; } + private IrisExternalDatapack resolveExternalDatapackForObjectKey(String normalizedLoadKey) { + if (normalizedLoadKey == null || normalizedLoadKey.isBlank()) { + return null; + } + + int slash = normalizedLoadKey.indexOf('/'); + if (slash <= 0) { + return null; + } + String candidateId = normalizedLoadKey.substring(0, slash); + if (candidateId.isBlank()) { + return null; + } + + IrisDimension dimension = getDimension(); + if (dimension == null || dimension.getExternalDatapacks() == null || dimension.getExternalDatapacks().isEmpty()) { + return null; + } + + for (IrisExternalDatapack externalDatapack : dimension.getExternalDatapacks()) { + if (externalDatapack == null || !externalDatapack.isEnabled()) { + continue; + } + + String id = externalDatapack.getId(); + if (id == null || id.isBlank()) { + continue; + } + if (candidateId.equals(id.toLowerCase(Locale.ROOT))) { + return externalDatapack; + } + } + + return null; + } + private int findCaveAnchorY(MantleWriter writer, RNG rng, int x, int z, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, KMap> anchorCache) { long key = Cache.key(x, z); KList anchors = anchorCache.computeIfAbsent(key, (k) -> scanCaveAnchorColumn(writer, anchorMode, anchorScanStep, objectMinDepthBelowSurface, x, z)); @@ -662,6 +861,11 @@ public class MantleObjectComponent extends IrisMantleComponent { private record ObjectPlacementResult(int attempts, int placed, int rejected, int nullObjects, int errors) { } + private static final class CaveRejectLogState { + private final AtomicLong lastLogMs = new AtomicLong(0L); + private final AtomicInteger suppressed = new AtomicInteger(0); + } + @BlockCoordinates private Set guessPlacedKeys(RNG rng, int x, int z, IrisObjectPlacement objectPlacement) { Set f = new KSet<>(); diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisEntity.java b/core/src/main/java/art/arcane/iris/engine/object/IrisEntity.java index 398c33936..b6d61d865 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisEntity.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisEntity.java @@ -242,8 +242,14 @@ public class IrisEntity extends IrisRegistrant { int gg = 0; for (IrisEntity i : passengers) { Entity passenger = i.spawn(gen, at, rng.nextParallelRNG(234858 + gg++)); - if (!Bukkit.isPrimaryThread()) { - J.s(() -> e.addPassenger(passenger)); + if (passenger == null) { + continue; + } + + if (Bukkit.isPrimaryThread()) { + e.addPassenger(passenger); + } else { + J.runEntity(e, () -> e.addPassenger(passenger)); } } @@ -338,7 +344,7 @@ public class IrisEntity extends IrisRegistrant { if (e instanceof Villager) { Villager villager = (Villager) e; villager.setRemoveWhenFarAway(false); - J.s(() -> villager.setPersistent(true), 1); + J.runEntity(villager, () -> villager.setPersistent(true), 1); } if (e instanceof Mob) { @@ -365,7 +371,7 @@ public class IrisEntity extends IrisRegistrant { Location finalAt1 = at; - J.s(() -> { + J.runEntity(e, () -> { if (isSpawnEffectRiseOutOfGround() && e instanceof LivingEntity && Chunks.hasPlayersNearby(finalAt1)) { Location start = finalAt1.clone(); e.setInvulnerable(true); @@ -373,10 +379,13 @@ public class IrisEntity extends IrisRegistrant { ((LivingEntity) e).setCollidable(false); ((LivingEntity) e).setNoDamageTicks(100000); AtomicInteger t = new AtomicInteger(0); - AtomicInteger v = new AtomicInteger(0); - v.set(J.sr(() -> { - if (t.get() > 100) { - J.csr(v.get()); + Runnable[] loop = new Runnable[1]; + loop[0] = () -> { + if (t.get() > 100 || e.isDead()) { + ((LivingEntity) e).setNoDamageTicks(0); + ((LivingEntity) e).setCollidable(true); + ((LivingEntity) e).setAI(true); + e.setInvulnerable(false); return; } @@ -388,14 +397,20 @@ public class IrisEntity extends IrisRegistrant { if (M.r(0.2)) { e.getWorld().playSound(e.getLocation(), Sound.BLOCK_CHORUS_FLOWER_GROW, 0.8f, 0.1f); } + if (!J.runEntity(e, loop[0], 1)) { + ((LivingEntity) e).setNoDamageTicks(0); + ((LivingEntity) e).setCollidable(true); + ((LivingEntity) e).setAI(true); + e.setInvulnerable(false); + } } else { - J.csr(v.get()); ((LivingEntity) e).setNoDamageTicks(0); ((LivingEntity) e).setCollidable(true); ((LivingEntity) e).setAI(true); e.setInvulnerable(false); } - }, 0)); + }; + J.runEntity(e, loop[0]); } }); @@ -437,7 +452,9 @@ public class IrisEntity extends IrisRegistrant { AtomicReference ae = new AtomicReference<>(); try { - J.s(() -> ae.set(doSpawn(at))); + if (!J.runAt(at, () -> ae.set(doSpawn(at)))) { + return null; + } } catch (Throwable e) { return null; } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisExternalDatapack.java b/core/src/main/java/art/arcane/iris/engine/object/IrisExternalDatapack.java index 6b0136d9d..92f1b6bfa 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisExternalDatapack.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisExternalDatapack.java @@ -29,6 +29,9 @@ public class IrisExternalDatapack { @Desc("If true, minecraft namespace worldgen assets may replace vanilla targets listed in replaceTargets") private boolean replaceVanilla = false; + @Desc("If true, structures projected from this datapack id receive smartbore foundation extension during generation") + private boolean supportSmartBore = true; + @Desc("Explicit replacement targets for minecraft namespace assets") private IrisExternalDatapackReplaceTargets replaceTargets = new IrisExternalDatapackReplaceTargets(); diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisObject.java b/core/src/main/java/art/arcane/iris/engine/object/IrisObject.java index fb181a267..181fc100c 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisObject.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisObject.java @@ -954,7 +954,7 @@ public class IrisObject extends IrisRegistrant { i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone(); i = config.getTranslate().translate(i.clone(), config.getRotation(), spinx, spiny, spinz).clone(); - if (stilting && i.getBlockY() < lowest && !B.isAir(data)) { + if (stilting && i.getBlockY() < lowest && B.isSolid(data)) { lowest = i.getBlockY(); } @@ -1013,8 +1013,7 @@ public class IrisObject extends IrisRegistrant { continue; } - if ((config.isWaterloggable() || config.isUnderwater()) && yy <= placer.getFluidHeight() && data instanceof Waterlogged) { - // TODO Here + if (data instanceof Waterlogged && shouldAutoWaterlogBlock(placer, config, yv, xx, yy, zz)) { ((Waterlogged) data).setWaterlogged(true); } @@ -1056,24 +1055,28 @@ public class IrisObject extends IrisRegistrant { readLock.lock(); IrisStiltSettings settings = config.getStiltSettings(); for (BlockVector g : blocks.keys()) { - BlockData d; + BlockData sourceData; + try { + sourceData = blocks.get(g); + } catch (Throwable e) { + Iris.reportError(e); + Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt cme)"); + sourceData = AIR; + } - if (settings == null || settings.getPalette() == null) { - try { - d = blocks.get(g); - } catch (Throwable e) { - Iris.reportError(e); - Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt cme)"); - d = AIR; - } + if (sourceData == null) { + Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt null)"); + sourceData = AIR; + } - if (d == null) { - Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt null)"); - d = AIR; - } - } else + if (!B.isSolid(sourceData)) { + continue; + } + + BlockData d = sourceData; + if (settings != null && settings.getPalette() != null) { d = config.getStiltSettings().getPalette().get(rng, x, y, z, rdata); - + } BlockVector i = g.clone(); i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone(); @@ -1099,7 +1102,7 @@ public class IrisObject extends IrisRegistrant { } } - if (d == null || B.isAir(d)) + if (d == null || !B.isSolid(d)) continue; xx = x + (int) Math.round(i.getX()); @@ -1112,7 +1115,7 @@ public class IrisObject extends IrisRegistrant { int highest = placer.getHighest(xx, zz, getLoader(), true); - if ((config.isWaterloggable() || config.isUnderwater()) && highest <= placer.getFluidHeight() && d instanceof Waterlogged) + if (d instanceof Waterlogged && shouldAutoWaterlogBlock(placer, config, yv, xx, highest, zz)) ((Waterlogged) d).setWaterlogged(true); if (yv >= 0 && config.isBottom()) @@ -1177,6 +1180,23 @@ public class IrisObject extends IrisRegistrant { || placer.isCarved(x, y - 3, z); } + private boolean shouldAutoWaterlogBlock(IObjectPlacer placer, IrisObjectPlacement placement, int yv, int x, int y, int z) { + if (!(placement.isWaterloggable() || placement.isUnderwater())) { + return false; + } + + if (yv >= 0 && placement.getCarvingSupport().equals(CarvingMode.CARVING_ONLY)) { + return false; + } + + BlockData existing = placer.get(x, y, z); + if (existing == null) { + return false; + } + + return B.isWater(existing) || B.isWaterLogged(existing); + } + public IrisObject rotateCopy(IrisObjectRotation rt) { IrisObject copy = copy(); copy.rotate(rt, 0, 0, 0); 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 1a4d1cc90..55faaf1af 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 @@ -201,7 +201,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator { List starts = new ArrayList<>(structureManager.startsForStructure(chunkAccess.getPos(), structure -> true)); starts.sort(Comparator.comparingInt(start -> structureOrder.getOrDefault(start.getStructure(), Integer.MAX_VALUE))); - Set externalLocateStructures = ExternalDataPackPipeline.snapshotLocateStructureKeys(); + Set externalSmartBoreStructures = ExternalDataPackPipeline.snapshotSmartBoreStructureKeys(); + Set suppressedVanillaStructures = ExternalDataPackPipeline.snapshotSuppressedVanillaStructureKeys(); int seededStructureIndex = Integer.MIN_VALUE; for (int j = 0; j < starts.size(); j++) { @@ -213,17 +214,20 @@ public class IrisChunkGenerator extends CustomChunkGenerator { seededStructureIndex = structureIndex; } Supplier supplier = () -> structureRegistry.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); - String structureKey = supplier.get().toLowerCase(Locale.ROOT); - boolean isExternalLocateStructure = externalLocateStructures.contains(structureKey); + String structureKey = resolveStructureKey(structureRegistry, structure); + if (suppressedVanillaStructures.contains(structureKey)) { + continue; + } + boolean isExternalSmartBoreStructure = externalSmartBoreStructures.contains(structureKey); BitSet[] beforeSolidColumns = null; - if (isExternalLocateStructure) { + if (isExternalSmartBoreStructure) { beforeSolidColumns = snapshotChunkSolidColumns(level, chunkAccess); } try { level.setCurrentlyGenerating(supplier); start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos()); - if (isExternalLocateStructure && beforeSolidColumns != null) { + if (isExternalSmartBoreStructure && beforeSolidColumns != null) { applyExternalStructureFoundations(level, chunkAccess, beforeSolidColumns, EXTERNAL_FOUNDATION_MAX_DEPTH); } } catch (Exception exception) { @@ -237,6 +241,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator { Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS); } + private static String resolveStructureKey(Registry structureRegistry, Structure structure) { + Identifier directKey = structureRegistry.getKey(structure); + if (directKey != null) { + return directKey.toString().toLowerCase(Locale.ROOT); + } + + String fallback = String.valueOf(structure); + int slash = fallback.lastIndexOf('/'); + int end = fallback.lastIndexOf(']'); + if (slash >= 0 && end > slash) { + return fallback.substring(slash + 1, end).toLowerCase(Locale.ROOT); + } + + return fallback.toLowerCase(Locale.ROOT); + } + private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) { ChunkPos chunkPos = ichunkaccess.getPos(); int minX = chunkPos.getMinBlockX();