Headless Classload

This commit is contained in:
Brian Neumann-Fopiano
2026-06-11 20:30:20 -04:00
parent e1196b0364
commit a3d7269a99
22 changed files with 695 additions and 624 deletions
@@ -23,6 +23,7 @@ import art.arcane.iris.platform.bukkit.BukkitPlatform;
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.platform.EngineBukkitOps;
import art.arcane.iris.engine.framework.StructureReachability;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisRegion;
@@ -57,7 +58,7 @@ public class CommandFind implements DirectorExecutor {
return;
}
e.gotoBiome(biome, player(), teleport);
EngineBukkitOps.gotoBiome(e, biome, player(), teleport);
}
@Director(description = "Find a region")
@@ -74,7 +75,7 @@ public class CommandFind implements DirectorExecutor {
return;
}
e.gotoRegion(region, player(), teleport);
EngineBukkitOps.gotoRegion(e, region, player(), teleport);
}
@Director(description = "Find a point of interest.")
@@ -90,7 +91,7 @@ public class CommandFind implements DirectorExecutor {
return;
}
e.gotoPOI(type, player(), teleport);
EngineBukkitOps.gotoPOI(e, type, player(), teleport);
}
@Director(description = "Find a structure (a vanilla key like minecraft:village_plains or minecraft:stronghold, or an imported iris structure key)")
@@ -106,7 +107,7 @@ public class CommandFind implements DirectorExecutor {
}
if (IrisStructureLocator.isPlaced(e, structure)) {
e.gotoStructure(structure, player(), true);
EngineBukkitOps.gotoStructure(e, structure, player(), true);
return;
}
@@ -180,7 +181,7 @@ public class CommandFind implements DirectorExecutor {
}
if (e.hasObjectPlacement(object)) {
e.gotoObject(object, player(), teleport);
EngineBukkitOps.gotoObject(e, object, player(), teleport);
return;
}
@@ -32,6 +32,7 @@ import art.arcane.iris.core.structure.StructureImporter;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.*;
import art.arcane.iris.engine.platform.EngineBukkitOps;
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
@@ -246,11 +247,11 @@ public class CommandStudio implements DirectorExecutor {
) {
if (noStudio()) return;
KList<IrisLootTable> tables = engine().getLootTables(RNG.r, player().getLocation().getBlock());
KList<IrisLootTable> tables = EngineBukkitOps.getLootTables(engine(), RNG.r, player().getLocation().getBlock());
Inventory inv = Bukkit.createInventory(null, 27 * 2);
try {
engine().addItems(true, inv, RNG.r, tables, InventorySlotType.STORAGE, player().getWorld(), player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1);
EngineBukkitOps.addItems(engine(), true, inv, RNG.r, tables, InventorySlotType.STORAGE, player().getWorld(), player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1);
} catch (Throwable e) {
Iris.reportError(e);
sender().sendMessage(C.RED + "Cannot add items to virtual inventory because of: " + e.getMessage());
@@ -277,7 +278,7 @@ public class CommandStudio implements DirectorExecutor {
inv.clear();
}
engine.addItems(true, inv, new RNG(RNG.r.imax()), tables, InventorySlotType.STORAGE, player.getWorld(), player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ(), 1);
EngineBukkitOps.addItems(engine, true, inv, new RNG(RNG.r.imax()), tables, InventorySlotType.STORAGE, player.getWorld(), player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ(), 1);
}, fast ? 5 : 35));
sender().sendMessage(C.GREEN + "Opening inventory now!");
@@ -26,6 +26,7 @@ import art.arcane.iris.core.edit.BlockSignal;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.platform.EngineBukkitOps;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.util.common.director.DirectorExecutor;
@@ -96,7 +97,7 @@ public class CommandWhat implements DirectorExecutor {
public void region() {
try {
Chunk chunk = world().getChunkAt(player().getLocation().getBlockZ() / 16, player().getLocation().getBlockZ() / 16);
IrisRegion r = engine().getRegion(chunk);
IrisRegion r = EngineBukkitOps.getRegion(engine(), chunk);
sender().sendMessage("IRegion: " + r.getLoadKey() + " (" + r.getName() + ")");
} catch (Throwable e) {
@@ -21,6 +21,7 @@ package art.arcane.iris.core.link;
import art.arcane.iris.Iris;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.platform.EngineBukkitOps;
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.Location;
@@ -73,15 +74,15 @@ public class IrisPapiExpansion extends PlaceholderExpansion {
}
} else if (p.equalsIgnoreCase("region_name")) {
if (a != null) {
return a.getEngine().getRegion(l).getName();
return EngineBukkitOps.getRegion(a.getEngine(), l).getName();
}
} else if (p.equalsIgnoreCase("region_id")) {
if (a != null) {
return a.getEngine().getRegion(l).getLoadKey();
return EngineBukkitOps.getRegion(a.getEngine(), l).getLoadKey();
}
} else if (p.equalsIgnoreCase("region_file")) {
if (a != null) {
return a.getEngine().getRegion(l).getLoadFile().getPath();
return EngineBukkitOps.getRegion(a.getEngine(), l).getLoadFile().getPath();
}
} else if (p.equalsIgnoreCase("terrain_slope")) {
if (a != null) {
+1 -6
View File
@@ -51,7 +51,6 @@ art/arcane/iris/core/runtime/ChunkClearer.java
art/arcane/iris/core/runtime/ChunkJobReporter.java
art/arcane/iris/core/runtime/GoldenHashScanner.java
art/arcane/iris/core/runtime/InPlaceChunkRegenerator.java
art/arcane/iris/core/runtime/ObjectStudioLayout.java
art/arcane/iris/core/runtime/PaperLikeRuntimeControlBackend.java
art/arcane/iris/core/runtime/StudioOpenCoordinator.java
art/arcane/iris/core/runtime/WorldRuntimeControlBackend.java
@@ -83,12 +82,10 @@ art/arcane/iris/engine/actuator/IrisBiomeActuator.java
art/arcane/iris/engine/data/chunk/LinkedTerrainChunk.java
art/arcane/iris/engine/data/chunk/TerrainChunk.java
art/arcane/iris/engine/decorator/DecoratorCore.java
art/arcane/iris/engine/framework/BlockUpdater.java
art/arcane/iris/engine/framework/Engine.java
art/arcane/iris/engine/framework/EngineAssignedWorldManager.java
art/arcane/iris/engine/framework/EnginePlayer.java
art/arcane/iris/engine/framework/EngineWorldManager.java
art/arcane/iris/engine/framework/LootProvider.java
art/arcane/iris/engine/framework/placer/WorldObjectPlacer.java
art/arcane/iris/engine/mantle/EngineMantle.java
art/arcane/iris/engine/mantle/MantleWriter.java
@@ -123,7 +120,6 @@ art/arcane/iris/engine/object/IrisLootTable.java
art/arcane/iris/engine/object/IrisObject.java
art/arcane/iris/engine/object/IrisObjectPlacement.java
art/arcane/iris/engine/object/IrisObjectRotation.java
art/arcane/iris/engine/object/IrisObjectTranslate.java
art/arcane/iris/engine/object/IrisPotionEffect.java
art/arcane/iris/engine/object/IrisProceduralBlocks.java
art/arcane/iris/engine/object/IrisSurface.java
@@ -151,6 +147,7 @@ art/arcane/iris/engine/object/tree/TreePlausibility.java
art/arcane/iris/engine/platform/BukkitChunkGenerator.java
art/arcane/iris/engine/platform/DummyBiomeProvider.java
art/arcane/iris/engine/platform/DummyChunkGenerator.java
art/arcane/iris/engine/platform/EngineBukkitOps.java
art/arcane/iris/engine/platform/studio/generators/ObjectStudioGenerator.java
art/arcane/iris/platform/bukkit/BukkitBiome.java
art/arcane/iris/platform/bukkit/BukkitBlockResolution.java
@@ -162,7 +159,6 @@ art/arcane/iris/platform/bukkit/BukkitRegistries.java
art/arcane/iris/platform/bukkit/BukkitScheduler.java
art/arcane/iris/platform/bukkit/BukkitWorld.java
art/arcane/iris/util/common/data/IrisCustomData.java
art/arcane/iris/util/common/data/VectorMap.java
art/arcane/iris/util/common/data/registry/Attributes.java
art/arcane/iris/util/common/data/registry/KeyedRegistry.java
art/arcane/iris/util/common/data/registry/Materials.java
@@ -183,7 +179,6 @@ art/arcane/iris/util/common/inventorygui/Window.java
art/arcane/iris/util/common/inventorygui/WindowResolution.java
art/arcane/iris/util/common/math/AxisAlignedBB.java
art/arcane/iris/util/common/math/Direction.java
art/arcane/iris/util/common/math/Vector3i.java
art/arcane/iris/util/common/math/VectorMath.java
art/arcane/iris/util/common/misc/Bindings.java
art/arcane/iris/util/common/nbt/mca/Chunk.java
@@ -21,6 +21,7 @@ package art.arcane.iris.core.runtime;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.engine.data.chunk.TerrainChunk;
import art.arcane.iris.engine.platform.EngineBukkitOps;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.mantle.EngineMantle;
import art.arcane.iris.spi.PlatformBiome;
@@ -175,7 +176,7 @@ public final class InPlaceChunkRegenerator {
}
}
engine.updateChunk(chunk);
EngineBukkitOps.updateChunk(engine, chunk);
world.refreshChunk(chunkX, chunkZ);
}
@@ -25,6 +25,7 @@ import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.data.cache.Cache;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.EngineAssignedWorldManager;
import art.arcane.iris.engine.platform.EngineBukkitOps;
import art.arcane.iris.engine.object.*;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.volmlib.util.collection.KList;
@@ -281,7 +282,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
warmupMantleChunkAsync(chunkX, chunkZ);
return;
}
getEngine().updateChunk(chunk);
EngineBukkitOps.updateChunk(getEngine(), chunk);
}
if (!isEntitySpawningEnabledForCurrentWorld()) {
@@ -585,7 +586,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
Predicate<IrisSpawner> filter = i -> i.canSpawn(getEngine(), c.getX(), c.getZ());
ChunkCounter counter = new ChunkCounter(c.getEntities());
IrisBiome biome = getEngine().getSurfaceBiome(c);
IrisBiome biome = EngineBukkitOps.getSurfaceBiome(getEngine(), c);
IrisEntitySpawn v = spawnRandomly(Stream.concat(getData().getSpawnerLoader()
.loadAll(getDimension().getEntitySpawners())
.shuffleCopy(RNG.r)
@@ -978,11 +979,11 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
});
KList<ItemStack> d = new KList<>();
IrisBiome b = getEngine().getBiome(e.getBlock().getLocation().clone().subtract(0, getEngine().getWorld().minHeight(), 0));
IrisBiome b = EngineBukkitOps.getBiome(getEngine(), e.getBlock().getLocation().clone().subtract(0, getEngine().getWorld().minHeight(), 0));
List<IrisBlockDrops> dropProviders = filterDrops(b.getBlockDrops(), e, getData());
if (dropProviders.stream().noneMatch(IrisBlockDrops::isSkipParents)) {
IrisRegion r = getEngine().getRegion(e.getBlock().getLocation());
IrisRegion r = EngineBukkitOps.getRegion(getEngine(), e.getBlock().getLocation());
dropProviders.addAll(filterDrops(r.getBlockDrops(), e, getData()));
dropProviders.addAll(filterDrops(getEngine().getDimension().getBlockDrops(), e, getData()));
}
@@ -19,16 +19,8 @@
package art.arcane.iris.engine.framework;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.matter.Matter;
import org.bukkit.Chunk;
public interface BlockUpdater {
void catchBlockUpdates(int x, int y, int z, PlatformBlockState data);
void updateChunk(Chunk c);
void update(int x, int y, int z, Chunk c, MantleChunk<Matter> mc, RNG rf);
}
@@ -20,30 +20,23 @@ package art.arcane.iris.engine.framework;
import art.arcane.iris.platform.bukkit.BukkitBlockResolution;
import art.arcane.iris.spi.IrisServices;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.events.IrisLootEvent;
import art.arcane.iris.engine.framework.render.RenderType;
import art.arcane.iris.engine.framework.render.Renderer;
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.loader.IrisRegistrant;
import art.arcane.iris.core.nms.container.BlockPos;
import art.arcane.iris.core.nms.container.Pair;
import art.arcane.iris.core.service.ExternalDataSVC;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.UpperDimensionContext;
import art.arcane.iris.engine.data.cache.Cache;
import art.arcane.iris.engine.data.chunk.TerrainChunk;
import art.arcane.iris.engine.mantle.EngineMantle;
import art.arcane.iris.engine.object.*;
import art.arcane.iris.platform.bukkit.BukkitBlockState;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformBiome;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.project.matter.TileWrapper;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.iris.util.project.context.ChunkContext;
@@ -52,48 +45,30 @@ import art.arcane.iris.util.common.data.DataProvider;
import art.arcane.iris.util.common.data.IrisCustomData;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
import art.arcane.iris.util.common.format.C;
import art.arcane.volmlib.util.function.Function2;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
import art.arcane.volmlib.util.math.BlockPosition;
import art.arcane.volmlib.util.format.Form;
import art.arcane.volmlib.util.math.M;
import art.arcane.volmlib.util.math.Position2;
import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern;
import art.arcane.volmlib.util.matter.MatterUpdate;
import art.arcane.iris.util.common.parallel.BurstExecutor;
import art.arcane.iris.util.common.parallel.MultiBurst;
import art.arcane.iris.util.common.plugin.VolmitSender;
import art.arcane.iris.util.common.reflect.KeyedType;
import art.arcane.iris.util.common.reflect.W;
import art.arcane.volmlib.util.scheduling.ChronoLatch;
import art.arcane.iris.util.common.scheduling.J;
import art.arcane.iris.util.common.scheduling.jobs.SingleJob;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import art.arcane.iris.util.project.stream.ProceduralStream;
import io.papermc.lib.PaperLib;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.awt.Color;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import java.lang.reflect.Method;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -339,243 +314,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
void blockUpdatedMetric();
@ChunkCoordinates
@Override
default void updateChunk(Chunk c) {
for (int x = -1; x <= 1; x++) {
for (int z = -1; z <= 1; z++) {
if (c.getWorld().isChunkLoaded(c.getX() + x, c.getZ() + z))
continue;
var msg = "Chunk %s, %s [%s, %s] is not loaded".formatted(c.getX() + x, c.getZ() + z, x, z);
IrisLogging.debug(msg);
return;
}
}
var mantle = getMantle().getMantle();
if (!mantle.isLoaded(c)) {
var msg = "Mantle Chunk " + c.getX() + "," + c.getZ() + " is not loaded";
IrisLogging.debug(msg);
return;
}
if (!J.isFolia() && !J.isPrimaryThread()) {
CompletableFuture<?> scheduled = J.sfut(() -> updateChunk(c));
if (scheduled != null) {
try {
scheduled.join();
} catch (Throwable e) {
IrisLogging.reportError(e);
}
}
return;
}
var chunk = mantle.getChunk(c).use();
try {
Runnable tileTask = () -> {
chunk.iterate(TileWrapper.class, (x, y, z, v) -> {
Block block = c.getBlock(x & 15, y + getWorld().minHeight(), z & 15);
if (!TileData.setTileState(block, v.getData())) {
NamespacedKey blockTypeKey = KeyedType.getKey(block.getType());
NamespacedKey tileTypeKey = KeyedType.getKey(v.getData().getMaterial());
String blockType = blockTypeKey == null ? block.getType().name() : blockTypeKey.toString();
String tileType = tileTypeKey == null ? v.getData().getMaterial().name() : tileTypeKey.toString();
IrisLogging.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", block.getX(), block.getY(), block.getZ(), blockType, tileType);
}
});
};
Runnable customTask = () -> {
chunk.iterate(Identifier.class, (x, y, z, v) -> {
IrisServices.get(ExternalDataSVC.class).processUpdate(this, c.getBlock(x & 15, y + getWorld().minHeight(), z & 15), v);
});
};
Runnable updateTask = () -> {
PrecisionStopwatch p = PrecisionStopwatch.start();
int[][] grid = new int[16][16];
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
grid[x][z] = Integer.MIN_VALUE;
}
}
RNG rng = new RNG(Cache.key(c.getX(), c.getZ()));
chunk.iterate(MatterCavern.class, (x, yf, z, v) -> {
int y = yf + getWorld().minHeight();
x &= 15;
z &= 15;
Block block = c.getBlock(x, y, z);
if (!BukkitBlockResolution.isFluid(block.getBlockData())) {
return;
}
boolean u = BukkitBlockResolution.isAir(block.getRelative(BlockFace.DOWN).getBlockData())
|| BukkitBlockResolution.isAir(block.getRelative(BlockFace.WEST).getBlockData())
|| BukkitBlockResolution.isAir(block.getRelative(BlockFace.EAST).getBlockData())
|| BukkitBlockResolution.isAir(block.getRelative(BlockFace.SOUTH).getBlockData())
|| BukkitBlockResolution.isAir(block.getRelative(BlockFace.NORTH).getBlockData());
if (u) grid[x][z] = Math.max(grid[x][z], y);
});
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
if (grid[x][z] == Integer.MIN_VALUE) {
continue;
}
update(x, grid[x][z], z, c, chunk, rng);
}
}
chunk.iterate(MatterUpdate.class, (x, yf, z, v) -> {
int y = yf + getWorld().minHeight();
if (v != null && v.isUpdate()) {
update(x, y, z, c, chunk, rng);
}
});
chunk.deleteSlices(MatterUpdate.class);
getMetrics().getUpdates().put(p.getMilliseconds());
};
if (shouldRunChunkUpdateInline(c)) {
chunk.raiseFlagUnchecked(MantleFlag.ETCHED, () -> {
chunk.raiseFlagUnchecked(MantleFlag.TILE, tileTask);
chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, customTask);
chunk.raiseFlagUnchecked(MantleFlag.UPDATE, updateTask);
});
return;
}
Semaphore semaphore = new Semaphore(1024);
chunk.raiseFlagUnchecked(MantleFlag.ETCHED, () -> {
chunk.raiseFlagUnchecked(MantleFlag.TILE, run(semaphore, c, tileTask, 0));
chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, run(semaphore, c, customTask, 0));
chunk.raiseFlagUnchecked(MantleFlag.UPDATE, run(semaphore, c, updateTask, RNG.r.i(1, 20)));
});
try {
semaphore.acquire(1024);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
IrisLogging.reportError(ex);
}
} finally {
chunk.release();
}
}
private static boolean shouldRunChunkUpdateInline(Chunk chunk) {
if (chunk == null) {
return false;
}
if (!J.isFolia()) {
return true;
}
return J.isOwnedByCurrentRegion(chunk.getWorld(), chunk.getX(), chunk.getZ());
}
private static Runnable run(Semaphore semaphore, Chunk contextChunk, Runnable runnable, int delay) {
return () -> {
try {
semaphore.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
int effectiveDelay = J.isFolia() ? 0 : delay;
boolean scheduled = J.runRegion(contextChunk.getWorld(), contextChunk.getX(), contextChunk.getZ(), () -> {
try {
runnable.run();
} finally {
semaphore.release();
}
}, effectiveDelay);
if (!scheduled) {
try {
if (J.isPrimaryThread()) {
runnable.run();
}
} finally {
semaphore.release();
}
}
};
}
@BlockCoordinates
@Override
default void update(int x, int y, int z, Chunk c, MantleChunk<Matter> mc, RNG rf) {
Block block = c.getBlock(x, y, z);
BlockData data = block.getBlockData();
blockUpdatedMetric();
if (BukkitBlockResolution.isStorage(data)) {
RNG rx = rf.nextParallelRNG(BlockPosition.toLong(x, y, z));
InventorySlotType slot = null;
if (BukkitBlockResolution.isStorageChest(data)) {
slot = InventorySlotType.STORAGE;
}
if (slot != null) {
KList<IrisLootTable> tables = getLootTables(rx, block, mc);
try {
Bukkit.getPluginManager().callEvent(new IrisLootEvent(this, block, slot, tables));
if (tables.isEmpty()) return;
InventoryHolder m = (InventoryHolder) block.getState();
addItems(false, m.getInventory(), rx, tables, slot, c.getWorld(), x, y, z, 15);
} catch (Throwable e) {
IrisLogging.reportError(e);
}
}
} else {
block.setType(Material.AIR, false);
block.setBlockData(data, true);
}
}
@Override
default void scramble(Inventory inventory, RNG rng) {
org.bukkit.inventory.ItemStack[] items = inventory.getContents();
org.bukkit.inventory.ItemStack[] nitems = new org.bukkit.inventory.ItemStack[inventory.getSize()];
System.arraycopy(items, 0, nitems, 0, items.length);
boolean packedFull = false;
splitting:
for (int i = 0; i < nitems.length; i++) {
ItemStack is = nitems[i];
if (is != null && is.getAmount() > 1 && !packedFull) {
for (int j = 0; j < nitems.length; j++) {
if (nitems[j] == null) {
int take = rng.nextInt(is.getAmount());
take = take == 0 ? 1 : take;
is.setAmount(is.getAmount() - take);
nitems[j] = is.clone();
nitems[j].setAmount(take);
continue splitting;
}
}
packedFull = true;
}
}
for (int i = nitems.length; i > 1; i--) {
int j = rng.nextInt(i);
ItemStack tmp = nitems[i - 1];
nitems[i - 1] = nitems[j];
nitems[j] = tmp;
}
inventory.setContents(nitems);
}
@Override
default void injectTables(KList<IrisLootTable> list, IrisLootReference r, boolean fallback) {
if (r.getMode().equals(IrisLootMode.FALLBACK) && !fallback)
@@ -588,101 +326,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
list.addAll(r.getLootTables(getComplex()));
}
@BlockCoordinates
@Override
default KList<IrisLootTable> getLootTables(RNG rng, Block b) {
MantleChunk<Matter> mc = getMantle().getMantle().getChunk(b.getChunk()).use();
try {
return getLootTables(rng, b, mc);
} finally {
mc.release();
}
}
@BlockCoordinates
default KList<IrisLootTable> getLootTables(RNG rng, Block b, MantleChunk<Matter> mc) {
int rx = b.getX();
int rz = b.getZ();
int ry = b.getY() - getWorld().minHeight();
double he = getComplex().getHeightStream().get(rx, rz);
KList<IrisLootTable> tables = new KList<>();
PlacedObject po = getObjectPlacement(rx, ry, rz, mc);
if (po != null && po.getPlacement() != null) {
if (BukkitBlockResolution.isStorageChest(b.getBlockData())) {
IrisLootTable table = po.getPlacement().getTable(BukkitBlockState.of(b.getBlockData()), getData());
if (table != null) {
tables.add(table);
if (po.getPlacement().isOverrideGlobalLoot()) {
return new KList<>(table);
}
}
}
}
IrisRegion region = getComplex().getRegionStream().get(rx, rz);
IrisBiome biomeSurface = getComplex().getTrueBiomeStream().get(rx, rz);
IrisBiome biomeUnder = ry < he ? getCaveBiome(rx, ry, rz) : biomeSurface;
double multiplier = 1D * getDimension().getLoot().getMultiplier() * region.getLoot().getMultiplier() * biomeSurface.getLoot().getMultiplier() * biomeUnder.getLoot().getMultiplier();
boolean fallback = tables.isEmpty();
injectTables(tables, getDimension().getLoot(), fallback);
injectTables(tables, region.getLoot(), fallback);
injectTables(tables, biomeSurface.getLoot(), fallback);
injectTables(tables, biomeUnder.getLoot(), fallback);
if (tables.isNotEmpty()) {
int target = (int) Math.round(tables.size() * multiplier);
while (tables.size() < target && tables.isNotEmpty()) {
tables.add(tables.get(rng.i(tables.size() - 1)));
}
while (tables.size() > target && tables.isNotEmpty()) {
tables.remove(rng.i(tables.size() - 1));
}
}
return tables;
}
@Override
default void addItems(boolean debug, Inventory inv, RNG rng, KList<IrisLootTable> tables, InventorySlotType slot, World world, int x, int y, int z, int mgf) {
KList<ItemStack> items = new KList<>();
for (IrisLootTable i : tables) {
if (i == null)
continue;
items.addAll(i.getLoot(debug, rng, slot, world, x, y, z));
}
if (IrisLootEvent.callLootEvent(items, inv, world, x, y, z))
return;
if (PaperLib.isPaper() && getWorld().hasRealWorld()) {
PaperLib.getChunkAtAsync(getWorld().realWorld(), x >> 4, z >> 4).thenAccept((c) -> {
Runnable r = () -> {
for (ItemStack i : items) {
inv.addItem(i);
}
scramble(inv, rng);
};
if (Bukkit.isPrimaryThread()) {
r.run();
} else {
J.s(r);
}
});
} else {
for (ItemStack i : items) {
inv.addItem(i);
}
scramble(inv, rng);
}
}
EngineEffects getEffects();
default MultiBurst burst() {
@@ -693,16 +336,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
burst().lazy(() -> getMantle().trim(10));
}
@BlockCoordinates
default IrisBiome getBiome(Location l) {
return getBiome(l.getBlockX(), l.getBlockY() - getWorld().minHeight(), l.getBlockZ());
}
@BlockCoordinates
default IrisRegion getRegion(Location l) {
return getRegion(l.getBlockX(), l.getBlockZ());
}
IrisBiome getFocus();
IrisRegion getFocusRegion();
@@ -710,14 +343,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
IrisEngineData getEngineData();
default IrisBiome getSurfaceBiome(Chunk c) {
return getSurfaceBiome((c.getX() << 4) + 8, (c.getZ() << 4) + 8);
}
default IrisRegion getRegion(Chunk c) {
return getRegion((c.getX() << 4) + 8, (c.getZ() << 4) + 8);
}
default KList<IrisBiome> getAllBiomes() {
KMap<String, IrisBiome> v = new KMap<>();
@@ -981,107 +606,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
int getCacheID();
default IrisBiome getBiomeOrMantle(Location l) {
return getBiomeOrMantle(l.getBlockX(), l.getBlockY(), l.getBlockZ());
}
default void gotoBiome(IrisBiome biome, Player player, boolean teleport) {
find(Locator.surfaceBiome(biome.getLoadKey()), player, teleport, "Biome " + biome.getName());
}
private static void find(Locator<?> locator, Player player, boolean teleport, String message) {
find(locator, player, 120_000, location -> {
if (location == null) {
player.sendMessage(C.RED + "Could not find " + message + " within search range.");
return;
}
if (teleport) {
J.runEntity(player, () -> teleportAsyncSafely(player, location));
player.sendMessage(C.GREEN + "Teleporting to " + message + "...");
} else {
player.sendMessage(C.GREEN + message + " at: " + location.getBlockX() + " " + location.getBlockY() + " " + location.getBlockZ());
}
});
}
private static void find(Locator<?> locator, Player player, long timeout, Consumer<Location> consumer) {
AtomicLong checks = new AtomicLong();
long ms = M.ms();
new SingleJob("Searching", () -> {
try {
World world = player.getWorld();
Engine engine = IrisToolbelt.access(world).getEngine();
Position2 at = locator.find(engine, new Position2(player.getLocation().getBlockX() >> 4, player.getLocation().getBlockZ() >> 4), timeout, checks::set).get();
if (at != null) {
int bx = (at.getX() << 4) + 8;
int bz = (at.getZ() << 4) + 8;
consumer.accept(new Location(world, bx,
world.getHighestBlockYAt(bx, bz) + 2,
bz));
} else {
consumer.accept(null);
}
} catch (WrongEngineBroException | InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}) {
@Override
public String getName() {
return "Searched " + Form.f(checks.get()) + " Chunks";
}
@Override
public int getTotalWork() {
return (int) timeout;
}
@Override
public int getWorkCompleted() {
return (int) Math.min(M.ms() - ms, timeout - 1);
}
}.execute(new VolmitSender(player));
}
private static void teleportAsyncSafely(Player player, Location location) {
if (player == null || location == null) {
return;
}
if (invokeNativeTeleportAsync(player, location)) {
return;
}
try {
CompletableFuture<Boolean> teleportFuture = PaperLib.teleportAsync(player, location);
if (teleportFuture != null) {
teleportFuture.exceptionally(throwable -> {
IrisLogging.reportError(throwable);
return false;
});
}
} catch (Throwable throwable) {
IrisLogging.reportError(throwable);
}
}
private static boolean invokeNativeTeleportAsync(Player player, Location location) {
try {
Method teleportAsyncMethod = player.getClass().getMethod("teleportAsync", Location.class);
teleportAsyncMethod.invoke(player, location);
return true;
} catch (NoSuchMethodException ignored) {
return false;
} catch (Throwable throwable) {
IrisLogging.reportError(throwable);
return false;
}
}
default void gotoObject(String s, Player player, boolean teleport) {
find(Locator.object(s), player, teleport, "Object " + s);
}
default boolean hasObjectPlacement(String objectKey) {
String normalizedObjectKey = normalizeObjectPlacementKey(objectKey);
if (normalizedObjectKey.isBlank()) {
@@ -1100,23 +624,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
return !regionKeys.isEmpty();
}
default void gotoRegion(IrisRegion r, Player player, boolean teleport) {
if (!getDimension().getRegions().contains(r.getLoadKey())) {
player.sendMessage(C.RED + r.getName() + " is not defined in the dimension!");
return;
}
find(Locator.region(r.getLoadKey()), player, teleport, "Region " + r.getName());
}
default void gotoPOI(String type, Player p, boolean teleport) {
find(Locator.poi(type), p, teleport, "POI " + type);
}
default void gotoStructure(String key, Player player, boolean teleport) {
find(Locator.structure(key), player, teleport, "Structure " + key);
}
private static boolean containsObjectPlacement(KList<IrisObjectPlacement> placements, String normalizedObjectKey) {
if (placements == null || placements.isEmpty() || normalizedObjectKey.isBlank()) {
return false;
@@ -19,6 +19,7 @@
package art.arcane.iris.engine.framework;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.engine.platform.EngineBukkitOps;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisEffect;
import art.arcane.iris.engine.object.IrisRegion;
@@ -87,8 +88,8 @@ public class EnginePlayer {
if (ticksSinceLastSample() > 55 && current.distanceSquared(lastLocation) > 9 * 9) {
lastLocation = current;
lastSample = M.ms();
biome = engine.getBiome(current);
region = engine.getRegion(current);
biome = EngineBukkitOps.getBiome(engine, current);
region = EngineBukkitOps.getRegion(engine, current);
}
return false;
} catch (Throwable e) {
@@ -18,21 +18,10 @@
package art.arcane.iris.engine.framework;
import art.arcane.iris.engine.object.InventorySlotType;
import art.arcane.iris.engine.object.IrisLootReference;
import art.arcane.iris.engine.object.IrisLootTable;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.inventory.Inventory;
public interface LootProvider {
void scramble(Inventory inventory, RNG rng);
void injectTables(KList<IrisLootTable> list, IrisLootReference r, boolean fallback);
KList<IrisLootTable> getLootTables(RNG rng, Block b);
void addItems(boolean debug, Inventory inv, RNG rng, KList<IrisLootTable> tables, InventorySlotType slot, World world, int x, int y, int z, int mgf);
}
@@ -8,6 +8,7 @@ import art.arcane.iris.engine.data.cache.Cache;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.core.events.IrisLootEvent;
import art.arcane.iris.engine.mantle.EngineMantle;
import art.arcane.iris.engine.platform.EngineBukkitOps;
import art.arcane.iris.engine.object.IObjectPlacer;
import art.arcane.iris.engine.object.InventorySlotType;
import art.arcane.iris.engine.object.IrisLootTable;
@@ -71,7 +72,7 @@ public class WorldObjectPlacer implements IObjectPlacer {
if (slot != null) {
RNG rx = new RNG(Cache.key(x, z));
KList<IrisLootTable> tables = engine.getLootTables(rx, block);
KList<IrisLootTable> tables = EngineBukkitOps.getLootTables(engine, rx, block);
try {
Bukkit.getPluginManager().callEvent(new IrisLootEvent(engine, block, slot, tables));
@@ -83,7 +84,7 @@ public class WorldObjectPlacer implements IObjectPlacer {
if (tables.isEmpty())
return;
InventoryHolder m = (InventoryHolder) block.getState();
engine.addItems(false, m.getInventory(), rx, tables, slot, world, x, y, z, 15);
EngineBukkitOps.addItems(engine, false, m.getInventory(), rx, tables, slot, world, x, y, z, 15);
} catch (Throwable e) {
IrisLogging.reportError(e);
}
@@ -179,6 +179,12 @@ public class IrisEffect {
});
}
private static final class BukkitFx {
private static void run(Player p, Runnable r) {
J.runEntity(p, r);
}
}
public void apply(Player p, Engine g) {
if (!canTick()) {
return;
@@ -188,7 +194,7 @@ public class IrisEffect {
return;
}
J.runEntity(p, () -> {
BukkitFx.run(p, () -> {
if (sound != null) {
Location part = p.getLocation().clone().add(RNG.r.i(-soundDistance, soundDistance), RNG.r.i(-soundDistance, soundDistance), RNG.r.i(-soundDistance, soundDistance));
p.playSound(part, getSound(), (float) volume, (float) RNG.r.d(minPitch, maxPitch));
@@ -24,6 +24,7 @@ import art.arcane.iris.core.link.Identifier;
import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.iris.core.service.ExternalDataSVC;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.platform.EngineBukkitOps;
import art.arcane.iris.engine.object.annotations.*;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.platform.bukkit.BukkitWorld;
@@ -247,34 +248,7 @@ public class IrisEntity extends IrisRegistrant {
Lootable l = (Lootable) e;
if (getLoot().getTables().isNotEmpty()) {
Location finalAt = at;
l.setLootTable(new LootTable() {
@Override
public NamespacedKey getKey() {
return new NamespacedKey("iris", "loot-" + IrisEntity.this.hashCode());
}
@Override
public Collection<ItemStack> populateLoot(Random random, LootContext context) {
KList<ItemStack> items = new KList<>();
for (String fi : getLoot().getTables()) {
IrisLootTable i = gen.getData().getLootLoader().load(fi);
items.addAll(i.getLoot(gen.isStudio(), rng.nextParallelRNG(345911), InventorySlotType.STORAGE, finalAt.getWorld(), finalAt.getBlockX(), finalAt.getBlockY(), finalAt.getBlockZ()));
}
return items;
}
@Override
public void fillInventory(Inventory inventory, Random random, LootContext context) {
for (ItemStack i : populateLoot(random, context)) {
inventory.addItem(i);
}
gen.scramble(inventory, rng);
}
});
BukkitOps.bindLoot(this, gen, l, at, rng);
}
}
@@ -326,7 +300,7 @@ public class IrisEntity extends IrisRegistrant {
if (e instanceof Villager) {
Villager villager = (Villager) e;
villager.setRemoveWhenFarAway(false);
J.runEntity(villager, () -> villager.setPersistent(true), 1);
BukkitOps.persistVillager(villager);
}
if (e instanceof Mob) {
@@ -458,6 +432,42 @@ public class IrisEntity extends IrisRegistrant {
return specialType != null && !specialType.equals("");
}
private static final class BukkitOps {
private static void persistVillager(Villager villager) {
J.runEntity(villager, () -> villager.setPersistent(true), 1);
}
private static void bindLoot(IrisEntity entity, Engine gen, Lootable l, Location finalAt, RNG rng) {
l.setLootTable(new LootTable() {
@Override
public NamespacedKey getKey() {
return new NamespacedKey("iris", "loot-" + entity.hashCode());
}
@Override
public Collection<ItemStack> populateLoot(Random random, LootContext context) {
KList<ItemStack> items = new KList<>();
for (String fi : entity.getLoot().getTables()) {
IrisLootTable i = gen.getData().getLootLoader().load(fi);
items.addAll(i.getLoot(gen.isStudio(), rng.nextParallelRNG(345911), InventorySlotType.STORAGE, finalAt.getWorld(), finalAt.getBlockX(), finalAt.getBlockY(), finalAt.getBlockZ()));
}
return items;
}
@Override
public void fillInventory(Inventory inventory, Random random, LootContext context) {
for (ItemStack i : populateLoot(random, context)) {
inventory.addItem(i);
}
EngineBukkitOps.scramble(gen, inventory, rng);
}
});
}
}
@Override
public String getFolderName() {
return "entities";
@@ -1059,8 +1059,8 @@ public class IrisObject extends IrisRegistrant {
}
if (placer.isPreventingDecay() && ((BlockData) data.nativeHandle()) instanceof Leaves && !((Leaves) ((BlockData) data.nativeHandle())).isPersistent()) {
Leaves leaves = (Leaves) ((BlockData) data.nativeHandle()).clone();
leaves.setPersistent(true);
BlockData leaves = ((BlockData) data.nativeHandle()).clone();
((Leaves) leaves).setPersistent(true);
data = BukkitBlockState.of(leaves);
}
@@ -1119,13 +1119,14 @@ public class IrisObject extends IrisRegistrant {
}
if (((BlockData) data.nativeHandle()) instanceof Waterlogged && shouldAutoWaterlogBlock(placer, config, yv, xx, yy, zz)) {
Waterlogged waterlogged = (Waterlogged) ((BlockData) data.nativeHandle()).clone();
waterlogged.setWaterlogged(true);
BlockData waterlogged = ((BlockData) data.nativeHandle()).clone();
((Waterlogged) waterlogged).setWaterlogged(true);
data = BukkitBlockState.of(waterlogged);
}
if (BukkitBlockResolution.isVineBlock((BlockData) data.nativeHandle())) {
MultipleFacing f = (MultipleFacing) ((BlockData) data.nativeHandle()).clone();
BlockData fData = ((BlockData) data.nativeHandle()).clone();
MultipleFacing f = (MultipleFacing) fData;
boolean facesChanged = false;
for (BlockFace face : f.getAllowedFaces()) {
PlatformBlockState facingState = placer.get(xx + face.getModX(), yy + face.getModY(), zz + face.getModZ());
@@ -1136,7 +1137,7 @@ public class IrisObject extends IrisRegistrant {
}
}
if (facesChanged) {
data = BukkitBlockState.of(f);
data = BukkitBlockState.of(fData);
}
}
@@ -1344,8 +1345,8 @@ public class IrisObject extends IrisRegistrant {
int highest = placer.getHighest(xx, zz, getLoader(), true);
if (((BlockData) d.nativeHandle()) instanceof Waterlogged && shouldAutoWaterlogBlock(placer, config, yv, xx, highest, zz)) {
Waterlogged waterlogged = (Waterlogged) ((BlockData) d.nativeHandle()).clone();
waterlogged.setWaterlogged(true);
BlockData waterlogged = ((BlockData) d.nativeHandle()).clone();
((Waterlogged) waterlogged).setWaterlogged(true);
d = BukkitBlockState.of(waterlogged);
}
@@ -1385,7 +1386,8 @@ public class IrisObject extends IrisRegistrant {
}
if (BukkitBlockResolution.isVineBlock((BlockData) d.nativeHandle())) {
MultipleFacing f = (MultipleFacing) ((BlockData) d.nativeHandle()).clone();
BlockData fData = ((BlockData) d.nativeHandle()).clone();
MultipleFacing f = (MultipleFacing) fData;
boolean facesChanged = false;
for (BlockFace face : f.getAllowedFaces()) {
PlatformBlockState facingState = placer.get(xx + face.getModX(), j + face.getModY(), zz + face.getModZ());
@@ -1396,7 +1398,7 @@ public class IrisObject extends IrisRegistrant {
}
}
if (facesChanged) {
d = BukkitBlockState.of(f);
d = BukkitBlockState.of(fData);
}
}
placer.set(xx, j, zz, d);
@@ -47,7 +47,9 @@ import java.util.Map;
@Desc("Configures rotation for iris")
@Data
public class IrisObjectRotation {
private static final List<BlockFace> WALL_FACES = List.of(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST);
private static final class Faces {
private static final List<BlockFace> WALL_FACES = List.of(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST);
}
@Desc("If this rotator is enabled or not")
private boolean enabled = true;
@@ -332,17 +334,17 @@ public class IrisObjectRotation {
} else if (d instanceof Wall wall) {
KMap<BlockFace, Wall.Height> faces = new KMap<>();
for (BlockFace i : WALL_FACES) {
for (BlockFace i : Faces.WALL_FACES) {
Wall.Height h = wall.getHeight(i);
IrisBlockVector bv = new IrisBlockVector(i.getModX(), i.getModY(), i.getModZ());
bv = rotate(bv.clone(), spinx, spiny, spinz);
BlockFace r = getFace(bv);
if (WALL_FACES.contains(r)) {
if (Faces.WALL_FACES.contains(r)) {
faces.put(r, h);
}
}
for (BlockFace i : WALL_FACES) {
for (BlockFace i : Faces.WALL_FACES) {
wall.setHeight(i, faces.getOrDefault(i, Wall.Height.NONE));
}
} else if (d instanceof RedstoneWire wire) {
@@ -36,18 +36,12 @@ import java.util.stream.StreamSupport;
@ToString
@EqualsAndHashCode(callSuper = false)
public class LegacyTileData extends TileData {
private static final Map<Integer, Pair<Builder, IOFunction<DataInputStream, Handler>>> legacy = Map.of(
0, new Pair<>(SignHandler::fromBukkit, SignHandler::new),
1, new Pair<>(SpawnerHandler::fromBukkit, SpawnerHandler::new),
2, new Pair<>(BannerHandler::fromBukkit, BannerHandler::new),
3, new Pair<>(LootableHandler::fromBukkit, LootableHandler::new));
private static final AtomicCache<Tag<Material>> SIGNS = new AtomicCache<>();
private final int id;
private final Handler handler;
public LegacyTileData(DataInputStream in) throws IOException {
id = in.readShort();
var factory = legacy.get(id);
Pair<Builder, IOFunction<DataInputStream, Handler>> factory = Handlers.LEGACY.get(id);
if (factory == null)
throw new IOException("Unknown tile type: " + id);
handler = factory.getB().apply(in);
@@ -60,16 +54,24 @@ public class LegacyTileData extends TileData {
@Nullable
public static LegacyTileData fromBukkit(@NonNull BlockState tileState) {
var type = tileState.getType();
for (var id : legacy.keySet()) {
var factory = legacy.get(id);
var handler = factory.getA().apply(tileState, type);
Material type = tileState.getType();
for (Integer id : Handlers.LEGACY.keySet()) {
Pair<Builder, IOFunction<DataInputStream, Handler>> factory = Handlers.LEGACY.get(id);
Handler handler = factory.getA().apply(tileState, type);
if (handler != null)
return new LegacyTileData(id, handler);
}
return null;
}
private static final class Handlers {
private static final Map<Integer, Pair<Builder, IOFunction<DataInputStream, Handler>>> LEGACY = Map.of(
0, new Pair<>(SignHandler::fromBukkit, SignHandler::new),
1, new Pair<>(SpawnerHandler::fromBukkit, SpawnerHandler::new),
2, new Pair<>(BannerHandler::fromBukkit, BannerHandler::new),
3, new Pair<>(LootableHandler::fromBukkit, LootableHandler::new));
}
@Override
public @NonNull KMap<String, Object> getProperties() {
return new KMap<>();
@@ -411,34 +413,42 @@ public class LegacyTileData extends TileData {
}
private static Tag<Material> signsTag() {
return SIGNS.aquire(() -> {
var signs = Bukkit.getTag("blocks", NamespacedKey.minecraft("all_signs"), Material.class);
if (signs != null)
return signs;
return new Tag<>() {
@Override
public boolean isTagged(@NotNull Material item) {
NamespacedKey key = KeyedType.getKey(item);
if (key != null) {
return key.getKey().endsWith("_sign");
return SignTags.get();
}
private static final class SignTags {
private static final AtomicCache<Tag<Material>> SIGNS = new AtomicCache<>();
private static Tag<Material> get() {
return SIGNS.aquire(() -> {
Tag<Material> signs = Bukkit.getTag("blocks", NamespacedKey.minecraft("all_signs"), Material.class);
if (signs != null)
return signs;
return new Tag<>() {
@Override
public boolean isTagged(@NotNull Material item) {
NamespacedKey key = KeyedType.getKey(item);
if (key != null) {
return key.getKey().endsWith("_sign");
}
return item.name().toLowerCase(Locale.ROOT).endsWith("_sign");
}
return item.name().toLowerCase(Locale.ROOT).endsWith("_sign");
}
@NotNull
@Override
public Set<Material> getValues() {
return StreamSupport.stream(Registry.MATERIAL.spliterator(), false)
.filter(this::isTagged)
.collect(Collectors.toUnmodifiableSet());
}
@NotNull
@Override
public Set<Material> getValues() {
return StreamSupport.stream(Registry.MATERIAL.spliterator(), false)
.filter(this::isTagged)
.collect(Collectors.toUnmodifiableSet());
}
@NotNull
@Override
public NamespacedKey getKey() {
return NamespacedKey.minecraft("all_signs");
}
};
});
@NotNull
@Override
public NamespacedKey getKey() {
return NamespacedKey.minecraft("all_signs");
}
};
});
}
}
}
@@ -0,0 +1,539 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.engine.platform;
import art.arcane.iris.core.events.IrisLootEvent;
import art.arcane.iris.core.link.Identifier;
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;
import art.arcane.iris.engine.framework.Locator;
import art.arcane.iris.engine.framework.PlacedObject;
import art.arcane.iris.engine.framework.WrongEngineBroException;
import art.arcane.iris.engine.object.InventorySlotType;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisLootTable;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.engine.object.TileData;
import art.arcane.iris.platform.bukkit.BukkitBlockResolution;
import art.arcane.iris.platform.bukkit.BukkitBlockState;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.IrisServices;
import art.arcane.iris.util.common.format.C;
import art.arcane.iris.util.common.plugin.VolmitSender;
import art.arcane.iris.util.common.reflect.KeyedType;
import art.arcane.iris.util.common.scheduling.J;
import art.arcane.iris.util.common.scheduling.jobs.SingleJob;
import art.arcane.iris.util.project.matter.TileWrapper;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.format.Form;
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.math.BlockPosition;
import art.arcane.volmlib.util.math.M;
import art.arcane.volmlib.util.math.Position2;
import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern;
import art.arcane.volmlib.util.matter.MatterUpdate;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import io.papermc.lib.PaperLib;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import java.lang.reflect.Method;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
public final class EngineBukkitOps {
private EngineBukkitOps() {
}
public static void updateChunk(Engine engine, Chunk c) {
for (int x = -1; x <= 1; x++) {
for (int z = -1; z <= 1; z++) {
if (c.getWorld().isChunkLoaded(c.getX() + x, c.getZ() + z))
continue;
String msg = "Chunk %s, %s [%s, %s] is not loaded".formatted(c.getX() + x, c.getZ() + z, x, z);
IrisLogging.debug(msg);
return;
}
}
Mantle<Matter> mantle = engine.getMantle().getMantle();
if (!mantle.isLoaded(c)) {
String msg = "Mantle Chunk " + c.getX() + "," + c.getZ() + " is not loaded";
IrisLogging.debug(msg);
return;
}
if (!J.isFolia() && !J.isPrimaryThread()) {
CompletableFuture<?> scheduled = J.sfut(() -> updateChunk(engine, c));
if (scheduled != null) {
try {
scheduled.join();
} catch (Throwable e) {
IrisLogging.reportError(e);
}
}
return;
}
MantleChunk<Matter> chunk = mantle.getChunk(c).use();
try {
Runnable tileTask = () -> {
chunk.iterate(TileWrapper.class, (x, y, z, v) -> {
Block block = c.getBlock(x & 15, y + engine.getWorld().minHeight(), z & 15);
if (!TileData.setTileState(block, v.getData())) {
NamespacedKey blockTypeKey = KeyedType.getKey(block.getType());
NamespacedKey tileTypeKey = KeyedType.getKey(v.getData().getMaterial());
String blockType = blockTypeKey == null ? block.getType().name() : blockTypeKey.toString();
String tileType = tileTypeKey == null ? v.getData().getMaterial().name() : tileTypeKey.toString();
IrisLogging.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", block.getX(), block.getY(), block.getZ(), blockType, tileType);
}
});
};
Runnable customTask = () -> {
chunk.iterate(Identifier.class, (x, y, z, v) -> {
IrisServices.get(ExternalDataSVC.class).processUpdate(engine, c.getBlock(x & 15, y + engine.getWorld().minHeight(), z & 15), v);
});
};
Runnable updateTask = () -> {
PrecisionStopwatch p = PrecisionStopwatch.start();
int[][] grid = new int[16][16];
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
grid[x][z] = Integer.MIN_VALUE;
}
}
RNG rng = new RNG(Cache.key(c.getX(), c.getZ()));
chunk.iterate(MatterCavern.class, (x, yf, z, v) -> {
int y = yf + engine.getWorld().minHeight();
x &= 15;
z &= 15;
Block block = c.getBlock(x, y, z);
if (!BukkitBlockResolution.isFluid(block.getBlockData())) {
return;
}
boolean u = BukkitBlockResolution.isAir(block.getRelative(BlockFace.DOWN).getBlockData())
|| BukkitBlockResolution.isAir(block.getRelative(BlockFace.WEST).getBlockData())
|| BukkitBlockResolution.isAir(block.getRelative(BlockFace.EAST).getBlockData())
|| BukkitBlockResolution.isAir(block.getRelative(BlockFace.SOUTH).getBlockData())
|| BukkitBlockResolution.isAir(block.getRelative(BlockFace.NORTH).getBlockData());
if (u) grid[x][z] = Math.max(grid[x][z], y);
});
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
if (grid[x][z] == Integer.MIN_VALUE) {
continue;
}
update(engine, x, grid[x][z], z, c, chunk, rng);
}
}
chunk.iterate(MatterUpdate.class, (x, yf, z, v) -> {
int y = yf + engine.getWorld().minHeight();
if (v != null && v.isUpdate()) {
update(engine, x, y, z, c, chunk, rng);
}
});
chunk.deleteSlices(MatterUpdate.class);
engine.getMetrics().getUpdates().put(p.getMilliseconds());
};
if (shouldRunChunkUpdateInline(c)) {
chunk.raiseFlagUnchecked(MantleFlag.ETCHED, () -> {
chunk.raiseFlagUnchecked(MantleFlag.TILE, tileTask);
chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, customTask);
chunk.raiseFlagUnchecked(MantleFlag.UPDATE, updateTask);
});
return;
}
Semaphore semaphore = new Semaphore(1024);
chunk.raiseFlagUnchecked(MantleFlag.ETCHED, () -> {
chunk.raiseFlagUnchecked(MantleFlag.TILE, run(semaphore, c, tileTask, 0));
chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, run(semaphore, c, customTask, 0));
chunk.raiseFlagUnchecked(MantleFlag.UPDATE, run(semaphore, c, updateTask, RNG.r.i(1, 20)));
});
try {
semaphore.acquire(1024);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
IrisLogging.reportError(ex);
}
} finally {
chunk.release();
}
}
private static boolean shouldRunChunkUpdateInline(Chunk chunk) {
if (chunk == null) {
return false;
}
if (!J.isFolia()) {
return true;
}
return J.isOwnedByCurrentRegion(chunk.getWorld(), chunk.getX(), chunk.getZ());
}
private static Runnable run(Semaphore semaphore, Chunk contextChunk, Runnable runnable, int delay) {
return () -> {
try {
semaphore.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
int effectiveDelay = J.isFolia() ? 0 : delay;
boolean scheduled = J.runRegion(contextChunk.getWorld(), contextChunk.getX(), contextChunk.getZ(), () -> {
try {
runnable.run();
} finally {
semaphore.release();
}
}, effectiveDelay);
if (!scheduled) {
try {
if (J.isPrimaryThread()) {
runnable.run();
}
} finally {
semaphore.release();
}
}
};
}
public static void update(Engine engine, int x, int y, int z, Chunk c, MantleChunk<Matter> mc, RNG rf) {
Block block = c.getBlock(x, y, z);
BlockData data = block.getBlockData();
engine.blockUpdatedMetric();
if (BukkitBlockResolution.isStorage(data)) {
RNG rx = rf.nextParallelRNG(BlockPosition.toLong(x, y, z));
InventorySlotType slot = null;
if (BukkitBlockResolution.isStorageChest(data)) {
slot = InventorySlotType.STORAGE;
}
if (slot != null) {
KList<IrisLootTable> tables = getLootTables(engine, rx, block, mc);
try {
Bukkit.getPluginManager().callEvent(new IrisLootEvent(engine, block, slot, tables));
if (tables.isEmpty()) return;
InventoryHolder m = (InventoryHolder) block.getState();
addItems(engine, false, m.getInventory(), rx, tables, slot, c.getWorld(), x, y, z, 15);
} catch (Throwable e) {
IrisLogging.reportError(e);
}
}
} else {
block.setType(Material.AIR, false);
block.setBlockData(data, true);
}
}
public static void scramble(Engine engine, Inventory inventory, RNG rng) {
org.bukkit.inventory.ItemStack[] items = inventory.getContents();
org.bukkit.inventory.ItemStack[] nitems = new org.bukkit.inventory.ItemStack[inventory.getSize()];
System.arraycopy(items, 0, nitems, 0, items.length);
boolean packedFull = false;
splitting:
for (int i = 0; i < nitems.length; i++) {
ItemStack is = nitems[i];
if (is != null && is.getAmount() > 1 && !packedFull) {
for (int j = 0; j < nitems.length; j++) {
if (nitems[j] == null) {
int take = rng.nextInt(is.getAmount());
take = take == 0 ? 1 : take;
is.setAmount(is.getAmount() - take);
nitems[j] = is.clone();
nitems[j].setAmount(take);
continue splitting;
}
}
packedFull = true;
}
}
for (int i = nitems.length; i > 1; i--) {
int j = rng.nextInt(i);
ItemStack tmp = nitems[i - 1];
nitems[i - 1] = nitems[j];
nitems[j] = tmp;
}
inventory.setContents(nitems);
}
public static KList<IrisLootTable> getLootTables(Engine engine, RNG rng, Block b) {
MantleChunk<Matter> mc = engine.getMantle().getMantle().getChunk(b.getChunk()).use();
try {
return getLootTables(engine, rng, b, mc);
} finally {
mc.release();
}
}
public static KList<IrisLootTable> getLootTables(Engine engine, RNG rng, Block b, MantleChunk<Matter> mc) {
int rx = b.getX();
int rz = b.getZ();
int ry = b.getY() - engine.getWorld().minHeight();
double he = engine.getComplex().getHeightStream().get(rx, rz);
KList<IrisLootTable> tables = new KList<>();
PlacedObject po = engine.getObjectPlacement(rx, ry, rz, mc);
if (po != null && po.getPlacement() != null) {
if (BukkitBlockResolution.isStorageChest(b.getBlockData())) {
IrisLootTable table = po.getPlacement().getTable(BukkitBlockState.of(b.getBlockData()), engine.getData());
if (table != null) {
tables.add(table);
if (po.getPlacement().isOverrideGlobalLoot()) {
return new KList<>(table);
}
}
}
}
IrisRegion region = engine.getComplex().getRegionStream().get(rx, rz);
IrisBiome biomeSurface = engine.getComplex().getTrueBiomeStream().get(rx, rz);
IrisBiome biomeUnder = ry < he ? engine.getCaveBiome(rx, ry, rz) : biomeSurface;
double multiplier = 1D * engine.getDimension().getLoot().getMultiplier() * region.getLoot().getMultiplier() * biomeSurface.getLoot().getMultiplier() * biomeUnder.getLoot().getMultiplier();
boolean fallback = tables.isEmpty();
engine.injectTables(tables, engine.getDimension().getLoot(), fallback);
engine.injectTables(tables, region.getLoot(), fallback);
engine.injectTables(tables, biomeSurface.getLoot(), fallback);
engine.injectTables(tables, biomeUnder.getLoot(), fallback);
if (tables.isNotEmpty()) {
int target = (int) Math.round(tables.size() * multiplier);
while (tables.size() < target && tables.isNotEmpty()) {
tables.add(tables.get(rng.i(tables.size() - 1)));
}
while (tables.size() > target && tables.isNotEmpty()) {
tables.remove(rng.i(tables.size() - 1));
}
}
return tables;
}
public static void addItems(Engine engine, boolean debug, Inventory inv, RNG rng, KList<IrisLootTable> tables, InventorySlotType slot, World world, int x, int y, int z, int mgf) {
KList<ItemStack> items = new KList<>();
for (IrisLootTable i : tables) {
if (i == null)
continue;
items.addAll(i.getLoot(debug, rng, slot, world, x, y, z));
}
if (IrisLootEvent.callLootEvent(items, inv, world, x, y, z))
return;
if (PaperLib.isPaper() && engine.getWorld().hasRealWorld()) {
PaperLib.getChunkAtAsync(engine.getWorld().realWorld(), x >> 4, z >> 4).thenAccept((c) -> {
Runnable r = () -> {
for (ItemStack i : items) {
inv.addItem(i);
}
scramble(engine, inv, rng);
};
if (Bukkit.isPrimaryThread()) {
r.run();
} else {
J.s(r);
}
});
} else {
for (ItemStack i : items) {
inv.addItem(i);
}
scramble(engine, inv, rng);
}
}
public static IrisBiome getBiome(Engine engine, Location l) {
return engine.getBiome(l.getBlockX(), l.getBlockY() - engine.getWorld().minHeight(), l.getBlockZ());
}
public static IrisRegion getRegion(Engine engine, Location l) {
return engine.getRegion(l.getBlockX(), l.getBlockZ());
}
public static IrisBiome getBiomeOrMantle(Engine engine, Location l) {
return engine.getBiomeOrMantle(l.getBlockX(), l.getBlockY(), l.getBlockZ());
}
public static IrisBiome getSurfaceBiome(Engine engine, Chunk c) {
return engine.getSurfaceBiome((c.getX() << 4) + 8, (c.getZ() << 4) + 8);
}
public static IrisRegion getRegion(Engine engine, Chunk c) {
return engine.getRegion((c.getX() << 4) + 8, (c.getZ() << 4) + 8);
}
public static void gotoBiome(Engine engine, IrisBiome biome, Player player, boolean teleport) {
find(Locator.surfaceBiome(biome.getLoadKey()), player, teleport, "Biome " + biome.getName());
}
public static void gotoObject(Engine engine, String s, Player player, boolean teleport) {
find(Locator.object(s), player, teleport, "Object " + s);
}
public static void gotoRegion(Engine engine, IrisRegion r, Player player, boolean teleport) {
if (!engine.getDimension().getRegions().contains(r.getLoadKey())) {
player.sendMessage(C.RED + r.getName() + " is not defined in the dimension!");
return;
}
find(Locator.region(r.getLoadKey()), player, teleport, "Region " + r.getName());
}
public static void gotoPOI(Engine engine, String type, Player p, boolean teleport) {
find(Locator.poi(type), p, teleport, "POI " + type);
}
public static void gotoStructure(Engine engine, String key, Player player, boolean teleport) {
find(Locator.structure(key), player, teleport, "Structure " + key);
}
private static void find(Locator<?> locator, Player player, boolean teleport, String message) {
find(locator, player, 120_000, location -> {
if (location == null) {
player.sendMessage(C.RED + "Could not find " + message + " within search range.");
return;
}
if (teleport) {
J.runEntity(player, () -> teleportAsyncSafely(player, location));
player.sendMessage(C.GREEN + "Teleporting to " + message + "...");
} else {
player.sendMessage(C.GREEN + message + " at: " + location.getBlockX() + " " + location.getBlockY() + " " + location.getBlockZ());
}
});
}
private static void find(Locator<?> locator, Player player, long timeout, Consumer<Location> consumer) {
AtomicLong checks = new AtomicLong();
long ms = M.ms();
new SingleJob("Searching", () -> {
try {
World world = player.getWorld();
Engine engine = IrisToolbelt.access(world).getEngine();
Position2 at = locator.find(engine, new Position2(player.getLocation().getBlockX() >> 4, player.getLocation().getBlockZ() >> 4), timeout, checks::set).get();
if (at != null) {
int bx = (at.getX() << 4) + 8;
int bz = (at.getZ() << 4) + 8;
consumer.accept(new Location(world, bx,
world.getHighestBlockYAt(bx, bz) + 2,
bz));
} else {
consumer.accept(null);
}
} catch (WrongEngineBroException | InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}) {
@Override
public String getName() {
return "Searched " + Form.f(checks.get()) + " Chunks";
}
@Override
public int getTotalWork() {
return (int) timeout;
}
@Override
public int getWorkCompleted() {
return (int) Math.min(M.ms() - ms, timeout - 1);
}
}.execute(new VolmitSender(player));
}
private static void teleportAsyncSafely(Player player, Location location) {
if (player == null || location == null) {
return;
}
if (invokeNativeTeleportAsync(player, location)) {
return;
}
try {
CompletableFuture<Boolean> teleportFuture = PaperLib.teleportAsync(player, location);
if (teleportFuture != null) {
teleportFuture.exceptionally(throwable -> {
IrisLogging.reportError(throwable);
return false;
});
}
} catch (Throwable throwable) {
IrisLogging.reportError(throwable);
}
}
private static boolean invokeNativeTeleportAsync(Player player, Location location) {
try {
Method teleportAsyncMethod = player.getClass().getMethod("teleportAsync", Location.class);
teleportAsyncMethod.invoke(player, location);
return true;
} catch (NoSuchMethodException ignored) {
return false;
} catch (Throwable throwable) {
IrisLogging.reportError(throwable);
return false;
}
}
}
@@ -20,6 +20,7 @@ package art.arcane.iris.util.common.director.context;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.platform.EngineBukkitOps;
import art.arcane.iris.util.common.director.DirectorContextHandler;
import art.arcane.iris.util.common.plugin.VolmitSender;
@@ -32,7 +33,7 @@ public class BiomeContextHandler implements DirectorContextHandler<IrisBiome> {
if (sender.isPlayer()
&& IrisToolbelt.isIrisWorld(sender.player().getWorld())
&& IrisToolbelt.access(sender.player().getWorld()).getEngine() != null) {
return IrisToolbelt.access(sender.player().getWorld()).getEngine().getBiomeOrMantle(sender.player().getLocation());
return EngineBukkitOps.getBiomeOrMantle(IrisToolbelt.access(sender.player().getWorld()).getEngine(), sender.player().getLocation());
}
return null;
@@ -21,6 +21,7 @@ package art.arcane.iris.util.common.director.context;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisGenerator;
import art.arcane.iris.engine.platform.EngineBukkitOps;
import art.arcane.iris.util.common.director.DirectorContextHandler;
import art.arcane.iris.util.common.plugin.VolmitSender;
@@ -36,7 +37,7 @@ public class GeneratorContextHandler implements DirectorContextHandler<IrisGener
&& IrisToolbelt.isIrisWorld(sender.player().getWorld())
&& IrisToolbelt.access(sender.player().getWorld()).getEngine() != null) {
Engine engine = IrisToolbelt.access(sender.player().getWorld()).getEngine();
return engine.getData().getGeneratorLoader().load(engine.getBiome(sender.player().getLocation()).getGenerators().getRandom().getGenerator());
return engine.getData().getGeneratorLoader().load(EngineBukkitOps.getBiome(engine, sender.player().getLocation()).getGenerators().getRandom().getGenerator());
}
return null;
@@ -20,6 +20,7 @@ package art.arcane.iris.util.common.director.context;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.engine.platform.EngineBukkitOps;
import art.arcane.iris.util.common.director.DirectorContextHandler;
import art.arcane.iris.util.common.plugin.VolmitSender;
@@ -32,7 +33,7 @@ public class RegionContextHandler implements DirectorContextHandler<IrisRegion>
if (sender.isPlayer()
&& IrisToolbelt.isIrisWorld(sender.player().getWorld())
&& IrisToolbelt.access(sender.player().getWorld()).getEngine() != null) {
return IrisToolbelt.access(sender.player().getWorld()).getEngine().getRegion(sender.player().getLocation());
return EngineBukkitOps.getRegion(IrisToolbelt.access(sender.player().getWorld()).getEngine(), sender.player().getLocation());
}
return null;
@@ -47,6 +47,11 @@ public final class ClassloadProbe {
"art.arcane.iris.util.common.misc.Bindings",
"art.arcane.iris.util.common.misc.SlimJar",
"art.arcane.iris.util.common.misc.ServerProperties",
"art.arcane.iris.util.common.data.IrisCustomData",
"art.arcane.iris.engine.IrisWorldManager",
"art.arcane.iris.engine.framework.EngineAssignedWorldManager",
"art.arcane.iris.engine.object.StudioMode",
"art.arcane.iris.engine.framework.placer.WorldObjectPlacer",
};
public static void main(String[] args) throws IOException {
@@ -87,6 +92,9 @@ public final class ClassloadProbe {
Class.forName(name, true, ClassloadProbe.class.getClassLoader());
loaded++;
} catch (Throwable failure) {
if (System.getenv("PROBE_TRACE") != null && name.contains(System.getenv("PROBE_TRACE"))) {
failure.printStackTrace(System.out);
}
String cause = rootCause(failure);
if (critical) {
criticalFailures.put(name, cause);