This commit is contained in:
Brian Neumann-Fopiano
2026-02-21 18:56:10 -05:00
parent 9f35855599
commit 2ca8cc7ad3
15 changed files with 1014 additions and 195 deletions
@@ -91,6 +91,8 @@ public final class ExternalDataPackPipeline {
private static final String IMPORT_PREFIX = "imports"; private static final String IMPORT_PREFIX = "imports";
private static final String LOCATE_MANIFEST_PATH = "cache/external-datapack-locate-manifest.json"; 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 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 CONNECT_TIMEOUT_MS = 4000;
private static final int READ_TIMEOUT_MS = 8000; private static final int READ_TIMEOUT_MS = 8000;
private static final int IMPORT_PARALLELISM = Math.max(1, Math.min(8, Runtime.getRuntime().availableProcessors())); 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<String, String> PACK_ENVIRONMENT_CACHE = new ConcurrentHashMap<>(); private static final Map<String, String> PACK_ENVIRONMENT_CACHE = new ConcurrentHashMap<>();
private static final Map<String, Set<String>> RESOLVED_LOCATE_STRUCTURES_BY_ID = new ConcurrentHashMap<>(); private static final Map<String, Set<String>> RESOLVED_LOCATE_STRUCTURES_BY_ID = new ConcurrentHashMap<>();
private static final Map<String, Set<String>> RESOLVED_LOCATE_STRUCTURES_BY_OBJECT_KEY = new ConcurrentHashMap<>(); private static final Map<String, Set<String>> RESOLVED_LOCATE_STRUCTURES_BY_OBJECT_KEY = new ConcurrentHashMap<>();
private static final Map<String, Set<String>> RESOLVED_SMARTBORE_STRUCTURES_BY_ID = new ConcurrentHashMap<>();
private static final Set<String> SUPPRESSED_VANILLA_STRUCTURE_KEYS = ConcurrentHashMap.newKeySet();
private static final AtomicCache<KMap<Identifier, StructurePlacement>> VANILLA_STRUCTURE_PLACEMENTS = new AtomicCache<>(); private static final AtomicCache<KMap<Identifier, StructurePlacement>> VANILLA_STRUCTURE_PLACEMENTS = new AtomicCache<>();
private static final BlockData AIR = B.getAir(); private static final BlockData AIR = B.getAir();
@@ -203,11 +207,48 @@ public final class ExternalDataPackPipeline {
return Set.copyOf(structures); return Set.copyOf(structures);
} }
public static Set<String> snapshotSmartBoreStructureKeys() {
if (RESOLVED_SMARTBORE_STRUCTURES_BY_ID.isEmpty()) {
Map<String, Set<String>> manifest = readSmartBoreManifest();
if (!manifest.isEmpty()) {
RESOLVED_SMARTBORE_STRUCTURES_BY_ID.putAll(manifest);
}
}
LinkedHashSet<String> structures = new LinkedHashSet<>();
for (Set<String> 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<String> snapshotSuppressedVanillaStructureKeys() {
if (SUPPRESSED_VANILLA_STRUCTURE_KEYS.isEmpty()) {
Set<String> manifest = readSuppressedVanillaStructureManifest();
if (!manifest.isEmpty()) {
SUPPRESSED_VANILLA_STRUCTURE_KEYS.addAll(manifest);
}
}
return Set.copyOf(SUPPRESSED_VANILLA_STRUCTURE_KEYS);
}
public static PipelineSummary processDatapacks(List<DatapackRequest> requests, Map<String, KList<File>> worldDatapackFoldersByPack) { public static PipelineSummary processDatapacks(List<DatapackRequest> requests, Map<String, KList<File>> worldDatapackFoldersByPack) {
PipelineSummary summary = new PipelineSummary(); PipelineSummary summary = new PipelineSummary();
PACK_ENVIRONMENT_CACHE.clear(); PACK_ENVIRONMENT_CACHE.clear();
RESOLVED_LOCATE_STRUCTURES_BY_ID.clear(); RESOLVED_LOCATE_STRUCTURES_BY_ID.clear();
RESOLVED_LOCATE_STRUCTURES_BY_OBJECT_KEY.clear(); RESOLVED_LOCATE_STRUCTURES_BY_OBJECT_KEY.clear();
RESOLVED_SMARTBORE_STRUCTURES_BY_ID.clear();
SUPPRESSED_VANILLA_STRUCTURE_KEYS.clear();
Set<File> knownWorldDatapackFolders = new LinkedHashSet<>(); Set<File> knownWorldDatapackFolders = new LinkedHashSet<>();
if (worldDatapackFoldersByPack != null) { if (worldDatapackFoldersByPack != null) {
@@ -233,6 +274,8 @@ public final class ExternalDataPackPipeline {
Iris.info("Downloading datapacks [0/0] Downloading/Done!"); Iris.info("Downloading datapacks [0/0] Downloading/Done!");
writeLocateManifest(Map.of()); writeLocateManifest(Map.of());
writeObjectLocateManifest(Map.of()); writeObjectLocateManifest(Map.of());
writeSmartBoreManifest(Map.of());
writeSuppressedVanillaStructureManifest(Set.of());
summary.legacyWorldCopyRemovals += pruneManagedWorldDatapacks(knownWorldDatapackFolders, Set.of()); summary.legacyWorldCopyRemovals += pruneManagedWorldDatapacks(knownWorldDatapackFolders, Set.of());
return summary; return summary;
} }
@@ -240,6 +283,8 @@ public final class ExternalDataPackPipeline {
List<RequestedSourceInput> sourceInputs = new ArrayList<>(); List<RequestedSourceInput> sourceInputs = new ArrayList<>();
LinkedHashMap<String, Set<String>> resolvedLocateStructuresById = new LinkedHashMap<>(); LinkedHashMap<String, Set<String>> resolvedLocateStructuresById = new LinkedHashMap<>();
LinkedHashMap<String, Set<String>> resolvedLocateStructuresByObjectKey = new LinkedHashMap<>(); LinkedHashMap<String, Set<String>> resolvedLocateStructuresByObjectKey = new LinkedHashMap<>();
LinkedHashMap<String, Set<String>> resolvedSmartBoreStructuresById = new LinkedHashMap<>();
LinkedHashSet<String> suppressedVanillaStructures = new LinkedHashSet<>();
for (int requestIndex = 0; requestIndex < normalizedRequests.size(); requestIndex++) { for (int requestIndex = 0; requestIndex < normalizedRequests.size(); requestIndex++) {
DatapackRequest request = normalizedRequests.get(requestIndex); DatapackRequest request = normalizedRequests.get(requestIndex);
if (request == null) { if (request == null) {
@@ -285,8 +330,12 @@ public final class ExternalDataPackPipeline {
} }
writeLocateManifest(resolvedLocateStructuresById); writeLocateManifest(resolvedLocateStructuresById);
writeObjectLocateManifest(resolvedLocateStructuresByObjectKey); writeObjectLocateManifest(resolvedLocateStructuresByObjectKey);
writeSmartBoreManifest(resolvedSmartBoreStructuresById);
writeSuppressedVanillaStructureManifest(suppressedVanillaStructures);
RESOLVED_LOCATE_STRUCTURES_BY_ID.putAll(resolvedLocateStructuresById); RESOLVED_LOCATE_STRUCTURES_BY_ID.putAll(resolvedLocateStructuresById);
RESOLVED_LOCATE_STRUCTURES_BY_OBJECT_KEY.putAll(resolvedLocateStructuresByObjectKey); RESOLVED_LOCATE_STRUCTURES_BY_OBJECT_KEY.putAll(resolvedLocateStructuresByObjectKey);
RESOLVED_SMARTBORE_STRUCTURES_BY_ID.putAll(resolvedSmartBoreStructuresById);
SUPPRESSED_VANILLA_STRUCTURE_KEYS.addAll(suppressedVanillaStructures);
return summary; return summary;
} }
@@ -376,6 +425,13 @@ public final class ExternalDataPackPipeline {
summary.worldDatapacksInstalled += projectionResult.installedDatapacks(); summary.worldDatapacksInstalled += projectionResult.installedDatapacks();
summary.worldAssetsInstalled += projectionResult.installedAssets(); summary.worldAssetsInstalled += projectionResult.installedAssets();
mergeResolvedLocateStructures(resolvedLocateStructuresById, request.id(), projectionResult.resolvedLocateStructures()); mergeResolvedLocateStructures(resolvedLocateStructuresById, request.id(), projectionResult.resolvedLocateStructures());
suppressedVanillaStructures.addAll(determineSuppressedVanillaStructures(request, projectionResult.projectedStructureKeys()));
if (request.supportSmartBore()) {
LinkedHashSet<String> smartBoreTargets = new LinkedHashSet<>();
smartBoreTargets.addAll(request.resolvedLocateStructures());
smartBoreTargets.addAll(projectionResult.resolvedLocateStructures());
mergeResolvedLocateStructures(resolvedSmartBoreStructuresById, request.id(), smartBoreTargets);
}
LinkedHashSet<String> objectLocateTargets = new LinkedHashSet<>(); LinkedHashSet<String> objectLocateTargets = new LinkedHashSet<>();
objectLocateTargets.addAll(request.resolvedLocateStructures()); objectLocateTargets.addAll(request.resolvedLocateStructures());
objectLocateTargets.addAll(projectionResult.resolvedLocateStructures()); objectLocateTargets.addAll(projectionResult.resolvedLocateStructures());
@@ -425,8 +481,12 @@ public final class ExternalDataPackPipeline {
writeLocateManifest(resolvedLocateStructuresById); writeLocateManifest(resolvedLocateStructuresById);
writeObjectLocateManifest(resolvedLocateStructuresByObjectKey); writeObjectLocateManifest(resolvedLocateStructuresByObjectKey);
writeSmartBoreManifest(resolvedSmartBoreStructuresById);
writeSuppressedVanillaStructureManifest(suppressedVanillaStructures);
RESOLVED_LOCATE_STRUCTURES_BY_ID.putAll(resolvedLocateStructuresById); RESOLVED_LOCATE_STRUCTURES_BY_ID.putAll(resolvedLocateStructuresById);
RESOLVED_LOCATE_STRUCTURES_BY_OBJECT_KEY.putAll(resolvedLocateStructuresByObjectKey); RESOLVED_LOCATE_STRUCTURES_BY_OBJECT_KEY.putAll(resolvedLocateStructuresByObjectKey);
RESOLVED_SMARTBORE_STRUCTURES_BY_ID.putAll(resolvedSmartBoreStructuresById);
SUPPRESSED_VANILLA_STRUCTURE_KEYS.addAll(suppressedVanillaStructures);
return summary; return summary;
} }
@@ -438,6 +498,14 @@ public final class ExternalDataPackPipeline {
return Iris.instance.getDataFile(OBJECT_LOCATE_MANIFEST_PATH); 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) { private static String normalizeLocateId(String id) {
if (id == null) { if (id == null) {
return ""; return "";
@@ -464,6 +532,26 @@ public final class ExternalDataPackPipeline {
return normalized; return normalized;
} }
private static Set<String> determineSuppressedVanillaStructures(DatapackRequest request, Set<String> projectedStructureKeys) {
LinkedHashSet<String> suppressed = new LinkedHashSet<>();
if (request == null || !request.replaceVanilla() || request.alongsideMode()) {
return suppressed;
}
Set<String> 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) { private static String normalizeObjectLoadKey(String objectKey) {
if (objectKey == null) { if (objectKey == null) {
return ""; return "";
@@ -678,6 +766,93 @@ public final class ExternalDataPackPipeline {
} }
} }
private static void writeSmartBoreManifest(Map<String, Set<String>> resolvedSmartBoreStructuresById) {
File output = getSmartBoreManifestFile();
LinkedHashMap<String, Set<String>> normalized = new LinkedHashMap<>();
if (resolvedSmartBoreStructuresById != null) {
for (Map.Entry<String, Set<String>> entry : resolvedSmartBoreStructuresById.entrySet()) {
String normalizedId = normalizeLocateId(entry.getKey());
if (normalizedId.isBlank()) {
continue;
}
LinkedHashSet<String> structures = new LinkedHashSet<>();
Set<String> 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<String> ids = new ArrayList<>(normalized.keySet());
ids.sort(String::compareTo);
for (String id : ids) {
Set<String> structures = normalized.get(id);
if (structures == null || structures.isEmpty()) {
continue;
}
ArrayList<String> 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<String> suppressedStructures) {
File output = getSuppressedVanillaStructureManifestFile();
LinkedHashSet<String> 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<String> 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<String, Set<String>> readLocateManifest() { private static Map<String, Set<String>> readLocateManifest() {
LinkedHashMap<String, Set<String>> mapped = new LinkedHashMap<>(); LinkedHashMap<String, Set<String>> mapped = new LinkedHashMap<>();
File input = getLocateManifestFile(); File input = getLocateManifestFile();
@@ -778,6 +953,88 @@ public final class ExternalDataPackPipeline {
return mapped; return mapped;
} }
private static Map<String, Set<String>> readSmartBoreManifest() {
LinkedHashMap<String, Set<String>> 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<String> keys = new ArrayList<>(ids.keySet());
keys.sort(String::compareTo);
for (String key : keys) {
String normalizedId = normalizeLocateId(key);
if (normalizedId.isBlank()) {
continue;
}
LinkedHashSet<String> 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<String> readSuppressedVanillaStructureManifest() {
LinkedHashSet<String> 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<DatapackRequest> normalizeRequests(List<DatapackRequest> requests) { private static List<DatapackRequest> normalizeRequests(List<DatapackRequest> requests) {
Map<String, DatapackRequest> deduplicated = new HashMap<>(); Map<String, DatapackRequest> deduplicated = new HashMap<>();
if (requests == null) { if (requests == null) {
@@ -1028,7 +1285,10 @@ public final class ExternalDataPackPipeline {
String managedName = buildManagedWorldDatapackName(sourceDescriptor.targetPack(), sourceDescriptor.sourceKey()); String managedName = buildManagedWorldDatapackName(sourceDescriptor.targetPack(), sourceDescriptor.sourceKey());
if (worldDatapackFolders == null || worldDatapackFolders.isEmpty()) { 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; ProjectionAssetSummary projectionAssetSummary;
@@ -1041,7 +1301,14 @@ public final class ExternalDataPackPipeline {
} }
if (projectionAssetSummary.assets().isEmpty()) { 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; 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 { private static ProjectionAssetSummary buildProjectedAssets(File source, SourceDescriptor sourceDescriptor, DatapackRequest request) throws IOException {
@@ -1081,7 +1355,16 @@ public final class ExternalDataPackPipeline {
List<ProjectionInputAsset> inputAssets = projectionSelection.assets(); List<ProjectionInputAsset> inputAssets = projectionSelection.assets();
if (inputAssets.isEmpty()) { 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); String scopeNamespace = buildScopeNamespace(sourceDescriptor, request);
@@ -1103,9 +1386,11 @@ public final class ExternalDataPackPipeline {
LinkedHashSet<String> resolvedLocateStructures = new LinkedHashSet<>(); LinkedHashSet<String> resolvedLocateStructures = new LinkedHashSet<>();
resolvedLocateStructures.addAll(request.resolvedLocateStructures()); resolvedLocateStructures.addAll(request.resolvedLocateStructures());
LinkedHashSet<String> remappedStructureKeys = new LinkedHashSet<>(); LinkedHashSet<String> remappedStructureKeys = new LinkedHashSet<>();
LinkedHashSet<String> projectedStructureKeys = new LinkedHashSet<>();
LinkedHashSet<String> structureSetReferences = new LinkedHashSet<>(); LinkedHashSet<String> structureSetReferences = new LinkedHashSet<>();
LinkedHashSet<String> writtenPaths = new LinkedHashSet<>(); LinkedHashSet<String> writtenPaths = new LinkedHashSet<>();
ArrayList<ProjectionOutputAsset> outputAssets = new ArrayList<>(); ArrayList<ProjectionOutputAsset> outputAssets = new ArrayList<>();
int projectedCanonicalStructureNbtCount = 0;
for (ProjectionInputAsset inputAsset : inputAssets) { for (ProjectionInputAsset inputAsset : inputAssets) {
ProjectedEntry projectedEntry = inputAsset.entry(); ProjectedEntry projectedEntry = inputAsset.entry();
@@ -1152,6 +1437,10 @@ public final class ExternalDataPackPipeline {
remappedStructureKeys.add(effectiveEntry.key()); remappedStructureKeys.add(effectiveEntry.key());
resolvedLocateStructures.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) { } else if (projectedEntry.type() == ProjectedEntryType.STRUCTURE_SET) {
structureSetReferences.addAll(readStructureSetReferences(root)); structureSetReferences.addAll(readStructureSetReferences(root));
} }
@@ -1160,6 +1449,11 @@ public final class ExternalDataPackPipeline {
} }
outputAssets.add(new ProjectionOutputAsset(outputRelativePath, outputBytes)); outputAssets.add(new ProjectionOutputAsset(outputRelativePath, outputBytes));
if (projectedEntry.type() == ProjectedEntryType.STRUCTURE_NBT
&& outputRelativePath.endsWith(".nbt")
&& outputRelativePath.contains("/structure/")) {
projectedCanonicalStructureNbtCount++;
}
} }
int syntheticStructureSets = 0; int syntheticStructureSets = 0;
@@ -1195,7 +1489,11 @@ public final class ExternalDataPackPipeline {
outputAssets.add(new ProjectionOutputAsset(tagPath, root.toString(4).getBytes(StandardCharsets.UTF_8))); 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 { 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 TEMPLATE_POOL -> "data/" + namespace + "/worldgen/template_pool/" + path + ".json";
case PROCESSOR_LIST -> "data/" + namespace + "/worldgen/processor_list/" + 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 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, String requiredEnvironment,
boolean required, boolean required,
boolean replaceVanilla, boolean replaceVanilla,
boolean supportSmartBore,
Set<String> structures, Set<String> structures,
Set<String> structureSets, Set<String> structureSets,
Set<String> configuredFeatures, Set<String> configuredFeatures,
@@ -3110,6 +3409,7 @@ public final class ExternalDataPackPipeline {
String requiredEnvironment, String requiredEnvironment,
boolean required, boolean required,
boolean replaceVanilla, boolean replaceVanilla,
boolean supportSmartBore,
IrisExternalDatapackReplaceTargets replaceTargets, IrisExternalDatapackReplaceTargets replaceTargets,
KList<IrisExternalDatapackStructurePatch> structurePatches KList<IrisExternalDatapackStructurePatch> structurePatches
) { ) {
@@ -3120,6 +3420,7 @@ public final class ExternalDataPackPipeline {
requiredEnvironment, requiredEnvironment,
required, required,
replaceVanilla, replaceVanilla,
supportSmartBore,
replaceTargets, replaceTargets,
structurePatches, structurePatches,
Set.of(), Set.of(),
@@ -3136,6 +3437,7 @@ public final class ExternalDataPackPipeline {
String requiredEnvironment, String requiredEnvironment,
boolean required, boolean required,
boolean replaceVanilla, boolean replaceVanilla,
boolean supportSmartBore,
IrisExternalDatapackReplaceTargets replaceTargets, IrisExternalDatapackReplaceTargets replaceTargets,
KList<IrisExternalDatapackStructurePatch> structurePatches, KList<IrisExternalDatapackStructurePatch> structurePatches,
Set<String> forcedBiomeKeys, Set<String> forcedBiomeKeys,
@@ -3150,6 +3452,7 @@ public final class ExternalDataPackPipeline {
normalizeEnvironment(requiredEnvironment), normalizeEnvironment(requiredEnvironment),
required, required,
replaceVanilla, replaceVanilla,
supportSmartBore,
normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructures(), "worldgen/structure/"), normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructures(), "worldgen/structure/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructureSets(), "worldgen/structure_set/"), normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructureSets(), "worldgen/structure_set/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getConfiguredFeatures(), "worldgen/configured_feature/"), normalizeTargets(replaceTargets == null ? null : replaceTargets.getConfiguredFeatures(), "worldgen/configured_feature/"),
@@ -3216,6 +3519,7 @@ public final class ExternalDataPackPipeline {
environment, environment,
required || other.required, required || other.required,
replaceVanilla || other.replaceVanilla, replaceVanilla || other.replaceVanilla,
supportSmartBore || other.supportSmartBore,
union(structures, other.structures), union(structures, other.structures),
union(structureSets, other.structureSets), union(structureSets, other.structureSets),
union(configuredFeatures, other.configuredFeatures), union(configuredFeatures, other.configuredFeatures),
@@ -3573,6 +3877,7 @@ public final class ExternalDataPackPipeline {
int installedAssets, int installedAssets,
Set<String> resolvedLocateStructures, Set<String> resolvedLocateStructures,
int syntheticStructureSets, int syntheticStructureSets,
Set<String> projectedStructureKeys,
String managedName, String managedName,
String error String error
) { ) {
@@ -3587,6 +3892,16 @@ public final class ExternalDataPackPipeline {
} }
} }
resolvedLocateStructures = Set.copyOf(normalized); resolvedLocateStructures = Set.copyOf(normalized);
LinkedHashSet<String> 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); syntheticStructureSets = Math.max(0, syntheticStructureSets);
if (error == null) { if (error == null) {
error = ""; error = "";
@@ -3598,14 +3913,15 @@ public final class ExternalDataPackPipeline {
int installedDatapacks, int installedDatapacks,
int installedAssets, int installedAssets,
Set<String> resolvedLocateStructures, Set<String> resolvedLocateStructures,
int syntheticStructureSets int syntheticStructureSets,
Set<String> 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) { private static ProjectionResult failure(String managedName, String error) {
String message = error == null || error.isBlank() ? "projection failed" : 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<ProjectionOutputAsset> assets, Set<String> resolvedLocateStructures, int syntheticStructureSets) { private record ProjectionAssetSummary(
List<ProjectionOutputAsset> assets,
Set<String> resolvedLocateStructures,
int syntheticStructureSets,
Set<String> projectedStructureKeys
) {
private ProjectionAssetSummary { private ProjectionAssetSummary {
assets = assets == null ? List.of() : List.copyOf(assets); assets = assets == null ? List.of() : List.copyOf(assets);
LinkedHashSet<String> normalized = new LinkedHashSet<>(); LinkedHashSet<String> normalized = new LinkedHashSet<>();
@@ -3634,6 +3955,16 @@ public final class ExternalDataPackPipeline {
} }
} }
resolvedLocateStructures = Set.copyOf(normalized); resolvedLocateStructures = Set.copyOf(normalized);
LinkedHashSet<String> 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); syntheticStructureSets = Math.max(0, syntheticStructureSets);
} }
} }
@@ -24,6 +24,7 @@ import art.arcane.iris.core.loader.ResourceLoader;
import art.arcane.iris.core.nms.INMS; import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.core.nms.datapack.DataVersion; import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.iris.core.nms.datapack.IDataFixer; import art.arcane.iris.core.nms.datapack.IDataFixer;
import art.arcane.iris.engine.IrisNoisemapPrebakePipeline;
import art.arcane.iris.engine.object.*; import art.arcane.iris.engine.object.*;
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;
@@ -76,7 +77,10 @@ public class ServerConfigurator {
} }
deferredInstallPending = false; deferredInstallPending = false;
installDataPacks(true); boolean datapacksMissing = installDataPacks(true);
if (!datapacksMissing) {
IrisNoisemapPrebakePipeline.scheduleInstalledPacksPrebakeAsync();
}
} }
public static void configureIfDeferred() { public static void configureIfDeferred() {
@@ -135,13 +139,21 @@ public class ServerConfigurator {
} }
public static boolean installDataPacks(boolean fullInstall, boolean includeExternal) { public static boolean installDataPacks(boolean fullInstall, boolean includeExternal) {
return installDataPacks(fullInstall, includeExternal, null);
}
public static boolean installDataPacks(
boolean fullInstall,
boolean includeExternal,
KMap<String, KList<File>> extraWorldDatapackFoldersByPack
) {
IDataFixer fixer = DataVersion.getDefault(); IDataFixer fixer = DataVersion.getDefault();
if (fixer == null) { if (fixer == null) {
DataVersion fallback = DataVersion.getLatest(); DataVersion fallback = DataVersion.getLatest();
Iris.warn("Primary datapack fixer was null, forcing latest fixer: " + fallback.getVersion()); Iris.warn("Primary datapack fixer was null, forcing latest fixer: " + fallback.getVersion());
fixer = fallback.get(); fixer = fallback.get();
} }
return installDataPacks(fixer, fullInstall, includeExternal); return installDataPacks(fixer, fullInstall, includeExternal, extraWorldDatapackFoldersByPack);
} }
public static boolean installDataPacks(IDataFixer fixer, boolean fullInstall) { 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) { 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<String, KList<File>> extraWorldDatapackFoldersByPack
) {
if (fixer == null) { if (fixer == null) {
Iris.error("Unable to install datapacks, fixer is null!"); Iris.error("Unable to install datapacks, fixer is null!");
return false; return false;
@@ -161,7 +182,7 @@ public class ServerConfigurator {
DimensionHeight height = new DimensionHeight(fixer); DimensionHeight height = new DimensionHeight(fixer);
KList<File> folders = getDatapacksFolder(); KList<File> folders = getDatapacksFolder();
if (includeExternal) { if (includeExternal) {
installExternalDataPacks(folders); installExternalDataPacks(folders, extraWorldDatapackFoldersByPack);
} }
KMap<String, KSet<String>> biomes = new KMap<>(); KMap<String, KSet<String>> biomes = new KMap<>();
@@ -184,13 +205,16 @@ public class ServerConfigurator {
return fullInstall && verifyDataPacksPost(IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall()); return fullInstall && verifyDataPacksPost(IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall());
} }
private static void installExternalDataPacks(KList<File> folders) { private static void installExternalDataPacks(
KList<File> folders,
KMap<String, KList<File>> extraWorldDatapackFoldersByPack
) {
if (!IrisSettings.get().getGeneral().isImportExternalDatapacks()) { if (!IrisSettings.get().getGeneral().isImportExternalDatapacks()) {
return; return;
} }
KList<ExternalDataPackPipeline.DatapackRequest> requests = collectExternalDatapackRequests(); KList<ExternalDataPackPipeline.DatapackRequest> requests = collectExternalDatapackRequests();
KMap<String, KList<File>> worldDatapackFoldersByPack = collectWorldDatapackFoldersByPack(folders); KMap<String, KList<File>> worldDatapackFoldersByPack = collectWorldDatapackFoldersByPack(folders, extraWorldDatapackFoldersByPack);
ExternalDataPackPipeline.PipelineSummary summary = ExternalDataPackPipeline.processDatapacks(requests, worldDatapackFoldersByPack); ExternalDataPackPipeline.PipelineSummary summary = ExternalDataPackPipeline.processDatapacks(requests, worldDatapackFoldersByPack);
if (summary.getLegacyDownloadRemovals() > 0) { if (summary.getLegacyDownloadRemovals() > 0) {
Iris.verbose("Removed " + summary.getLegacyDownloadRemovals() + " legacy global datapack downloads."); Iris.verbose("Removed " + summary.getLegacyDownloadRemovals() + " legacy global datapack downloads.");
@@ -315,6 +339,7 @@ public class ServerConfigurator {
environment, environment,
definition.isRequired(), definition.isRequired(),
definition.isReplaceVanilla(), definition.isReplaceVanilla(),
definition.isSupportSmartBore(),
definition.getReplaceTargets(), definition.getReplaceTargets(),
definition.getStructurePatches(), definition.getStructurePatches(),
Set.of(), Set.of(),
@@ -343,6 +368,7 @@ public class ServerConfigurator {
environment, environment,
group.required(), group.required(),
group.replaceVanilla(), group.replaceVanilla(),
definition.isSupportSmartBore(),
definition.getReplaceTargets(), definition.getReplaceTargets(),
definition.getStructurePatches(), definition.getStructurePatches(),
group.forcedBiomeKeys(), group.forcedBiomeKeys(),
@@ -871,7 +897,10 @@ public class ServerConfigurator {
) { ) {
} }
private static KMap<String, KList<File>> collectWorldDatapackFoldersByPack(KList<File> fallbackFolders) { private static KMap<String, KList<File>> collectWorldDatapackFoldersByPack(
KList<File> fallbackFolders,
KMap<String, KList<File>> extraWorldDatapackFoldersByPack
) {
KMap<String, KList<File>> foldersByPack = new KMap<>(); KMap<String, KList<File>> foldersByPack = new KMap<>();
KMap<String, String> mappedWorlds = IrisWorlds.get().getWorlds(); KMap<String, String> mappedWorlds = IrisWorlds.get().getWorlds();
@@ -905,6 +934,22 @@ public class ServerConfigurator {
} }
} }
if (extraWorldDatapackFoldersByPack != null && !extraWorldDatapackFoldersByPack.isEmpty()) {
for (Map.Entry<String, KList<File>> entry : extraWorldDatapackFoldersByPack.entrySet()) {
String packName = sanitizePackName(entry.getKey());
if (packName.isBlank()) {
continue;
}
KList<File> folders = entry.getValue();
if (folders == null || folders.isEmpty()) {
continue;
}
for (File folder : folders) {
addWorldDatapackFolder(foldersByPack, packName, folder);
}
}
}
return foldersByPack; return foldersByPack;
} }
@@ -32,7 +32,6 @@ import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param; import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.util.common.format.C; import art.arcane.iris.util.common.format.C;
import art.arcane.volmlib.util.matter.MatterMarker; import art.arcane.volmlib.util.matter.MatterMarker;
import art.arcane.iris.util.common.scheduling.J;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.FluidCollisionMode; import org.bukkit.FluidCollisionMode;
import org.bukkit.Material; import org.bukkit.Material;
@@ -161,7 +160,7 @@ public class CommandWhat implements DirectorExecutor {
for (int zzz = c.getZ() - 4; zzz <= c.getZ() + 4; zzz++) { for (int zzz = c.getZ() - 4; zzz <= c.getZ() + 4; zzz++) {
IrisToolbelt.access(c.getWorld()).getEngine().getMantle().findMarkers(xxx, zzz, new MatterMarker(marker)) IrisToolbelt.access(c.getWorld()).getEngine().getMantle().findMarkers(xxx, zzz, new MatterMarker(marker))
.convert((i) -> i.toLocation(c.getWorld())).forEach((i) -> { .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(); v.incrementAndGet();
}); });
} }
@@ -18,10 +18,11 @@
package art.arcane.iris.core.edit; 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.iris.util.common.scheduling.J;
import art.arcane.volmlib.util.scheduling.SR; import art.arcane.volmlib.util.scheduling.SR;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.entity.FallingBlock; import org.bukkit.entity.FallingBlock;
@@ -46,26 +47,51 @@ public class BlockSignal {
e.setSilent(true); e.setSilent(true);
e.setTicksLived(1); e.setTicksLived(1);
e.setVelocity(new Vector(0, 0, 0)); e.setVelocity(new Vector(0, 0, 0));
J.s(() -> { Location blockLocation = block.getLocation();
Runnable removeTask = () -> {
if (!J.runEntity(e, e::remove) && !e.isDead()) {
e.remove(); e.remove();
active.decrementAndGet();
BlockData type = block.getBlockData();
MultiBurst.burst.lazy(() -> {
for (Player i : block.getWorld().getPlayers()) {
i.sendBlockChange(block.getLocation(), block.getBlockData());
} }
}); active.decrementAndGet();
}, ticks); sendBlockRefresh(block);
};
if (!J.runAt(blockLocation, removeTask, ticks)) {
if (!J.isFolia()) {
J.s(removeTask, ticks);
}
}
} }
public static void of(Block block, int 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) { public static void of(Block block) {
of(block, 100); 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) { public static Runnable forever(Block block) {
Location tg = block.getLocation().clone().add(0.5, 0, 0.5).clone(); Location tg = block.getLocation().clone().add(0.5, 0, 0.5).clone();
FallingBlock e = block.getWorld().spawnFallingBlock(tg.clone(), block.getBlockData()); FallingBlock e = block.getWorld().spawnFallingBlock(tg.clone(), block.getBlockData());
@@ -82,6 +108,7 @@ public class BlockSignal {
new SR(20) { new SR(20) {
@Override @Override
public void run() { public void run() {
if (!J.runEntity(e, () -> {
if (e.isDead()) { if (e.isDead()) {
cancel(); cancel();
return; return;
@@ -90,18 +117,37 @@ public class BlockSignal {
e.setTicksLived(1); e.setTicksLived(1);
e.teleport(tg.clone()); e.teleport(tg.clone());
e.setVelocity(new Vector(0, 0, 0)); e.setVelocity(new Vector(0, 0, 0));
})) {
cancel();
}
} }
}; };
return () -> { return () -> {
if (!J.runEntity(e, e::remove) && !e.isDead()) {
e.remove(); e.remove();
BlockData type = block.getBlockData();
MultiBurst.burst.lazy(() -> {
for (Player i : block.getWorld().getPlayers()) {
i.sendBlockChange(block.getLocation(), block.getBlockData());
} }
}); 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));
}
}
} }
@@ -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.plugin.VolmitSender;
import art.arcane.iris.util.common.scheduling.J; import art.arcane.iris.util.common.scheduling.J;
import lombok.Data; import lombok.Data;
import org.bukkit.Location;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
@@ -48,8 +49,9 @@ public class DustRevealer {
this.key = key; this.key = key;
this.hits = hits; this.hits = hits;
J.s(() -> { Location blockLocation = block.toBlock(world).getLocation();
new BlockSignal(world.getBlockAt(block.getX(), block.getY(), block.getZ()), 10); Runnable revealTask = () -> {
BlockSignal.of(world, block.getX(), block.getY(), block.getZ(), 10);
if (M.r(0.25)) { if (M.r(0.25)) {
world.playSound(block.toBlock(world).getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 1f, RNG.r.f(0.2f, 2f)); 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(); 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) { public static void spawn(Block block, VolmitSender sender) {
@@ -33,6 +33,8 @@ import art.arcane.iris.engine.IrisNoisemapPrebakePipeline;
import art.arcane.iris.engine.framework.SeedManager; import art.arcane.iris.engine.framework.SeedManager;
import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.platform.PlatformChunkGenerator; 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.volmlib.util.exceptions.IrisException;
import art.arcane.iris.util.common.format.C; import art.arcane.iris.util.common.format.C;
import art.arcane.volmlib.util.format.Form; import art.arcane.volmlib.util.format.Form;
@@ -166,8 +168,16 @@ public class IrisCreator {
IrisWorlds.get().put(name(), dimension()); IrisWorlds.get().put(name(), dimension());
} }
boolean verifyDataPacks = !studio(); boolean verifyDataPacks = !studio();
boolean includeExternalDataPacks = !studio(); boolean includeExternalDataPacks = true;
if (ServerConfigurator.installDataPacks(verifyDataPacks, includeExternalDataPacks)) { KMap<String, KList<File>> extraWorldDatapackFoldersByPack = null;
if (studio()) {
File studioDatapackFolder = new File(new File(Bukkit.getWorldContainer(), name()), "datapacks");
KList<File> 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!"); throw new IrisException("Datapacks were missing!");
} }
@@ -295,6 +305,13 @@ public class IrisCreator {
return; return;
} }
if (studio() && !benchmark) {
boolean startupPrebakeReady = IrisNoisemapPrebakePipeline.awaitInstalledPacksPrebakeForStudio();
if (startupPrebakeReady) {
return;
}
}
try { try {
File targetDataFolder = new File(Bukkit.getWorldContainer(), name()); File targetDataFolder = new File(Bukkit.getWorldContainer(), name());
if (studio() && !benchmark) { if (studio() && !benchmark) {
@@ -38,6 +38,8 @@ import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletionService; import java.util.concurrent.CompletionService;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@@ -45,7 +47,9 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
public final class IrisNoisemapPrebakePipeline { 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 long STARTUP_PROGRESS_INTERVAL_MS = Long.getLong("iris.prebake.progress.interval", 30000L);
private static final int STATE_VERSION = 1; private static final int STATE_VERSION = 1;
private static final String STATE_FILE = "noisemap-prebake.state"; 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 AtomicInteger STARTUP_WORKER_SEQUENCE = new AtomicInteger();
private static final AtomicReference<CompletableFuture<Void>> STARTUP_PREBAKE_COMPLETION = new AtomicReference<>();
private static final ConcurrentHashMap<Class<?>, Field[]> FIELD_CACHE = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<Class<?>, Field[]> FIELD_CACHE = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Boolean> SKIP_ONCE = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, Boolean> SKIP_ONCE = new ConcurrentHashMap<>();
private static final Set<String> PREBAKE_LOADERS = Set.of( private static final Set<String> PREBAKE_LOADERS = Set.of(
@@ -83,9 +90,64 @@ public final class IrisNoisemapPrebakePipeline {
private IrisNoisemapPrebakePipeline() { private IrisNoisemapPrebakePipeline() {
} }
public static void prebakeInstalledPacksAtStartup() { public static void scheduleInstalledPacksPrebakeAsync() {
IrisSettings.IrisSettingsPregen settings = IrisSettings.get().getPregen(); if (!IrisSettings.get().getPregen().isStartupNoisemapPrebake()) {
return;
}
if (!STARTUP_PREBAKE_SCHEDULED.compareAndSet(false, true)) {
return;
}
CompletableFuture<Void> 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<Void> 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<PrebakeTarget> targets = collectStartupTargets(); List<PrebakeTarget> targets = collectStartupTargets();
if (targets.isEmpty()) { if (targets.isEmpty()) {
Iris.info("Startup noisemap pre-bake skipped (no installed or self-contained packs found)."); Iris.info("Startup noisemap pre-bake skipped (no installed or self-contained packs found).");
@@ -213,8 +213,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
return; return;
} }
J.s(() -> { for (Player player : getEngine().getWorld().getPlayers()) {
for (Player player : world.getPlayers()) {
if (player == null || !player.isOnline()) { if (player == null || !player.isOnline()) {
continue; continue;
} }
@@ -232,7 +231,6 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
} }
}); });
} }
});
} }
private void raiseDiscoveredChunkFlag(World world, int chunkX, int chunkZ) { private void raiseDiscoveredChunkFlag(World world, int chunkX, int chunkZ) {
@@ -274,8 +272,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
return; return;
} }
J.s(() -> { for (Player player : getEngine().getWorld().getPlayers()) {
for (Player player : world.getPlayers()) {
if (player == null || !player.isOnline()) { if (player == null || !player.isOnline()) {
continue; continue;
} }
@@ -294,7 +291,6 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
} }
}); });
} }
});
} }
private void updateChunkRegion(World world, int chunkX, int chunkZ) { private void updateChunkRegion(World world, int chunkX, int chunkZ) {
@@ -432,7 +428,22 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
if (cl.flip()) { if (cl.flip()) {
try { 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<List<Entity>> 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) { } catch (Throwable e) {
close(); close();
} }
@@ -499,6 +510,47 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
} }
} }
private List<Entity> getFoliaEntitySnapshot(World world) {
Map<String, Entity> snapshot = new ConcurrentHashMap<>();
List<Player> 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<Entity> entities = new KList<>();
entities.addAll(snapshot.values());
return entities;
}
private void spawnChunkSafely(World world, int chunkX, int chunkZ, boolean initial) { private void spawnChunkSafely(World world, int chunkX, int chunkZ, boolean initial) {
if (world == null) { if (world == null) {
return; return;
@@ -756,7 +808,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
public void teleportAsync(PlayerTeleportEvent e) { public void teleportAsync(PlayerTeleportEvent e) {
if (IrisSettings.get().getWorld().getAsyncTeleport().isEnabled()) { if (IrisSettings.get().getWorld().getAsyncTeleport().isEnabled()) {
e.setCancelled(true); e.setCancelled(true);
warmupAreaAsync(e.getPlayer(), e.getTo(), () -> J.s(() -> { warmupAreaAsync(e.getPlayer(), e.getTo(), () -> J.runEntity(e.getPlayer(), () -> {
ignoreTP.set(true); ignoreTP.set(true);
e.getPlayer().teleport(e.getTo(), e.getCause()); e.getPlayer().teleport(e.getTo(), e.getCause());
ignoreTP.set(false); ignoreTP.set(false);
@@ -1018,7 +1070,13 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
if (d.isNotEmpty()) { if (d.isNotEmpty()) {
World w = e.getBlock().getWorld(); 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);
}
}
} }
} }
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.engine.framework; package art.arcane.iris.engine.framework;
import art.arcane.iris.Iris;
import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.nms.container.BlockPos; import art.arcane.iris.core.nms.container.BlockPos;
import art.arcane.iris.core.nms.container.Pair; 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.iris.util.common.scheduling.J;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch; import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import art.arcane.iris.util.common.scheduling.jobs.SingleJob; import art.arcane.iris.util.common.scheduling.jobs.SingleJob;
import io.papermc.lib.PaperLib;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.lang.reflect.Method;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -106,7 +110,7 @@ public interface Locator<T> {
default void find(Player player, boolean teleport, String message) { default void find(Player player, boolean teleport, String message) {
find(player, location -> { find(player, location -> {
if (teleport) { if (teleport) {
J.s(() -> player.teleport(location)); J.runEntity(player, () -> teleportAsyncSafely(player, location));
} else { } else {
player.sendMessage(C.GREEN + message + " at: " + location.getBlockX() + " " + location.getBlockY() + " " + location.getBlockZ()); player.sendMessage(C.GREEN + message + " at: " + location.getBlockX() + " " + location.getBlockY() + " " + location.getBlockZ());
} }
@@ -206,4 +210,39 @@ public interface Locator<T> {
return null; return null;
}); });
} }
static void teleportAsyncSafely(Player player, Location location) {
if (player == null || location == null) {
return;
}
if (invokeNativeTeleportAsync(player, location)) {
return;
}
try {
CompletableFuture<Boolean> 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;
}
}
} }
@@ -33,7 +33,6 @@ import java.util.Arrays;
public class IrisCaveCarver3D { public class IrisCaveCarver3D {
private static final byte LIQUID_AIR = 0; 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_LAVA = 2;
private static final byte LIQUID_FORCED_AIR = 3; private static final byte LIQUID_FORCED_AIR = 3;
@@ -48,7 +47,6 @@ public class IrisCaveCarver3D {
private final KList<ModuleState> modules; private final KList<ModuleState> modules;
private final double normalization; private final double normalization;
private final MatterCavern carveAir; private final MatterCavern carveAir;
private final MatterCavern carveWater;
private final MatterCavern carveLava; private final MatterCavern carveLava;
private final MatterCavern carveForcedAir; private final MatterCavern carveForcedAir;
@@ -57,7 +55,6 @@ public class IrisCaveCarver3D {
this.data = engine.getData(); this.data = engine.getData();
this.profile = profile; this.profile = profile;
this.carveAir = new MatterCavern(true, "", LIQUID_AIR); this.carveAir = new MatterCavern(true, "", LIQUID_AIR);
this.carveWater = new MatterCavern(true, "", LIQUID_WATER);
this.carveLava = new MatterCavern(true, "", LIQUID_LAVA); this.carveLava = new MatterCavern(true, "", LIQUID_LAVA);
this.carveForcedAir = new MatterCavern(true, "", LIQUID_FORCED_AIR); this.carveForcedAir = new MatterCavern(true, "", LIQUID_FORCED_AIR);
this.modules = new KList<>(); this.modules = new KList<>();
@@ -352,24 +349,9 @@ public class IrisCaveCarver3D {
} }
if (profile.isAllowWater() && y <= fluidHeight) { if (profile.isAllowWater() && y <= fluidHeight) {
if (surfaceY - y < waterMinDepthBelowSurface) {
return carveAir; 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;
}
if (!profile.isAllowLava() && y <= lavaHeight) { if (!profile.isAllowLava() && y <= lavaHeight) {
return carveForcedAir; return carveForcedAir;
} }
@@ -377,38 +359,6 @@ public class IrisCaveCarver3D {
return carveAir; 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) { private double clampColumnWeight(double weight) {
if (Double.isNaN(weight) || Double.isInfinite(weight)) { if (Double.isNaN(weight) || Double.isInfinite(weight)) {
return 0D; return 0D;
@@ -48,10 +48,15 @@ import java.io.IOException;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; 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.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@ComponentFlag(ReservedFlag.OBJECT) @ComponentFlag(ReservedFlag.OBJECT)
public class MantleObjectComponent extends IrisMantleComponent { public class MantleObjectComponent extends IrisMantleComponent {
private static final long CAVE_REJECT_LOG_THROTTLE_MS = 5000L;
private static final Map<String, CaveRejectLogState> CAVE_REJECT_LOG_STATE = new ConcurrentHashMap<>();
public MantleObjectComponent(EngineMantle engineMantle) { public MantleObjectComponent(EngineMantle engineMantle) {
super(engineMantle, ReservedFlag.OBJECT, 1); super(engineMantle, ReservedFlag.OBJECT, 1);
@@ -66,11 +71,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
IrisRegion region = getComplex().getRegionStream().get(xxx, zzz); IrisRegion region = getComplex().getRegionStream().get(xxx, zzz);
IrisBiome surfaceBiome = getComplex().getTrueBiomeStream().get(xxx, zzz); IrisBiome surfaceBiome = getComplex().getTrueBiomeStream().get(xxx, zzz);
int surfaceY = getEngineMantle().getEngine().getHeight(xxx, zzz, true); int surfaceY = getEngineMantle().getEngine().getHeight(xxx, zzz, true);
int sampleY = Math.max(1, surfaceY - 48); IrisBiome caveBiome = resolveCaveObjectBiome(xxx, zzz, surfaceY, surfaceBiome);
IrisBiome caveBiome = getEngineMantle().getEngine().getCaveBiome(xxx, sampleY, zzz);
if (caveBiome == null) {
caveBiome = surfaceBiome;
}
if (traceRegen) { if (traceRegen) {
Iris.info("Regen object layer start: chunk=" + x + "," + z Iris.info("Regen object layer start: chunk=" + x + "," + z
+ " surfaceBiome=" + surfaceBiome.getLoadKey() + " surfaceBiome=" + surfaceBiome.getLoadKey()
@@ -105,6 +106,41 @@ public class MantleObjectComponent extends IrisMantleComponent {
return new RNG((long) (seed * noise.noise(x, z))); 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 @ChunkCoordinates
private ObjectPlacementSummary placeObjects(MantleWriter writer, RNG rng, int x, int z, IrisBiome surfaceBiome, IrisBiome caveBiome, IrisRegion region, boolean traceRegen) { private ObjectPlacementSummary placeObjects(MantleWriter writer, RNG rng, int x, int z, IrisBiome surfaceBiome, IrisBiome caveBiome, IrisRegion region, boolean traceRegen) {
int biomeSurfaceChecked = 0; int biomeSurfaceChecked = 0;
@@ -393,6 +429,22 @@ public class MantleObjectComponent extends IrisMantleComponent {
+ " density=" + density + " density=" + density
+ " placementKeys=" + objectPlacement.getPlace().toString(",")); + " placementKeys=" + objectPlacement.getPlace().toString(","));
} }
logCaveReject(
scope,
"NULL_OBJECT",
metricChunkX,
metricChunkZ,
objectPlacement,
null,
i,
density,
anchorMode,
anchorSearchAttempts,
anchorScanStep,
objectMinDepthBelowSurface,
null,
null
);
continue; continue;
} }
@@ -415,24 +467,59 @@ public class MantleObjectComponent extends IrisMantleComponent {
if (y < 0) { if (y < 0) {
rejected++; rejected++;
logCaveReject(
scope,
"NO_ANCHOR",
metricChunkX,
metricChunkZ,
objectPlacement,
object,
i,
density,
anchorMode,
anchorSearchAttempts,
anchorScanStep,
objectMinDepthBelowSurface,
null,
null
);
continue; continue;
} }
int id = rng.i(0, Integer.MAX_VALUE); int id = rng.i(0, Integer.MAX_VALUE);
IrisObjectPlacement effectivePlacement = resolveEffectivePlacement(objectPlacement, object); IrisObjectPlacement effectivePlacement = resolveEffectivePlacement(objectPlacement, object);
AtomicBoolean wrotePlacementData = new AtomicBoolean(false);
try { try {
int result = object.place(x, y, z, writer, effectivePlacement, rng, (b, data) -> { 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); writer.setData(b.getX(), b.getY(), b.getZ(), object.getLoadKey() + "@" + id);
if (effectivePlacement.isDolphinTarget() && effectivePlacement.isUnderwater() && B.isStorageChest(data)) { if (effectivePlacement.isDolphinTarget() && effectivePlacement.isUnderwater() && B.isStorageChest(data)) {
writer.setData(b.getX(), b.getY(), b.getZ(), MatterStructurePOI.BURIED_TREASURE); writer.setData(b.getX(), b.getY(), b.getZ(), MatterStructurePOI.BURIED_TREASURE);
} }
}, null, getData()); }, null, getData());
if (result >= 0) { boolean wroteBlocks = wrotePlacementData.get();
if (wroteBlocks) {
placed++; placed++;
} else { } else if (result < 0) {
rejected++; rejected++;
logCaveReject(
scope,
"PLACE_NEGATIVE",
metricChunkX,
metricChunkZ,
objectPlacement,
object,
i,
density,
anchorMode,
anchorSearchAttempts,
anchorScanStep,
objectMinDepthBelowSurface,
y,
null
);
} }
if (traceRegen) { if (traceRegen) {
@@ -443,6 +530,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
+ " anchorY=" + y + " anchorY=" + y
+ " px=" + x + " px=" + x
+ " pz=" + z + " pz=" + z
+ " wroteBlocks=" + wroteBlocks
+ " densityIndex=" + i + " densityIndex=" + i
+ " density=" + density); + " density=" + density);
} }
@@ -455,13 +543,87 @@ public class MantleObjectComponent extends IrisMantleComponent {
+ " densityIndex=" + i + " densityIndex=" + i
+ " density=" + density + " density=" + density
+ " error=" + e.getClass().getSimpleName() + ":" + e.getMessage()); + " 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); 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) { if (objectPlacement == null || object == null) {
return objectPlacement; return objectPlacement;
} }
@@ -472,19 +634,20 @@ public class MantleObjectComponent extends IrisMantleComponent {
} }
String normalized = loadKey.toLowerCase(Locale.ROOT); String normalized = loadKey.toLowerCase(Locale.ROOT);
boolean imported = normalized.startsWith("imports/") boolean legacyImported = normalized.startsWith("imports/")
|| normalized.contains("/imports/") || normalized.contains("/imports/")
|| normalized.contains("imports/"); || normalized.contains("imports/");
IrisExternalDatapack externalDatapack = resolveExternalDatapackForObjectKey(normalized);
boolean externalImported = externalDatapack != null;
boolean imported = legacyImported || externalImported;
if (!imported) { if (!imported) {
return objectPlacement; return objectPlacement;
} }
ObjectPlaceMode mode = objectPlacement.getMode(); ObjectPlaceMode mode = objectPlacement.getMode();
if (mode == ObjectPlaceMode.STILT boolean needsModeChange = mode != ObjectPlaceMode.FAST_MIN_STILT;
|| mode == ObjectPlaceMode.FAST_STILT if (!needsModeChange) {
|| mode == ObjectPlaceMode.MIN_STILT
|| mode == ObjectPlaceMode.FAST_MIN_STILT
|| mode == ObjectPlaceMode.CENTER_STILT) {
return objectPlacement; return objectPlacement;
} }
@@ -493,6 +656,42 @@ public class MantleObjectComponent extends IrisMantleComponent {
return effectivePlacement; 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<Long, KList<Integer>> anchorCache) { private int findCaveAnchorY(MantleWriter writer, RNG rng, int x, int z, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, KMap<Long, KList<Integer>> anchorCache) {
long key = Cache.key(x, z); long key = Cache.key(x, z);
KList<Integer> anchors = anchorCache.computeIfAbsent(key, (k) -> scanCaveAnchorColumn(writer, anchorMode, anchorScanStep, objectMinDepthBelowSurface, x, z)); KList<Integer> 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 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 @BlockCoordinates
private Set<String> guessPlacedKeys(RNG rng, int x, int z, IrisObjectPlacement objectPlacement) { private Set<String> guessPlacedKeys(RNG rng, int x, int z, IrisObjectPlacement objectPlacement) {
Set<String> f = new KSet<>(); Set<String> f = new KSet<>();
@@ -242,8 +242,14 @@ public class IrisEntity extends IrisRegistrant {
int gg = 0; int gg = 0;
for (IrisEntity i : passengers) { for (IrisEntity i : passengers) {
Entity passenger = i.spawn(gen, at, rng.nextParallelRNG(234858 + gg++)); Entity passenger = i.spawn(gen, at, rng.nextParallelRNG(234858 + gg++));
if (!Bukkit.isPrimaryThread()) { if (passenger == null) {
J.s(() -> e.addPassenger(passenger)); 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) { if (e instanceof Villager) {
Villager villager = (Villager) e; Villager villager = (Villager) e;
villager.setRemoveWhenFarAway(false); villager.setRemoveWhenFarAway(false);
J.s(() -> villager.setPersistent(true), 1); J.runEntity(villager, () -> villager.setPersistent(true), 1);
} }
if (e instanceof Mob) { if (e instanceof Mob) {
@@ -365,7 +371,7 @@ public class IrisEntity extends IrisRegistrant {
Location finalAt1 = at; Location finalAt1 = at;
J.s(() -> { J.runEntity(e, () -> {
if (isSpawnEffectRiseOutOfGround() && e instanceof LivingEntity && Chunks.hasPlayersNearby(finalAt1)) { if (isSpawnEffectRiseOutOfGround() && e instanceof LivingEntity && Chunks.hasPlayersNearby(finalAt1)) {
Location start = finalAt1.clone(); Location start = finalAt1.clone();
e.setInvulnerable(true); e.setInvulnerable(true);
@@ -373,10 +379,13 @@ public class IrisEntity extends IrisRegistrant {
((LivingEntity) e).setCollidable(false); ((LivingEntity) e).setCollidable(false);
((LivingEntity) e).setNoDamageTicks(100000); ((LivingEntity) e).setNoDamageTicks(100000);
AtomicInteger t = new AtomicInteger(0); AtomicInteger t = new AtomicInteger(0);
AtomicInteger v = new AtomicInteger(0); Runnable[] loop = new Runnable[1];
v.set(J.sr(() -> { loop[0] = () -> {
if (t.get() > 100) { if (t.get() > 100 || e.isDead()) {
J.csr(v.get()); ((LivingEntity) e).setNoDamageTicks(0);
((LivingEntity) e).setCollidable(true);
((LivingEntity) e).setAI(true);
e.setInvulnerable(false);
return; return;
} }
@@ -388,14 +397,20 @@ public class IrisEntity extends IrisRegistrant {
if (M.r(0.2)) { if (M.r(0.2)) {
e.getWorld().playSound(e.getLocation(), Sound.BLOCK_CHORUS_FLOWER_GROW, 0.8f, 0.1f); e.getWorld().playSound(e.getLocation(), Sound.BLOCK_CHORUS_FLOWER_GROW, 0.8f, 0.1f);
} }
} else { if (!J.runEntity(e, loop[0], 1)) {
J.csr(v.get());
((LivingEntity) e).setNoDamageTicks(0); ((LivingEntity) e).setNoDamageTicks(0);
((LivingEntity) e).setCollidable(true); ((LivingEntity) e).setCollidable(true);
((LivingEntity) e).setAI(true); ((LivingEntity) e).setAI(true);
e.setInvulnerable(false); e.setInvulnerable(false);
} }
}, 0)); } else {
((LivingEntity) e).setNoDamageTicks(0);
((LivingEntity) e).setCollidable(true);
((LivingEntity) e).setAI(true);
e.setInvulnerable(false);
}
};
J.runEntity(e, loop[0]);
} }
}); });
@@ -437,7 +452,9 @@ public class IrisEntity extends IrisRegistrant {
AtomicReference<Entity> ae = new AtomicReference<>(); AtomicReference<Entity> ae = new AtomicReference<>();
try { try {
J.s(() -> ae.set(doSpawn(at))); if (!J.runAt(at, () -> ae.set(doSpawn(at)))) {
return null;
}
} catch (Throwable e) { } catch (Throwable e) {
return null; return null;
} }
@@ -29,6 +29,9 @@ public class IrisExternalDatapack {
@Desc("If true, minecraft namespace worldgen assets may replace vanilla targets listed in replaceTargets") @Desc("If true, minecraft namespace worldgen assets may replace vanilla targets listed in replaceTargets")
private boolean replaceVanilla = false; 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") @Desc("Explicit replacement targets for minecraft namespace assets")
private IrisExternalDatapackReplaceTargets replaceTargets = new IrisExternalDatapackReplaceTargets(); private IrisExternalDatapackReplaceTargets replaceTargets = new IrisExternalDatapackReplaceTargets();
@@ -954,7 +954,7 @@ public class IrisObject extends IrisRegistrant {
i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone(); i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone();
i = config.getTranslate().translate(i.clone(), config.getRotation(), 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(); lowest = i.getBlockY();
} }
@@ -1013,8 +1013,7 @@ public class IrisObject extends IrisRegistrant {
continue; continue;
} }
if ((config.isWaterloggable() || config.isUnderwater()) && yy <= placer.getFluidHeight() && data instanceof Waterlogged) { if (data instanceof Waterlogged && shouldAutoWaterlogBlock(placer, config, yv, xx, yy, zz)) {
// TODO Here
((Waterlogged) data).setWaterlogged(true); ((Waterlogged) data).setWaterlogged(true);
} }
@@ -1056,24 +1055,28 @@ public class IrisObject extends IrisRegistrant {
readLock.lock(); readLock.lock();
IrisStiltSettings settings = config.getStiltSettings(); IrisStiltSettings settings = config.getStiltSettings();
for (BlockVector g : blocks.keys()) { for (BlockVector g : blocks.keys()) {
BlockData d; BlockData sourceData;
if (settings == null || settings.getPalette() == null) {
try { try {
d = blocks.get(g); sourceData = blocks.get(g);
} catch (Throwable e) { } catch (Throwable e) {
Iris.reportError(e); Iris.reportError(e);
Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt cme)"); Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt cme)");
d = AIR; sourceData = AIR;
} }
if (d == null) { if (sourceData == null) {
Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt null)"); Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt null)");
d = AIR; sourceData = AIR;
} }
} else
d = config.getStiltSettings().getPalette().get(rng, x, y, z, rdata);
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(); BlockVector i = g.clone();
i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).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; continue;
xx = x + (int) Math.round(i.getX()); xx = x + (int) Math.round(i.getX());
@@ -1112,7 +1115,7 @@ public class IrisObject extends IrisRegistrant {
int highest = placer.getHighest(xx, zz, getLoader(), true); 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); ((Waterlogged) d).setWaterlogged(true);
if (yv >= 0 && config.isBottom()) if (yv >= 0 && config.isBottom())
@@ -1177,6 +1180,23 @@ public class IrisObject extends IrisRegistrant {
|| placer.isCarved(x, y - 3, z); || 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) { public IrisObject rotateCopy(IrisObjectRotation rt) {
IrisObject copy = copy(); IrisObject copy = copy();
copy.rotate(rt, 0, 0, 0); copy.rotate(rt, 0, 0, 0);
@@ -201,7 +201,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
List<StructureStart> starts = new ArrayList<>(structureManager.startsForStructure(chunkAccess.getPos(), structure -> true)); List<StructureStart> starts = new ArrayList<>(structureManager.startsForStructure(chunkAccess.getPos(), structure -> true));
starts.sort(Comparator.comparingInt(start -> structureOrder.getOrDefault(start.getStructure(), Integer.MAX_VALUE))); starts.sort(Comparator.comparingInt(start -> structureOrder.getOrDefault(start.getStructure(), Integer.MAX_VALUE)));
Set<String> externalLocateStructures = ExternalDataPackPipeline.snapshotLocateStructureKeys(); Set<String> externalSmartBoreStructures = ExternalDataPackPipeline.snapshotSmartBoreStructureKeys();
Set<String> suppressedVanillaStructures = ExternalDataPackPipeline.snapshotSuppressedVanillaStructureKeys();
int seededStructureIndex = Integer.MIN_VALUE; int seededStructureIndex = Integer.MIN_VALUE;
for (int j = 0; j < starts.size(); j++) { for (int j = 0; j < starts.size(); j++) {
@@ -213,17 +214,20 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
seededStructureIndex = structureIndex; seededStructureIndex = structureIndex;
} }
Supplier<String> supplier = () -> structureRegistry.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); Supplier<String> supplier = () -> structureRegistry.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString);
String structureKey = supplier.get().toLowerCase(Locale.ROOT); String structureKey = resolveStructureKey(structureRegistry, structure);
boolean isExternalLocateStructure = externalLocateStructures.contains(structureKey); if (suppressedVanillaStructures.contains(structureKey)) {
continue;
}
boolean isExternalSmartBoreStructure = externalSmartBoreStructures.contains(structureKey);
BitSet[] beforeSolidColumns = null; BitSet[] beforeSolidColumns = null;
if (isExternalLocateStructure) { if (isExternalSmartBoreStructure) {
beforeSolidColumns = snapshotChunkSolidColumns(level, chunkAccess); beforeSolidColumns = snapshotChunkSolidColumns(level, chunkAccess);
} }
try { try {
level.setCurrentlyGenerating(supplier); level.setCurrentlyGenerating(supplier);
start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos()); 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); applyExternalStructureFoundations(level, chunkAccess, beforeSolidColumns, EXTERNAL_FOUNDATION_MAX_DEPTH);
} }
} catch (Exception exception) { } catch (Exception exception) {
@@ -237,6 +241,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS); Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS);
} }
private static String resolveStructureKey(Registry<Structure> 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) { private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) {
ChunkPos chunkPos = ichunkaccess.getPos(); ChunkPos chunkPos = ichunkaccess.getPos();
int minX = chunkPos.getMinBlockX(); int minX = chunkPos.getMinBlockX();