mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-12 18:04:01 +00:00
Good points
This commit is contained in:
@@ -110,16 +110,8 @@ public class IrisSettings {
|
||||
public boolean autoRestartOnCustomBiomeInstall = true;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisAsyncTeleport {
|
||||
public boolean enabled = false;
|
||||
public int loadViewDistance = 2;
|
||||
public boolean urgent = false;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsWorld {
|
||||
public IrisAsyncTeleport asyncTeleport = new IrisAsyncTeleport();
|
||||
public boolean postLoadBlockUpdates = true;
|
||||
public boolean forcePersistEntities = true;
|
||||
public boolean anbientEntitySpawningSystem = true;
|
||||
@@ -133,33 +125,27 @@ public class IrisSettings {
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsConcurrency {
|
||||
public int parallelism = -1;
|
||||
public int ioParallelism = -2;
|
||||
public int worldGenParallelism = -1;
|
||||
public int getParallelism() {
|
||||
return Math.max(2, Runtime.getRuntime().availableProcessors());
|
||||
}
|
||||
|
||||
public int getIoParallelism() {
|
||||
return Math.max(2, Runtime.getRuntime().availableProcessors() / 2);
|
||||
}
|
||||
|
||||
public int getWorldGenThreads() {
|
||||
return getThreadCount(worldGenParallelism);
|
||||
return Math.max(2, Runtime.getRuntime().availableProcessors());
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsPregen {
|
||||
public boolean useCacheByDefault = true;
|
||||
public boolean useHighPriority = false;
|
||||
public boolean useVirtualThreads = false;
|
||||
public boolean useTicketQueue = true;
|
||||
public IrisRuntimeSchedulerMode runtimeSchedulerMode = IrisRuntimeSchedulerMode.AUTO;
|
||||
public IrisPaperLikeBackendMode paperLikeBackendMode = IrisPaperLikeBackendMode.AUTO;
|
||||
public int maxConcurrency = 256;
|
||||
public int paperLikeMaxConcurrency = 96;
|
||||
public int foliaMaxConcurrency = 32;
|
||||
public int chunkLoadTimeoutSeconds = 15;
|
||||
public int timeoutWarnIntervalMs = 500;
|
||||
public int saveIntervalMs = 30_000;
|
||||
public boolean enablePregenPerformanceProfile = true;
|
||||
public int pregenProfileNoiseCacheSize = 4_096;
|
||||
public boolean pregenProfileEnableFastCache = true;
|
||||
public boolean pregenProfileLogJvmHints = true;
|
||||
|
||||
public int getChunkLoadTimeoutSeconds() {
|
||||
return Math.max(5, Math.min(chunkLoadTimeoutSeconds, 120));
|
||||
@@ -169,14 +155,6 @@ public class IrisSettings {
|
||||
return Math.max(timeoutWarnIntervalMs, 250);
|
||||
}
|
||||
|
||||
public int getPaperLikeMaxConcurrency() {
|
||||
return Math.max(1, paperLikeMaxConcurrency);
|
||||
}
|
||||
|
||||
public int getFoliaMaxConcurrency() {
|
||||
return Math.max(1, foliaMaxConcurrency);
|
||||
}
|
||||
|
||||
public IrisPaperLikeBackendMode getPaperLikeBackendMode() {
|
||||
if (paperLikeBackendMode == null) {
|
||||
return IrisPaperLikeBackendMode.AUTO;
|
||||
@@ -200,16 +178,6 @@ public class IrisSettings {
|
||||
public int objectLoaderCacheSize = 4_096;
|
||||
public int tectonicPlateSize = -1;
|
||||
public int mantleCleanupDelay = 200;
|
||||
public int heightBoundsInterpolationGrid = 4;
|
||||
|
||||
public int getHeightBoundsInterpolationGrid() {
|
||||
int grid = heightBoundsInterpolationGrid;
|
||||
if (grid <= 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Math.min(16, Integer.highestOneBit(grid));
|
||||
}
|
||||
|
||||
public int getTectonicPlateSize() {
|
||||
if (tectonicPlateSize > 0)
|
||||
@@ -234,7 +202,6 @@ public class IrisSettings {
|
||||
public int spinh = -20;
|
||||
public int spins = 7;
|
||||
public int spinb = 8;
|
||||
public String cartographerMessage = "Iris does not allow cartographers in its world due to crashes.";
|
||||
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
@@ -262,9 +229,6 @@ public class IrisSettings {
|
||||
public String defaultWorldType = "overworld";
|
||||
public int maxBiomeChildDepth = 4;
|
||||
public boolean preventLeafDecay = true;
|
||||
public boolean useMulticore = false;
|
||||
public boolean useMulticoreMantle = false;
|
||||
public boolean earlyCustomBlocks = false;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
@@ -235,6 +235,9 @@ public class ServerConfigurator {
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
if (file.getName().startsWith(".")) {
|
||||
continue;
|
||||
}
|
||||
collectFingerprintEntries(file, rootPath, entries);
|
||||
} else {
|
||||
String relative = file.getAbsolutePath().substring(rootPath.length());
|
||||
|
||||
@@ -21,15 +21,23 @@ package art.arcane.iris.core.commands;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.service.ObjectStudioSaveService;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.ObjectHandler;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.StructureHandler;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.StructureSearchResult;
|
||||
|
||||
@Director(name = "find", origin = DirectorOrigin.PLAYER, description = "Iris Find commands", aliases = "goto")
|
||||
public class CommandFind implements DirectorExecutor {
|
||||
@@ -83,6 +91,60 @@ public class CommandFind implements DirectorExecutor {
|
||||
e.gotoPOI(type, player(), teleport);
|
||||
}
|
||||
|
||||
@Director(description = "Find a structure (a vanilla key like minecraft:village_plains or minecraft:stronghold, or an imported iris structure key)")
|
||||
public void structure(
|
||||
@Param(description = "The structure to look for (e.g. minecraft:village_plains, minecraft:stronghold, minecraft_ancient_city)", customHandler = StructureHandler.class)
|
||||
String structure
|
||||
) {
|
||||
Engine e = engine();
|
||||
|
||||
if (e == null) {
|
||||
sender().sendMessage(C.GOLD + "Not in an Iris World!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (IrisStructureLocator.isPlaced(e, structure)) {
|
||||
e.gotoStructure(structure, player(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
Player target = player();
|
||||
if (target == null) {
|
||||
sender().sendMessage(C.GOLD + "Run this in-game to teleport to a structure.");
|
||||
return;
|
||||
}
|
||||
|
||||
sender().sendMessage(C.GRAY + "Locating " + structure + "...");
|
||||
J.s(() -> {
|
||||
try {
|
||||
org.bukkit.generator.structure.Structure match = null;
|
||||
for (org.bukkit.generator.structure.Structure s : Registry.STRUCTURE) {
|
||||
NamespacedKey key = s.getKey();
|
||||
if (key != null && key.toString().equalsIgnoreCase(structure)) {
|
||||
match = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match == null) {
|
||||
sender().sendMessage(C.RED + "Unknown structure: " + structure);
|
||||
return;
|
||||
}
|
||||
StructureSearchResult result = target.getWorld().locateNearestStructure(target.getLocation(), match, 100, true);
|
||||
if (result == null || result.getLocation() == null) {
|
||||
sender().sendMessage(C.YELLOW + "No " + structure + " found within range of you.");
|
||||
return;
|
||||
}
|
||||
Location at = result.getLocation();
|
||||
int y = target.getWorld().getHighestBlockYAt(at.getBlockX(), at.getBlockZ()) + 2;
|
||||
Location dest = new Location(target.getWorld(), at.getBlockX() + 0.5, y, at.getBlockZ() + 0.5);
|
||||
PaperLib.teleportAsync(target, dest);
|
||||
sender().sendMessage(C.GREEN + "Teleported to " + structure + " @ " + at.getBlockX() + ", " + at.getBlockZ());
|
||||
} catch (Throwable t) {
|
||||
sender().sendMessage(C.RED + "Could not locate " + structure + ": " + t.getClass().getSimpleName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Director(description = "Find an object")
|
||||
public void object(
|
||||
@Param(description = "The object to look for", customHandler = ObjectHandler.class)
|
||||
|
||||
@@ -23,33 +23,31 @@ import art.arcane.iris.core.structure.BulkStructureImporter;
|
||||
import art.arcane.iris.core.structure.StructureImporter;
|
||||
import art.arcane.iris.core.structure.StructureIndexService;
|
||||
import art.arcane.iris.core.structure.VillageImporter;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.PlacedStructurePiece;
|
||||
import art.arcane.iris.engine.framework.StructureAssembler;
|
||||
import art.arcane.iris.engine.framework.StructurePlacementGrid;
|
||||
import art.arcane.iris.engine.object.IObjectPlacer;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisObjectPlacement;
|
||||
import art.arcane.iris.engine.object.IrisStructure;
|
||||
import art.arcane.iris.engine.object.IrisStructurePlacement;
|
||||
import art.arcane.iris.engine.object.ObjectPlaceMode;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.iris.util.common.director.DirectorHelp;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.iris.engine.data.cache.Cache;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.util.StructureSearchResult;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
@@ -173,6 +171,93 @@ public class CommandStructure implements DirectorExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Re-ingest EVERY vanilla structure and jigsaw template from scratch (overwrite), regenerating all .iob objects so they pick up the latest jigsaw/structure-block conversion. Use this after updating Iris if imported structures show raw markers.", aliases = {"reimport", "ri"}, origin = DirectorOrigin.BOTH)
|
||||
public void reingest(
|
||||
@Param(description = "The dimension whose pack to re-ingest", aliases = "dim")
|
||||
IrisDimension dimension
|
||||
) {
|
||||
IrisData data = dimension.getLoader();
|
||||
if (data == null) {
|
||||
sender().sendMessage(C.RED + "Could not resolve the pack for dimension " + dimension.getLoadKey());
|
||||
return;
|
||||
}
|
||||
sender().sendMessage(C.GREEN + "Re-ingesting all vanilla structures and jigsaws for " + C.WHITE + dimension.getLoadKey() + C.GREEN + " (overwrite)...");
|
||||
BulkStructureImporter.Report jigsaws = BulkStructureImporter.importAllVanilla(data, StructureImporter.Mode.OVERWRITE, true, sender());
|
||||
BulkStructureImporter.Report templates = BulkStructureImporter.importAllTemplates(data, StructureImporter.Mode.OVERWRITE, sender());
|
||||
BulkStructureImporter.Report groups = BulkStructureImporter.importTemplateGroups(data, StructureImporter.Mode.OVERWRITE, sender());
|
||||
int imported = jigsaws.imported() + templates.imported() + groups.imported();
|
||||
int failed = jigsaws.failed() + templates.failed() + groups.failed();
|
||||
sender().sendMessage(C.GREEN + "Re-ingest complete: " + C.WHITE + imported + C.GREEN + " objects rewritten, " + C.WHITE + failed + C.GREEN + " failed.");
|
||||
sender().sendMessage(C.GRAY + "Regenerate chunks (or use a fresh world) for structures to place from the rewritten objects. Run /iris structure list " + dimension.getLoadKey() + " to refresh the index.");
|
||||
}
|
||||
|
||||
@Director(description = "Locate every vanilla/datapack/iris structure to verify which are locatable in this world. Heavy synchronous search per structure - keep the radius modest. 'Not found' can mean rarer than the radius, a different dimension (nether/end structures never appear in the overworld), or a structure that generates but cannot be located; generation itself happens during chunk decoration, independent of locate.", aliases = {"verify", "test", "locateall"}, origin = DirectorOrigin.BOTH, sync = true)
|
||||
public void verify(
|
||||
@Param(description = "The dimension to verify", aliases = "dim")
|
||||
IrisDimension dimension,
|
||||
@Param(description = "Search radius in chunks around the origin (larger is much slower)", defaultValue = "48")
|
||||
int radius
|
||||
) {
|
||||
World world = resolveIrisWorld(dimension);
|
||||
if (world == null) {
|
||||
sender().sendMessage(C.RED + "No loaded Iris world found for " + dimension.getLoadKey() + ". Join or create one first (the search runs against a live world).");
|
||||
return;
|
||||
}
|
||||
boolean senderIsPlayer = sender() != null && sender().isPlayer();
|
||||
Location center = (senderIsPlayer && player().getWorld() == world) ? player().getLocation() : world.getSpawnLocation();
|
||||
int searchRadius = Math.max(1, Math.min(radius, 1000));
|
||||
|
||||
sender().sendMessage(C.GREEN + "Verifying structures in " + C.WHITE + world.getName() + C.GREEN + " from " + center.getBlockX() + "," + center.getBlockZ() + " within " + searchRadius + " chunks...");
|
||||
|
||||
int found = 0;
|
||||
int missing = 0;
|
||||
KList<String> notFound = new KList<>();
|
||||
for (org.bukkit.generator.structure.Structure structure : Registry.STRUCTURE) {
|
||||
NamespacedKey key = structure.getKey();
|
||||
String keyName = key == null ? structure.toString() : key.toString();
|
||||
try {
|
||||
StructureSearchResult result = world.locateNearestStructure(center, structure, searchRadius, true);
|
||||
if (result != null && result.getLocation() != null) {
|
||||
found++;
|
||||
Location l = result.getLocation();
|
||||
sender().sendMessage(C.GREEN + "[ok] " + C.WHITE + keyName + C.GREEN + " @ " + l.getBlockX() + "," + l.getBlockZ());
|
||||
} else {
|
||||
missing++;
|
||||
notFound.add(keyName);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
missing++;
|
||||
notFound.add(keyName + " (error: " + e.getClass().getSimpleName() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
sender().sendMessage(C.GREEN + "Structure verify: " + C.WHITE + found + C.GREEN + " generate, " + C.WHITE + missing + C.GREEN + " not found within " + searchRadius + " chunks.");
|
||||
if (!notFound.isEmpty()) {
|
||||
sender().sendMessage(C.YELLOW + "Not found (rarer than radius, disabled, or non-generating): " + C.GRAY + String.join(", ", notFound));
|
||||
}
|
||||
}
|
||||
|
||||
private World resolveIrisWorld(IrisDimension dimension) {
|
||||
if (sender() != null && sender().isPlayer() && IrisToolbelt.isIrisWorld(player().getWorld())) {
|
||||
return player().getWorld();
|
||||
}
|
||||
World fallback = null;
|
||||
for (World w : Bukkit.getWorlds()) {
|
||||
if (!IrisToolbelt.isIrisWorld(w)) {
|
||||
continue;
|
||||
}
|
||||
if (fallback == null) {
|
||||
fallback = w;
|
||||
}
|
||||
PlatformChunkGenerator gen = IrisToolbelt.access(w);
|
||||
if (gen != null && gen.getEngine() != null && gen.getEngine().getDimension() != null
|
||||
&& dimension.getLoadKey().equals(gen.getEngine().getDimension().getLoadKey())) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@Director(description = "Resolve an iris structure's jigsaw graph and report piece count & bounds", origin = DirectorOrigin.BOTH)
|
||||
public void info(
|
||||
@Param(description = "The dimension whose pack holds the structure", aliases = "dim")
|
||||
@@ -249,64 +334,4 @@ public class CommandStructure implements DirectorExecutor {
|
||||
sender().sendMessage(C.GREEN + "Placed '" + structure + "' (" + pieces.size() + " pieces) at your location.");
|
||||
}
|
||||
|
||||
@Director(description = "Find the nearest placement of an iris structure around you", aliases = {"l"}, origin = DirectorOrigin.PLAYER)
|
||||
public void locate(
|
||||
@Param(description = "The iris structure key to find")
|
||||
String structure,
|
||||
@Param(description = "Search radius in chunks", defaultValue = "96")
|
||||
int radius
|
||||
) {
|
||||
World world = player().getWorld();
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
sender().sendMessage(C.RED + "You must be in an Iris world");
|
||||
return;
|
||||
}
|
||||
Engine engine = IrisToolbelt.access(world).getEngine();
|
||||
IrisData data = engine.getData();
|
||||
long seed = engine.getSeedManager().getMantle();
|
||||
int pcx = player().getLocation().getBlockX() >> 4;
|
||||
int pcz = player().getLocation().getBlockZ() >> 4;
|
||||
int max = Math.max(1, Math.min(radius, 512));
|
||||
for (int r = 0; r <= max; r++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
for (int dz = -r; dz <= r; dz++) {
|
||||
if (Math.max(Math.abs(dx), Math.abs(dz)) != r) {
|
||||
continue;
|
||||
}
|
||||
int cx = pcx + dx;
|
||||
int cz = pcz + dz;
|
||||
if (chunkStarts(engine, seed, structure, cx, cz)) {
|
||||
sender().sendMessage(C.GREEN + "Nearest '" + structure + "' starts near chunk " + cx + ", " + cz + " (block " + (cx << 4) + ", " + (cz << 4) + ")");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sender().sendMessage(C.YELLOW + "No '" + structure + "' placement found within " + max + " chunks.");
|
||||
}
|
||||
|
||||
private boolean chunkStarts(Engine engine, long seed, String structure, int cx, int cz) {
|
||||
int bx = 8 + (cx << 4);
|
||||
int bz = 8 + (cz << 4);
|
||||
IrisBiome biome = engine.getComplex().getTrueBiomeStream().get(bx, bz);
|
||||
IrisRegion region = engine.getComplex().getRegionStream().get(bx, bz);
|
||||
KList<IrisStructurePlacement> placements = new KList<>();
|
||||
if (biome != null) {
|
||||
placements.addAll(biome.getStructures());
|
||||
}
|
||||
if (region != null) {
|
||||
placements.addAll(region.getStructures());
|
||||
}
|
||||
placements.addAll(engine.getDimension().getStructures());
|
||||
RNG rng = new RNG(Cache.key(cx, cz) + seed);
|
||||
for (IrisStructurePlacement placement : placements) {
|
||||
if (!placement.getStructures().contains(structure)) {
|
||||
continue;
|
||||
}
|
||||
if (StructurePlacementGrid.startsInChunk(placement, cx, cz, seed, rng)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-28
@@ -42,7 +42,6 @@ import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -64,7 +63,6 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
private final int runtimeCpuThreads;
|
||||
private final int effectiveWorkerThreads;
|
||||
private final int recommendedRuntimeConcurrencyCap;
|
||||
private final int configuredMaxConcurrency;
|
||||
private final Method directChunkAtAsyncUrgentMethod;
|
||||
private final Method directChunkAtAsyncMethod;
|
||||
private final String chunkAccessMode;
|
||||
@@ -124,26 +122,18 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
this.backendMode = "paper-ticket";
|
||||
}
|
||||
}
|
||||
int runtimeMaxConcurrency = foliaRuntime
|
||||
? pregen.getFoliaMaxConcurrency()
|
||||
: pregen.getPaperLikeMaxConcurrency();
|
||||
int configuredThreads = applyRuntimeConcurrencyCap(
|
||||
runtimeMaxConcurrency,
|
||||
foliaRuntime,
|
||||
workerThreadsForCap
|
||||
);
|
||||
this.configuredMaxConcurrency = Math.max(1, pregen.getMaxConcurrency());
|
||||
int configuredThreads = foliaRuntime
|
||||
? computeFoliaRecommendedCap(workerThreadsForCap)
|
||||
: computePaperLikeRecommendedCap(workerThreadsForCap);
|
||||
this.threads = Math.max(1, configuredThreads);
|
||||
this.workerPoolThreads = detectedWorkerPoolThreads;
|
||||
this.runtimeCpuThreads = detectedCpuThreads;
|
||||
this.effectiveWorkerThreads = workerThreadsForCap;
|
||||
this.recommendedRuntimeConcurrencyCap = foliaRuntime
|
||||
? computeFoliaRecommendedCap(workerThreadsForCap)
|
||||
: computePaperLikeRecommendedCap(workerThreadsForCap);
|
||||
this.recommendedRuntimeConcurrencyCap = configuredThreads;
|
||||
this.semaphore = new Semaphore(this.threads, true);
|
||||
this.timeoutSeconds = pregen.getChunkLoadTimeoutSeconds();
|
||||
this.timeoutWarnIntervalMs = pregen.getTimeoutWarnIntervalMs();
|
||||
this.urgent = IrisSettings.get().getPregen().useHighPriority;
|
||||
this.urgent = false;
|
||||
this.lastUse = new ConcurrentHashMap<>();
|
||||
this.adaptiveInFlightLimit = new AtomicInteger(this.threads);
|
||||
this.adaptiveMinInFlightLimit = Math.max(4, Math.min(16, Math.max(1, this.threads / 4)));
|
||||
@@ -155,7 +145,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
return configuredMode;
|
||||
}
|
||||
|
||||
return pregen.isUseVirtualThreads() ? IrisPaperLikeBackendMode.SERVICE : IrisPaperLikeBackendMode.TICKET;
|
||||
return IrisPaperLikeBackendMode.TICKET;
|
||||
}
|
||||
|
||||
private int resolveWorkerPoolThreads() {
|
||||
@@ -394,14 +384,6 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
return Math.max(detectedCpuThreads, Math.max(configuredWorldGenThreads, Math.max(1, detectedWorkerPoolThreads)));
|
||||
}
|
||||
|
||||
static int applyRuntimeConcurrencyCap(int maxConcurrency, boolean foliaRuntime, int workerThreads) {
|
||||
int normalizedMaxConcurrency = Math.max(1, maxConcurrency);
|
||||
int recommendedCap = foliaRuntime
|
||||
? computeFoliaRecommendedCap(workerThreads)
|
||||
: computePaperLikeRecommendedCap(workerThreads);
|
||||
return Math.min(normalizedMaxConcurrency, recommendedCap);
|
||||
}
|
||||
|
||||
private String metricsSnapshot() {
|
||||
long stalledFor = Math.max(0L, M.ms() - lastProgressAt.get());
|
||||
return "world=" + world.getName()
|
||||
@@ -509,7 +491,6 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
+ ", workerPoolThreads=" + workerPoolThreads
|
||||
+ ", cpuThreads=" + runtimeCpuThreads
|
||||
+ ", effectiveWorkerThreads=" + effectiveWorkerThreads
|
||||
+ ", maxConcurrency=" + configuredMaxConcurrency
|
||||
+ ", recommendedCap=" + recommendedRuntimeConcurrencyCap
|
||||
+ ", urgent=" + urgent
|
||||
+ ", timeout=" + timeoutSeconds + "s");
|
||||
@@ -779,9 +760,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
}
|
||||
|
||||
private class ServiceExecutor implements Executor {
|
||||
private final ExecutorService service = IrisSettings.get().getPregen().isUseVirtualThreads() ?
|
||||
Executors.newVirtualThreadPerTaskExecutor() :
|
||||
new MultiBurst("Iris Async Pregen");
|
||||
private final ExecutorService service = new MultiBurst("Iris Async Pregen");
|
||||
|
||||
public void generate(int x, int z, PregenListener listener) {
|
||||
service.submit(() -> {
|
||||
|
||||
@@ -85,6 +85,9 @@ public final class StudioOpenCoordinator {
|
||||
World world = null;
|
||||
PlatformChunkGenerator provider = null;
|
||||
try {
|
||||
long openStart = System.currentTimeMillis();
|
||||
long t = openStart;
|
||||
Iris.info("[Studio timing] ===== studio open START: " + request.worldName() + " =====");
|
||||
updateStage(request, "resolve_dimension", 0.04D);
|
||||
if (IrisToolbelt.getDimension(request.dimensionKey()) == null) {
|
||||
throw new IrisException("Dimension cannot be found for id " + request.dimensionKey() + ".");
|
||||
@@ -92,6 +95,7 @@ public final class StudioOpenCoordinator {
|
||||
|
||||
updateStage(request, "prepare_world_pack", 0.10D);
|
||||
cleanupStaleTransientWorlds(request.worldName());
|
||||
t = logStudioPhase("resolveDimension + cleanupStaleWorlds", t, openStart);
|
||||
|
||||
updateStage(request, "install_datapacks", 0.18D);
|
||||
IrisCreator creator = IrisToolbelt.createWorld()
|
||||
@@ -102,6 +106,7 @@ public final class StudioOpenCoordinator {
|
||||
.dimension(request.dimensionKey())
|
||||
.studioProgressConsumer((progress, stage) -> updateStage(request, mapCreatorStage(stage), progress));
|
||||
world = creator.create();
|
||||
t = logStudioPhase("createWorld (datapacks + bukkit world + engine setup)", t, openStart);
|
||||
provider = IrisToolbelt.access(world);
|
||||
if (provider == null) {
|
||||
throw new IllegalStateException("Studio runtime provider is unavailable for world \"" + request.worldName() + "\".");
|
||||
@@ -109,14 +114,17 @@ public final class StudioOpenCoordinator {
|
||||
|
||||
updateStage(request, "apply_world_rules", 0.72D);
|
||||
WorldRuntimeControlService.get().applyStudioWorldRules(world);
|
||||
t = logStudioPhase("applyStudioWorldRules", t, openStart);
|
||||
|
||||
updateStage(request, "prepare_generator", 0.78D);
|
||||
WorldRuntimeControlService.get().prepareGenerator(world);
|
||||
t = logStudioPhase("prepareGenerator", t, openStart);
|
||||
|
||||
Location entryAnchor = WorldRuntimeControlService.get().resolveEntryAnchor(world);
|
||||
if (entryAnchor == null) {
|
||||
throw new IllegalStateException("Studio entry anchor could not be resolved.");
|
||||
}
|
||||
t = logStudioPhase("resolveEntryAnchor", t, openStart);
|
||||
|
||||
updateStage(request, "resolve_safe_entry", 0.84D);
|
||||
Location safeEntry;
|
||||
@@ -129,6 +137,7 @@ public final class StudioOpenCoordinator {
|
||||
if (safeEntry == null) {
|
||||
throw new IllegalStateException("Studio entry point could not be resolved for world \"" + request.worldName() + "\".");
|
||||
}
|
||||
t = logStudioPhase("resolveSafeEntry (generates/loads spawn chunk to FULL)", t, openStart);
|
||||
|
||||
if (request.playerName() != null && !request.playerName().isBlank()) {
|
||||
updateStage(request, "teleport_player", 0.96D);
|
||||
@@ -141,6 +150,7 @@ public final class StudioOpenCoordinator {
|
||||
if (!Boolean.TRUE.equals(teleported)) {
|
||||
throw new IllegalStateException("Studio teleport did not complete successfully.");
|
||||
}
|
||||
t = logStudioPhase("teleportPlayer", t, openStart);
|
||||
}
|
||||
|
||||
updateStage(request, "finalize_open", 1.00D);
|
||||
@@ -153,7 +163,9 @@ public final class StudioOpenCoordinator {
|
||||
if (request.onDone() != null) {
|
||||
request.onDone().accept(world);
|
||||
}
|
||||
t = logStudioPhase("finalize + openVSCode", t, openStart);
|
||||
|
||||
Iris.info("[Studio timing] ===== server-side open PREP done = " + (System.currentTimeMillis() - openStart) + "ms (world " + world.getName() + ") — player-view terrain gen follows; the REAL wait is the [Studio timing] player-view lines below =====");
|
||||
future.complete(new StudioOpenResult(world, safeEntry));
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("Studio open failed for world \"" + request.worldName() + "\".", e);
|
||||
@@ -169,6 +181,12 @@ public final class StudioOpenCoordinator {
|
||||
}
|
||||
}
|
||||
|
||||
private long logStudioPhase(String phase, long t, long openStart) {
|
||||
long now = System.currentTimeMillis();
|
||||
Iris.info("[Studio timing] " + phase + " = " + (now - t) + "ms (cumulative " + (now - openStart) + "ms)");
|
||||
return now;
|
||||
}
|
||||
|
||||
private StudioCloseResult closeWorld(
|
||||
PlatformChunkGenerator provider,
|
||||
String worldName,
|
||||
|
||||
@@ -34,6 +34,7 @@ import art.arcane.iris.util.common.plugin.IrisService;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import lombok.Data;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -41,6 +42,10 @@ import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.scoreboard.DisplaySlot;
|
||||
import org.bukkit.scoreboard.Objective;
|
||||
import org.bukkit.scoreboard.Scoreboard;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -58,11 +63,42 @@ public class BoardSVC implements IrisService, BoardProvider {
|
||||
.scoreDirection(ScoreDirection.DOWN)
|
||||
.build();
|
||||
|
||||
cleanupLeakedMainScoreboard();
|
||||
|
||||
for (Player player : Iris.instance.getServer().getOnlinePlayers()) {
|
||||
J.runEntity(player, () -> updatePlayer(player));
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupLeakedMainScoreboard() {
|
||||
try {
|
||||
if (Bukkit.getScoreboardManager() == null) {
|
||||
return;
|
||||
}
|
||||
Scoreboard main = Bukkit.getScoreboardManager().getMainScoreboard();
|
||||
if (main == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Objective named = main.getObjective("board");
|
||||
if (named != null) {
|
||||
named.unregister();
|
||||
}
|
||||
|
||||
Objective sidebar = main.getObjective(DisplaySlot.SIDEBAR);
|
||||
if (sidebar != null && "board".equals(sidebar.getName())) {
|
||||
sidebar.unregister();
|
||||
}
|
||||
|
||||
Team team = main.getTeam("board");
|
||||
if (team != null) {
|
||||
team.unregister();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
boardEnabled = false;
|
||||
@@ -159,6 +195,13 @@ public class BoardSVC implements IrisService, BoardProvider {
|
||||
|
||||
public PlayerBoard(Player player) {
|
||||
this.player = player;
|
||||
try {
|
||||
if (Bukkit.getScoreboardManager() != null
|
||||
&& player.getScoreboard().equals(Bukkit.getScoreboardManager().getMainScoreboard())) {
|
||||
player.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard());
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
this.board = new Board(player, settings);
|
||||
this.lines = new ArrayList<>();
|
||||
this.cancelled = false;
|
||||
|
||||
@@ -206,11 +206,14 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
|
||||
}
|
||||
|
||||
private List<String> runDirectorTab(CommandSender sender, String alias, String[] args) {
|
||||
DirectorContext.touch(new VolmitSender(sender));
|
||||
try {
|
||||
return getDirector().tabComplete(new DirectorInvocation(new BukkitDirectorSender(sender), alias, Arrays.asList(args)));
|
||||
} catch (Throwable e) {
|
||||
Iris.warn("Director tab completion failed: " + e.getClass().getSimpleName() + " " + e.getMessage());
|
||||
return List.of();
|
||||
} finally {
|
||||
DirectorContext.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,9 @@ public final class BulkStructureImporter {
|
||||
sender.sendMessage(C.GRAY + "[single] " + keyString + " -> " + name);
|
||||
} else if (single.message() != null && single.message().startsWith("Skipped")) {
|
||||
skipped++;
|
||||
} else if (single.message() != null && single.message().contains("No loadable structure NBT")) {
|
||||
skipped++;
|
||||
sender.sendMessage(C.YELLOW + "[skip] " + keyString + ": no single-template NBT - vanilla builds this in code or from separate piece templates (imported via the templates pass); nothing to import as one structure.");
|
||||
} else {
|
||||
failed++;
|
||||
sender.sendMessage(C.RED + "[fail] " + keyString + ": " + single.message());
|
||||
@@ -113,6 +116,42 @@ public final class BulkStructureImporter {
|
||||
return new Report(total, imported, skipped, failed);
|
||||
}
|
||||
|
||||
public static Report importTemplateGroups(IrisData data, StructureImporter.Mode mode, VolmitSender sender) {
|
||||
String[][] groups = {
|
||||
{"shipwreck", "minecraft:shipwreck", "shipwreck"},
|
||||
{"ruined_portal", "minecraft:ruined_portal", "ruined_portal"},
|
||||
{"ocean_ruin", "minecraft:ocean_ruin_cold", "underwater_ruin"},
|
||||
{"nether_fossil", "minecraft:nether_fossil", "nether_fossils"},
|
||||
};
|
||||
|
||||
int imported = 0;
|
||||
int skipped = 0;
|
||||
int failed = 0;
|
||||
|
||||
sender.sendMessage(C.GREEN + "Building single-template structures from imported pieces (one variant placed per generation)...");
|
||||
for (String[] g : groups) {
|
||||
try {
|
||||
StructureImporter.Result result = StructureImporter.importTemplateGroup(data, g[0], g[1], g[2], mode);
|
||||
if (result.success()) {
|
||||
imported++;
|
||||
sender.sendMessage(C.GRAY + "[group] " + g[1] + " -> " + g[0] + " (" + result.blocks() + " variants)");
|
||||
} else if (result.message() != null && result.message().startsWith("Skipped")) {
|
||||
skipped++;
|
||||
} else {
|
||||
skipped++;
|
||||
sender.sendMessage(C.YELLOW + "[skip] " + g[0] + ": " + result.message());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
failed++;
|
||||
sender.sendMessage(C.RED + "[fail] " + g[0] + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
StructureIndexService.write(data);
|
||||
sender.sendMessage(C.GREEN + "Single-template structures: " + C.WHITE + imported + C.GREEN + " built, " + C.WHITE + skipped + C.GREEN + " skipped, " + C.WHITE + failed + C.GREEN + " failed.");
|
||||
return new Report(groups.length, imported, skipped, failed);
|
||||
}
|
||||
|
||||
public static Report importAllTemplates(IrisData data, StructureImporter.Mode mode, VolmitSender sender) {
|
||||
List<String> templateKeys;
|
||||
try {
|
||||
|
||||
@@ -24,13 +24,20 @@ import art.arcane.iris.engine.object.LegacyTileData;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.structure.Palette;
|
||||
import org.bukkit.structure.Structure;
|
||||
import org.bukkit.util.BlockVector;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
@@ -108,12 +115,29 @@ public final class StructureImporter {
|
||||
if (x < 0 || y < 0 || z < 0 || x >= w || y >= h || z >= d) {
|
||||
continue;
|
||||
}
|
||||
object.setUnsigned(x, y, z, block.getBlockData());
|
||||
BlockData blockData = block.getBlockData();
|
||||
Material mat = blockData.getMaterial();
|
||||
if (mat == Material.STRUCTURE_VOID) {
|
||||
continue;
|
||||
}
|
||||
boolean structural = mat == Material.JIGSAW || mat == Material.STRUCTURE_BLOCK;
|
||||
if (mat == Material.JIGSAW) {
|
||||
BlockData resolved = readJigsawFinalState(block);
|
||||
if (resolved == null || isAir(resolved)) {
|
||||
continue;
|
||||
}
|
||||
blockData = resolved;
|
||||
} else if (mat == Material.STRUCTURE_BLOCK) {
|
||||
continue;
|
||||
}
|
||||
object.setUnsigned(x, y, z, blockData);
|
||||
count++;
|
||||
LegacyTileData tile = captureTile(block);
|
||||
if (tile != null) {
|
||||
object.setUnsignedTile(x, y, z, tile);
|
||||
tiles++;
|
||||
if (!structural) {
|
||||
LegacyTileData tile = captureTile(block);
|
||||
if (tile != null) {
|
||||
object.setUnsignedTile(x, y, z, tile);
|
||||
tiles++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +154,33 @@ public final class StructureImporter {
|
||||
return new Result(true, "Imported " + key + " as '" + name + "' (" + count + " blocks, " + tiles + " tiles, " + w + "x" + h + "x" + d + ")", count);
|
||||
}
|
||||
|
||||
private static boolean isAir(BlockData data) {
|
||||
Material m = data.getMaterial();
|
||||
return m == Material.AIR || m == Material.CAVE_AIR || m == Material.VOID_AIR;
|
||||
}
|
||||
|
||||
private static BlockData readJigsawFinalState(BlockState block) {
|
||||
try {
|
||||
Object nbt = block.getClass().getMethod("getSnapshotNBT").invoke(block);
|
||||
if (nbt == null) {
|
||||
return null;
|
||||
}
|
||||
Object res = nbt.getClass().getMethod("getString", String.class).invoke(nbt, "final_state");
|
||||
String finalState = null;
|
||||
if (res instanceof String s) {
|
||||
finalState = s;
|
||||
} else if (res instanceof Optional<?> o && o.isPresent()) {
|
||||
finalState = String.valueOf(o.get());
|
||||
}
|
||||
if (finalState == null || finalState.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return Bukkit.createBlockData(finalState);
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static LegacyTileData captureTile(BlockState block) {
|
||||
try {
|
||||
return LegacyTileData.fromBukkit(block);
|
||||
@@ -138,6 +189,87 @@ public final class StructureImporter {
|
||||
}
|
||||
}
|
||||
|
||||
public static Result importTemplateGroup(IrisData data, String groupName, String vanillaSource, String prefix, Mode mode) {
|
||||
File objectsRoot = new File(data.getDataFolder(), "objects");
|
||||
File prefixDir = new File(objectsRoot, prefix);
|
||||
if (!prefixDir.isDirectory()) {
|
||||
return new Result(false, "No imported templates under objects/" + prefix + " for " + groupName, 0);
|
||||
}
|
||||
|
||||
List<File> iobs = new ArrayList<>();
|
||||
collectIob(prefixDir, iobs);
|
||||
if (iobs.isEmpty()) {
|
||||
return new Result(false, "No .iob templates under objects/" + prefix + " for " + groupName, 0);
|
||||
}
|
||||
|
||||
File structureFile = new File(data.getDataFolder(), "structures/" + groupName + ".json");
|
||||
if (mode == Mode.ADD_ONLY && structureFile.exists()) {
|
||||
return new Result(false, "Skipped (add-only): structure '" + groupName + "' already exists", 0);
|
||||
}
|
||||
|
||||
String rootPath = objectsRoot.getAbsolutePath() + File.separator;
|
||||
List<String> pieceNames = new ArrayList<>();
|
||||
int maxSpan = 1;
|
||||
for (File iob : iobs) {
|
||||
String rel = iob.getAbsolutePath().substring(rootPath.length()).replace(File.separatorChar, '/');
|
||||
if (rel.endsWith(".iob")) {
|
||||
rel = rel.substring(0, rel.length() - ".iob".length());
|
||||
}
|
||||
pieceNames.add(rel);
|
||||
maxSpan = Math.max(maxSpan, readObjectSpan(iob));
|
||||
}
|
||||
|
||||
try {
|
||||
for (String piece : pieceNames) {
|
||||
writeJson(new File(data.getDataFolder(), "jigsaw-pieces/" + piece + ".json"), pieceJson(piece));
|
||||
}
|
||||
writeJson(new File(data.getDataFolder(), "jigsaw-pools/" + groupName + ".json"), poolJsonMulti(pieceNames));
|
||||
writeJson(structureFile, structureJson(groupName, vanillaSource, maxSpan));
|
||||
} catch (Throwable e) {
|
||||
return new Result(false, "Failed writing group structure '" + groupName + "': " + e.getMessage(), 0);
|
||||
}
|
||||
|
||||
return new Result(true, "Built structure '" + groupName + "' from " + pieceNames.size() + " template variants", pieceNames.size());
|
||||
}
|
||||
|
||||
private static void collectIob(File dir, List<File> out) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
collectIob(f, out);
|
||||
} else if (f.getName().endsWith(".iob")) {
|
||||
out.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int readObjectSpan(File iob) {
|
||||
try (DataInputStream din = new DataInputStream(new BufferedInputStream(new FileInputStream(iob)))) {
|
||||
int w = din.readInt();
|
||||
din.readInt();
|
||||
int d = din.readInt();
|
||||
return Math.max(1, Math.max(w, d));
|
||||
} catch (Throwable e) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> poolJsonMulti(List<String> pieceNames) {
|
||||
List<Object> pieces = new ArrayList<>();
|
||||
for (String name : pieceNames) {
|
||||
Map<String, Object> entry = new LinkedHashMap<>();
|
||||
entry.put("piece", name);
|
||||
entry.put("weight", 1);
|
||||
pieces.add(entry);
|
||||
}
|
||||
Map<String, Object> pool = new LinkedHashMap<>();
|
||||
pool.put("pieces", pieces);
|
||||
return pool;
|
||||
}
|
||||
|
||||
private static Map<String, Object> pieceJson(String name) {
|
||||
Map<String, Object> piece = new LinkedHashMap<>();
|
||||
piece.put("object", name);
|
||||
|
||||
@@ -164,6 +164,7 @@ public class IrisCreator {
|
||||
throw new IrisException("You cannot invoke create() on the main thread.");
|
||||
}
|
||||
|
||||
long createStart = System.currentTimeMillis();
|
||||
reportStudioProgress(0.02D, "resolve_dimension");
|
||||
reportStudioProgress(0.08D, "resolve_dimension");
|
||||
IrisDimension d = IrisToolbelt.getDimension(dimension());
|
||||
@@ -198,6 +199,7 @@ public class IrisCreator {
|
||||
IrisWorlds.get().put(name(), dimension());
|
||||
}
|
||||
ServerConfigurator.installDataPacksIfChanged(!studio());
|
||||
Iris.info("[Studio timing] create.packPrep + datapacks = " + (System.currentTimeMillis() - createStart) + "ms (cumulative in create)");
|
||||
reportStudioProgress(0.40D, "install_datapacks");
|
||||
|
||||
PlatformChunkGenerator access = (PlatformChunkGenerator) wc.generator();
|
||||
@@ -207,12 +209,14 @@ public class IrisCreator {
|
||||
|
||||
World world;
|
||||
reportStudioProgress(0.46D, "create_world");
|
||||
long nmsStart = System.currentTimeMillis();
|
||||
try {
|
||||
WorldLifecycleCaller callerKind = benchmark ? WorldLifecycleCaller.BENCHMARK : studio() ? WorldLifecycleCaller.STUDIO : WorldLifecycleCaller.CREATE;
|
||||
WorldLifecycleRequest request = WorldLifecycleRequest.fromCreator(wc, studio(), benchmark, callerKind);
|
||||
world = J.sfut(() -> INMS.get().createWorldAsync(wc, request))
|
||||
.thenCompose(Function.identity())
|
||||
.get();
|
||||
Iris.info("[Studio timing] create.createWorldAsync (NMS bukkit world load + spawn prep) = " + (System.currentTimeMillis() - nmsStart) + "ms");
|
||||
} catch (Throwable e) {
|
||||
done.set(true);
|
||||
cancelRepeatingTask(createProgressTask);
|
||||
|
||||
@@ -222,7 +222,7 @@ public class IrisToolbelt {
|
||||
* @return the pregenerator job (already started)
|
||||
*/
|
||||
public static PregeneratorJob pregenerate(PregenTask task, PregeneratorMethod method, Engine engine) {
|
||||
return pregenerate(task, method, engine, IrisSettings.get().getPregen().useCacheByDefault);
|
||||
return pregenerate(task, method, engine, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,14 +243,9 @@ public class IrisToolbelt {
|
||||
}
|
||||
|
||||
public static boolean applyPregenPerformanceProfile() {
|
||||
IrisSettings.IrisSettingsPregen pregen = IrisSettings.get().getPregen();
|
||||
if (!pregen.isEnablePregenPerformanceProfile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IrisSettings.IrisSettingsPerformance performance = IrisSettings.get().getPerformance();
|
||||
int previousNoiseCacheSize = performance.getNoiseCacheSize();
|
||||
int targetNoiseCacheSize = Math.max(previousNoiseCacheSize, Math.max(1, pregen.getPregenProfileNoiseCacheSize()));
|
||||
int targetNoiseCacheSize = Math.max(previousNoiseCacheSize, 4_096);
|
||||
boolean fastCacheEnabledBefore = Boolean.getBoolean("iris.cache.fast");
|
||||
boolean changed = false;
|
||||
|
||||
@@ -259,15 +254,12 @@ public class IrisToolbelt {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (pregen.isPregenProfileEnableFastCache() && !fastCacheEnabledBefore) {
|
||||
if (!fastCacheEnabledBefore) {
|
||||
System.setProperty("iris.cache.fast", "true");
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (pregen.isPregenProfileLogJvmHints()
|
||||
&& pregen.isPregenProfileEnableFastCache()
|
||||
&& PREGEN_PROFILE_JVM_HINT_LOGGED.compareAndSet(false, true)
|
||||
&& !fastCacheEnabledBefore) {
|
||||
if (PREGEN_PROFILE_JVM_HINT_LOGGED.compareAndSet(false, true) && !fastCacheEnabledBefore) {
|
||||
Iris.info("For startup-wide cache-fast coverage, set JVM argument: -Diris.cache.fast=true");
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ public class IrisComplex implements DataProvider {
|
||||
private static final BlockData AIR = Material.AIR.createBlockData();
|
||||
private static final NoiseBounds ZERO_NOISE_BOUNDS = new NoiseBounds(0D, 0D);
|
||||
private static final int GRID_BOUNDS_CACHE_SIZE = 8192;
|
||||
private static final int HEIGHT_BOUNDS_GRID = 4;
|
||||
private static final ThreadLocal<GridBoundsCache> GRID_BOUNDS_CACHE = ThreadLocal.withInitial(GridBoundsCache::new);
|
||||
private RNG rng;
|
||||
private double fluidHeight;
|
||||
@@ -337,7 +338,7 @@ public class IrisComplex implements DataProvider {
|
||||
}
|
||||
|
||||
private NoiseBounds gridSampleBounds(Engine engine, IrisInterpolator interpolator, int interpolatorIndex, Set<IrisGenerator> generators, double x, double z) {
|
||||
int grid = IrisSettings.get().getPerformance().getHeightBoundsInterpolationGrid();
|
||||
int grid = HEIGHT_BOUNDS_GRID;
|
||||
if (grid <= 1) {
|
||||
return sampleBoundsRaw(engine, interpolator, generators, x, z);
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ public class IrisEngine implements Engine {
|
||||
.filter(File::exists)
|
||||
.filter(File::isDirectory)
|
||||
.toArray(File[]::new);
|
||||
hash32.complete(IO.hashRecursive(roots));
|
||||
hash32.complete(IO.hashRecursiveMeta(roots));
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
Iris.error("FAILED TO SETUP ENGINE!");
|
||||
|
||||
@@ -21,9 +21,7 @@ package art.arcane.iris.engine;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.gui.PregeneratorJob;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.service.ExternalDataSVC;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.data.cache.Cache;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
@@ -699,48 +697,6 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
cleanup.remove(key);
|
||||
getEngine().cleanupMantleChunk(cX, cZ);
|
||||
}, Math.max(IrisSettings.get().getPerformance().mantleCleanupDelay * 50L, 0), TimeUnit.MILLISECONDS));
|
||||
|
||||
if (generated) {
|
||||
//INMS.get().injectBiomesFromMantle(e, getMantle());
|
||||
|
||||
if (!IrisSettings.get().getGenerator().earlyCustomBlocks) return;
|
||||
if (isPregenActiveForThisWorld()) return;
|
||||
|
||||
World world = e.getWorld();
|
||||
int chunkX = e.getX();
|
||||
int chunkZ = e.getZ();
|
||||
int minY = getTarget().getWorld().minHeight();
|
||||
int delay = RNG.r.i(20, 60);
|
||||
Iris.tickets.addTicket(e);
|
||||
|
||||
Runnable applyCustomBlocks = () -> {
|
||||
if (J.isFolia() && (!world.isChunkLoaded(chunkX, chunkZ) || !Chunks.isSafe(world, chunkX, chunkZ))) {
|
||||
Iris.tickets.removeTicket(e);
|
||||
return;
|
||||
}
|
||||
|
||||
Chunk chunkRef = world.getChunkAt(chunkX, chunkZ);
|
||||
MantleChunk<Matter> mantleChunk = getMantle().getChunk(chunkRef).use();
|
||||
try {
|
||||
mantleChunk.raiseFlagUnchecked(MantleFlag.CUSTOM, () -> {
|
||||
mantleChunk.iterate(Identifier.class, (x, y, z, v) -> {
|
||||
Iris.service(ExternalDataSVC.class).processUpdate(getEngine(), chunkRef.getBlock(x & 15, y + minY, z & 15), v);
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
mantleChunk.release();
|
||||
Iris.tickets.removeTicket(e);
|
||||
}
|
||||
};
|
||||
|
||||
if (J.isFolia()) {
|
||||
if (!J.runRegion(world, chunkX, chunkZ, applyCustomBlocks, delay)) {
|
||||
Iris.tickets.removeTicket(e);
|
||||
}
|
||||
} else {
|
||||
J.s(applyCustomBlocks, delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -777,19 +733,17 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
|
||||
@Override
|
||||
public void teleportAsync(PlayerTeleportEvent e) {
|
||||
if (IrisSettings.get().getWorld().getAsyncTeleport().isEnabled()) {
|
||||
e.setCancelled(true);
|
||||
warmupAreaAsync(e.getPlayer(), e.getTo(), () -> J.runEntity(e.getPlayer(), () -> {
|
||||
ignoreTP.set(true);
|
||||
e.getPlayer().teleport(e.getTo(), e.getCause());
|
||||
ignoreTP.set(false);
|
||||
}));
|
||||
}
|
||||
e.setCancelled(true);
|
||||
warmupAreaAsync(e.getPlayer(), e.getTo(), () -> J.runEntity(e.getPlayer(), () -> {
|
||||
ignoreTP.set(true);
|
||||
e.getPlayer().teleport(e.getTo(), e.getCause());
|
||||
ignoreTP.set(false);
|
||||
}));
|
||||
}
|
||||
|
||||
private void warmupAreaAsync(Player player, Location to, Runnable r) {
|
||||
J.a(() -> {
|
||||
int viewDistance = IrisSettings.get().getWorld().getAsyncTeleport().getLoadViewDistance();
|
||||
int viewDistance = 2;
|
||||
KList<Future<Chunk>> futures = new KList<>();
|
||||
for (int i = -viewDistance; i <= viewDistance; i++) {
|
||||
for (int j = -viewDistance; j <= viewDistance; j++) {
|
||||
@@ -805,7 +759,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
-> PaperLib.getChunkAtAsync(to.getWorld(),
|
||||
(to.getBlockX() >> 4) + finalI,
|
||||
(to.getBlockZ() >> 4) + finalJ,
|
||||
true, IrisSettings.get().getWorld().getAsyncTeleport().isUrgent()).get()));
|
||||
true, false).get()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1012,6 +1012,10 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
Locator.poi(type).find(p, teleport, "POI " + type);
|
||||
}
|
||||
|
||||
default void gotoStructure(String key, Player player, boolean teleport) {
|
||||
Locator.structure(key).find(player, teleport, "Structure " + key);
|
||||
}
|
||||
|
||||
private static boolean containsObjectPlacement(KList<IrisObjectPlacement> placements, String normalizedObjectKey) {
|
||||
if (placements == null || placements.isEmpty() || normalizedObjectKey.isBlank()) {
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* 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.framework;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.data.cache.Cache;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisStructure;
|
||||
import art.arcane.iris.engine.object.IrisStructurePlacement;
|
||||
import art.arcane.iris.engine.object.StructureDistribution;
|
||||
import art.arcane.iris.engine.object.StructurePlacementRoute;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Finds where IRIS_PLACED structures generate. A structure key matches either the iris
|
||||
* structure's own load key or its {@code vanillaSource} (so a vanilla key like
|
||||
* {@code minecraft:ancient_city} resolves to the imported {@code minecraft_ancient_city}).
|
||||
*
|
||||
* A per-pack index of placed keys is cached so {@code /locate} and {@code /iris goto} skip
|
||||
* the grid scan entirely for keys that are not placed (keeping them as fast as vanilla).
|
||||
*
|
||||
* For RANDOM_SPREAD placements (the common, vanilla-style spaced grid) the locator jumps
|
||||
* straight to the single candidate chunk in each spacing-sized grid cell rather than testing
|
||||
* every chunk -- roughly a {@code spacing^2} reduction in placement checks, matching vanilla
|
||||
* locate performance. Non-grid distributions (DENSITY / CONCENTRIC_RINGS) fall back to the
|
||||
* exhaustive per-chunk ring scan.
|
||||
*/
|
||||
public final class IrisStructureLocator {
|
||||
private static final Map<IrisData, PlacementIndex> INDEX_CACHE = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
|
||||
private IrisStructureLocator() {
|
||||
}
|
||||
|
||||
/** Iris structure load keys that are referenced by any IRIS_PLACED placement (for autocomplete). */
|
||||
public static Set<String> placedKeys(Engine engine) {
|
||||
if (engine == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return index(engine).loadKeys;
|
||||
}
|
||||
|
||||
public static boolean isPlaced(Engine engine, String key) {
|
||||
if (engine == null || key == null || key.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
PlacementIndex idx = index(engine);
|
||||
return idx.loadKeys.contains(key) || idx.vanillaSources.contains(key);
|
||||
}
|
||||
|
||||
public static boolean startsInChunk(Engine engine, String key, int cx, int cz) {
|
||||
if (!isPlaced(engine, key)) {
|
||||
return false;
|
||||
}
|
||||
long seed = engine.getSeedManager().getMantle();
|
||||
RNG rng = new RNG(Cache.key(cx, cz) + seed);
|
||||
for (IrisStructurePlacement placement : placementsAt(engine, cx, cz)) {
|
||||
if (placement.getRoute() == StructurePlacementRoute.NATIVE_AT_POINT) {
|
||||
continue;
|
||||
}
|
||||
if (!matches(placement, key, engine.getData())) {
|
||||
continue;
|
||||
}
|
||||
if (StructurePlacementGrid.startsInChunk(placement, cx, cz, seed, rng)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int[] locate(Engine engine, String key, int fromBlockX, int fromBlockZ, int maxRadiusChunks) {
|
||||
if (!isPlaced(engine, key)) {
|
||||
return null;
|
||||
}
|
||||
int max = Math.max(1, Math.min(maxRadiusChunks, 2048));
|
||||
|
||||
List<int[]> gridParams = collectRandomSpreadParams(engine, key);
|
||||
if (gridParams == null || gridParams.isEmpty()) {
|
||||
// A non-grid distribution (DENSITY / CONCENTRIC_RINGS) matches this key, or no grid
|
||||
// params could be resolved; fall back to the exhaustive per-chunk scan.
|
||||
return locateByChunkScan(engine, key, fromBlockX, fromBlockZ, max);
|
||||
}
|
||||
|
||||
long seed = engine.getSeedManager().getMantle();
|
||||
int pcx = fromBlockX >> 4;
|
||||
int pcz = fromBlockZ >> 4;
|
||||
long maxDistSq = (long) max * (long) max;
|
||||
long bestDistSq = Long.MAX_VALUE;
|
||||
int[] best = null;
|
||||
|
||||
for (int[] params : gridParams) {
|
||||
int spacing = Math.max(1, params[0]);
|
||||
int separation = params[1];
|
||||
int salt = params[2];
|
||||
int centerCellX = Math.floorDiv(pcx, spacing);
|
||||
int centerCellZ = Math.floorDiv(pcz, spacing);
|
||||
int cellRadius = (max / spacing) + 2;
|
||||
int firstHitRing = -1;
|
||||
|
||||
for (int r = 0; r <= cellRadius; r++) {
|
||||
if (firstHitRing >= 0 && r > firstHitRing + 1) {
|
||||
break;
|
||||
}
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
for (int dz = -r; dz <= r; dz++) {
|
||||
if (Math.max(Math.abs(dx), Math.abs(dz)) != r) {
|
||||
continue;
|
||||
}
|
||||
int[] candidate = StructurePlacementGrid.randomSpreadCellChunk(
|
||||
centerCellX + dx, centerCellZ + dz, spacing, separation, salt, seed);
|
||||
int cx = candidate[0];
|
||||
int cz = candidate[1];
|
||||
long ddx = (long) cx - pcx;
|
||||
long ddz = (long) cz - pcz;
|
||||
long distSq = ddx * ddx + ddz * ddz;
|
||||
if (distSq > maxDistSq || distSq >= bestDistSq) {
|
||||
continue;
|
||||
}
|
||||
if (startsInChunk(engine, key, cx, cz)) {
|
||||
bestDistSq = distSq;
|
||||
best = new int[]{cx << 4, structureY(engine, key, cx, cz), cz << 4};
|
||||
if (firstHitRing < 0) {
|
||||
firstHitRing = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
private static int[] locateByChunkScan(Engine engine, String key, int fromBlockX, int fromBlockZ, int max) {
|
||||
int pcx = fromBlockX >> 4;
|
||||
int pcz = fromBlockZ >> 4;
|
||||
for (int r = 0; r <= max; r++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
for (int dz = -r; dz <= r; dz++) {
|
||||
if (Math.max(Math.abs(dx), Math.abs(dz)) != r) {
|
||||
continue;
|
||||
}
|
||||
int cx = pcx + dx;
|
||||
int cz = pcz + dz;
|
||||
if (startsInChunk(engine, key, cx, cz)) {
|
||||
return new int[]{cx << 4, structureY(engine, key, cx, cz), cz << 4};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the distinct {spacing, separation, salt} tuples of every RANDOM_SPREAD placement that
|
||||
* matches the key across the dimension, its regions and biomes. Returns {@code null} if any
|
||||
* matching placement uses a non-grid distribution (DENSITY / CONCENTRIC_RINGS), signalling the
|
||||
* caller to fall back to the exhaustive per-chunk scan.
|
||||
*/
|
||||
private static List<int[]> collectRandomSpreadParams(Engine engine, String key) {
|
||||
IrisData data = engine.getData();
|
||||
List<int[]> params = new ArrayList<>();
|
||||
Set<Long> seen = new LinkedHashSet<>();
|
||||
|
||||
KList<IrisStructurePlacement> all = new KList<>();
|
||||
all.addAll(engine.getDimension().getStructures());
|
||||
for (IrisRegion region : engine.getDimension().getAllRegions(engine)) {
|
||||
all.addAll(region.getStructures());
|
||||
}
|
||||
for (IrisBiome biome : engine.getDimension().getAllBiomes(engine)) {
|
||||
all.addAll(biome.getStructures());
|
||||
}
|
||||
|
||||
for (IrisStructurePlacement placement : all) {
|
||||
if (placement == null || placement.getRoute() == StructurePlacementRoute.NATIVE_AT_POINT) {
|
||||
continue;
|
||||
}
|
||||
if (!matches(placement, key, data)) {
|
||||
continue;
|
||||
}
|
||||
if (placement.getDistribution() != StructureDistribution.RANDOM_SPREAD) {
|
||||
return null;
|
||||
}
|
||||
int spacing = Math.max(1, placement.getSpacing());
|
||||
int separation = placement.getSeparation();
|
||||
int salt = placement.getSalt();
|
||||
long packed = ((long) spacing << 42) ^ ((long) (separation & 0x1FFFFF) << 21) ^ (salt & 0x1FFFFFL);
|
||||
if (seen.add(packed)) {
|
||||
params.add(new int[]{spacing, separation, salt});
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
private static KList<IrisStructurePlacement> placementsAt(Engine engine, int cx, int cz) {
|
||||
int bx = 8 + (cx << 4);
|
||||
int bz = 8 + (cz << 4);
|
||||
IrisBiome biome = engine.getComplex().getTrueBiomeStream().get(bx, bz);
|
||||
IrisRegion region = engine.getComplex().getRegionStream().get(bx, bz);
|
||||
KList<IrisStructurePlacement> placements = new KList<>();
|
||||
if (biome != null) {
|
||||
placements.addAll(biome.getStructures());
|
||||
}
|
||||
if (region != null) {
|
||||
placements.addAll(region.getStructures());
|
||||
}
|
||||
placements.addAll(engine.getDimension().getStructures());
|
||||
return placements;
|
||||
}
|
||||
|
||||
private static boolean matches(IrisStructurePlacement placement, String key, IrisData data) {
|
||||
for (String structureKey : placement.getStructures()) {
|
||||
if (structureKey == null) {
|
||||
continue;
|
||||
}
|
||||
if (structureKey.equalsIgnoreCase(key)) {
|
||||
return true;
|
||||
}
|
||||
IrisStructure structure = IrisData.loadAnyStructure(structureKey, data);
|
||||
if (structure != null) {
|
||||
String vanillaSource = structure.getVanillaSource();
|
||||
if (vanillaSource != null && !vanillaSource.isEmpty() && vanillaSource.equalsIgnoreCase(key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int structureY(Engine engine, String key, int cx, int cz) {
|
||||
IrisData data = engine.getData();
|
||||
int bx = 8 + (cx << 4);
|
||||
int bz = 8 + (cz << 4);
|
||||
for (IrisStructurePlacement placement : placementsAt(engine, cx, cz)) {
|
||||
if (placement.getRoute() == StructurePlacementRoute.NATIVE_AT_POINT) {
|
||||
continue;
|
||||
}
|
||||
if (!matches(placement, key, data)) {
|
||||
continue;
|
||||
}
|
||||
if (placement.isUnderground()) {
|
||||
int lo = Math.max(engine.getMinHeight() + 1, Math.min(placement.getMinHeight(), placement.getMaxHeight()));
|
||||
int hi = Math.min(engine.getMinHeight() + engine.getHeight() - 1, Math.max(placement.getMinHeight(), placement.getMaxHeight()));
|
||||
return (lo + hi) / 2;
|
||||
}
|
||||
}
|
||||
return engine.getHeight(bx, bz) + engine.getMinHeight();
|
||||
}
|
||||
|
||||
private static PlacementIndex index(Engine engine) {
|
||||
IrisData data = engine.getData();
|
||||
PlacementIndex cached = INDEX_CACHE.get(data);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
PlacementIndex built = build(engine, data);
|
||||
INDEX_CACHE.put(data, built);
|
||||
return built;
|
||||
}
|
||||
|
||||
private static PlacementIndex build(Engine engine, IrisData data) {
|
||||
Set<String> loadKeys = new LinkedHashSet<>();
|
||||
Set<String> vanillaSources = new LinkedHashSet<>();
|
||||
collect(engine.getDimension().getStructures(), data, loadKeys, vanillaSources);
|
||||
for (IrisRegion region : engine.getDimension().getAllRegions(engine)) {
|
||||
collect(region.getStructures(), data, loadKeys, vanillaSources);
|
||||
}
|
||||
for (IrisBiome biome : engine.getDimension().getAllBiomes(engine)) {
|
||||
collect(biome.getStructures(), data, loadKeys, vanillaSources);
|
||||
}
|
||||
return new PlacementIndex(loadKeys, vanillaSources);
|
||||
}
|
||||
|
||||
private static void collect(KList<IrisStructurePlacement> placements, IrisData data, Set<String> loadKeys, Set<String> vanillaSources) {
|
||||
if (placements == null) {
|
||||
return;
|
||||
}
|
||||
for (IrisStructurePlacement placement : placements) {
|
||||
if (placement == null || placement.getRoute() == StructurePlacementRoute.NATIVE_AT_POINT) {
|
||||
continue;
|
||||
}
|
||||
for (String structureKey : placement.getStructures()) {
|
||||
if (structureKey == null || structureKey.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
loadKeys.add(structureKey);
|
||||
IrisStructure structure = IrisData.loadAnyStructure(structureKey, data);
|
||||
if (structure != null) {
|
||||
String vanillaSource = structure.getVanillaSource();
|
||||
if (vanillaSource != null && !vanillaSource.isEmpty()) {
|
||||
vanillaSources.add(vanillaSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PlacementIndex {
|
||||
private final Set<String> loadKeys;
|
||||
private final Set<String> vanillaSources;
|
||||
|
||||
private PlacementIndex(Set<String> loadKeys, Set<String> vanillaSources) {
|
||||
this.loadKeys = loadKeys;
|
||||
this.vanillaSources = vanillaSources;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,10 @@ public interface Locator<T> {
|
||||
return (e, c) -> e.getSurfaceBiome((c.getX() << 4) + 8, (c.getZ() << 4) + 8).getLoadKey().equals(loadKey);
|
||||
}
|
||||
|
||||
static Locator<art.arcane.iris.engine.object.IrisStructure> structure(String key) {
|
||||
return (e, c) -> IrisStructureLocator.startsInChunk(e, key, c.getX(), c.getZ());
|
||||
}
|
||||
|
||||
static Locator<BlockPos> poi(String type) {
|
||||
return (e, c) -> {
|
||||
Set<Pair<String, BlockPos>> pos = e.getPOIsAt((c.getX() << 4) + 8, (c.getZ() << 4) + 8);
|
||||
|
||||
@@ -35,14 +35,25 @@ public final class StructurePlacementGrid {
|
||||
|
||||
public static boolean randomSpreadStart(int cx, int cz, int spacing, int separation, int salt, long seed) {
|
||||
int sp = Math.max(1, spacing);
|
||||
int sep = Math.max(0, Math.min(separation, sp - 1));
|
||||
int cellX = Math.floorDiv(cx, sp);
|
||||
int cellZ = Math.floorDiv(cz, sp);
|
||||
int[] start = randomSpreadCellChunk(cellX, cellZ, spacing, separation, salt, seed);
|
||||
return start[0] == cx && start[1] == cz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the single {chunkX, chunkZ} that a RANDOM_SPREAD placement occupies within the given
|
||||
* grid cell. This is the inverse of {@link #randomSpreadStart} and uses the identical formula, so
|
||||
* a locator can jump straight to the one candidate per cell instead of testing every chunk.
|
||||
*/
|
||||
public static int[] randomSpreadCellChunk(int cellX, int cellZ, int spacing, int separation, int salt, long seed) {
|
||||
int sp = Math.max(1, spacing);
|
||||
int sep = Math.max(0, Math.min(separation, sp - 1));
|
||||
RNG r = new RNG(mix(seed, cellX, cellZ, salt));
|
||||
int range = Math.max(1, sp - sep);
|
||||
int startCx = cellX * sp + r.i(0, range - 1);
|
||||
int startCz = cellZ * sp + r.i(0, range - 1);
|
||||
return startCx == cx && startCz == cz;
|
||||
return new int[]{startCx, startCz};
|
||||
}
|
||||
|
||||
private static boolean concentricRingsStart(int cx, int cz, IrisStructurePlacement placement, long seed) {
|
||||
|
||||
@@ -201,6 +201,25 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void clearBlock(int x, int y, int z) {
|
||||
if (y < 0 || y >= mantle.getWorldHeight()) {
|
||||
return;
|
||||
}
|
||||
MantleChunk<Matter> chunk = acquireChunk(x >> 4, z >> 4);
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
int section = y >> 4;
|
||||
if (!chunk.exists(section)) {
|
||||
return;
|
||||
}
|
||||
Matter matter = chunk.get(section);
|
||||
if (matter == null) {
|
||||
return;
|
||||
}
|
||||
matter.<BlockData>slice(BlockData.class).set(x & 15, y & 15, z & 15, null);
|
||||
}
|
||||
|
||||
public <T> T getData(int x, int y, int z, Class<T> type) {
|
||||
int cx = x >> 4;
|
||||
int cz = z >> 4;
|
||||
|
||||
@@ -50,7 +50,7 @@ public interface MatterGenerator {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean useMulticore = multicore || IrisSettings.get().getGenerator().isUseMulticoreMantle();
|
||||
boolean useMulticore = multicore;
|
||||
String threadName = Thread.currentThread().getName();
|
||||
boolean regenThread = threadName.startsWith("Iris-Regen-");
|
||||
boolean traceRegen = regenThread && IrisSettings.get().getGeneral().isDebug();
|
||||
|
||||
@@ -69,6 +69,7 @@ public class IrisCaveCarver3D {
|
||||
private final double warpStrength;
|
||||
private final boolean hasWarp;
|
||||
private final boolean hasModules;
|
||||
private final int warpResolution;
|
||||
|
||||
public IrisCaveCarver3D(Engine engine, IrisCaveProfile profile) {
|
||||
this.engine = engine;
|
||||
@@ -89,6 +90,7 @@ public class IrisCaveCarver3D {
|
||||
this.detailWeight = profile.getDetailWeight();
|
||||
this.warpStrength = profile.getWarpStrength();
|
||||
this.hasWarp = this.warpStrength > 0D;
|
||||
this.warpResolution = 2;
|
||||
|
||||
double weight = Math.abs(baseWeight) + Math.abs(detailWeight);
|
||||
int index = 0;
|
||||
@@ -1589,9 +1591,17 @@ public class IrisCaveCarver3D {
|
||||
return density <= thresholdLimit;
|
||||
}
|
||||
|
||||
private int snapWarp(int c) {
|
||||
int g = warpResolution;
|
||||
return g <= 1 ? c : Math.floorDiv(c, g) * g;
|
||||
}
|
||||
|
||||
private boolean classifyDensityPointWarpOnly(int x, int y, int z, double thresholdLimit) {
|
||||
double warpA = warpDensity.noiseFastSigned3D(x, y, z);
|
||||
double warpB = warpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
|
||||
int sx = snapWarp(x);
|
||||
int sy = snapWarp(y);
|
||||
int sz = snapWarp(z);
|
||||
double warpA = warpDensity.noiseFastSigned3D(sx, sy, sz);
|
||||
double warpB = warpDensity.noiseFastSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D);
|
||||
double warpedX = x + (warpA * warpStrength);
|
||||
double warpedY = y + (warpB * warpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
|
||||
@@ -1621,8 +1631,11 @@ public class IrisCaveCarver3D {
|
||||
return classifyDensityPointWarpOnly(x, y, z, thresholdLimit);
|
||||
}
|
||||
|
||||
double warpA = warpDensity.noiseFastSigned3D(x, y, z);
|
||||
double warpB = warpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
|
||||
int sx = snapWarp(x);
|
||||
int sy = snapWarp(y);
|
||||
int sz = snapWarp(z);
|
||||
double warpA = warpDensity.noiseFastSigned3D(sx, sy, sz);
|
||||
double warpB = warpDensity.noiseFastSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D);
|
||||
double warpedX = x + (warpA * warpStrength);
|
||||
double warpedY = y + (warpB * warpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
|
||||
@@ -1716,8 +1729,11 @@ public class IrisCaveCarver3D {
|
||||
return true;
|
||||
}
|
||||
|
||||
double warpA = warpDensity.noiseFastSigned3D(x, y, z);
|
||||
double warpB = warpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
|
||||
int sx = snapWarp(x);
|
||||
int sy = snapWarp(y);
|
||||
int sz = snapWarp(z);
|
||||
double warpA = warpDensity.noiseFastSigned3D(sx, sy, sz);
|
||||
double warpB = warpDensity.noiseFastSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D);
|
||||
double warpedX = x + (warpA * warpStrength);
|
||||
double warpedY = y + (warpB * warpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
|
||||
@@ -1844,8 +1860,11 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
|
||||
private double sampleDensityWarpOnly(int x, int y, int z) {
|
||||
double warpA = warpDensity.noiseFastSigned3D(x, y, z);
|
||||
double warpB = warpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
|
||||
int sx = snapWarp(x);
|
||||
int sy = snapWarp(y);
|
||||
int sz = snapWarp(z);
|
||||
double warpA = warpDensity.noiseFastSigned3D(sx, sy, sz);
|
||||
double warpB = warpDensity.noiseFastSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D);
|
||||
double warpedX = x + (warpA * warpStrength);
|
||||
double warpedY = y + (warpB * warpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
|
||||
@@ -1866,8 +1885,11 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
|
||||
private double sampleDensityWarpModules(int x, int y, int z, ModuleState[] localModules, int activeModuleCount) {
|
||||
double warpA = warpDensity.noiseFastSigned3D(x, y, z);
|
||||
double warpB = warpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
|
||||
int sx = snapWarp(x);
|
||||
int sy = snapWarp(y);
|
||||
int sz = snapWarp(z);
|
||||
double warpA = warpDensity.noiseFastSigned3D(sx, sy, sz);
|
||||
double warpB = warpDensity.noiseFastSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D);
|
||||
double warpedX = x + (warpA * warpStrength);
|
||||
double warpedY = y + (warpB * warpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
|
||||
|
||||
+120
-73
@@ -38,23 +38,22 @@ import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisStructure;
|
||||
import art.arcane.iris.engine.object.IrisStructurePlacement;
|
||||
import art.arcane.iris.engine.object.StructurePlacementRoute;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
@ComponentFlag(ReservedFlag.JIGSAW)
|
||||
public class IrisStructureComponent extends IrisMantleComponent {
|
||||
private static final long MAX_BORE_VOLUME = 6_000_000L;
|
||||
private static final long MAX_OVERBORE_VOLUME = 48_000_000L;
|
||||
private static final BlockData CARVE_AIR = B.get("CAVE_AIR");
|
||||
private static final MatterCavern CARVE_CAVERN = new MatterCavern(true, "", (byte) 0);
|
||||
|
||||
public IrisStructureComponent(EngineMantle engineMantle) {
|
||||
super(engineMantle, ReservedFlag.JIGSAW, 1);
|
||||
super(engineMantle, ReservedFlag.JIGSAW, 3);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,6 +92,12 @@ public class IrisStructureComponent extends IrisMantleComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean trace = IrisSettings.get().getGeneral().isDebug();
|
||||
if (trace) {
|
||||
Iris.info("[StructTrace] ORIGIN chunk=" + cx + "," + cz + " structures=" + placement.getStructures()
|
||||
+ " underground=" + placement.isUnderground() + " band=" + placement.getMinHeight() + ".." + placement.getMaxHeight());
|
||||
}
|
||||
|
||||
int sx = (cx << 4) + rng.i(0, 15);
|
||||
int sz = (cz << 4) + rng.i(0, 15);
|
||||
int baseY;
|
||||
@@ -102,6 +107,10 @@ public class IrisStructureComponent extends IrisMantleComponent {
|
||||
int bandMin = Math.max(worldMinY, Math.min(placement.getMinHeight(), placement.getMaxHeight()));
|
||||
int bandMax = Math.min(worldMaxY, Math.max(placement.getMinHeight(), placement.getMaxHeight()));
|
||||
if (bandMin > bandMax) {
|
||||
if (trace) {
|
||||
Iris.info("[StructTrace] BAIL band-inverted chunk=" + cx + "," + cz + " bandMin=" + bandMin + " bandMax=" + bandMax
|
||||
+ " worldMinY=" + worldMinY + " worldMaxY=" + worldMaxY);
|
||||
}
|
||||
return;
|
||||
}
|
||||
baseY = bandMin == bandMax ? bandMin : rng.i(bandMin, bandMax);
|
||||
@@ -116,14 +125,24 @@ public class IrisStructureComponent extends IrisMantleComponent {
|
||||
String key = placement.getStructures().get(rng.i(0, placement.getStructures().size() - 1));
|
||||
IrisStructure structure = art.arcane.iris.core.loader.IrisData.loadAnyStructure(key, getData());
|
||||
if (structure == null) {
|
||||
if (trace) {
|
||||
Iris.info("[StructTrace] BAIL structure-load-null chunk=" + cx + "," + cz + " key=" + key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
StructureAssembler assembler = new StructureAssembler(getData(), structure, sx, baseY, sz);
|
||||
KList<PlacedStructurePiece> pieces = assembler.assemble(rng);
|
||||
if (pieces == null || pieces.isEmpty()) {
|
||||
if (trace) {
|
||||
Iris.info("[StructTrace] BAIL no-pieces chunk=" + cx + "," + cz + " key=" + key + " baseY=" + baseY
|
||||
+ " pieces=" + (pieces == null ? "null" : "empty"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (trace) {
|
||||
Iris.info("[StructTrace] ASSEMBLED chunk=" + cx + "," + cz + " key=" + key + " baseY=" + baseY + " pieces=" + pieces.size());
|
||||
}
|
||||
|
||||
if (placement.isOverbore()) {
|
||||
overboreStructure(writer, pieces, placement.getOverboreRadius(), placement.getOverboreHeight(), placement.getOverboreFloor());
|
||||
@@ -132,7 +151,13 @@ public class IrisStructureComponent extends IrisMantleComponent {
|
||||
}
|
||||
|
||||
ObjectPlaceMode mode = structure.getPlaceMode();
|
||||
if (placement.isUnderground() || mode == ObjectPlaceMode.STRUCTURE_PIECE || mode == ObjectPlaceMode.FLOATING) {
|
||||
if (placement.isUnderground()) {
|
||||
ObjectPlaceMode undergroundMode = (mode == ObjectPlaceMode.ORGANIC_STILT || mode == ObjectPlaceMode.CEILING_HANG)
|
||||
? mode : ObjectPlaceMode.STRUCTURE_PIECE;
|
||||
for (PlacedStructurePiece p : pieces) {
|
||||
placeObject(writer, structure, p, undergroundMode, p.getY(), rng);
|
||||
}
|
||||
} else if (mode == ObjectPlaceMode.STRUCTURE_PIECE || mode == ObjectPlaceMode.FLOATING) {
|
||||
for (PlacedStructurePiece p : pieces) {
|
||||
placeObject(writer, structure, p, ObjectPlaceMode.STRUCTURE_PIECE, p.getY(), rng);
|
||||
}
|
||||
@@ -174,10 +199,11 @@ public class IrisStructureComponent extends IrisMantleComponent {
|
||||
Iris.warn("Skipping structure bore of " + volume + " blocks (cap " + MAX_BORE_VOLUME + "); use a smaller structure or larger spacing.");
|
||||
return;
|
||||
}
|
||||
int mantleOffset = getEngineMantle().getEngine().getMinHeight();
|
||||
for (int bx = minX; bx <= maxX; bx++) {
|
||||
for (int by = minY; by <= maxY; by++) {
|
||||
for (int bz = minZ; bz <= maxZ; bz++) {
|
||||
writer.set(bx, by, bz, CARVE_AIR);
|
||||
writer.setDataIfAbsent(bx, by - mantleOffset, bz, CARVE_CAVERN);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,82 +214,99 @@ public class IrisStructureComponent extends IrisMantleComponent {
|
||||
if (bounds == null) {
|
||||
return;
|
||||
}
|
||||
int r = Math.max(0, radius);
|
||||
int boxMinX = bounds[0];
|
||||
int boxMinZ = bounds[2];
|
||||
int boxMaxX = bounds[3];
|
||||
int boxMaxZ = bounds[5];
|
||||
int margin = Math.max(1, radius);
|
||||
int head = Math.max(0, ceiling);
|
||||
int floorCut = Math.max(0, floorDepth);
|
||||
int mantleOffset = getEngineMantle().getEngine().getMinHeight();
|
||||
int worldMin = getEngineMantle().getEngine().getMinHeight() + 1;
|
||||
int worldMax = getEngineMantle().getEngine().getMinHeight() + getEngineMantle().getEngine().getHeight() - 1;
|
||||
int floorY = Math.max(worldMin, bounds[1] - Math.max(0, floorDepth));
|
||||
int apexY = Math.min(worldMax, bounds[4] + Math.max(0, ceiling));
|
||||
if (apexY < floorY) {
|
||||
return;
|
||||
}
|
||||
int expMinX = boxMinX - r;
|
||||
int expMaxX = boxMaxX + r;
|
||||
int expMinZ = boxMinZ - r;
|
||||
int expMaxZ = boxMaxZ + r;
|
||||
long volume = (long) (expMaxX - expMinX + 1) * (long) (apexY - floorY + 1) * (long) (expMaxZ - expMinZ + 1);
|
||||
if (volume > MAX_OVERBORE_VOLUME) {
|
||||
Iris.warn("Skipping structure overbore of " + volume + " blocks (cap " + MAX_OVERBORE_VOLUME + "); reduce overboreRadius or use larger spacing.");
|
||||
return;
|
||||
}
|
||||
int span = apexY - floorY;
|
||||
double rr = r <= 0 ? 1.0 : (double) r;
|
||||
|
||||
RNG noiseRng = new RNG(seed() + Cache.key(boxMinX, boxMinZ));
|
||||
CNG outlineNoise = CNG.signature(noiseRng);
|
||||
CNG ceilNoise = CNG.signature(noiseRng.nextParallelRNG(0x51E10));
|
||||
CNG floorNoise = CNG.signature(noiseRng.nextParallelRNG(0xF1009));
|
||||
double outlineFreq = 0.045;
|
||||
double ceilFreq = 0.055;
|
||||
double floorFreq = 0.07;
|
||||
int floorAmp = Math.min(5, Math.max(1, span / 8));
|
||||
double ceilLump = Math.min(4.0, span * 0.15);
|
||||
double freq = 0.07;
|
||||
double rollFreq = 0.03;
|
||||
double clearMax = 1.45;
|
||||
double reachSide = margin;
|
||||
double reachUp = head < 1 ? 1.0 : head;
|
||||
double reachDown = floorCut < 1 ? 1.0 : floorCut;
|
||||
double upReachMin = 0.4;
|
||||
double upReachSpan = 1.4;
|
||||
int sideExt = (int) Math.ceil(reachSide * clearMax);
|
||||
int upExt = (int) Math.ceil(reachUp * (upReachMin + upReachSpan) * clearMax);
|
||||
|
||||
long work = 0L;
|
||||
for (PlacedStructurePiece p : pieces) {
|
||||
long wx = (long) (p.getMaxX() - p.getMinX() + 1) + 2L * sideExt;
|
||||
long wz = (long) (p.getMaxZ() - p.getMinZ() + 1) + 2L * sideExt;
|
||||
long wy = (long) (p.getMaxY() - p.getMinY() + 1) + upExt + floorCut;
|
||||
work += wx * wy * wz;
|
||||
}
|
||||
if (work > MAX_OVERBORE_VOLUME) {
|
||||
Iris.warn("Skipping structure overbore of " + work + " blocks (cap " + MAX_OVERBORE_VOLUME + "); reduce overboreRadius/overboreHeight or use larger spacing.");
|
||||
return;
|
||||
}
|
||||
|
||||
RNG noiseRng = new RNG(seed() + Cache.key(bounds[0], bounds[2]));
|
||||
CNG blob = CNG.signature(noiseRng);
|
||||
CNG roll = CNG.signature(noiseRng.nextParallelRNG(0x2A17));
|
||||
|
||||
if (IrisSettings.get().getGeneral().isDebug()) {
|
||||
Iris.info("Overbore carving cavern: box=[" + boxMinX + "," + floorY + "," + boxMinZ + " -> " + boxMaxX + "," + apexY + "," + boxMaxZ + "] radius=" + r + " volume=" + volume);
|
||||
Iris.info("Overbore carving organic cavern: pieces=" + pieces.size() + " margin=" + margin + " head=" + head + " floorCut=" + floorCut + " work=" + work);
|
||||
}
|
||||
|
||||
BlockData air = CARVE_AIR;
|
||||
for (int bx = expMinX; bx <= expMaxX; bx++) {
|
||||
int dx = bx < boxMinX ? boxMinX - bx : bx > boxMaxX ? bx - boxMaxX : 0;
|
||||
for (int bz = expMinZ; bz <= expMaxZ; bz++) {
|
||||
int dz = bz < boxMinZ ? boxMinZ - bz : bz > boxMaxZ ? bz - boxMaxZ : 0;
|
||||
int floorYcol = floorY + (int) Math.round(floorNoise.fitDouble(-1.0, 1.0, bx * floorFreq, bz * floorFreq) * floorAmp);
|
||||
if (floorYcol < worldMin) {
|
||||
floorYcol = worldMin;
|
||||
}
|
||||
int columnCeil;
|
||||
if (dx == 0 && dz == 0) {
|
||||
int bump = (int) Math.round(ceilNoise.fitDouble(0.0, 1.0, bx * ceilFreq, bz * ceilFreq) * ceilLump);
|
||||
columnCeil = Math.min(worldMax, apexY - Math.max(0, bump));
|
||||
} else {
|
||||
double dist = Math.sqrt((double) dx * dx + (double) dz * dz);
|
||||
double outline = outlineNoise.fitDouble(-1.0, 1.0, bx * outlineFreq, bz * outlineFreq);
|
||||
double rEff = rr * (0.80 + 0.35 * outline);
|
||||
if (rEff < 1.0) {
|
||||
rEff = 1.0;
|
||||
for (PlacedStructurePiece p : pieces) {
|
||||
int pMinX = p.getMinX();
|
||||
int pMinY = p.getMinY();
|
||||
int pMinZ = p.getMinZ();
|
||||
int pMaxX = p.getMaxX();
|
||||
int pMaxY = p.getMaxY();
|
||||
int pMaxZ = p.getMaxZ();
|
||||
int exMinX = pMinX - sideExt;
|
||||
int exMaxX = pMaxX + sideExt;
|
||||
int exMinZ = pMinZ - sideExt;
|
||||
int exMaxZ = pMaxZ + sideExt;
|
||||
int exMinY = Math.max(worldMin, pMinY - floorCut);
|
||||
int exMaxY = Math.min(worldMax, pMaxY + upExt);
|
||||
for (int bx = exMinX; bx <= exMaxX; bx++) {
|
||||
double dx = bx < pMinX ? pMinX - bx : bx > pMaxX ? bx - pMaxX : 0;
|
||||
double nx = dx / reachSide;
|
||||
for (int bz = exMinZ; bz <= exMaxZ; bz++) {
|
||||
double dz = bz < pMinZ ? pMinZ - bz : bz > pMaxZ ? bz - pMaxZ : 0;
|
||||
double nz = dz / reachSide;
|
||||
double nxz = nx * nx + nz * nz;
|
||||
double w = roll.fitDouble(0.0, 1.0, bx * rollFreq, bz * rollFreq) * 0.7
|
||||
+ roll.fitDouble(0.0, 1.0, bx * rollFreq * 3.0, bz * rollFreq * 3.0) * 0.3;
|
||||
double contrast = (w - 0.5) * 2.6 + 0.5;
|
||||
if (contrast < 0.0) {
|
||||
contrast = 0.0;
|
||||
} else if (contrast > 1.0) {
|
||||
contrast = 1.0;
|
||||
}
|
||||
if (dist > rEff) {
|
||||
continue;
|
||||
double upReach = reachUp * (upReachMin + upReachSpan * contrast);
|
||||
if (upReach < 1.0) {
|
||||
upReach = 1.0;
|
||||
}
|
||||
double t = dist / rEff;
|
||||
double dome = Math.sqrt(Math.max(0.0, 1.0 - t * t));
|
||||
double lump = ceilNoise.fitDouble(-1.0, 1.0, bx * ceilFreq, bz * ceilFreq);
|
||||
double mix = dome * (0.80 + 0.40 * lump);
|
||||
if (mix < 0.0) {
|
||||
mix = 0.0;
|
||||
for (int by = exMinY; by <= exMaxY; by++) {
|
||||
double ny;
|
||||
if (by > pMaxY) {
|
||||
ny = (by - pMaxY) / upReach;
|
||||
} else if (by < pMinY) {
|
||||
ny = (pMinY - by) / reachDown;
|
||||
} else {
|
||||
ny = 0.0;
|
||||
}
|
||||
double nd = Math.sqrt(nxz + ny * ny);
|
||||
if (nd > clearMax) {
|
||||
continue;
|
||||
}
|
||||
boolean carve = nd <= 0.45;
|
||||
if (!carve) {
|
||||
double n = blob.fitDouble(0.0, 1.0, bx * freq, by * freq, bz * freq);
|
||||
carve = nd <= 0.5 + 0.5 * n;
|
||||
}
|
||||
writer.clearBlock(bx, by - mantleOffset, bz);
|
||||
if (carve) {
|
||||
writer.setDataIfAbsent(bx, by - mantleOffset, bz, CARVE_CAVERN);
|
||||
}
|
||||
}
|
||||
columnCeil = floorYcol + (int) Math.round(span * mix);
|
||||
if (columnCeil <= floorYcol) {
|
||||
continue;
|
||||
}
|
||||
columnCeil = Math.min(worldMax, columnCeil);
|
||||
}
|
||||
for (int by = floorYcol; by <= columnCeil; by++) {
|
||||
writer.set(bx, by, bz, air);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,7 +342,11 @@ public class IrisStructureComponent extends IrisMantleComponent {
|
||||
if (!structure.getEdit().isEmpty()) {
|
||||
config.setEdit(structure.getEdit());
|
||||
}
|
||||
object.place(p.getX(), y, p.getZ(), writer, config, rng, null, null, getData());
|
||||
if (mode != ObjectPlaceMode.STRUCTURE_PIECE && mode != ObjectPlaceMode.FLOATING) {
|
||||
config.setForcePlace(true);
|
||||
}
|
||||
int placeY = (y == -1) ? -1 : y - getEngineMantle().getEngine().getMinHeight();
|
||||
object.place(p.getX(), placeY, p.getZ(), writer, config, rng, null, null, getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+30
-4
@@ -62,6 +62,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@ComponentFlag(ReservedFlag.OBJECT)
|
||||
public class MantleObjectComponent extends IrisMantleComponent {
|
||||
private static final long CAVE_REJECT_LOG_THROTTLE_MS = 5000L;
|
||||
private static final int BEDROCK_CLEARANCE = 6;
|
||||
private static final int SURFACE_HEIGHT_CHUNK_FILL_THRESHOLD = 128;
|
||||
private static final Map<String, CaveRejectLogState> CAVE_REJECT_LOG_STATE = new ConcurrentHashMap<>();
|
||||
private static final Set<String> MISSING_LOAD_KEY_WARNED = ConcurrentHashMap.newKeySet();
|
||||
@@ -258,7 +259,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
if (chance) {
|
||||
biomeCaveTriggered++;
|
||||
try {
|
||||
ObjectPlacementResult result = placeCaveObject(writer, rng, x, z, i, biomeCaveProfile, complex, traceRegen, x, z, "biome-cave");
|
||||
ObjectPlacementResult result = placeCaveObject(writer, rng, x, z, i, biomeCaveProfile, complex, traceRegen, x, z, "biome-cave", caveBiome.getLoadKey());
|
||||
attempts += result.attempts();
|
||||
placed += result.placed();
|
||||
rejected += result.rejected();
|
||||
@@ -323,7 +324,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
if (chance) {
|
||||
regionCaveTriggered++;
|
||||
try {
|
||||
ObjectPlacementResult result = placeCaveObject(writer, rng, x, z, i, regionCaveProfile, complex, traceRegen, x, z, "region-cave");
|
||||
ObjectPlacementResult result = placeCaveObject(writer, rng, x, z, i, regionCaveProfile, complex, traceRegen, x, z, "region-cave", null);
|
||||
attempts += result.attempts();
|
||||
placed += result.placed();
|
||||
rejected += result.rejected();
|
||||
@@ -484,6 +485,26 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
}
|
||||
|
||||
@ChunkCoordinates
|
||||
private boolean caveAnchorBiomeConflicts(int x, int y, int z, String expectedCaveBiomeKey) {
|
||||
if (expectedCaveBiomeKey == null) {
|
||||
return false;
|
||||
}
|
||||
Engine engine = getEngineMantle().getEngine();
|
||||
IrisBiome at = engine.getCaveBiome(x, y, z);
|
||||
if (at == null) {
|
||||
return false;
|
||||
}
|
||||
String atKey = at.getLoadKey();
|
||||
if (atKey == null || atKey.equals(expectedCaveBiomeKey)) {
|
||||
return false;
|
||||
}
|
||||
IrisBiome surface = engine.getSurfaceBiome(x, z);
|
||||
if (surface != null && atKey.equals(surface.getLoadKey())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private ObjectPlacementResult placeCaveObject(
|
||||
MantleWriter writer,
|
||||
RNG rng,
|
||||
@@ -495,7 +516,8 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
boolean traceRegen,
|
||||
int metricChunkX,
|
||||
int metricChunkZ,
|
||||
String scope
|
||||
String scope,
|
||||
String expectedCaveBiomeKey
|
||||
) {
|
||||
int attempts = 0;
|
||||
int placed = 0;
|
||||
@@ -556,6 +578,10 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (caveAnchorBiomeConflicts(candidateX, candidateY, candidateZ, expectedCaveBiomeKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
x = candidateX;
|
||||
z = candidateZ;
|
||||
y = candidateY;
|
||||
@@ -1053,7 +1079,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
|
||||
private KList<Integer> scanCaveAnchorRange(MantleWriter writer, IrisCaveAnchorMode anchorMode, int step, int x, int z, int height, int maxAnchorY) {
|
||||
KList<Integer> anchors = new KList<>();
|
||||
for (int y = 1; y < maxAnchorY; y += step) {
|
||||
for (int y = BEDROCK_CLEARANCE; y < maxAnchorY; y += step) {
|
||||
if (!writer.isCarved(x, y, z)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -321,7 +321,12 @@ public class IrisObject extends IrisRegistrant {
|
||||
int s = din.readInt();
|
||||
|
||||
for (int i = 0; i < s; i++) {
|
||||
blocks.put(new Vector3i(din.readShort(), din.readShort(), din.readShort()), B.get(din.readUTF()));
|
||||
Vector3i pos = new Vector3i(din.readShort(), din.readShort(), din.readShort());
|
||||
BlockData data = B.get(din.readUTF());
|
||||
if (isStructureMarker(data)) {
|
||||
continue;
|
||||
}
|
||||
blocks.put(pos, data);
|
||||
}
|
||||
|
||||
if (din.available() == 0)
|
||||
@@ -358,7 +363,12 @@ public class IrisObject extends IrisRegistrant {
|
||||
s = din.readInt();
|
||||
|
||||
for (i = 0; i < s; i++) {
|
||||
blocks.put(new Vector3i(din.readShort(), din.readShort(), din.readShort()), B.get(palette.get(din.readShort())));
|
||||
Vector3i pos = new Vector3i(din.readShort(), din.readShort(), din.readShort());
|
||||
BlockData data = B.get(palette.get(din.readShort()));
|
||||
if (isStructureMarker(data)) {
|
||||
continue;
|
||||
}
|
||||
blocks.put(pos, data);
|
||||
}
|
||||
|
||||
s = din.readInt();
|
||||
@@ -368,6 +378,14 @@ public class IrisObject extends IrisRegistrant {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isStructureMarker(BlockData data) {
|
||||
if (data == null) {
|
||||
return false;
|
||||
}
|
||||
Material m = data.getMaterial();
|
||||
return m == Material.JIGSAW || m == Material.STRUCTURE_BLOCK || m == Material.STRUCTURE_VOID;
|
||||
}
|
||||
|
||||
public void write(OutputStream o) throws IOException {
|
||||
DataOutputStream dos = new DataOutputStream(o);
|
||||
dos.writeInt(w);
|
||||
|
||||
@@ -96,6 +96,10 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
private final AtomicBoolean setup;
|
||||
private final boolean studio;
|
||||
private final AtomicInteger a = new AtomicInteger(0);
|
||||
private final AtomicBoolean firstChunkLogged = new AtomicBoolean(false);
|
||||
private volatile long firstChunkTime = 0L;
|
||||
private final AtomicInteger chunkGenCount = new AtomicInteger(0);
|
||||
private volatile long lastChunkGenTime = 0L;
|
||||
private final CompletableFuture<Integer> spawnChunks = new CompletableFuture<>();
|
||||
private final AtomicCache<EngineTarget> targetCache = new AtomicCache<>();
|
||||
private final AtomicReference<CompletableFuture<Void>> closeFuture = new AtomicReference<>();
|
||||
@@ -308,7 +312,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
|
||||
setChunkReplacementPhase(phaseRef, effectiveListener, "generate", x, z);
|
||||
long generateStart = System.currentTimeMillis();
|
||||
boolean useMulticore = IrisSettings.get().getGenerator().useMulticore && !J.isFolia();
|
||||
boolean useMulticore = false;
|
||||
AtomicBoolean generateDone = new AtomicBoolean(false);
|
||||
AtomicLong generationWatchdogStart = new AtomicLong(System.currentTimeMillis());
|
||||
Thread generateThread = Thread.currentThread();
|
||||
@@ -699,6 +703,18 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
|
||||
try {
|
||||
Engine engine = getEngine(world);
|
||||
lastChunkGenTime = System.currentTimeMillis();
|
||||
if (firstChunkLogged.compareAndSet(false, true)) {
|
||||
firstChunkTime = System.currentTimeMillis();
|
||||
Iris.info("[Studio timing] FIRST chunk generating in " + world.getName() + " at chunk " + x + "," + z + " (player-view terrain starts here — server-side open prep already done)");
|
||||
}
|
||||
if (studio) {
|
||||
int gen = chunkGenCount.incrementAndGet();
|
||||
if (gen % 64 == 0) {
|
||||
long el = Math.max(1L, System.currentTimeMillis() - firstChunkTime);
|
||||
Iris.info("[Studio timing] player-view terrain: " + gen + " chunks in " + el + "ms (" + (gen * 1000L / el) + " chunks/sec) — THIS is the wait you actually see");
|
||||
}
|
||||
}
|
||||
computeStudioGenerator();
|
||||
TerrainChunk tc = TerrainChunk.create(d);
|
||||
this.world.bind(world);
|
||||
@@ -707,7 +723,8 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
} else {
|
||||
ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(d);
|
||||
Hunk<Biome> biomes = Hunk.viewBiomes(tc);
|
||||
engine.generate(x << 4, z << 4, blocks, biomes, IrisSettings.get().getGenerator().useMulticore);
|
||||
boolean useMulticore = studio && !J.isFolia();
|
||||
engine.generate(x << 4, z << 4, blocks, biomes, useMulticore);
|
||||
blocks.apply();
|
||||
}
|
||||
|
||||
@@ -763,6 +780,10 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
return true;
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - lastChunkGenTime < 2000L) {
|
||||
return true;
|
||||
}
|
||||
|
||||
World realWorld = this.world.realWorld();
|
||||
PregeneratorJob pregeneratorJob = PregeneratorJob.getInstance();
|
||||
return realWorld != null && pregeneratorJob != null && pregeneratorJob.targetsWorld(realWorld);
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.util.common.director.specialhandlers;
|
||||
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||
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.stream.Collectors;
|
||||
|
||||
public class StructureHandler implements DirectorParameterHandler<String> {
|
||||
@Override
|
||||
public KList<String> getPossibilities() {
|
||||
KList<String> keys = new KList<>();
|
||||
|
||||
try {
|
||||
for (String k : INMS.get().getStructureKeys()) {
|
||||
if (k != null && !k.isEmpty()) {
|
||||
keys.addIfMissing(k);
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
Engine e = engine();
|
||||
if (e != null) {
|
||||
for (String k : IrisStructureLocator.placedKeys(e)) {
|
||||
if (k != null && !k.isEmpty()) {
|
||||
keys.addIfMissing(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String structure) {
|
||||
return structure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parse(String in, boolean force) throws DirectorParsingException {
|
||||
KList<String> options = getPossibilities(in);
|
||||
|
||||
if (options.isEmpty()) {
|
||||
return in;
|
||||
}
|
||||
try {
|
||||
return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0);
|
||||
} catch (Throwable e) {
|
||||
return in;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> type) {
|
||||
return type.equals(String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRandomDefault() {
|
||||
String f = getPossibilities().getRandom();
|
||||
|
||||
return f == null ? "minecraft_ancient_city" : f;
|
||||
}
|
||||
}
|
||||
-9
@@ -21,15 +21,6 @@ public class AsyncPregenMethodConcurrencyCapTest {
|
||||
assertEquals(192, AsyncPregenMethod.computeFoliaRecommendedCap(80));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runtimeCapUsesGlobalCeilingAndWorkerRecommendation() {
|
||||
assertEquals(80, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, true, 20));
|
||||
assertEquals(12, AsyncPregenMethod.applyRuntimeConcurrencyCap(12, true, 20));
|
||||
assertEquals(64, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, true, 8));
|
||||
assertEquals(16, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, false, 8));
|
||||
assertEquals(20, AsyncPregenMethod.applyRuntimeConcurrencyCap(20, false, 40));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void paperLikeConcurrencyProvisionsForWorldGenThreadBump() {
|
||||
assertEquals(32, AsyncPregenMethod.resolvePaperLikeConcurrencyWorkerThreads(4, 16, 32));
|
||||
|
||||
+35
-1
@@ -31,6 +31,7 @@ import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import java.util.stream.Collectors;
|
||||
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||
import art.arcane.iris.engine.framework.StructurePlacementGrid;
|
||||
import art.arcane.iris.engine.object.IrisStructurePlacement;
|
||||
import art.arcane.iris.engine.object.StructurePlacementRoute;
|
||||
@@ -66,10 +67,43 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
||||
try {
|
||||
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
BlockPos best = null;
|
||||
Holder<Structure> bestHolder = null;
|
||||
long bestDist = Long.MAX_VALUE;
|
||||
for (Holder<Structure> holder : holders) {
|
||||
Object id = registry.getKey(holder.value());
|
||||
if (id == null) {
|
||||
continue;
|
||||
}
|
||||
int[] at = IrisStructureLocator.locate(engine, id.toString(), pos.getX(), pos.getZ(), Math.max(1, radius));
|
||||
if (at == null) {
|
||||
continue;
|
||||
}
|
||||
long dx = (long) at[0] - pos.getX();
|
||||
long dz = (long) at[2] - pos.getZ();
|
||||
long d = dx * dx + dz * dz;
|
||||
if (d < bestDist) {
|
||||
bestDist = d;
|
||||
best = new BlockPos(at[0], at[1], at[2]);
|
||||
bestHolder = holder;
|
||||
}
|
||||
}
|
||||
if (best != null) {
|
||||
return Pair.of(best, bestHolder);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
}
|
||||
if (!vanillaControl().active()) {
|
||||
return null;
|
||||
}
|
||||
return delegate.findNearestMapStructure(level, holders, pos, radius, findUnexplored);
|
||||
try {
|
||||
return delegate.findNearestMapStructure(level, holders, pos, radius, findUnexplored);
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+35
-1
@@ -31,6 +31,7 @@ import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import java.util.stream.Collectors;
|
||||
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||
import art.arcane.iris.engine.framework.StructurePlacementGrid;
|
||||
import art.arcane.iris.engine.object.IrisStructurePlacement;
|
||||
import art.arcane.iris.engine.object.StructurePlacementRoute;
|
||||
@@ -66,10 +67,43 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
||||
try {
|
||||
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
BlockPos best = null;
|
||||
Holder<Structure> bestHolder = null;
|
||||
long bestDist = Long.MAX_VALUE;
|
||||
for (Holder<Structure> holder : holders) {
|
||||
Object id = registry.getKey(holder.value());
|
||||
if (id == null) {
|
||||
continue;
|
||||
}
|
||||
int[] at = IrisStructureLocator.locate(engine, id.toString(), pos.getX(), pos.getZ(), Math.max(1, radius));
|
||||
if (at == null) {
|
||||
continue;
|
||||
}
|
||||
long dx = (long) at[0] - pos.getX();
|
||||
long dz = (long) at[2] - pos.getZ();
|
||||
long d = dx * dx + dz * dz;
|
||||
if (d < bestDist) {
|
||||
bestDist = d;
|
||||
best = new BlockPos(at[0], at[1], at[2]);
|
||||
bestHolder = holder;
|
||||
}
|
||||
}
|
||||
if (best != null) {
|
||||
return Pair.of(best, bestHolder);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
}
|
||||
if (!vanillaControl().active()) {
|
||||
return null;
|
||||
}
|
||||
return delegate.findNearestMapStructure(level, holders, pos, radius, findUnexplored);
|
||||
try {
|
||||
return delegate.findNearestMapStructure(level, holders, pos, radius, findUnexplored);
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user