mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-06-18 23:01:07 +00:00
dwa
This commit is contained in:
@@ -528,7 +528,7 @@ public class Iris extends VolmitPlugin implements Listener {
|
|||||||
J.ar(this::checkConfigHotload, 60);
|
J.ar(this::checkConfigHotload, 60);
|
||||||
J.sr(this::tickQueue, 0);
|
J.sr(this::tickQueue, 0);
|
||||||
J.s(this::setupPapi);
|
J.s(this::setupPapi);
|
||||||
J.a(ServerConfigurator::configure, 20);
|
J.a(ServerConfigurator::configureIfDeferred, 20);
|
||||||
|
|
||||||
autoStartStudio();
|
autoStartStudio();
|
||||||
if (!J.isFolia()) {
|
if (!J.isFolia()) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,8 @@ 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.NonNull;
|
import lombok.NonNull;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.configuration.InvalidConfigurationException;
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
@@ -42,13 +44,22 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicIntegerArray;
|
import java.util.concurrent.atomic.AtomicIntegerArray;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class ServerConfigurator {
|
public class ServerConfigurator {
|
||||||
|
private static volatile boolean deferredInstallPending = false;
|
||||||
|
|
||||||
public static void configure() {
|
public static void configure() {
|
||||||
IrisSettings.IrisSettingsAutoconfiguration s = IrisSettings.get().getAutoConfiguration();
|
IrisSettings.IrisSettingsAutoconfiguration s = IrisSettings.get().getAutoConfiguration();
|
||||||
if (s.isConfigureSpigotTimeoutTime()) {
|
if (s.isConfigureSpigotTimeoutTime()) {
|
||||||
@@ -59,9 +70,26 @@ public class ServerConfigurator {
|
|||||||
J.attempt(ServerConfigurator::increasePaperWatchdog);
|
J.attempt(ServerConfigurator::increasePaperWatchdog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldDeferInstallUntilWorldsReady()) {
|
||||||
|
deferredInstallPending = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deferredInstallPending = false;
|
||||||
installDataPacks(true);
|
installDataPacks(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void configureIfDeferred() {
|
||||||
|
if (!deferredInstallPending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
configure();
|
||||||
|
if (deferredInstallPending) {
|
||||||
|
J.a(ServerConfigurator::configureIfDeferred, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void increaseKeepAliveSpigot() throws IOException, InvalidConfigurationException {
|
private static void increaseKeepAliveSpigot() throws IOException, InvalidConfigurationException {
|
||||||
File spigotConfig = new File("spigot.yml");
|
File spigotConfig = new File("spigot.yml");
|
||||||
FileConfiguration f = new YamlConfiguration();
|
FileConfiguration f = new YamlConfiguration();
|
||||||
@@ -103,24 +131,38 @@ public class ServerConfigurator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean installDataPacks(boolean fullInstall) {
|
public static boolean installDataPacks(boolean fullInstall) {
|
||||||
|
return installDataPacks(fullInstall, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean installDataPacks(boolean fullInstall, boolean includeExternal) {
|
||||||
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);
|
return installDataPacks(fixer, fullInstall, includeExternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean installDataPacks(IDataFixer fixer, boolean fullInstall) {
|
public static boolean installDataPacks(IDataFixer fixer, boolean fullInstall) {
|
||||||
|
return installDataPacks(fixer, fullInstall, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean installDataPacks(IDataFixer fixer, boolean fullInstall, boolean includeExternal) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
if (fullInstall || includeExternal) {
|
||||||
Iris.info("Checking Data Packs...");
|
Iris.info("Checking Data Packs...");
|
||||||
|
} else {
|
||||||
|
Iris.verbose("Checking Data Packs...");
|
||||||
|
}
|
||||||
DimensionHeight height = new DimensionHeight(fixer);
|
DimensionHeight height = new DimensionHeight(fixer);
|
||||||
KList<File> folders = getDatapacksFolder();
|
KList<File> folders = getDatapacksFolder();
|
||||||
|
if (includeExternal) {
|
||||||
installExternalDataPacks(folders);
|
installExternalDataPacks(folders);
|
||||||
|
}
|
||||||
KMap<String, KSet<String>> biomes = new KMap<>();
|
KMap<String, KSet<String>> biomes = new KMap<>();
|
||||||
|
|
||||||
try (Stream<IrisData> stream = allPacks()) {
|
try (Stream<IrisData> stream = allPacks()) {
|
||||||
@@ -133,7 +175,11 @@ public class ServerConfigurator {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
IrisDimension.writeShared(folders, height);
|
IrisDimension.writeShared(folders, height);
|
||||||
|
if (fullInstall || includeExternal) {
|
||||||
Iris.info("Data Packs Setup!");
|
Iris.info("Data Packs Setup!");
|
||||||
|
} else {
|
||||||
|
Iris.verbose("Data Packs Setup!");
|
||||||
|
}
|
||||||
|
|
||||||
return fullInstall && verifyDataPacksPost(IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall());
|
return fullInstall && verifyDataPacksPost(IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall());
|
||||||
}
|
}
|
||||||
@@ -147,91 +193,682 @@ public class ServerConfigurator {
|
|||||||
KMap<String, KList<File>> worldDatapackFoldersByPack = collectWorldDatapackFoldersByPack(folders);
|
KMap<String, KList<File>> worldDatapackFoldersByPack = collectWorldDatapackFoldersByPack(folders);
|
||||||
ExternalDataPackPipeline.PipelineSummary summary = ExternalDataPackPipeline.processDatapacks(requests, worldDatapackFoldersByPack);
|
ExternalDataPackPipeline.PipelineSummary summary = ExternalDataPackPipeline.processDatapacks(requests, worldDatapackFoldersByPack);
|
||||||
if (summary.getLegacyDownloadRemovals() > 0) {
|
if (summary.getLegacyDownloadRemovals() > 0) {
|
||||||
Iris.info("Removed " + summary.getLegacyDownloadRemovals() + " legacy global datapack downloads.");
|
Iris.verbose("Removed " + summary.getLegacyDownloadRemovals() + " legacy global datapack downloads.");
|
||||||
}
|
}
|
||||||
if (summary.getLegacyWorldCopyRemovals() > 0) {
|
if (summary.getLegacyWorldCopyRemovals() > 0) {
|
||||||
Iris.info("Removed " + summary.getLegacyWorldCopyRemovals() + " legacy managed world datapack copies.");
|
Iris.verbose("Removed " + summary.getLegacyWorldCopyRemovals() + " legacy managed world datapack copies.");
|
||||||
}
|
|
||||||
if (summary.getRequests() > 0 || summary.getImportedSources() > 0 || summary.getWorldDatapacksInstalled() > 0) {
|
|
||||||
Iris.info("External datapack sync/import/install: requests=" + summary.getRequests()
|
|
||||||
+ ", synced=" + summary.getSyncedRequests()
|
|
||||||
+ ", restored=" + summary.getRestoredRequests()
|
|
||||||
+ ", importedSources=" + summary.getImportedSources()
|
|
||||||
+ ", cachedSources=" + summary.getCachedSources()
|
|
||||||
+ ", converted=" + summary.getConvertedStructures()
|
|
||||||
+ ", failedConversions=" + summary.getFailedConversions()
|
|
||||||
+ ", worldDatapacks=" + summary.getWorldDatapacksInstalled()
|
|
||||||
+ ", worldAssets=" + summary.getWorldAssetsInstalled()
|
|
||||||
+ ", optionalFailures=" + summary.getOptionalFailures()
|
|
||||||
+ ", requiredFailures=" + summary.getRequiredFailures());
|
|
||||||
}
|
}
|
||||||
|
int loadedDatapackCount = Math.max(0, summary.getRequests() - summary.getOptionalFailures() - summary.getRequiredFailures());
|
||||||
|
Iris.info("Loaded Datapacks into Iris: " + loadedDatapackCount + "!");
|
||||||
if (summary.getRequiredFailures() > 0) {
|
if (summary.getRequiredFailures() > 0) {
|
||||||
throw new IllegalStateException("Required external datapack setup failed for " + summary.getRequiredFailures() + " request(s).");
|
throw new IllegalStateException("Required external datapack setup failed for " + summary.getRequiredFailures() + " request(s).");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldDeferInstallUntilWorldsReady() {
|
||||||
|
String forcedMainWorld = IrisSettings.get().getGeneral().forceMainWorld;
|
||||||
|
if (forcedMainWorld != null && !forcedMainWorld.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Bukkit.getServer().getWorlds().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
private static KList<ExternalDataPackPipeline.DatapackRequest> collectExternalDatapackRequests() {
|
private static KList<ExternalDataPackPipeline.DatapackRequest> collectExternalDatapackRequests() {
|
||||||
KMap<String, ExternalDataPackPipeline.DatapackRequest> deduplicated = new KMap<>();
|
KMap<String, ExternalDataPackPipeline.DatapackRequest> deduplicated = new KMap<>();
|
||||||
try (Stream<IrisData> stream = allPacks()) {
|
try (Stream<IrisData> stream = allPacks()) {
|
||||||
stream.forEach(data -> {
|
stream.forEach(data -> collectExternalDatapackRequestsForPack(data, deduplicated));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new KList<>(deduplicated.v());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void collectExternalDatapackRequestsForPack(IrisData data, KMap<String, ExternalDataPackPipeline.DatapackRequest> deduplicated) {
|
||||||
ResourceLoader<IrisDimension> loader = data.getDimensionLoader();
|
ResourceLoader<IrisDimension> loader = data.getDimensionLoader();
|
||||||
if (loader == null) {
|
if (loader == null) {
|
||||||
|
Iris.warn("Skipping external datapack request discovery for pack " + data.getDataFolder().getName() + " because dimension loader is unavailable.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
KList<IrisDimension> dimensions = loader.loadAll(loader.getPossibleKeys());
|
String[] possibleKeys = loader.getPossibleKeys();
|
||||||
|
if (possibleKeys == null || possibleKeys.length == 0) {
|
||||||
|
File dimensionsFolder = new File(data.getDataFolder(), "dimensions");
|
||||||
|
File[] dimensionFiles = dimensionsFolder.listFiles((dir, name) -> name != null && name.toLowerCase().endsWith(".json"));
|
||||||
|
int dimensionFileCount = dimensionFiles == null ? 0 : dimensionFiles.length;
|
||||||
|
Iris.warn("Pack " + data.getDataFolder().getName() + " has no loadable dimension keys. Dimension folder json files=" + dimensionFileCount + ". External datapacks in this pack cannot be discovered.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KList<IrisDimension> dimensions = loader.loadAll(possibleKeys);
|
||||||
|
int scannedDimensions = 0;
|
||||||
|
int dimensionsWithExternalEntries = 0;
|
||||||
|
int enabledEntries = 0;
|
||||||
|
int disabledEntries = 0;
|
||||||
|
int skippedBlankUrl = 0;
|
||||||
|
int scopedRequests = 0;
|
||||||
|
int unscopedRequests = 0;
|
||||||
|
int dedupeMerges = 0;
|
||||||
for (IrisDimension dimension : dimensions) {
|
for (IrisDimension dimension : dimensions) {
|
||||||
if (dimension == null || dimension.getExternalDatapacks() == null || dimension.getExternalDatapacks().isEmpty()) {
|
if (dimension == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scannedDimensions++;
|
||||||
|
KList<IrisExternalDatapack> externalDatapacks = dimension.getExternalDatapacks();
|
||||||
|
if (externalDatapacks == null || externalDatapacks.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
dimensionsWithExternalEntries++;
|
||||||
String targetPack = sanitizePackName(dimension.getLoadKey());
|
String targetPack = sanitizePackName(dimension.getLoadKey());
|
||||||
if (targetPack.isBlank()) {
|
if (targetPack.isBlank()) {
|
||||||
targetPack = sanitizePackName(data.getDataFolder().getName());
|
targetPack = sanitizePackName(data.getDataFolder().getName());
|
||||||
}
|
}
|
||||||
String environment = ExternalDataPackPipeline.normalizeEnvironmentValue(dimension.getEnvironment() == null ? null : dimension.getEnvironment().name());
|
|
||||||
|
|
||||||
for (IrisExternalDatapack externalDatapack : dimension.getExternalDatapacks()) {
|
String environment = ExternalDataPackPipeline.normalizeEnvironmentValue(dimension.getEnvironment() == null ? null : dimension.getEnvironment().name());
|
||||||
if (externalDatapack == null || !externalDatapack.isEnabled()) {
|
LinkedHashMap<String, IrisExternalDatapack> definitionsById = new LinkedHashMap<>();
|
||||||
|
for (IrisExternalDatapack externalDatapack : externalDatapacks) {
|
||||||
|
if (externalDatapack == null) {
|
||||||
|
disabledEntries++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!externalDatapack.isEnabled()) {
|
||||||
|
disabledEntries++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = externalDatapack.getUrl() == null ? "" : externalDatapack.getUrl().trim();
|
String url = externalDatapack.getUrl() == null ? "" : externalDatapack.getUrl().trim();
|
||||||
if (url.isBlank()) {
|
if (url.isBlank()) {
|
||||||
|
skippedBlankUrl++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String requestId = externalDatapack.getId() == null ? "" : externalDatapack.getId().trim();
|
enabledEntries++;
|
||||||
if (requestId.isBlank()) {
|
String requestId = normalizeExternalDatapackId(externalDatapack.getId(), url);
|
||||||
requestId = url;
|
IrisExternalDatapack existingDefinition = definitionsById.put(requestId, externalDatapack);
|
||||||
|
if (existingDefinition != null) {
|
||||||
|
Iris.warn("Duplicate external datapack id '" + requestId + "' in dimension " + dimension.getLoadKey() + ". Latest entry wins.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IrisExternalDatapackReplaceTargets replaceTargets = externalDatapack.getReplaceTargets();
|
if (definitionsById.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
KMap<String, KList<ScopedBindingGroup>> scopedGroups = resolveScopedBindingGroups(data, dimension, definitionsById);
|
||||||
|
for (Map.Entry<String, IrisExternalDatapack> entry : definitionsById.entrySet()) {
|
||||||
|
String requestId = entry.getKey();
|
||||||
|
IrisExternalDatapack definition = entry.getValue();
|
||||||
|
String url = definition.getUrl() == null ? "" : definition.getUrl().trim();
|
||||||
|
if (url.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
KList<ScopedBindingGroup> groups = scopedGroups.get(requestId);
|
||||||
|
if (groups == null || groups.isEmpty()) {
|
||||||
|
String scopeKey = buildRootScopeKey(dimension.getLoadKey(), requestId);
|
||||||
ExternalDataPackPipeline.DatapackRequest request = new ExternalDataPackPipeline.DatapackRequest(
|
ExternalDataPackPipeline.DatapackRequest request = new ExternalDataPackPipeline.DatapackRequest(
|
||||||
requestId,
|
requestId,
|
||||||
url,
|
url,
|
||||||
targetPack,
|
targetPack,
|
||||||
environment,
|
environment,
|
||||||
externalDatapack.isRequired(),
|
definition.isRequired(),
|
||||||
externalDatapack.isReplaceVanilla(),
|
definition.isReplaceVanilla(),
|
||||||
replaceTargets,
|
definition.getReplaceTargets(),
|
||||||
externalDatapack.getStructurePatches()
|
definition.getStructurePatches(),
|
||||||
|
Set.of(),
|
||||||
|
scopeKey,
|
||||||
|
!definition.isReplaceVanilla(),
|
||||||
|
Set.of()
|
||||||
);
|
);
|
||||||
|
dedupeMerges += mergeDeduplicatedRequest(deduplicated, request);
|
||||||
|
unscopedRequests++;
|
||||||
|
Iris.verbose("External datapack scope resolved: id=" + requestId
|
||||||
|
+ ", targetPack=" + targetPack
|
||||||
|
+ ", dimension=" + dimension.getLoadKey()
|
||||||
|
+ ", scope=dimension-root"
|
||||||
|
+ ", forcedBiomes=0"
|
||||||
|
+ ", replaceVanilla=" + definition.isReplaceVanilla()
|
||||||
|
+ ", alongsideMode=" + (!definition.isReplaceVanilla())
|
||||||
|
+ ", required=" + definition.isRequired());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ScopedBindingGroup group : groups) {
|
||||||
|
ExternalDataPackPipeline.DatapackRequest request = new ExternalDataPackPipeline.DatapackRequest(
|
||||||
|
requestId,
|
||||||
|
url,
|
||||||
|
targetPack,
|
||||||
|
environment,
|
||||||
|
group.required(),
|
||||||
|
group.replaceVanilla(),
|
||||||
|
definition.getReplaceTargets(),
|
||||||
|
definition.getStructurePatches(),
|
||||||
|
group.forcedBiomeKeys(),
|
||||||
|
group.scopeKey(),
|
||||||
|
!group.replaceVanilla(),
|
||||||
|
Set.of()
|
||||||
|
);
|
||||||
|
dedupeMerges += mergeDeduplicatedRequest(deduplicated, request);
|
||||||
|
scopedRequests++;
|
||||||
|
Iris.verbose("External datapack scope resolved: id=" + requestId
|
||||||
|
+ ", targetPack=" + targetPack
|
||||||
|
+ ", dimension=" + dimension.getLoadKey()
|
||||||
|
+ ", scope=" + group.source()
|
||||||
|
+ ", forcedBiomes=" + group.forcedBiomeKeys().size()
|
||||||
|
+ ", replaceVanilla=" + group.replaceVanilla()
|
||||||
|
+ ", alongsideMode=" + (!group.replaceVanilla())
|
||||||
|
+ ", required=" + group.required());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scannedDimensions == 0) {
|
||||||
|
Iris.warn("Pack " + data.getDataFolder().getName() + " did not resolve any dimensions during external datapack discovery.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dimensionsWithExternalEntries > 0 || enabledEntries > 0 || disabledEntries > 0 || skippedBlankUrl > 0) {
|
||||||
|
Iris.verbose("External datapack discovery for pack " + data.getDataFolder().getName()
|
||||||
|
+ ": dimensions=" + scannedDimensions
|
||||||
|
+ ", withEntries=" + dimensionsWithExternalEntries
|
||||||
|
+ ", enabled=" + enabledEntries
|
||||||
|
+ ", disabled=" + disabledEntries
|
||||||
|
+ ", skippedBlankUrl=" + skippedBlankUrl
|
||||||
|
+ ", scopedRequests=" + scopedRequests
|
||||||
|
+ ", unscopedRequests=" + unscopedRequests
|
||||||
|
+ ", dedupeMerges=" + dedupeMerges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KMap<String, KList<ScopedBindingGroup>> resolveScopedBindingGroups(
|
||||||
|
IrisData data,
|
||||||
|
IrisDimension dimension,
|
||||||
|
Map<String, IrisExternalDatapack> definitionsById
|
||||||
|
) {
|
||||||
|
KMap<String, KList<ScopedBindingGroup>> groupedRequestsById = new KMap<>();
|
||||||
|
if (definitionsById == null || definitionsById.isEmpty()) {
|
||||||
|
return groupedRequestsById;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceLoader<IrisRegion> regionLoader = data.getRegionLoader();
|
||||||
|
ResourceLoader<IrisBiome> biomeLoader = data.getBiomeLoader();
|
||||||
|
if (regionLoader == null || biomeLoader == null) {
|
||||||
|
return groupedRequestsById;
|
||||||
|
}
|
||||||
|
|
||||||
|
String biomeNamespace = resolveBiomeNamespace(dimension);
|
||||||
|
LinkedHashMap<String, IrisBiome> biomeCache = new LinkedHashMap<>();
|
||||||
|
LinkedHashMap<String, IrisRegion> regions = new LinkedHashMap<>();
|
||||||
|
KList<String> dimensionRegions = dimension.getRegions();
|
||||||
|
if (dimensionRegions != null) {
|
||||||
|
for (String regionKey : dimensionRegions) {
|
||||||
|
String normalizedRegion = normalizeResourceReference(regionKey);
|
||||||
|
if (normalizedRegion.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisRegion region = regionLoader.load(normalizedRegion, false);
|
||||||
|
if (region != null) {
|
||||||
|
regions.put(normalizedRegion, region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedHashMap<String, KList<ScopedBindingCandidate>> candidatesById = new LinkedHashMap<>();
|
||||||
|
LinkedHashSet<String> discoveryBiomeKeys = new LinkedHashSet<>();
|
||||||
|
for (IrisRegion region : regions.values()) {
|
||||||
|
Set<String> expandedRegionBiomes = collectRegionBiomeKeys(region, true, biomeLoader, biomeCache);
|
||||||
|
discoveryBiomeKeys.addAll(expandedRegionBiomes);
|
||||||
|
|
||||||
|
KList<IrisExternalDatapackBinding> bindings = region.getExternalDatapacks();
|
||||||
|
if (bindings == null || bindings.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (IrisExternalDatapackBinding binding : bindings) {
|
||||||
|
if (binding == null || !binding.isEnabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String id = normalizeExternalDatapackId(binding.getId(), "");
|
||||||
|
if (id.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisExternalDatapack definition = definitionsById.get(id);
|
||||||
|
if (definition == null) {
|
||||||
|
Iris.warn("Ignoring region external datapack binding id '" + id + "' in " + region.getLoadKey() + " because no matching dimension externalDatapacks entry exists.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean replaceVanilla = binding.getReplaceVanillaOverride() == null
|
||||||
|
? definition.isReplaceVanilla()
|
||||||
|
: binding.getReplaceVanillaOverride();
|
||||||
|
boolean required = binding.getRequiredOverride() == null
|
||||||
|
? definition.isRequired()
|
||||||
|
: binding.getRequiredOverride();
|
||||||
|
Set<String> regionBiomeKeys = collectRegionBiomeKeys(region, binding.isIncludeChildren(), biomeLoader, biomeCache);
|
||||||
|
Set<String> runtimeBiomeKeys = resolveRuntimeBiomeKeys(regionBiomeKeys, biomeNamespace, biomeLoader, biomeCache);
|
||||||
|
if (runtimeBiomeKeys.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
KList<ScopedBindingCandidate> candidates = candidatesById.computeIfAbsent(id, key -> new KList<>());
|
||||||
|
candidates.add(new ScopedBindingCandidate("region", region.getLoadKey(), 1, replaceVanilla, required, runtimeBiomeKeys));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String biomeKey : discoveryBiomeKeys) {
|
||||||
|
IrisBiome biome = loadBiomeFromCache(biomeKey, biomeLoader, biomeCache);
|
||||||
|
if (biome == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
KList<IrisExternalDatapackBinding> bindings = biome.getExternalDatapacks();
|
||||||
|
if (bindings == null || bindings.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (IrisExternalDatapackBinding binding : bindings) {
|
||||||
|
if (binding == null || !binding.isEnabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String id = normalizeExternalDatapackId(binding.getId(), "");
|
||||||
|
if (id.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisExternalDatapack definition = definitionsById.get(id);
|
||||||
|
if (definition == null) {
|
||||||
|
Iris.warn("Ignoring biome external datapack binding id '" + id + "' in " + biome.getLoadKey() + " because no matching dimension externalDatapacks entry exists.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean replaceVanilla = binding.getReplaceVanillaOverride() == null
|
||||||
|
? definition.isReplaceVanilla()
|
||||||
|
: binding.getReplaceVanillaOverride();
|
||||||
|
boolean required = binding.getRequiredOverride() == null
|
||||||
|
? definition.isRequired()
|
||||||
|
: binding.getRequiredOverride();
|
||||||
|
Set<String> biomeSelection = collectBiomeKeys(biome.getLoadKey(), binding.isIncludeChildren(), biomeLoader, biomeCache);
|
||||||
|
Set<String> runtimeBiomeKeys = resolveRuntimeBiomeKeys(biomeSelection, biomeNamespace, biomeLoader, biomeCache);
|
||||||
|
if (runtimeBiomeKeys.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
KList<ScopedBindingCandidate> candidates = candidatesById.computeIfAbsent(id, key -> new KList<>());
|
||||||
|
candidates.add(new ScopedBindingCandidate("biome", biome.getLoadKey(), 2, replaceVanilla, required, runtimeBiomeKeys));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, KList<ScopedBindingCandidate>> entry : candidatesById.entrySet()) {
|
||||||
|
String id = entry.getKey();
|
||||||
|
KList<ScopedBindingCandidate> candidates = entry.getValue();
|
||||||
|
if (candidates == null || candidates.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedHashMap<String, ScopedBindingSelection> selectedByBiome = new LinkedHashMap<>();
|
||||||
|
for (ScopedBindingCandidate candidate : candidates) {
|
||||||
|
if (candidate == null || candidate.forcedBiomeKeys() == null || candidate.forcedBiomeKeys().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<String> sortedBiomeKeys = new ArrayList<>(candidate.forcedBiomeKeys());
|
||||||
|
sortedBiomeKeys.sort(String::compareTo);
|
||||||
|
for (String runtimeBiomeKey : sortedBiomeKeys) {
|
||||||
|
ScopedBindingSelection selected = selectedByBiome.get(runtimeBiomeKey);
|
||||||
|
if (selected == null) {
|
||||||
|
selectedByBiome.put(runtimeBiomeKey, new ScopedBindingSelection(
|
||||||
|
candidate.priority(),
|
||||||
|
candidate.replaceVanilla(),
|
||||||
|
candidate.required(),
|
||||||
|
candidate.sourceType(),
|
||||||
|
candidate.sourceKey()
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidate.priority() > selected.priority()) {
|
||||||
|
selectedByBiome.put(runtimeBiomeKey, new ScopedBindingSelection(
|
||||||
|
candidate.priority(),
|
||||||
|
candidate.replaceVanilla(),
|
||||||
|
candidate.required(),
|
||||||
|
candidate.sourceType(),
|
||||||
|
candidate.sourceKey()
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidate.priority() == selected.priority()
|
||||||
|
&& (candidate.replaceVanilla() != selected.replaceVanilla() || candidate.required() != selected.required())) {
|
||||||
|
Iris.warn("External datapack scope conflict for id=" + id
|
||||||
|
+ ", biomeKey=" + runtimeBiomeKey
|
||||||
|
+ ", kept=" + selected.sourceType() + "/" + selected.sourceKey()
|
||||||
|
+ ", ignored=" + candidate.sourceType() + "/" + candidate.sourceKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedHashMap<String, LinkedHashSet<String>> groupedBiomes = new LinkedHashMap<>();
|
||||||
|
LinkedHashMap<String, ScopedBindingSelection> groupedSelection = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<String, ScopedBindingSelection> selectedEntry : selectedByBiome.entrySet()) {
|
||||||
|
String runtimeBiomeKey = selectedEntry.getKey();
|
||||||
|
ScopedBindingSelection selection = selectedEntry.getValue();
|
||||||
|
String groupKey = selection.replaceVanilla() + "|" + selection.required();
|
||||||
|
groupedBiomes.computeIfAbsent(groupKey, key -> new LinkedHashSet<>()).add(runtimeBiomeKey);
|
||||||
|
groupedSelection.putIfAbsent(groupKey, selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, LinkedHashSet<String>> groupedEntry : groupedBiomes.entrySet()) {
|
||||||
|
LinkedHashSet<String> runtimeBiomeKeys = groupedEntry.getValue();
|
||||||
|
if (runtimeBiomeKeys == null || runtimeBiomeKeys.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedBindingSelection selection = groupedSelection.get(groupedEntry.getKey());
|
||||||
|
if (selection == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> forcedBiomeKeys = Set.copyOf(runtimeBiomeKeys);
|
||||||
|
String scopeKey = buildScopedScopeKey(dimension.getLoadKey(), id, selection.sourceType(), selection.sourceKey(), forcedBiomeKeys);
|
||||||
|
String source = selection.sourceType() + ":" + selection.sourceKey();
|
||||||
|
KList<ScopedBindingGroup> groups = groupedRequestsById.computeIfAbsent(id, key -> new KList<>());
|
||||||
|
groups.add(new ScopedBindingGroup(selection.replaceVanilla(), selection.required(), forcedBiomeKeys, scopeKey, source));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupedRequestsById;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> collectRegionBiomeKeys(
|
||||||
|
IrisRegion region,
|
||||||
|
boolean includeChildren,
|
||||||
|
ResourceLoader<IrisBiome> biomeLoader,
|
||||||
|
Map<String, IrisBiome> biomeCache
|
||||||
|
) {
|
||||||
|
LinkedHashSet<String> regionBiomeKeys = new LinkedHashSet<>();
|
||||||
|
if (region == null) {
|
||||||
|
return regionBiomeKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
addAllResourceReferences(regionBiomeKeys, region.getLandBiomes());
|
||||||
|
addAllResourceReferences(regionBiomeKeys, region.getSeaBiomes());
|
||||||
|
addAllResourceReferences(regionBiomeKeys, region.getShoreBiomes());
|
||||||
|
addAllResourceReferences(regionBiomeKeys, region.getCaveBiomes());
|
||||||
|
if (!includeChildren) {
|
||||||
|
return regionBiomeKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedHashSet<String> expanded = new LinkedHashSet<>();
|
||||||
|
for (String biomeKey : regionBiomeKeys) {
|
||||||
|
expanded.addAll(collectBiomeKeys(biomeKey, true, biomeLoader, biomeCache));
|
||||||
|
}
|
||||||
|
return expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> collectBiomeKeys(
|
||||||
|
String biomeKey,
|
||||||
|
boolean includeChildren,
|
||||||
|
ResourceLoader<IrisBiome> biomeLoader,
|
||||||
|
Map<String, IrisBiome> biomeCache
|
||||||
|
) {
|
||||||
|
LinkedHashSet<String> resolved = new LinkedHashSet<>();
|
||||||
|
String normalizedBiomeKey = normalizeResourceReference(biomeKey);
|
||||||
|
if (normalizedBiomeKey.isBlank()) {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!includeChildren) {
|
||||||
|
resolved.add(normalizedBiomeKey);
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayDeque<String> queue = new ArrayDeque<>();
|
||||||
|
queue.add(normalizedBiomeKey);
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
String next = normalizeResourceReference(queue.removeFirst());
|
||||||
|
if (next.isBlank() || !resolved.add(next)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisBiome biome = loadBiomeFromCache(next, biomeLoader, biomeCache);
|
||||||
|
if (biome == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
addQueueResourceReferences(queue, biome.getChildren());
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> resolveRuntimeBiomeKeys(
|
||||||
|
Set<String> irisBiomeKeys,
|
||||||
|
String biomeNamespace,
|
||||||
|
ResourceLoader<IrisBiome> biomeLoader,
|
||||||
|
Map<String, IrisBiome> biomeCache
|
||||||
|
) {
|
||||||
|
LinkedHashSet<String> resolved = new LinkedHashSet<>();
|
||||||
|
if (irisBiomeKeys == null || irisBiomeKeys.isEmpty()) {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String irisBiomeKey : irisBiomeKeys) {
|
||||||
|
String normalizedBiomeKey = normalizeResourceReference(irisBiomeKey);
|
||||||
|
if (normalizedBiomeKey.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisBiome biome = loadBiomeFromCache(normalizedBiomeKey, biomeLoader, biomeCache);
|
||||||
|
if (biome == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (biome.isCustom() && biome.getCustomDerivitives() != null && !biome.getCustomDerivitives().isEmpty()) {
|
||||||
|
for (IrisBiomeCustom customDerivative : biome.getCustomDerivitives()) {
|
||||||
|
if (customDerivative == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String customId = normalizeResourceReference(customDerivative.getId());
|
||||||
|
if (customId.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
resolved.add((biomeNamespace + ":" + customId).toLowerCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Biome vanillaDerivative = biome.getVanillaDerivative();
|
||||||
|
NamespacedKey vanillaKey = vanillaDerivative == null ? null : vanillaDerivative.getKey();
|
||||||
|
if (vanillaKey != null) {
|
||||||
|
resolved.add(vanillaKey.toString().toLowerCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String resolveBiomeNamespace(IrisDimension dimension) {
|
||||||
|
if (dimension == null) {
|
||||||
|
return "iris";
|
||||||
|
}
|
||||||
|
|
||||||
|
String namespace = dimension.getLoadKey() == null ? "" : dimension.getLoadKey().trim().toLowerCase(Locale.ROOT);
|
||||||
|
namespace = namespace.replaceAll("[^a-z0-9_\\-.]", "_");
|
||||||
|
namespace = namespace.replaceAll("_+", "_");
|
||||||
|
namespace = namespace.replaceAll("^_+", "");
|
||||||
|
namespace = namespace.replaceAll("_+$", "");
|
||||||
|
if (namespace.isBlank()) {
|
||||||
|
return "iris";
|
||||||
|
}
|
||||||
|
return namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisBiome loadBiomeFromCache(
|
||||||
|
String biomeKey,
|
||||||
|
ResourceLoader<IrisBiome> biomeLoader,
|
||||||
|
Map<String, IrisBiome> biomeCache
|
||||||
|
) {
|
||||||
|
if (biomeLoader == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalizedBiomeKey = normalizeResourceReference(biomeKey);
|
||||||
|
if (normalizedBiomeKey.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (biomeCache.containsKey(normalizedBiomeKey)) {
|
||||||
|
return biomeCache.get(normalizedBiomeKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisBiome biome = biomeLoader.load(normalizedBiomeKey, false);
|
||||||
|
if (biome != null) {
|
||||||
|
biomeCache.put(normalizedBiomeKey, biome);
|
||||||
|
}
|
||||||
|
return biome;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addAllResourceReferences(Set<String> destination, KList<String> references) {
|
||||||
|
if (destination == null || references == null || references.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String reference : references) {
|
||||||
|
String normalized = normalizeResourceReference(reference);
|
||||||
|
if (!normalized.isBlank()) {
|
||||||
|
destination.add(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addQueueResourceReferences(ArrayDeque<String> queue, KList<String> references) {
|
||||||
|
if (queue == null || references == null || references.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String reference : references) {
|
||||||
|
String normalized = normalizeResourceReference(reference);
|
||||||
|
if (!normalized.isBlank()) {
|
||||||
|
queue.addLast(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeResourceReference(String reference) {
|
||||||
|
if (reference == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = reference.trim().replace('\\', '/');
|
||||||
|
normalized = normalized.replaceAll("/+", "/");
|
||||||
|
normalized = normalized.replaceAll("^/+", "");
|
||||||
|
normalized = normalized.replaceAll("/+$", "");
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int mergeDeduplicatedRequest(
|
||||||
|
KMap<String, ExternalDataPackPipeline.DatapackRequest> deduplicated,
|
||||||
|
ExternalDataPackPipeline.DatapackRequest request
|
||||||
|
) {
|
||||||
|
if (request == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
String dedupeKey = request.getDedupeKey();
|
String dedupeKey = request.getDedupeKey();
|
||||||
ExternalDataPackPipeline.DatapackRequest existing = deduplicated.get(dedupeKey);
|
ExternalDataPackPipeline.DatapackRequest existing = deduplicated.get(dedupeKey);
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
deduplicated.put(dedupeKey, request);
|
deduplicated.put(dedupeKey, request);
|
||||||
continue;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
deduplicated.put(dedupeKey, existing.merge(request));
|
deduplicated.put(dedupeKey, existing.merge(request));
|
||||||
}
|
return 1;
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new KList<>(deduplicated.v());
|
private static String normalizeExternalDatapackId(String id, String fallbackUrl) {
|
||||||
|
String normalized = id == null ? "" : id.trim();
|
||||||
|
if (!normalized.isBlank()) {
|
||||||
|
return normalized.toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
String fallback = fallbackUrl == null ? "" : fallbackUrl.trim();
|
||||||
|
if (fallback.isBlank()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return fallback.toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildRootScopeKey(String dimensionKey, String id) {
|
||||||
|
String normalizedDimension = ExternalDataPackPipeline.sanitizePackNameValue(dimensionKey);
|
||||||
|
if (normalizedDimension.isBlank()) {
|
||||||
|
normalizedDimension = "dimension";
|
||||||
|
}
|
||||||
|
String normalizedId = ExternalDataPackPipeline.sanitizePackNameValue(id);
|
||||||
|
if (normalizedId.isBlank()) {
|
||||||
|
normalizedId = "external";
|
||||||
|
}
|
||||||
|
return "root-" + normalizedDimension + "-" + normalizedId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildScopedScopeKey(String dimensionKey, String id, String sourceType, String sourceKey, Set<String> forcedBiomeKeys) {
|
||||||
|
ArrayList<String> sortedBiomes = new ArrayList<>();
|
||||||
|
if (forcedBiomeKeys != null) {
|
||||||
|
sortedBiomes.addAll(forcedBiomeKeys);
|
||||||
|
}
|
||||||
|
sortedBiomes.sort(String::compareTo);
|
||||||
|
String biomeFingerprint = Integer.toHexString(String.join(",", sortedBiomes).hashCode());
|
||||||
|
String normalizedDimension = ExternalDataPackPipeline.sanitizePackNameValue(dimensionKey);
|
||||||
|
if (normalizedDimension.isBlank()) {
|
||||||
|
normalizedDimension = "dimension";
|
||||||
|
}
|
||||||
|
String normalizedId = ExternalDataPackPipeline.sanitizePackNameValue(id);
|
||||||
|
if (normalizedId.isBlank()) {
|
||||||
|
normalizedId = "external";
|
||||||
|
}
|
||||||
|
String normalizedSourceType = ExternalDataPackPipeline.sanitizePackNameValue(sourceType);
|
||||||
|
if (normalizedSourceType.isBlank()) {
|
||||||
|
normalizedSourceType = "scope";
|
||||||
|
}
|
||||||
|
String normalizedSourceKey = ExternalDataPackPipeline.sanitizePackNameValue(sourceKey);
|
||||||
|
if (normalizedSourceKey.isBlank()) {
|
||||||
|
normalizedSourceKey = "entry";
|
||||||
|
}
|
||||||
|
return normalizedDimension + "-" + normalizedId + "-" + normalizedSourceType + "-" + normalizedSourceKey + "-" + biomeFingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ScopedBindingCandidate(
|
||||||
|
String sourceType,
|
||||||
|
String sourceKey,
|
||||||
|
int priority,
|
||||||
|
boolean replaceVanilla,
|
||||||
|
boolean required,
|
||||||
|
Set<String> forcedBiomeKeys
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ScopedBindingSelection(
|
||||||
|
int priority,
|
||||||
|
boolean replaceVanilla,
|
||||||
|
boolean required,
|
||||||
|
String sourceType,
|
||||||
|
String sourceKey
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ScopedBindingGroup(
|
||||||
|
boolean replaceVanilla,
|
||||||
|
boolean required,
|
||||||
|
Set<String> forcedBiomeKeys,
|
||||||
|
String scopeKey,
|
||||||
|
String source
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KMap<String, KList<File>> collectWorldDatapackFoldersByPack(KList<File> fallbackFolders) {
|
private static KMap<String, KList<File>> collectWorldDatapackFoldersByPack(KList<File> fallbackFolders) {
|
||||||
|
|||||||
@@ -442,7 +442,9 @@ public class CommandDeveloper implements DirectorExecutor {
|
|||||||
orchestrator.setDaemon(true);
|
orchestrator.setDaemon(true);
|
||||||
try {
|
try {
|
||||||
orchestrator.start();
|
orchestrator.start();
|
||||||
|
if (IrisSettings.get().getGeneral().isDebug()) {
|
||||||
Iris.info("Delete-chunk worker dispatched on dedicated thread=" + orchestrator.getName() + " id=" + runId + ".");
|
Iris.info("Delete-chunk worker dispatched on dedicated thread=" + orchestrator.getName() + " id=" + runId + ".");
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
ACTIVE_DELETE_CHUNK_WORLDS.remove(worldKey);
|
ACTIVE_DELETE_CHUNK_WORLDS.remove(worldKey);
|
||||||
sender.sendMessage(C.RED + "Failed to start delete-chunk worker thread. See console.");
|
sender.sendMessage(C.RED + "Failed to start delete-chunk worker thread. See console.");
|
||||||
@@ -519,9 +521,11 @@ public class CommandDeveloper implements DirectorExecutor {
|
|||||||
watchdog.interrupt();
|
watchdog.interrupt();
|
||||||
IrisToolbelt.endWorldMaintenance(world, "delete-chunk");
|
IrisToolbelt.endWorldMaintenance(world, "delete-chunk");
|
||||||
ACTIVE_DELETE_CHUNK_WORLDS.remove(worldKey);
|
ACTIVE_DELETE_CHUNK_WORLDS.remove(worldKey);
|
||||||
|
if (IrisSettings.get().getGeneral().isDebug()) {
|
||||||
Iris.info("Delete-chunk run closed: id=" + runId + " world=" + world.getName() + " totalMs=" + (System.currentTimeMillis() - runStart));
|
Iris.info("Delete-chunk run closed: id=" + runId + " world=" + world.getName() + " totalMs=" + (System.currentTimeMillis() - runStart));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private DeleteChunkSummary executeDeleteChunkQueue(
|
private DeleteChunkSummary executeDeleteChunkQueue(
|
||||||
World world,
|
World world,
|
||||||
@@ -773,8 +777,10 @@ public class CommandDeveloper implements DirectorExecutor {
|
|||||||
) {
|
) {
|
||||||
phase.set(next);
|
phase.set(next);
|
||||||
phaseSince.set(System.currentTimeMillis());
|
phaseSince.set(System.currentTimeMillis());
|
||||||
|
if (IrisSettings.get().getGeneral().isDebug()) {
|
||||||
Iris.info("Delete-chunk phase: id=" + runId + " phase=" + next + " world=" + world.getName());
|
Iris.info("Delete-chunk phase: id=" + runId + " phase=" + next + " world=" + world.getName());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String formatDeleteChunkFailedPreview(List<Position2> failedChunks) {
|
private String formatDeleteChunkFailedPreview(List<Position2> failedChunks) {
|
||||||
if (failedChunks.isEmpty()) {
|
if (failedChunks.isEmpty()) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,8 @@ import art.arcane.iris.engine.IrisNoisemapPrebakePipeline;
|
|||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
import art.arcane.iris.engine.framework.SeedManager;
|
import art.arcane.iris.engine.framework.SeedManager;
|
||||||
import art.arcane.iris.engine.object.*;
|
import art.arcane.iris.engine.object.*;
|
||||||
|
import art.arcane.iris.engine.platform.ChunkReplacementListener;
|
||||||
|
import art.arcane.iris.engine.platform.ChunkReplacementOptions;
|
||||||
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.KList;
|
||||||
import art.arcane.volmlib.util.collection.KMap;
|
import art.arcane.volmlib.util.collection.KMap;
|
||||||
@@ -227,6 +229,7 @@ public class CommandStudio implements DirectorExecutor {
|
|||||||
sender.sendMessage(C.YELLOW + "Folia fast regen: skipping outer mantle preservation stage.");
|
sender.sendMessage(C.YELLOW + "Folia fast regen: skipping outer mantle preservation stage.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String runId = "studio-regen-" + world.getName() + "-" + System.currentTimeMillis();
|
||||||
|
|
||||||
ParallelRadiusJob job = new ParallelRadiusJob(threadCount, service) {
|
ParallelRadiusJob job = new ParallelRadiusJob(threadCount, service) {
|
||||||
@Override
|
@Override
|
||||||
@@ -234,7 +237,14 @@ public class CommandStudio implements DirectorExecutor {
|
|||||||
if (foliaFastRegen) {
|
if (foliaFastRegen) {
|
||||||
Iris.verbose("Folia fast studio regen skipping mantle delete for " + x + "," + z + ".");
|
Iris.verbose("Folia fast studio regen skipping mantle delete for " + x + "," + z + ".");
|
||||||
}
|
}
|
||||||
plat.injectChunkReplacement(world, x, z, executor);
|
plat.injectChunkReplacement(
|
||||||
|
world,
|
||||||
|
x,
|
||||||
|
z,
|
||||||
|
executor,
|
||||||
|
ChunkReplacementOptions.terrain(runId, IrisSettings.get().getGeneral().isDebug()),
|
||||||
|
ChunkReplacementListener.NO_OP
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -161,7 +161,9 @@ public class IrisCreator {
|
|||||||
.seed(seed)
|
.seed(seed)
|
||||||
.studio(studio)
|
.studio(studio)
|
||||||
.create();
|
.create();
|
||||||
if (ServerConfigurator.installDataPacks(true)) {
|
boolean verifyDataPacks = !studio();
|
||||||
|
boolean includeExternalDataPacks = !studio();
|
||||||
|
if (ServerConfigurator.installDataPacks(verifyDataPacks, includeExternalDataPacks)) {
|
||||||
throw new IrisException("Datapacks were missing!");
|
throw new IrisException("Datapacks were missing!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -433,7 +433,11 @@ public class IrisToolbelt {
|
|||||||
if (bypassMantleStages) {
|
if (bypassMantleStages) {
|
||||||
worldMaintenanceMantleBypassDepth.computeIfAbsent(name, k -> new AtomicInteger()).incrementAndGet();
|
worldMaintenanceMantleBypassDepth.computeIfAbsent(name, k -> new AtomicInteger()).incrementAndGet();
|
||||||
}
|
}
|
||||||
|
if (IrisSettings.get().getGeneral().isDebug()) {
|
||||||
Iris.info("World maintenance enter: " + name + " reason=" + reason + " depth=" + depth + " bypassMantle=" + bypassMantleStages);
|
Iris.info("World maintenance enter: " + name + " reason=" + reason + " depth=" + depth + " bypassMantle=" + bypassMantleStages);
|
||||||
|
} else {
|
||||||
|
Iris.verbose("World maintenance enter: " + name + " reason=" + reason + " depth=" + depth + " bypassMantle=" + bypassMantleStages);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void endWorldMaintenance(World world, String reason) {
|
public static void endWorldMaintenance(World world, String reason) {
|
||||||
@@ -463,7 +467,11 @@ public class IrisToolbelt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IrisSettings.get().getGeneral().isDebug()) {
|
||||||
Iris.info("World maintenance exit: " + name + " reason=" + reason + " depth=" + depth + " bypassMantleDepth=" + bypassDepth);
|
Iris.info("World maintenance exit: " + name + " reason=" + reason + " depth=" + depth + " bypassMantleDepth=" + bypassDepth);
|
||||||
|
} else {
|
||||||
|
Iris.verbose("World maintenance exit: " + name + " reason=" + reason + " depth=" + depth + " bypassMantleDepth=" + bypassDepth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isWorldMaintenanceActive(World world) {
|
public static boolean isWorldMaintenanceActive(World world) {
|
||||||
|
|||||||
@@ -307,6 +307,10 @@ public class IrisEngine implements Engine {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (studio) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!noisemapPrebakeRunning.compareAndSet(false, true)) {
|
if (!noisemapPrebakeRunning.compareAndSet(false, true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -369,7 +373,7 @@ public class IrisEngine implements Engine {
|
|||||||
setupEngine();
|
setupEngine();
|
||||||
J.a(() -> {
|
J.a(() -> {
|
||||||
synchronized (ServerConfigurator.class) {
|
synchronized (ServerConfigurator.class) {
|
||||||
ServerConfigurator.installDataPacks(false);
|
ServerConfigurator.installDataPacks(false, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public interface EngineMode extends Staged {
|
|||||||
default void generate(int x, int z, Hunk<BlockData> blocks, Hunk<Biome> biomes, boolean multicore) {
|
default void generate(int x, int z, Hunk<BlockData> blocks, Hunk<Biome> biomes, boolean multicore) {
|
||||||
boolean cacheContext = true;
|
boolean cacheContext = true;
|
||||||
if (J.isFolia()) {
|
if (J.isFolia()) {
|
||||||
var world = getEngine().getWorld().realWorld();
|
org.bukkit.World world = getEngine().getWorld().realWorld();
|
||||||
if (world != null && IrisToolbelt.isWorldMaintenanceActive(world)) {
|
if (world != null && IrisToolbelt.isWorldMaintenanceActive(world)) {
|
||||||
cacheContext = false;
|
cacheContext = false;
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,8 @@ public interface EngineMode extends Staged {
|
|||||||
ChunkContext ctx = new ChunkContext(x, z, getComplex(), cacheContext);
|
ChunkContext ctx = new ChunkContext(x, z, getComplex(), cacheContext);
|
||||||
IrisContext.getOr(getEngine()).setChunkContext(ctx);
|
IrisContext.getOr(getEngine()).setChunkContext(ctx);
|
||||||
|
|
||||||
for (EngineStage i : getStages()) {
|
EngineStage[] stages = getStages().toArray(new EngineStage[0]);
|
||||||
|
for (EngineStage i : stages) {
|
||||||
i.generate(x, z, blocks, biomes, multicore, ctx);
|
i.generate(x, z, blocks, biomes, multicore, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-4
@@ -45,6 +45,7 @@ import art.arcane.iris.util.common.scheduling.J;
|
|||||||
import org.bukkit.util.BlockVector;
|
import org.bukkit.util.BlockVector;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
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.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@@ -313,10 +314,11 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int id = rng.i(0, Integer.MAX_VALUE);
|
int id = rng.i(0, Integer.MAX_VALUE);
|
||||||
|
IrisObjectPlacement effectivePlacement = resolveEffectivePlacement(objectPlacement, v);
|
||||||
try {
|
try {
|
||||||
int result = v.place(xx, -1, zz, writer, objectPlacement, rng, (b, data) -> {
|
int result = v.place(xx, -1, zz, writer, effectivePlacement, rng, (b, data) -> {
|
||||||
writer.setData(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id);
|
writer.setData(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id);
|
||||||
if (objectPlacement.isDolphinTarget() && objectPlacement.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());
|
||||||
@@ -417,11 +419,12 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int id = rng.i(0, Integer.MAX_VALUE);
|
int id = rng.i(0, Integer.MAX_VALUE);
|
||||||
|
IrisObjectPlacement effectivePlacement = resolveEffectivePlacement(objectPlacement, object);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int result = object.place(x, y, z, writer, objectPlacement, rng, (b, data) -> {
|
int result = object.place(x, y, z, writer, effectivePlacement, rng, (b, data) -> {
|
||||||
writer.setData(b.getX(), b.getY(), b.getZ(), object.getLoadKey() + "@" + id);
|
writer.setData(b.getX(), b.getY(), b.getZ(), object.getLoadKey() + "@" + id);
|
||||||
if (objectPlacement.isDolphinTarget() && objectPlacement.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());
|
||||||
@@ -458,6 +461,38 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
return new ObjectPlacementResult(attempts, placed, rejected, nullObjects, errors);
|
return new ObjectPlacementResult(attempts, placed, rejected, nullObjects, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IrisObjectPlacement resolveEffectivePlacement(IrisObjectPlacement objectPlacement, IrisObject object) {
|
||||||
|
if (objectPlacement == null || object == null) {
|
||||||
|
return objectPlacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
String loadKey = object.getLoadKey();
|
||||||
|
if (loadKey == null || loadKey.isBlank()) {
|
||||||
|
return objectPlacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = loadKey.toLowerCase(Locale.ROOT);
|
||||||
|
boolean imported = normalized.startsWith("imports/")
|
||||||
|
|| normalized.contains("/imports/")
|
||||||
|
|| normalized.contains("imports/");
|
||||||
|
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) {
|
||||||
|
return objectPlacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisObjectPlacement effectivePlacement = objectPlacement.toPlacement(loadKey);
|
||||||
|
effectivePlacement.setMode(ObjectPlaceMode.FAST_MIN_STILT);
|
||||||
|
return effectivePlacement;
|
||||||
|
}
|
||||||
|
|
||||||
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));
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ public class IrisBiome extends IrisRegistrant implements IRare {
|
|||||||
private IrisCaveProfile caveProfile = new IrisCaveProfile();
|
private IrisCaveProfile caveProfile = new IrisCaveProfile();
|
||||||
@Desc("Configuration of fluid bodies such as rivers & lakes")
|
@Desc("Configuration of fluid bodies such as rivers & lakes")
|
||||||
private IrisFluidBodies fluidBodies = new IrisFluidBodies();
|
private IrisFluidBodies fluidBodies = new IrisFluidBodies();
|
||||||
|
@ArrayType(type = IrisExternalDatapackBinding.class, min = 1)
|
||||||
|
@Desc("Scoped external datapack bindings for this biome")
|
||||||
|
private KList<IrisExternalDatapackBinding> externalDatapacks = new KList<>();
|
||||||
@MinNumber(1)
|
@MinNumber(1)
|
||||||
@MaxNumber(512)
|
@MaxNumber(512)
|
||||||
@Desc("The rarity of this biome (integer)")
|
@Desc("The rarity of this biome (integer)")
|
||||||
|
|||||||
@@ -197,10 +197,10 @@ public class IrisEffect {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
J.runEntity(p, () -> {
|
||||||
if (sound != null) {
|
if (sound != null) {
|
||||||
Location part = p.getLocation().clone().add(RNG.r.i(-soundDistance, soundDistance), RNG.r.i(-soundDistance, soundDistance), RNG.r.i(-soundDistance, soundDistance));
|
Location part = p.getLocation().clone().add(RNG.r.i(-soundDistance, soundDistance), RNG.r.i(-soundDistance, soundDistance), RNG.r.i(-soundDistance, soundDistance));
|
||||||
|
p.playSound(part, getSound(), (float) volume, (float) RNG.r.d(minPitch, maxPitch));
|
||||||
J.s(() -> p.playSound(part, getSound(), (float) volume, (float) RNG.r.d(minPitch, maxPitch)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (particleEffect != null) {
|
if (particleEffect != null) {
|
||||||
@@ -210,19 +210,19 @@ public class IrisEffect {
|
|||||||
part.add(RNG.r.d(), 0, RNG.r.d());
|
part.add(RNG.r.d(), 0, RNG.r.d());
|
||||||
int offset = p.getWorld().getMinHeight();
|
int offset = p.getWorld().getMinHeight();
|
||||||
if (extra != 0) {
|
if (extra != 0) {
|
||||||
J.s(() -> p.spawnParticle(particleEffect, part.getX(), part.getY() + offset + RNG.r.i(particleOffset),
|
p.spawnParticle(particleEffect, part.getX(), part.getY() + offset + RNG.r.i(particleOffset),
|
||||||
part.getZ(),
|
part.getZ(),
|
||||||
particleCount,
|
particleCount,
|
||||||
randomAltX ? RNG.r.d(-particleAltX, particleAltX) : particleAltX,
|
randomAltX ? RNG.r.d(-particleAltX, particleAltX) : particleAltX,
|
||||||
randomAltY ? RNG.r.d(-particleAltY, particleAltY) : particleAltY,
|
randomAltY ? RNG.r.d(-particleAltY, particleAltY) : particleAltY,
|
||||||
randomAltZ ? RNG.r.d(-particleAltZ, particleAltZ) : particleAltZ,
|
randomAltZ ? RNG.r.d(-particleAltZ, particleAltZ) : particleAltZ,
|
||||||
extra));
|
extra);
|
||||||
} else {
|
} else {
|
||||||
J.s(() -> p.spawnParticle(particleEffect, part.getX(), part.getY() + offset + RNG.r.i(particleOffset), part.getZ(),
|
p.spawnParticle(particleEffect, part.getX(), part.getY() + offset + RNG.r.i(particleOffset), part.getZ(),
|
||||||
particleCount,
|
particleCount,
|
||||||
randomAltX ? RNG.r.d(-particleAltX, particleAltX) : particleAltX,
|
randomAltX ? RNG.r.d(-particleAltX, particleAltX) : particleAltX,
|
||||||
randomAltY ? RNG.r.d(-particleAltY, particleAltY) : particleAltY,
|
randomAltY ? RNG.r.d(-particleAltY, particleAltY) : particleAltY,
|
||||||
randomAltZ ? RNG.r.d(-particleAltZ, particleAltZ) : particleAltZ));
|
randomAltZ ? RNG.r.d(-particleAltZ, particleAltZ) : particleAltZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,19 +233,20 @@ public class IrisEffect {
|
|||||||
if (potionStrength > -1) {
|
if (potionStrength > -1) {
|
||||||
if (p.hasPotionEffect(getRealType())) {
|
if (p.hasPotionEffect(getRealType())) {
|
||||||
PotionEffect e = p.getPotionEffect(getRealType());
|
PotionEffect e = p.getPotionEffect(getRealType());
|
||||||
if (e.getAmplifier() > getPotionStrength()) {
|
if (e != null && e.getAmplifier() > getPotionStrength()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
J.s(() -> p.removePotionEffect(getRealType()));
|
p.removePotionEffect(getRealType());
|
||||||
}
|
}
|
||||||
|
|
||||||
J.s(() -> p.addPotionEffect(new PotionEffect(getRealType(),
|
p.addPotionEffect(new PotionEffect(getRealType(),
|
||||||
RNG.r.i(Math.min(potionTicksMax, potionTicksMin),
|
RNG.r.i(Math.min(potionTicksMax, potionTicksMin),
|
||||||
Math.max(potionTicksMax, potionTicksMin)),
|
Math.max(potionTicksMax, potionTicksMin)),
|
||||||
getPotionStrength(),
|
getPotionStrength(),
|
||||||
true, false, false)));
|
true, false, false));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void apply(Entity p) {
|
public void apply(Entity p) {
|
||||||
@@ -257,10 +258,10 @@ public class IrisEffect {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
J.runEntity(p, () -> {
|
||||||
if (sound != null) {
|
if (sound != null) {
|
||||||
Location part = p.getLocation().clone().add(RNG.r.i(-soundDistance, soundDistance), RNG.r.i(-soundDistance, soundDistance), RNG.r.i(-soundDistance, soundDistance));
|
Location part = p.getLocation().clone().add(RNG.r.i(-soundDistance, soundDistance), RNG.r.i(-soundDistance, soundDistance), RNG.r.i(-soundDistance, soundDistance));
|
||||||
|
p.getWorld().playSound(part, getSound(), (float) volume, (float) RNG.r.d(minPitch, maxPitch));
|
||||||
J.s(() -> p.getWorld().playSound(part, getSound(), (float) volume, (float) RNG.r.d(minPitch, maxPitch)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (particleEffect != null) {
|
if (particleEffect != null) {
|
||||||
@@ -268,20 +269,21 @@ public class IrisEffect {
|
|||||||
part.add(RNG.r.d(), 0, RNG.r.d());
|
part.add(RNG.r.d(), 0, RNG.r.d());
|
||||||
int offset = p.getWorld().getMinHeight();
|
int offset = p.getWorld().getMinHeight();
|
||||||
if (extra != 0) {
|
if (extra != 0) {
|
||||||
J.s(() -> p.getWorld().spawnParticle(particleEffect, part.getX(), part.getY() + offset + RNG.r.i(particleOffset),
|
p.getWorld().spawnParticle(particleEffect, part.getX(), part.getY() + offset + RNG.r.i(particleOffset),
|
||||||
part.getZ(),
|
part.getZ(),
|
||||||
particleCount,
|
particleCount,
|
||||||
randomAltX ? RNG.r.d(-particleAltX, particleAltX) : particleAltX,
|
randomAltX ? RNG.r.d(-particleAltX, particleAltX) : particleAltX,
|
||||||
randomAltY ? RNG.r.d(-particleAltY, particleAltY) : particleAltY,
|
randomAltY ? RNG.r.d(-particleAltY, particleAltY) : particleAltY,
|
||||||
randomAltZ ? RNG.r.d(-particleAltZ, particleAltZ) : particleAltZ,
|
randomAltZ ? RNG.r.d(-particleAltZ, particleAltZ) : particleAltZ,
|
||||||
extra));
|
extra);
|
||||||
} else {
|
} else {
|
||||||
J.s(() -> p.getWorld().spawnParticle(particleEffect, part.getX(), part.getY() + offset + RNG.r.i(particleOffset), part.getZ(),
|
p.getWorld().spawnParticle(particleEffect, part.getX(), part.getY() + offset + RNG.r.i(particleOffset), part.getZ(),
|
||||||
particleCount,
|
particleCount,
|
||||||
randomAltX ? RNG.r.d(-particleAltX, particleAltX) : particleAltX,
|
randomAltX ? RNG.r.d(-particleAltX, particleAltX) : particleAltX,
|
||||||
randomAltY ? RNG.r.d(-particleAltY, particleAltY) : particleAltY,
|
randomAltY ? RNG.r.d(-particleAltY, particleAltY) : particleAltY,
|
||||||
randomAltZ ? RNG.r.d(-particleAltZ, particleAltZ) : particleAltZ));
|
randomAltZ ? RNG.r.d(-particleAltZ, particleAltZ) : particleAltZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package art.arcane.iris.engine.object;
|
||||||
|
|
||||||
|
import art.arcane.iris.engine.object.annotations.Desc;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@Desc("Scoped binding to a dimension external datapack id")
|
||||||
|
public class IrisExternalDatapackBinding {
|
||||||
|
@Desc("Target external datapack id defined on the dimension")
|
||||||
|
private String id = "";
|
||||||
|
|
||||||
|
@Desc("Enable or disable this scoped binding")
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
@Desc("Override replaceVanilla behavior for this scoped binding (null keeps dimension default)")
|
||||||
|
private Boolean replaceVanillaOverride = null;
|
||||||
|
|
||||||
|
@Desc("Include child biomes recursively when collecting scoped biome boundaries")
|
||||||
|
private boolean includeChildren = true;
|
||||||
|
|
||||||
|
@Desc("Override required behavior for this scoped binding (null keeps dimension default)")
|
||||||
|
private Boolean requiredOverride = null;
|
||||||
|
}
|
||||||
@@ -821,8 +821,11 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
|
|
||||||
if (yv >= 0 && config.isBottom()) {
|
if (yv >= 0 && config.isBottom()) {
|
||||||
y += Math.floorDiv(h, 2);
|
y += Math.floorDiv(h, 2);
|
||||||
if (!config.isForcePlace()) {
|
CarvingMode carvingMode = config.getCarvingSupport();
|
||||||
bail = shouldBailForCarvingAnchor(placer, config, x, y, z);
|
if (!config.isForcePlace() && !carvingMode.equals(CarvingMode.CARVING_ONLY)) {
|
||||||
|
if (shouldBailForCarvingAnchor(placer, config, x, y, z)) {
|
||||||
|
bail = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import art.arcane.iris.util.common.data.DataProvider;
|
|||||||
import art.arcane.volmlib.util.data.WeightedRandom;
|
import art.arcane.volmlib.util.data.WeightedRandom;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
import art.arcane.iris.util.project.noise.CNG;
|
import art.arcane.iris.util.project.noise.CNG;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -150,6 +151,7 @@ public class IrisObjectPlacement {
|
|||||||
@Desc("List of objects to this object is forbidden to collied with")
|
@Desc("List of objects to this object is forbidden to collied with")
|
||||||
private KList<String> forbiddenCollisions = new KList<>();
|
private KList<String> forbiddenCollisions = new KList<>();
|
||||||
@Desc("Ignore any placement restrictions for this object")
|
@Desc("Ignore any placement restrictions for this object")
|
||||||
|
@SerializedName(value = "forcePlace", alternate = {"force"})
|
||||||
private boolean forcePlace = false;
|
private boolean forcePlace = false;
|
||||||
private transient AtomicCache<TableCache> cache = new AtomicCache<>();
|
private transient AtomicCache<TableCache> cache = new AtomicCache<>();
|
||||||
|
|
||||||
@@ -178,6 +180,7 @@ public class IrisObjectPlacement {
|
|||||||
p.setClamp(clamp);
|
p.setClamp(clamp);
|
||||||
p.setRotation(rotation);
|
p.setRotation(rotation);
|
||||||
p.setLoot(loot);
|
p.setLoot(loot);
|
||||||
|
p.setForcePlace(forcePlace);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,9 @@ public class IrisRegion extends IrisRegistrant implements IRare {
|
|||||||
private IrisCaveProfile caveProfile = new IrisCaveProfile();
|
private IrisCaveProfile caveProfile = new IrisCaveProfile();
|
||||||
@Desc("Configuration of fluid bodies such as rivers & lakes")
|
@Desc("Configuration of fluid bodies such as rivers & lakes")
|
||||||
private IrisFluidBodies fluidBodies = new IrisFluidBodies();
|
private IrisFluidBodies fluidBodies = new IrisFluidBodies();
|
||||||
|
@ArrayType(type = IrisExternalDatapackBinding.class, min = 1)
|
||||||
|
@Desc("Scoped external datapack bindings for this region")
|
||||||
|
private KList<IrisExternalDatapackBinding> externalDatapacks = new KList<>();
|
||||||
@RegistryListResource(IrisBiome.class)
|
@RegistryListResource(IrisBiome.class)
|
||||||
@Required
|
@Required
|
||||||
@ArrayType(min = 1, type = String.class)
|
@ArrayType(min = 1, type = String.class)
|
||||||
|
|||||||
@@ -34,10 +34,14 @@ import art.arcane.iris.engine.object.IrisDimension;
|
|||||||
import art.arcane.iris.engine.object.IrisWorld;
|
import art.arcane.iris.engine.object.IrisWorld;
|
||||||
import art.arcane.iris.engine.object.StudioMode;
|
import art.arcane.iris.engine.object.StudioMode;
|
||||||
import art.arcane.iris.engine.platform.studio.StudioGenerator;
|
import art.arcane.iris.engine.platform.studio.StudioGenerator;
|
||||||
|
import art.arcane.iris.util.project.matter.TileWrapper;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
import art.arcane.iris.util.project.hunk.Hunk;
|
import art.arcane.iris.util.project.hunk.Hunk;
|
||||||
import art.arcane.iris.util.project.hunk.view.ChunkDataHunkHolder;
|
import art.arcane.iris.util.project.hunk.view.ChunkDataHunkHolder;
|
||||||
import art.arcane.volmlib.util.io.ReactiveFolder;
|
import art.arcane.volmlib.util.io.ReactiveFolder;
|
||||||
|
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||||
|
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||||
|
import art.arcane.volmlib.util.matter.Matter;
|
||||||
import art.arcane.volmlib.util.scheduling.ChronoLatch;
|
import art.arcane.volmlib.util.scheduling.ChronoLatch;
|
||||||
import art.arcane.iris.util.common.scheduling.J;
|
import art.arcane.iris.util.common.scheduling.J;
|
||||||
import art.arcane.volmlib.util.scheduling.Looper;
|
import art.arcane.volmlib.util.scheduling.Looper;
|
||||||
@@ -47,6 +51,7 @@ import lombok.EqualsAndHashCode;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
@@ -62,11 +67,13 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@@ -205,15 +212,25 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectChunkReplacement(World world, int x, int z, Executor syncExecutor) {
|
public void injectChunkReplacement(
|
||||||
|
World world,
|
||||||
|
int x,
|
||||||
|
int z,
|
||||||
|
Executor syncExecutor,
|
||||||
|
ChunkReplacementOptions options,
|
||||||
|
ChunkReplacementListener listener
|
||||||
|
) {
|
||||||
boolean acquired = false;
|
boolean acquired = false;
|
||||||
String phase = "start";
|
ChunkReplacementOptions effectiveOptions = Objects.requireNonNull(options, "options");
|
||||||
|
ChunkReplacementListener effectiveListener = Objects.requireNonNull(listener, "listener");
|
||||||
|
AtomicReference<String> phaseRef = new AtomicReference<>("start");
|
||||||
try {
|
try {
|
||||||
phase = "acquire-load-lock";
|
setChunkReplacementPhase(phaseRef, effectiveListener, "acquire-load-lock", x, z);
|
||||||
long acquireStart = System.currentTimeMillis();
|
long acquireStart = System.currentTimeMillis();
|
||||||
while (!loadLock.tryAcquire(5, TimeUnit.SECONDS)) {
|
while (!loadLock.tryAcquire(5, TimeUnit.SECONDS)) {
|
||||||
Iris.warn("Chunk replacement waiting for load lock at " + x + "," + z
|
Iris.warn("Chunk replacement waiting for load lock at " + x + "," + z
|
||||||
+ " for " + (System.currentTimeMillis() - acquireStart) + "ms.");
|
+ " for " + (System.currentTimeMillis() - acquireStart) + "ms.");
|
||||||
|
effectiveListener.onPhase(phaseRef.get(), x, z, System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
acquired = true;
|
acquired = true;
|
||||||
long acquireWait = System.currentTimeMillis() - acquireStart;
|
long acquireWait = System.currentTimeMillis() - acquireStart;
|
||||||
@@ -223,7 +240,12 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
TerrainChunk tc = TerrainChunk.create(world);
|
TerrainChunk tc = TerrainChunk.create(world);
|
||||||
this.world.bind(world);
|
this.world.bind(world);
|
||||||
|
|
||||||
phase = "engine-generate";
|
if (effectiveOptions.isFullMode()) {
|
||||||
|
setChunkReplacementPhase(phaseRef, effectiveListener, "reset-mantle", x, z);
|
||||||
|
resetMantleChunkForFullRegen(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
setChunkReplacementPhase(phaseRef, effectiveListener, "generate", x, z);
|
||||||
long generateStart = System.currentTimeMillis();
|
long generateStart = System.currentTimeMillis();
|
||||||
boolean useMulticore = IrisSettings.get().getGenerator().useMulticore && !J.isFolia();
|
boolean useMulticore = IrisSettings.get().getGenerator().useMulticore && !J.isFolia();
|
||||||
AtomicBoolean generateDone = new AtomicBoolean(false);
|
AtomicBoolean generateDone = new AtomicBoolean(false);
|
||||||
@@ -242,6 +264,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
+ " for " + (System.currentTimeMillis() - generationWatchdogStart.get()) + "ms"
|
+ " for " + (System.currentTimeMillis() - generationWatchdogStart.get()) + "ms"
|
||||||
+ " thread=" + generateThread.getName()
|
+ " thread=" + generateThread.getName()
|
||||||
+ " state=" + generateThread.getState());
|
+ " state=" + generateThread.getState());
|
||||||
|
effectiveListener.onPhase(phaseRef.get(), x, z, System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
@@ -255,12 +278,13 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (J.isFolia()) {
|
if (J.isFolia()) {
|
||||||
phase = "folia-run-region";
|
setChunkReplacementPhase(phaseRef, effectiveListener, "folia-run-region", x, z);
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
Throwable[] failure = new Throwable[1];
|
Throwable[] failure = new Throwable[1];
|
||||||
long regionScheduleStart = System.currentTimeMillis();
|
long regionScheduleStart = System.currentTimeMillis();
|
||||||
if (!J.runRegion(world, x, z, () -> {
|
if (!J.runRegion(world, x, z, () -> {
|
||||||
try {
|
try {
|
||||||
|
setChunkReplacementPhase(phaseRef, effectiveListener, "apply-terrain", x, z);
|
||||||
phaseUnsafeSet("folia-region-run", x, z);
|
phaseUnsafeSet("folia-region-run", x, z);
|
||||||
Chunk c = world.getChunkAt(x, z);
|
Chunk c = world.getChunkAt(x, z);
|
||||||
Iris.tickets.addTicket(c);
|
Iris.tickets.addTicket(c);
|
||||||
@@ -288,7 +312,15 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (effectiveOptions.isFullMode()) {
|
||||||
|
setChunkReplacementPhase(phaseRef, effectiveListener, "overlay", x, z);
|
||||||
|
OverlayMetrics overlayMetrics = applyMantleOverlay(c, world, x, z);
|
||||||
|
effectiveListener.onOverlay(x, z, overlayMetrics.appliedBlocks(), overlayMetrics.objectKeys(), System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
setChunkReplacementPhase(phaseRef, effectiveListener, "structures", x, z);
|
||||||
INMS.get().placeStructures(c);
|
INMS.get().placeStructures(c);
|
||||||
|
setChunkReplacementPhase(phaseRef, effectiveListener, "chunk-load-callback", x, z);
|
||||||
engine.getWorldManager().onChunkLoad(c, true);
|
engine.getWorldManager().onChunkLoad(c, true);
|
||||||
} finally {
|
} finally {
|
||||||
Iris.tickets.removeTicket(c);
|
Iris.tickets.removeTicket(c);
|
||||||
@@ -310,16 +342,18 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
while (!latch.await(5, TimeUnit.SECONDS)) {
|
while (!latch.await(5, TimeUnit.SECONDS)) {
|
||||||
Iris.warn("Chunk replacement waiting on region task at " + x + "," + z
|
Iris.warn("Chunk replacement waiting on region task at " + x + "," + z
|
||||||
+ " for " + (System.currentTimeMillis() - regionWaitStart) + "ms.");
|
+ " for " + (System.currentTimeMillis() - regionWaitStart) + "ms.");
|
||||||
|
effectiveListener.onPhase(phaseRef.get(), x, z, System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
long regionWaitTook = System.currentTimeMillis() - regionWaitStart;
|
long regionWaitTook = System.currentTimeMillis() - regionWaitStart;
|
||||||
if (regionWaitTook >= 5000L) {
|
if (regionWaitTook >= 5000L) {
|
||||||
Iris.warn("Chunk replacement region task completed after " + regionWaitTook + "ms at " + x + "," + z + ".");
|
Iris.warn("Chunk replacement region task completed after " + regionWaitTook + "ms at " + x + "," + z + ".");
|
||||||
}
|
}
|
||||||
if (failure[0] != null) {
|
if (failure[0] != null) {
|
||||||
|
effectiveListener.onFailurePhase(phaseRef.get(), x, z, failure[0], System.currentTimeMillis());
|
||||||
throw failure[0];
|
throw failure[0];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
phase = "paperlib-async-load";
|
setChunkReplacementPhase(phaseRef, effectiveListener, "paperlib-async-load", x, z);
|
||||||
long loadChunkStart = System.currentTimeMillis();
|
long loadChunkStart = System.currentTimeMillis();
|
||||||
Chunk c = PaperLib.getChunkAtAsync(world, x, z).get();
|
Chunk c = PaperLib.getChunkAtAsync(world, x, z).get();
|
||||||
long loadChunkTook = System.currentTimeMillis() - loadChunkStart;
|
long loadChunkTook = System.currentTimeMillis() - loadChunkStart;
|
||||||
@@ -327,8 +361,9 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
Iris.warn("Chunk replacement chunk load took " + loadChunkTook + "ms at " + x + "," + z + ".");
|
Iris.warn("Chunk replacement chunk load took " + loadChunkTook + "ms at " + x + "," + z + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
phase = "non-folia-apply";
|
setChunkReplacementPhase(phaseRef, effectiveListener, "apply-terrain", x, z);
|
||||||
Iris.tickets.addTicket(c);
|
Iris.tickets.addTicket(c);
|
||||||
|
try {
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
for (Entity ee : c.getEntities()) {
|
for (Entity ee : c.getEntities()) {
|
||||||
if (ee instanceof Player) {
|
if (ee instanceof Player) {
|
||||||
@@ -356,24 +391,36 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
}
|
}
|
||||||
}, syncExecutor));
|
}, syncExecutor));
|
||||||
}
|
}
|
||||||
futures.add(CompletableFuture.runAsync(() -> INMS.get().placeStructures(c), syncExecutor));
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get();
|
||||||
|
if (effectiveOptions.isFullMode()) {
|
||||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
|
CompletableFuture.runAsync(() -> {
|
||||||
.thenRunAsync(() -> {
|
setChunkReplacementPhase(phaseRef, effectiveListener, "overlay", x, z);
|
||||||
Iris.tickets.removeTicket(c);
|
OverlayMetrics overlayMetrics = applyMantleOverlay(c, world, x, z);
|
||||||
|
effectiveListener.onOverlay(x, z, overlayMetrics.appliedBlocks(), overlayMetrics.objectKeys(), System.currentTimeMillis());
|
||||||
|
}, syncExecutor).get();
|
||||||
|
}
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
setChunkReplacementPhase(phaseRef, effectiveListener, "structures", x, z);
|
||||||
|
INMS.get().placeStructures(c);
|
||||||
|
}, syncExecutor).get();
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
setChunkReplacementPhase(phaseRef, effectiveListener, "chunk-load-callback", x, z);
|
||||||
engine.getWorldManager().onChunkLoad(c, true);
|
engine.getWorldManager().onChunkLoad(c, true);
|
||||||
}, syncExecutor)
|
}, syncExecutor).get();
|
||||||
.get();
|
} finally {
|
||||||
|
Iris.tickets.removeTicket(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Iris.debug("Regenerated " + x + " " + z);
|
Iris.debug("Regenerated " + x + " " + z);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
effectiveListener.onFailurePhase(phaseRef.get(), x, z, e, System.currentTimeMillis());
|
||||||
Iris.error("======================================");
|
Iris.error("======================================");
|
||||||
Iris.error("Chunk replacement failed at phase=" + phase + " chunk=" + x + "," + z);
|
Iris.error("Chunk replacement failed at phase=" + phaseRef.get() + " chunk=" + x + "," + z);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Iris.reportErrorChunk(x, z, e, "CHUNK");
|
Iris.reportErrorChunk(x, z, e, "CHUNK");
|
||||||
Iris.error("======================================");
|
Iris.error("======================================");
|
||||||
throw new IllegalStateException("Chunk replacement failed at phase=" + phase + " chunk=" + x + "," + z, e);
|
throw new IllegalStateException("Chunk replacement failed at phase=" + phaseRef.get() + " chunk=" + x + "," + z, e);
|
||||||
} finally {
|
} finally {
|
||||||
if (acquired) {
|
if (acquired) {
|
||||||
loadLock.release();
|
loadLock.release();
|
||||||
@@ -385,6 +432,63 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
Iris.verbose("Chunk replacement phase=" + phase + " chunk=" + x + "," + z);
|
Iris.verbose("Chunk replacement phase=" + phase + " chunk=" + x + "," + z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void setChunkReplacementPhase(
|
||||||
|
AtomicReference<String> phaseRef,
|
||||||
|
ChunkReplacementListener listener,
|
||||||
|
String phase,
|
||||||
|
int x,
|
||||||
|
int z
|
||||||
|
) {
|
||||||
|
phaseRef.set(phase);
|
||||||
|
listener.onPhase(phase, x, z, System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetMantleChunkForFullRegen(int chunkX, int chunkZ) {
|
||||||
|
MantleChunk<Matter> mantleChunk = getEngine().getMantle().getMantle().getChunk(chunkX, chunkZ).use();
|
||||||
|
try {
|
||||||
|
mantleChunk.deleteSlices(BlockData.class);
|
||||||
|
mantleChunk.deleteSlices(String.class);
|
||||||
|
mantleChunk.deleteSlices(TileWrapper.class);
|
||||||
|
mantleChunk.flag(MantleFlag.PLANNED, false);
|
||||||
|
mantleChunk.flag(MantleFlag.OBJECT, false);
|
||||||
|
mantleChunk.flag(MantleFlag.REAL, false);
|
||||||
|
} finally {
|
||||||
|
mantleChunk.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OverlayMetrics applyMantleOverlay(Chunk chunk, World world, int chunkX, int chunkZ) {
|
||||||
|
int minWorldY = world.getMinHeight();
|
||||||
|
int maxWorldY = world.getMaxHeight();
|
||||||
|
AtomicInteger appliedBlocks = new AtomicInteger();
|
||||||
|
AtomicInteger objectKeys = new AtomicInteger();
|
||||||
|
MantleChunk<Matter> mantleChunk = getEngine().getMantle().getMantle().getChunk(chunkX, chunkZ).use();
|
||||||
|
try {
|
||||||
|
mantleChunk.iterate(String.class, (x, y, z, value) -> {
|
||||||
|
if (value != null && !value.isEmpty() && value.indexOf('@') > 0) {
|
||||||
|
objectKeys.incrementAndGet();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mantleChunk.iterate(BlockData.class, (x, y, z, blockData) -> {
|
||||||
|
if (blockData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int worldY = y + minWorldY;
|
||||||
|
if (worldY < minWorldY || worldY >= maxWorldY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chunk.getBlock(x & 15, worldY, z & 15).setBlockData(blockData, false);
|
||||||
|
appliedBlocks.incrementAndGet();
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
mantleChunk.release();
|
||||||
|
}
|
||||||
|
return new OverlayMetrics(appliedBlocks.get(), objectKeys.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private record OverlayMetrics(int appliedBlocks, int objectKeys) {
|
||||||
|
}
|
||||||
|
|
||||||
private Engine getEngine(WorldInfo world) {
|
private Engine getEngine(WorldInfo world) {
|
||||||
if (setup.get()) {
|
if (setup.get()) {
|
||||||
return getEngine();
|
return getEngine();
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||||
|
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package art.arcane.iris.engine.platform;
|
||||||
|
|
||||||
|
public interface ChunkReplacementListener {
|
||||||
|
ChunkReplacementListener NO_OP = new ChunkReplacementListener() {
|
||||||
|
};
|
||||||
|
|
||||||
|
default void onPhase(String phase, int chunkX, int chunkZ, long timestampMs) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onOverlay(int chunkX, int chunkZ, int appliedBlocks, int objectKeys, long timestampMs) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onFailurePhase(String phase, int chunkX, int chunkZ, Throwable error, long timestampMs) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||||
|
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package art.arcane.iris.engine.platform;
|
||||||
|
|
||||||
|
public final class ChunkReplacementOptions {
|
||||||
|
private final String runId;
|
||||||
|
private final boolean fullMode;
|
||||||
|
private final boolean diagnostics;
|
||||||
|
|
||||||
|
private ChunkReplacementOptions(String runId, boolean fullMode, boolean diagnostics) {
|
||||||
|
this.runId = runId == null ? "unknown" : runId;
|
||||||
|
this.fullMode = fullMode;
|
||||||
|
this.diagnostics = diagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChunkReplacementOptions terrain(String runId, boolean diagnostics) {
|
||||||
|
return new ChunkReplacementOptions(runId, false, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChunkReplacementOptions full(String runId, boolean diagnostics) {
|
||||||
|
return new ChunkReplacementOptions(runId, true, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String runId() {
|
||||||
|
return runId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFullMode() {
|
||||||
|
return fullMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean diagnostics() {
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,7 +42,14 @@ public interface PlatformChunkGenerator extends Hotloadable, DataProvider {
|
|||||||
@NotNull
|
@NotNull
|
||||||
EngineTarget getTarget();
|
EngineTarget getTarget();
|
||||||
|
|
||||||
void injectChunkReplacement(World world, int x, int z, Executor syncExecutor);
|
void injectChunkReplacement(
|
||||||
|
World world,
|
||||||
|
int x,
|
||||||
|
int z,
|
||||||
|
Executor syncExecutor,
|
||||||
|
ChunkReplacementOptions options,
|
||||||
|
ChunkReplacementListener listener
|
||||||
|
);
|
||||||
|
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
|
|||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
package art.arcane.iris.util.common.director.specialhandlers;
|
||||||
|
|
||||||
|
import art.arcane.iris.core.ExternalDataPackPipeline;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.iris.util.common.director.DirectorParameterHandler;
|
||||||
|
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class ExternalDatapackLocateHandler implements DirectorParameterHandler<String> {
|
||||||
|
@Override
|
||||||
|
public KList<String> getPossibilities() {
|
||||||
|
LinkedHashSet<String> tokens = new LinkedHashSet<>();
|
||||||
|
Map<String, Set<String>> locateById = ExternalDataPackPipeline.snapshotLocateStructuresById();
|
||||||
|
for (Map.Entry<String, Set<String>> entry : locateById.entrySet()) {
|
||||||
|
if (entry == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String id = entry.getKey();
|
||||||
|
if (id != null && !id.isBlank()) {
|
||||||
|
tokens.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> structures = entry.getValue();
|
||||||
|
if (structures == null || structures.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String structure : structures) {
|
||||||
|
if (structure != null && !structure.isBlank()) {
|
||||||
|
tokens.add(structure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KList<String> possibilities = new KList<>();
|
||||||
|
possibilities.add(tokens);
|
||||||
|
return possibilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KList<String> getPossibilities(String input) {
|
||||||
|
String rawInput = input == null ? "" : input;
|
||||||
|
String[] split = rawInput.split(",", -1);
|
||||||
|
String partial = split.length == 0 ? "" : split[split.length - 1].trim().toLowerCase(Locale.ROOT);
|
||||||
|
StringBuilder prefixBuilder = new StringBuilder();
|
||||||
|
if (split.length > 1) {
|
||||||
|
for (int index = 0; index < split.length - 1; index++) {
|
||||||
|
String value = split[index] == null ? "" : split[index].trim();
|
||||||
|
if (value.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!prefixBuilder.isEmpty()) {
|
||||||
|
prefixBuilder.append(',');
|
||||||
|
}
|
||||||
|
prefixBuilder.append(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String prefix = prefixBuilder.toString();
|
||||||
|
LinkedHashSet<String> completions = new LinkedHashSet<>();
|
||||||
|
for (String possibility : getPossibilities()) {
|
||||||
|
if (possibility == null || possibility.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String normalized = possibility.toLowerCase(Locale.ROOT);
|
||||||
|
if (!partial.isBlank() && !normalized.startsWith(partial)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix.isBlank()) {
|
||||||
|
completions.add(possibility);
|
||||||
|
} else {
|
||||||
|
completions.add(prefix + "," + possibility);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KList<String> results = new KList<>();
|
||||||
|
results.add(completions);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(String value) {
|
||||||
|
return value == null ? "" : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String parse(String in, boolean force) throws DirectorParsingException {
|
||||||
|
if (in == null || in.trim().isBlank()) {
|
||||||
|
throw new DirectorParsingException("You must provide at least one external datapack id or structure id.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return in.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> type) {
|
||||||
|
return type.equals(String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRandomDefault() {
|
||||||
|
KList<String> possibilities = getPossibilities();
|
||||||
|
String random = possibilities.getRandom();
|
||||||
|
return random == null ? "external-datapack-id" : random;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -205,13 +205,12 @@ public class CustomBiomeSource extends BiomeSource {
|
|||||||
int blockZ = z << 2;
|
int blockZ = z << 2;
|
||||||
int blockY = y << 2;
|
int blockY = y << 2;
|
||||||
int worldMinHeight = engine.getWorld().minHeight();
|
int worldMinHeight = engine.getWorld().minHeight();
|
||||||
int surfaceInternalY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue();
|
|
||||||
int surfaceWorldY = surfaceInternalY + worldMinHeight;
|
|
||||||
int caveSwitchWorldY = Math.min(-8, worldMinHeight + 40);
|
|
||||||
boolean deepUnderground = blockY <= caveSwitchWorldY;
|
|
||||||
boolean belowSurface = blockY <= surfaceWorldY - 8;
|
|
||||||
boolean underground = deepUnderground && belowSurface;
|
|
||||||
int internalY = blockY - worldMinHeight;
|
int internalY = blockY - worldMinHeight;
|
||||||
|
int surfaceInternalY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue();
|
||||||
|
int caveSwitchInternalY = Math.max(-8 - worldMinHeight, 40);
|
||||||
|
boolean deepUnderground = internalY <= caveSwitchInternalY;
|
||||||
|
boolean belowSurface = internalY <= surfaceInternalY - 8;
|
||||||
|
boolean underground = deepUnderground && belowSurface;
|
||||||
IrisBiome irisBiome = underground
|
IrisBiome irisBiome = underground
|
||||||
? engine.getCaveBiome(blockX, internalY, blockZ)
|
? engine.getCaveBiome(blockX, internalY, blockZ)
|
||||||
: engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
: engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package art.arcane.iris.core.nms.v1_21_R7;
|
|||||||
import com.mojang.datafixers.util.Pair;
|
import com.mojang.datafixers.util.Pair;
|
||||||
import com.mojang.serialization.MapCodec;
|
import com.mojang.serialization.MapCodec;
|
||||||
import art.arcane.iris.Iris;
|
import art.arcane.iris.Iris;
|
||||||
|
import art.arcane.iris.core.ExternalDataPackPipeline;
|
||||||
import art.arcane.iris.core.IrisSettings;
|
import art.arcane.iris.core.IrisSettings;
|
||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
import art.arcane.iris.util.common.reflect.WrappedField;
|
import art.arcane.iris.util.common.reflect.WrappedField;
|
||||||
@@ -49,6 +50,7 @@ import java.util.function.Supplier;
|
|||||||
public class IrisChunkGenerator extends CustomChunkGenerator {
|
public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||||
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
|
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
|
||||||
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
|
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
|
||||||
|
private static final int EXTERNAL_FOUNDATION_MAX_DEPTH = 96;
|
||||||
private final ChunkGenerator delegate;
|
private final ChunkGenerator delegate;
|
||||||
private final Engine engine;
|
private final Engine engine;
|
||||||
private volatile Registry<Structure> cachedStructureRegistry;
|
private volatile Registry<Structure> cachedStructureRegistry;
|
||||||
@@ -199,6 +201,7 @@ 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();
|
||||||
|
|
||||||
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++) {
|
||||||
@@ -210,10 +213,19 @@ 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);
|
||||||
|
boolean isExternalLocateStructure = externalLocateStructures.contains(structureKey);
|
||||||
|
BitSet[] beforeSolidColumns = null;
|
||||||
|
if (isExternalLocateStructure) {
|
||||||
|
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) {
|
||||||
|
applyExternalStructureFoundations(level, chunkAccess, beforeSolidColumns, EXTERNAL_FOUNDATION_MAX_DEPTH);
|
||||||
|
}
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement");
|
CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement");
|
||||||
CrashReportCategory category = crashReport.addCategory("Feature");
|
CrashReportCategory category = crashReport.addCategory("Feature");
|
||||||
@@ -235,6 +247,120 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
|||||||
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
|
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BitSet[] snapshotChunkSolidColumns(WorldGenLevel level, ChunkAccess chunkAccess) {
|
||||||
|
int minY = level.getMinY();
|
||||||
|
int maxY = level.getMaxY();
|
||||||
|
int ySpan = maxY - minY;
|
||||||
|
if (ySpan <= 0) {
|
||||||
|
return new BitSet[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkPos chunkPos = chunkAccess.getPos();
|
||||||
|
int minX = chunkPos.getMinBlockX();
|
||||||
|
int minZ = chunkPos.getMinBlockZ();
|
||||||
|
BitSet[] columns = new BitSet[16 * 16];
|
||||||
|
BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
|
||||||
|
for (int localX = 0; localX < 16; localX++) {
|
||||||
|
for (int localZ = 0; localZ < 16; localZ++) {
|
||||||
|
int index = (localX << 4) | localZ;
|
||||||
|
BitSet solids = new BitSet(ySpan);
|
||||||
|
int worldX = minX + localX;
|
||||||
|
int worldZ = minZ + localZ;
|
||||||
|
for (int y = minY; y < maxY; y++) {
|
||||||
|
mutablePos.set(worldX, y, worldZ);
|
||||||
|
if (isFoundationSolid(level.getBlockState(mutablePos))) {
|
||||||
|
solids.set(y - minY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
columns[index] = solids;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyExternalStructureFoundations(
|
||||||
|
WorldGenLevel level,
|
||||||
|
ChunkAccess chunkAccess,
|
||||||
|
BitSet[] beforeSolidColumns,
|
||||||
|
int maxDepth
|
||||||
|
) {
|
||||||
|
if (beforeSolidColumns == null || beforeSolidColumns.length == 0 || maxDepth <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minY = level.getMinY();
|
||||||
|
int maxY = level.getMaxY();
|
||||||
|
int ySpan = maxY - minY;
|
||||||
|
if (ySpan <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkPos chunkPos = chunkAccess.getPos();
|
||||||
|
int minX = chunkPos.getMinBlockX();
|
||||||
|
int minZ = chunkPos.getMinBlockZ();
|
||||||
|
BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
|
||||||
|
for (int localX = 0; localX < 16; localX++) {
|
||||||
|
for (int localZ = 0; localZ < 16; localZ++) {
|
||||||
|
int index = (localX << 4) | localZ;
|
||||||
|
BitSet before = beforeSolidColumns[index];
|
||||||
|
if (before == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int worldX = minX + localX;
|
||||||
|
int worldZ = minZ + localZ;
|
||||||
|
int lowestNewSolidY = Integer.MIN_VALUE;
|
||||||
|
for (int y = minY; y < maxY; y++) {
|
||||||
|
mutablePos.set(worldX, y, worldZ);
|
||||||
|
BlockState state = level.getBlockState(mutablePos);
|
||||||
|
if (!isFoundationSolid(state)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (before.get(y - minY)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
lowestNewSolidY = y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lowestNewSolidY == Integer.MIN_VALUE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutablePos.set(worldX, lowestNewSolidY, worldZ);
|
||||||
|
BlockState foundationState = level.getBlockState(mutablePos);
|
||||||
|
if (!isFoundationSolid(foundationState)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int depth = 0;
|
||||||
|
for (int y = lowestNewSolidY - 1; y >= minY && depth < maxDepth; y--) {
|
||||||
|
mutablePos.set(worldX, y, worldZ);
|
||||||
|
BlockState state = level.getBlockState(mutablePos);
|
||||||
|
if (isFoundationSolid(state)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
level.setBlock(mutablePos, foundationState, 2);
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isFoundationSolid(BlockState state) {
|
||||||
|
if (state == null || state.isAir()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!state.getFluidState().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Heightmap.Types.MOTION_BLOCKING_NO_LEAVES.isOpaque().test(state);
|
||||||
|
}
|
||||||
|
|
||||||
private Map<Structure, Integer> getStructureOrder(Registry<Structure> structureRegistry) {
|
private Map<Structure, Integer> getStructureOrder(Registry<Structure> structureRegistry) {
|
||||||
Map<Structure, Integer> localOrder = cachedStructureOrder;
|
Map<Structure, Integer> localOrder = cachedStructureOrder;
|
||||||
Registry<Structure> localRegistry = cachedStructureRegistry;
|
Registry<Structure> localRegistry = cachedStructureRegistry;
|
||||||
|
|||||||
@@ -832,7 +832,10 @@ public class NMSBinding implements INMSBinding {
|
|||||||
public void placeStructures(Chunk chunk) {
|
public void placeStructures(Chunk chunk) {
|
||||||
var craft = ((CraftChunk) chunk);
|
var craft = ((CraftChunk) chunk);
|
||||||
var level = craft.getCraftWorld().getHandle();
|
var level = craft.getCraftWorld().getHandle();
|
||||||
var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL);
|
var access = craft.getHandle(ChunkStatus.FEATURES);
|
||||||
|
if (access instanceof LevelChunk) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager());
|
level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user