This commit is contained in:
Brian Neumann-Fopiano
2026-05-30 01:27:53 -04:00
parent 08c6f7ddf9
commit 9ff1c502ef
7 changed files with 133 additions and 16 deletions
+34 -4
View File
@@ -707,6 +707,15 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
private void processPendingStartupWorldDeletes() {
try {
try {
int unregistered = art.arcane.iris.core.tools.IrisCreator.removeTransientStudioWorldsFromBukkitYml();
if (unregistered > 0) {
Iris.info("Unregistered " + unregistered + " transient studio world(s) from bukkit.yml on startup.");
}
} catch (Throwable e) {
Iris.reportError("Failed to unregister transient studio worlds from bukkit.yml on startup.", e);
}
LinkedHashMap<String, String> queue = loadPendingWorldDeleteMap();
for (String transientStudioWorld : TransientWorldCleanupSupport.collectTransientStudioWorldNames(Bukkit.getWorldContainer())) {
queue.putIfAbsent(transientStudioWorld.toLowerCase(Locale.ROOT), transientStudioWorld);
@@ -722,10 +731,31 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
continue;
}
if (Bukkit.getWorld(worldName) != null) {
Iris.warn("Skipping queued deletion for \"" + worldName + "\" because it is currently loaded.");
remaining.put(worldName.toLowerCase(Locale.ROOT), worldName);
continue;
org.bukkit.World loaded = Bukkit.getWorld(worldName);
if (loaded != null) {
if (TransientWorldCleanupSupport.isTransientStudioWorldName(worldName)) {
try {
PlatformChunkGenerator generator = IrisToolbelt.access(loaded);
if (generator != null) {
generator.close();
}
IrisToolbelt.evacuate(loaded);
Bukkit.unloadWorld(loaded, false);
Iris.info("Unloaded leftover studio world \"" + worldName + "\" for deletion.");
} catch (Throwable e) {
Iris.reportError("Failed to unload leftover studio world \"" + worldName + "\".", e);
}
if (Bukkit.getWorld(worldName) != null) {
Iris.warn("Studio world \"" + worldName + "\" is still loaded after unload; will retry next startup.");
remaining.put(worldName.toLowerCase(Locale.ROOT), worldName);
continue;
}
} else {
Iris.warn("Skipping queued deletion for \"" + worldName + "\" because it is currently loaded.");
remaining.put(worldName.toLowerCase(Locale.ROOT), worldName);
continue;
}
}
boolean foundAny = false;
@@ -23,7 +23,6 @@ import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.IrisWorlds;
import art.arcane.iris.core.lifecycle.WorldLifecycleService;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.core.service.StudioSVC;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.framework.Engine;
@@ -611,8 +610,7 @@ public class CommandIris implements DirectorExecutor {
) {
sender().sendMessage(C.GREEN + "Downloading pack: " + pack + "/" + branch + (overwrite ? " overwriting" : ""));
if (pack.equals("overworld")) {
String url = "https://github.com/IrisDimensions/overworld/releases/download/" + INMS.OVERWORLD_TAG + "/overworld.zip";
Iris.service(StudioSVC.class).downloadRelease(sender(), url, overwrite);
Iris.service(StudioSVC.class).downloadBranch(sender(), "IrisDimensions/overworld", "master", overwrite);
} else {
Iris.service(StudioSVC.class).downloadSearch(sender(), "IrisDimensions/" + pack + "/" + branch, overwrite);
}
@@ -37,13 +37,8 @@ public class INMS {
new Version(21, 11, "v1_21_R7")
);
private static final List<Version> PACKS = List.of(
new Version(21, 11, "31100")
);
//@done
private static final INMSBinding binding = bind();
public static final String OVERWORLD_TAG = getTag(PACKS, "31100");
public static INMSBinding get() {
return binding;
@@ -24,7 +24,6 @@ import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.ServerConfigurator;
import art.arcane.iris.core.lifecycle.WorldLifecycleService;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.core.pack.IrisPack;
import art.arcane.iris.core.pack.PackValidationRegistry;
import art.arcane.iris.core.pack.PackValidationResult;
@@ -72,9 +71,8 @@ public class StudioSVC implements IrisService {
if (!f.exists()) {
if (pack.equals("overworld")) {
Iris.info("Downloading Default Pack " + pack);
String url = "https://github.com/IrisDimensions/overworld/releases/download/" + INMS.OVERWORLD_TAG + "/overworld.zip";
Iris.service(StudioSVC.class).downloadRelease(Iris.getSender(), url, false);
Iris.info("Downloading Default Pack " + pack + " (latest on master)");
Iris.service(StudioSVC.class).downloadBranch(Iris.getSender(), "IrisDimensions/overworld", "master", false);
} else {
Iris.warn("Default pack '" + pack + "' is not installed. Please download it manually with /iris download");
}
@@ -120,6 +118,13 @@ public class StudioSVC implements IrisService {
}
activeProject = null;
try {
art.arcane.iris.core.tools.IrisCreator.removeTransientStudioWorldsFromBukkitYml();
} catch (Throwable e) {
Iris.reportError("Failed to unregister transient studio worlds from bukkit.yml during shutdown.", e);
}
queueStudioWorldDeletionOnStartup(worldNamesToDelete);
}
@@ -237,6 +242,16 @@ public class StudioSVC implements IrisService {
}
}
public void downloadBranch(VolmitSender sender, String repo, String branch, boolean forceOverwrite) {
try {
download(sender, repo, branch, forceOverwrite, false);
} catch (Throwable e) {
Iris.reportError(e);
e.printStackTrace();
sender.sendMessage("Failed to download '" + repo + "' (branch " + branch + ").");
}
}
public void download(VolmitSender sender, String repo, String branch) throws JsonSyntaxException, IOException {
download(sender, repo, branch, false, false);
}
@@ -18,6 +18,7 @@
package art.arcane.iris.core.tools;
import art.arcane.iris.core.runtime.TransientWorldCleanupSupport;
import com.google.common.util.concurrent.AtomicDouble;
import art.arcane.iris.Iris;
import art.arcane.iris.core.IrisRuntimeSchedulerMode;
@@ -125,6 +126,28 @@ public class IrisCreator {
yml.save(BUKKIT_YML);
return true;
}
public static int removeTransientStudioWorldsFromBukkitYml() throws IOException {
YamlConfiguration yml = YamlConfiguration.loadConfiguration(BUKKIT_YML);
ConfigurationSection section = yml.getConfigurationSection("worlds");
if (section == null) {
return 0;
}
int removed = 0;
for (String name : new java.util.ArrayList<>(section.getKeys(false))) {
if (TransientWorldCleanupSupport.isTransientStudioWorldName(name)) {
section.set(name, null);
removed++;
}
}
if (removed > 0) {
if (section.getKeys(false).isEmpty()) {
yml.set("worlds", null);
}
yml.save(BUKKIT_YML);
}
return removed;
}
public static boolean worldLoaded(){
return true;
}
@@ -18,6 +18,7 @@
package art.arcane.iris.engine.mantle.components;
import art.arcane.iris.Iris;
import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.data.cache.Cache;
import art.arcane.iris.engine.framework.PlacedStructurePiece;
@@ -44,6 +45,8 @@ import art.arcane.volmlib.util.math.RNG;
@ComponentFlag(ReservedFlag.JIGSAW)
public class IrisStructureComponent extends IrisMantleComponent {
private static final long MAX_BORE_VOLUME = 6_000_000L;
public IrisStructureComponent(EngineMantle engineMantle) {
super(engineMantle, ReservedFlag.JIGSAW, 1);
}
@@ -116,6 +119,10 @@ public class IrisStructureComponent extends IrisMantleComponent {
return;
}
if (placement.isBore()) {
boreStructure(writer, pieces, placement.getBorePadding());
}
ObjectPlaceMode mode = structure.getPlaceMode();
if (placement.isUnderground() || mode == ObjectPlaceMode.STRUCTURE_PIECE || mode == ObjectPlaceMode.FLOATING) {
for (PlacedStructurePiece p : pieces) {
@@ -135,6 +142,49 @@ public class IrisStructureComponent extends IrisMantleComponent {
}
}
private void boreStructure(MantleWriter writer, KList<PlacedStructurePiece> pieces, int padding) {
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int minZ = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
int maxZ = Integer.MIN_VALUE;
for (PlacedStructurePiece p : pieces) {
minX = Math.min(minX, p.getMinX());
minY = Math.min(minY, p.getMinY());
minZ = Math.min(minZ, p.getMinZ());
maxX = Math.max(maxX, p.getMaxX());
maxY = Math.max(maxY, p.getMaxY());
maxZ = Math.max(maxZ, p.getMaxZ());
}
int pad = Math.max(0, padding);
minX -= pad;
minZ -= pad;
maxX += pad;
maxZ += pad;
maxY += pad;
int worldMin = getEngineMantle().getEngine().getMinHeight() + 1;
int worldMax = getEngineMantle().getEngine().getMinHeight() + getEngineMantle().getEngine().getHeight() - 1;
minY = Math.max(minY, worldMin);
maxY = Math.min(maxY, worldMax);
if (maxX < minX || maxY < minY || maxZ < minZ) {
return;
}
long volume = (long) (maxX - minX + 1) * (long) (maxY - minY + 1) * (long) (maxZ - minZ + 1);
if (volume > MAX_BORE_VOLUME) {
Iris.warn("Skipping structure bore of " + volume + " blocks (cap " + MAX_BORE_VOLUME + "); use a smaller structure or larger spacing.");
return;
}
org.bukkit.block.data.BlockData air = org.bukkit.Material.AIR.createBlockData();
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, air);
}
}
}
}
private void placeObject(MantleWriter writer, IrisStructure structure, PlacedStructurePiece p, ObjectPlaceMode mode, int y, RNG rng) {
IrisObject object = p.getObject();
IrisObjectPlacement config = new IrisObjectPlacement();
@@ -99,6 +99,12 @@ public class IrisStructurePlacement {
@Desc("IRIS_PLACED only: if true the structure is placed underground at a random world Y inside [minHeight, maxHeight] (raw stamp, no terrain matching) instead of being dropped onto the terrain surface. Use this for deep structures like ancient cities in a deep cave band.")
private boolean underground = false;
@Desc("IRIS_PLACED only: if true, the structure's full bounding box (floor up to roof) is bored out to air before the pieces are stamped, so the structure sits inside an open cavern instead of being encased in solid terrain. Essential for underground structures such as ancient cities to be visible and enterable.")
private boolean bore = false;
@Desc("IRIS_PLACED only: extra blocks of air clearance added around the bored bounding box (horizontally and above) when bore=true. The floor is never bored below the structure so support is preserved.")
private int borePadding = 0;
@Desc("If false, this placement is skipped underwater.")
private boolean underwater = false;
}