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

View File

@@ -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<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_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 BlockData AIR = B.getAir();
@@ -203,11 +207,48 @@ public final class ExternalDataPackPipeline {
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) {
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<File> 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<RequestedSourceInput> sourceInputs = new ArrayList<>();
LinkedHashMap<String, Set<String>> resolvedLocateStructuresById = 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++) {
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<String> smartBoreTargets = new LinkedHashSet<>();
smartBoreTargets.addAll(request.resolvedLocateStructures());
smartBoreTargets.addAll(projectionResult.resolvedLocateStructures());
mergeResolvedLocateStructures(resolvedSmartBoreStructuresById, request.id(), smartBoreTargets);
}
LinkedHashSet<String> 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<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) {
if (objectKey == null) {
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() {
LinkedHashMap<String, Set<String>> mapped = new LinkedHashMap<>();
File input = getLocateManifestFile();
@@ -778,6 +953,88 @@ public final class ExternalDataPackPipeline {
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) {
Map<String, DatapackRequest> 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<ProjectionInputAsset> 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<String> resolvedLocateStructures = new LinkedHashSet<>();
resolvedLocateStructures.addAll(request.resolvedLocateStructures());
LinkedHashSet<String> remappedStructureKeys = new LinkedHashSet<>();
LinkedHashSet<String> projectedStructureKeys = new LinkedHashSet<>();
LinkedHashSet<String> structureSetReferences = new LinkedHashSet<>();
LinkedHashSet<String> writtenPaths = new LinkedHashSet<>();
ArrayList<ProjectionOutputAsset> 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<String> structures,
Set<String> structureSets,
Set<String> configuredFeatures,
@@ -3110,6 +3409,7 @@ public final class ExternalDataPackPipeline {
String requiredEnvironment,
boolean required,
boolean replaceVanilla,
boolean supportSmartBore,
IrisExternalDatapackReplaceTargets replaceTargets,
KList<IrisExternalDatapackStructurePatch> 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<IrisExternalDatapackStructurePatch> structurePatches,
Set<String> 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<String> resolvedLocateStructures,
int syntheticStructureSets,
Set<String> projectedStructureKeys,
String managedName,
String error
) {
@@ -3587,6 +3892,16 @@ public final class ExternalDataPackPipeline {
}
}
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);
if (error == null) {
error = "";
@@ -3598,14 +3913,15 @@ public final class ExternalDataPackPipeline {
int installedDatapacks,
int installedAssets,
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) {
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 {
assets = assets == null ? List.of() : List.copyOf(assets);
LinkedHashSet<String> normalized = new LinkedHashSet<>();
@@ -3634,6 +3955,16 @@ public final class ExternalDataPackPipeline {
}
}
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);
}
}

View File

@@ -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<String, KList<File>> 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<String, KList<File>> 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<File> folders = getDatapacksFolder();
if (includeExternal) {
installExternalDataPacks(folders);
installExternalDataPacks(folders, extraWorldDatapackFoldersByPack);
}
KMap<String, KSet<String>> biomes = new KMap<>();
@@ -184,13 +205,16 @@ public class ServerConfigurator {
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()) {
return;
}
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);
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<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, 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;
}

View File

@@ -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();
});
}

View File

@@ -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));
}
}
}

View File

@@ -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) {

View File

@@ -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<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!");
}
@@ -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) {

View File

@@ -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<CompletableFuture<Void>> STARTUP_PREBAKE_COMPLETION = new AtomicReference<>();
private static final ConcurrentHashMap<Class<?>, Field[]> FIELD_CACHE = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Boolean> SKIP_ONCE = new ConcurrentHashMap<>();
private static final Set<String> 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<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();
if (targets.isEmpty()) {
Iris.info("Startup noisemap pre-bake skipped (no installed or self-contained packs found).");

View File

@@ -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<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) {
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) {
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);
}
}
}
}
}

View File

@@ -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<T> {
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<T> {
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;
}
}
}

View File

@@ -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<ModuleState> 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;

View File

@@ -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<String, CaveRejectLogState> 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<Long, KList<Integer>> anchorCache) {
long key = Cache.key(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 static final class CaveRejectLogState {
private final AtomicLong lastLogMs = new AtomicLong(0L);
private final AtomicInteger suppressed = new AtomicInteger(0);
}
@BlockCoordinates
private Set<String> guessPlacedKeys(RNG rng, int x, int z, IrisObjectPlacement objectPlacement) {
Set<String> f = new KSet<>();

View File

@@ -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<Entity> 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;
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -201,7 +201,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
List<StructureStart> starts = new ArrayList<>(structureManager.startsForStructure(chunkAccess.getPos(), structure -> true));
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;
for (int j = 0; j < starts.size(); j++) {
@@ -213,17 +214,20 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
seededStructureIndex = structureIndex;
}
Supplier<String> 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<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) {
ChunkPos chunkPos = ichunkaccess.getPos();
int minX = chunkPos.getMinBlockX();