mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-13 02:15:46 +00:00
lmao
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
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;
|
||||
@@ -134,6 +135,44 @@ public class CommandStructure implements DirectorExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Import EVERY vanilla structure into a pack: jigsaw structures rebuilt as pool graphs, single-template structures imported as objects. Idempotent in add-only mode.", aliases = {"import-all", "ia"}, origin = DirectorOrigin.BOTH)
|
||||
public void importAllVanilla(
|
||||
@Param(description = "The dimension whose pack to import into", aliases = "dim")
|
||||
IrisDimension dimension,
|
||||
@Param(description = "overwrite | add-only | merge", defaultValue = "add-only")
|
||||
String mode,
|
||||
@Param(description = "Also import non-jigsaw single-template structures", name = "include-non-jigsaw", aliases = {"single", "nbt"}, defaultValue = "true")
|
||||
boolean includeNonJigsaw
|
||||
) {
|
||||
IrisData data = dimension.getLoader();
|
||||
if (data == null) {
|
||||
sender().sendMessage(C.RED + "Could not resolve the pack for dimension " + dimension.getLoadKey());
|
||||
return;
|
||||
}
|
||||
BulkStructureImporter.Report report = BulkStructureImporter.importAllVanilla(data, StructureImporter.parseMode(mode), includeNonJigsaw, sender());
|
||||
if (report.imported() > 0) {
|
||||
sender().sendMessage(C.GRAY + "Reference imported structures from a biome/region/dimension structures list, or run /iris structure list " + dimension.getLoadKey() + " to see the refreshed index.");
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Import EVERY vanilla structure TEMPLATE (the piece NBTs under minecraft:.../...) as editable Iris objects, including the non-jigsaw templates that import-all cannot reach. Idempotent in add-only mode.", aliases = {"import-templates", "it"}, origin = DirectorOrigin.BOTH)
|
||||
public void importTemplates(
|
||||
@Param(description = "The dimension whose pack to import into", aliases = "dim")
|
||||
IrisDimension dimension,
|
||||
@Param(description = "overwrite | add-only | merge", defaultValue = "add-only")
|
||||
String mode
|
||||
) {
|
||||
IrisData data = dimension.getLoader();
|
||||
if (data == null) {
|
||||
sender().sendMessage(C.RED + "Could not resolve the pack for dimension " + dimension.getLoadKey());
|
||||
return;
|
||||
}
|
||||
BulkStructureImporter.Report report = BulkStructureImporter.importAllTemplates(data, StructureImporter.parseMode(mode), sender());
|
||||
if (report.imported() > 0) {
|
||||
sender().sendMessage(C.GRAY + "Reference imported templates from a biome/region/dimension structures list, or run /iris structure list " + dimension.getLoadKey() + " to see the refreshed index.");
|
||||
}
|
||||
}
|
||||
|
||||
@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")
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* 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.core.structure;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class BulkStructureImporter {
|
||||
public record Report(int total, int imported, int skipped, int failed) {
|
||||
}
|
||||
|
||||
private BulkStructureImporter() {
|
||||
}
|
||||
|
||||
public static Report importAllVanilla(IrisData data, StructureImporter.Mode mode, boolean includeNonJigsaw, VolmitSender sender) {
|
||||
KList<String> keys = INMS.get().getStructureKeys();
|
||||
List<String> vanilla = new ArrayList<>();
|
||||
for (String k : keys) {
|
||||
if (k != null && k.startsWith("minecraft:")) {
|
||||
vanilla.add(k);
|
||||
}
|
||||
}
|
||||
Collections.sort(vanilla);
|
||||
|
||||
int total = vanilla.size();
|
||||
int imported = 0;
|
||||
int skipped = 0;
|
||||
int failed = 0;
|
||||
|
||||
sender.sendMessage(C.GREEN + "Importing " + C.WHITE + total + C.GREEN + " vanilla structures (mode=" + mode + ", includeNonJigsaw=" + includeNonJigsaw + ")...");
|
||||
|
||||
for (String keyString : vanilla) {
|
||||
NamespacedKey nk = NamespacedKey.fromString(keyString.toLowerCase());
|
||||
if (nk == null) {
|
||||
failed++;
|
||||
sender.sendMessage(C.RED + "[fail] " + keyString + ": invalid key");
|
||||
continue;
|
||||
}
|
||||
String name = StructureImporter.deriveName(nk);
|
||||
|
||||
try {
|
||||
VillageImporter.Result jigsaw = VillageImporter.importVillage(data, nk, name, mode);
|
||||
if (jigsaw.success()) {
|
||||
imported++;
|
||||
sender.sendMessage(C.GRAY + "[jigsaw] " + keyString + " -> " + name);
|
||||
continue;
|
||||
}
|
||||
|
||||
String message = jigsaw.message() == null ? "" : jigsaw.message();
|
||||
if (message.contains("is not a jigsaw structure")) {
|
||||
if (!includeNonJigsaw) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
StructureImporter.Result single = StructureImporter.importStructure(data, nk, name, mode);
|
||||
if (single.success()) {
|
||||
imported++;
|
||||
sender.sendMessage(C.GRAY + "[single] " + keyString + " -> " + name);
|
||||
} else if (single.message() != null && single.message().startsWith("Skipped")) {
|
||||
skipped++;
|
||||
} else {
|
||||
failed++;
|
||||
sender.sendMessage(C.RED + "[fail] " + keyString + ": " + single.message());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message.startsWith("Skipped")) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
failed++;
|
||||
sender.sendMessage(C.RED + "[fail] " + keyString + ": " + message);
|
||||
} catch (Throwable e) {
|
||||
failed++;
|
||||
sender.sendMessage(C.RED + "[fail] " + keyString + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
StructureIndexService.write(data);
|
||||
|
||||
sender.sendMessage(C.GREEN + "Bulk import complete: " + C.WHITE + imported + C.GREEN + " imported, " + C.WHITE + skipped + C.GREEN + " skipped, " + C.WHITE + failed + C.GREEN + " failed (" + C.WHITE + total + C.GREEN + " total).");
|
||||
return new Report(total, imported, skipped, failed);
|
||||
}
|
||||
|
||||
public static Report importAllTemplates(IrisData data, StructureImporter.Mode mode, VolmitSender sender) {
|
||||
List<String> templateKeys;
|
||||
try {
|
||||
templateKeys = enumerateTemplateKeys();
|
||||
} catch (Throwable e) {
|
||||
sender.sendMessage(C.RED + "Failed to enumerate vanilla structure templates via the server ResourceManager: " + e);
|
||||
return new Report(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
List<String> vanilla = new ArrayList<>();
|
||||
for (String key : templateKeys) {
|
||||
if (key != null && key.startsWith("minecraft:")) {
|
||||
vanilla.add(key);
|
||||
}
|
||||
}
|
||||
Collections.sort(vanilla);
|
||||
|
||||
int total = vanilla.size();
|
||||
int imported = 0;
|
||||
int skipped = 0;
|
||||
int failed = 0;
|
||||
|
||||
if (total == 0) {
|
||||
sender.sendMessage(C.YELLOW + "No vanilla structure templates were found under the 'structure' resource path.");
|
||||
return new Report(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
sender.sendMessage(C.GREEN + "Importing " + C.WHITE + total + C.GREEN + " vanilla structure templates (mode=" + mode + ")...");
|
||||
|
||||
for (String keyString : vanilla) {
|
||||
NamespacedKey nk = NamespacedKey.fromString(keyString.toLowerCase());
|
||||
if (nk == null) {
|
||||
failed++;
|
||||
sender.sendMessage(C.RED + "[fail] " + keyString + ": invalid key");
|
||||
continue;
|
||||
}
|
||||
String name = templateNameFor(keyString);
|
||||
|
||||
try {
|
||||
StructureImporter.Result result = StructureImporter.importStructure(data, nk, name, mode);
|
||||
if (result.success()) {
|
||||
imported++;
|
||||
} else if (result.message() != null && result.message().startsWith("Skipped")) {
|
||||
skipped++;
|
||||
} else {
|
||||
failed++;
|
||||
sender.sendMessage(C.RED + "[fail] " + keyString + ": " + result.message());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
failed++;
|
||||
sender.sendMessage(C.RED + "[fail] " + keyString + ": " + e.getMessage());
|
||||
}
|
||||
|
||||
int processed = imported + skipped + failed;
|
||||
if (processed % 50 == 0) {
|
||||
sender.sendMessage(C.GRAY + "..." + processed + "/" + total + " (" + imported + " imported, " + skipped + " skipped, " + failed + " failed)");
|
||||
}
|
||||
}
|
||||
|
||||
StructureIndexService.write(data);
|
||||
|
||||
sender.sendMessage(C.GREEN + "Template import complete: " + C.WHITE + imported + C.GREEN + " imported, " + C.WHITE + skipped + C.GREEN + " skipped, " + C.WHITE + failed + C.GREEN + " failed (" + C.WHITE + total + C.GREEN + " total).");
|
||||
return new Report(total, imported, skipped, failed);
|
||||
}
|
||||
|
||||
private static String templateNameFor(String key) {
|
||||
int colon = key.indexOf(':');
|
||||
String namespace = colon >= 0 ? key.substring(0, colon) : "minecraft";
|
||||
String path = colon >= 0 ? key.substring(colon + 1) : key;
|
||||
String safePath = path.toLowerCase().replaceAll("[^a-z0-9_/-]", "_");
|
||||
while (safePath.contains("//")) {
|
||||
safePath = safePath.replace("//", "/");
|
||||
}
|
||||
if (safePath.startsWith("/")) {
|
||||
safePath = safePath.substring(1);
|
||||
}
|
||||
return "minecraft".equals(namespace) ? safePath : namespace + "/" + safePath;
|
||||
}
|
||||
|
||||
private static List<String> enumerateTemplateKeys() throws Exception {
|
||||
Object craftServer = Bukkit.getServer();
|
||||
Object dedicated = invoke(craftServer, "getHandle");
|
||||
Object server = invoke(dedicated, "getServer");
|
||||
Object resourceManager = resolveResourceManager(server);
|
||||
if (resourceManager == null) {
|
||||
throw new IllegalStateException("Could not resolve the server ResourceManager via reflection");
|
||||
}
|
||||
|
||||
Method listResources = null;
|
||||
for (Method m : resourceManager.getClass().getMethods()) {
|
||||
if (m.getName().equals("listResources") && m.getParameterCount() == 2
|
||||
&& m.getParameterTypes()[0] == String.class
|
||||
&& m.getParameterTypes()[1] == Predicate.class
|
||||
&& Map.class.isAssignableFrom(m.getReturnType())) {
|
||||
listResources = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (listResources == null) {
|
||||
throw new NoSuchMethodException("listResources(String, Predicate) on " + resourceManager.getClass().getName());
|
||||
}
|
||||
listResources.setAccessible(true);
|
||||
|
||||
Predicate<Object> endsWithNbt = location -> {
|
||||
String path = identifierPath(location);
|
||||
return path != null && path.endsWith(".nbt");
|
||||
};
|
||||
|
||||
Object resultMap = listResources.invoke(resourceManager, "structure", endsWithNbt);
|
||||
TreeSet<String> keys = new TreeSet<>();
|
||||
if (resultMap instanceof Map<?, ?> map) {
|
||||
for (Object location : map.keySet()) {
|
||||
String namespace = identifierNamespace(location);
|
||||
String path = identifierPath(location);
|
||||
if (namespace == null || path == null) {
|
||||
continue;
|
||||
}
|
||||
String stripped = path;
|
||||
if (stripped.startsWith("structure/")) {
|
||||
stripped = stripped.substring("structure/".length());
|
||||
}
|
||||
if (stripped.endsWith(".nbt")) {
|
||||
stripped = stripped.substring(0, stripped.length() - ".nbt".length());
|
||||
}
|
||||
if (stripped.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
keys.add(namespace + ":" + stripped);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(keys);
|
||||
}
|
||||
|
||||
private static Object resolveResourceManager(Object server) {
|
||||
try {
|
||||
Class<?> resourceManagerClass = Class.forName("net.minecraft.server.packs.resources.ResourceManager");
|
||||
Method getter = null;
|
||||
for (Method m : server.getClass().getMethods()) {
|
||||
if (m.getName().equals("getResourceManager") && m.getParameterCount() == 0
|
||||
&& resourceManagerClass.isAssignableFrom(m.getReturnType())) {
|
||||
getter = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (getter == null) {
|
||||
for (Method m : server.getClass().getMethods()) {
|
||||
if (m.getParameterCount() == 0 && resourceManagerClass.isAssignableFrom(m.getReturnType())) {
|
||||
getter = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getter == null) {
|
||||
return null;
|
||||
}
|
||||
getter.setAccessible(true);
|
||||
return getter.invoke(server);
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String identifierNamespace(Object location) {
|
||||
try {
|
||||
Method m = location.getClass().getMethod("getNamespace");
|
||||
m.setAccessible(true);
|
||||
Object value = m.invoke(location);
|
||||
return value == null ? null : value.toString();
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String identifierPath(Object location) {
|
||||
try {
|
||||
Method m = location.getClass().getMethod("getPath");
|
||||
m.setAccessible(true);
|
||||
Object value = m.invoke(location);
|
||||
return value == null ? null : value.toString();
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Object invoke(Object target, String method) throws Exception {
|
||||
Class<?> c = target.getClass();
|
||||
while (c != null) {
|
||||
for (Method m : c.getDeclaredMethods()) {
|
||||
if (m.getName().equals(method) && m.getParameterCount() == 0) {
|
||||
m.setAccessible(true);
|
||||
return m.invoke(target);
|
||||
}
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
for (Method m : target.getClass().getMethods()) {
|
||||
if (m.getName().equals(method) && m.getParameterCount() == 0) {
|
||||
m.setAccessible(true);
|
||||
return m.invoke(target);
|
||||
}
|
||||
}
|
||||
throw new NoSuchMethodException(method + " on " + target.getClass().getName());
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,10 @@ public final class StructureImporter {
|
||||
return (key.getNamespace() + "_" + key.getKey()).replace('/', '_').replace(':', '_');
|
||||
}
|
||||
|
||||
public static String deriveName(String key) {
|
||||
return key.toLowerCase().replace('/', '_').replace(':', '_');
|
||||
}
|
||||
|
||||
public static Result importStructure(IrisData data, NamespacedKey key, String name, Mode mode) {
|
||||
Structure structure;
|
||||
try {
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
package art.arcane.iris.core.structure;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.object.IrisObject;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
|
||||
import java.io.File;
|
||||
@@ -98,6 +100,10 @@ public final class VillageImporter {
|
||||
return new Result(false, "Could not resolve the start pool key for " + structureKey, 0, 0);
|
||||
}
|
||||
|
||||
if (mode == StructureImporter.Mode.ADD_ONLY && new File(data.getDataFolder(), "structures/" + name + ".json").exists()) {
|
||||
return new Result(false, "Skipped (add-only): '" + name + "' already exists", 0, 0);
|
||||
}
|
||||
|
||||
Set<String> visitedPools = new HashSet<>();
|
||||
Set<String> importedPieces = new HashSet<>();
|
||||
Deque<String> poolQueue = new ArrayDeque<>();
|
||||
@@ -106,6 +112,7 @@ public final class VillageImporter {
|
||||
Map<String, Map<String, Object>> emittedPools = new LinkedHashMap<>();
|
||||
Map<String, Map<String, Object>> emittedPieces = new LinkedHashMap<>();
|
||||
List<String> errors = new ArrayList<>();
|
||||
String emptyPieceName = null;
|
||||
int pieceBlocks = 0;
|
||||
|
||||
while (!poolQueue.isEmpty()) {
|
||||
@@ -161,6 +168,21 @@ public final class VillageImporter {
|
||||
}
|
||||
String templateLocation = templateLocationOf(element);
|
||||
if (templateLocation == null) {
|
||||
String elementType = element.getClass().getSimpleName();
|
||||
if (elementType.endsWith("EmptyPoolElement")) {
|
||||
if (emptyPieceName == null) {
|
||||
emptyPieceName = name + "/piece/empty";
|
||||
if (importedPieces.add(emptyPieceName) && writeEmptyPiece(data, emptyPieceName)) {
|
||||
emittedPieces.put(emptyPieceName, pieceJson(emptyPieceName, new ArrayList<Map<String, Object>>()));
|
||||
}
|
||||
}
|
||||
Map<String, Object> emptyEntry = new LinkedHashMap<>();
|
||||
emptyEntry.put("piece", emptyPieceName);
|
||||
emptyEntry.put("weight", weight);
|
||||
pieceEntries.add(emptyEntry);
|
||||
} else {
|
||||
errors.add("skipped unsupported element " + elementType + " in pool " + poolKey);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
NamespacedKey pieceNbtKey = NamespacedKey.fromString(templateLocation.toLowerCase());
|
||||
@@ -588,6 +610,19 @@ public final class VillageImporter {
|
||||
new File(data.getDataFolder(), "structures/" + pieceName + ".json").delete();
|
||||
}
|
||||
|
||||
private static boolean writeEmptyPiece(IrisData data, String pieceName) {
|
||||
try {
|
||||
File objectFile = new File(data.getDataFolder(), "objects/" + pieceName + ".iob");
|
||||
objectFile.getParentFile().mkdirs();
|
||||
IrisObject object = new IrisObject(1, 1, 1);
|
||||
object.setUnsigned(0, 0, 0, Material.AIR.createBlockData());
|
||||
object.write(objectFile);
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeJson(File file, Map<String, Object> content) throws Exception {
|
||||
file.getParentFile().mkdirs();
|
||||
String json = new GsonBuilder().setPrettyPrinting().create().toJson(content);
|
||||
|
||||
@@ -41,8 +41,8 @@ public class PlacedStructurePiece {
|
||||
private final int maxZ;
|
||||
|
||||
public boolean intersects(PlacedStructurePiece o) {
|
||||
return minX <= o.maxX && maxX >= o.minX
|
||||
&& minY <= o.maxY && maxY >= o.minY
|
||||
&& minZ <= o.maxZ && maxZ >= o.minZ;
|
||||
return minX < o.maxX && maxX > o.minX
|
||||
&& minY < o.maxY && maxY > o.minY
|
||||
&& minZ < o.maxZ && maxZ > o.minZ;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-6
@@ -86,9 +86,22 @@ public class IrisStructureComponent extends IrisMantleComponent {
|
||||
|
||||
int sx = (cx << 4) + rng.i(0, 15);
|
||||
int sz = (cz << 4) + rng.i(0, 15);
|
||||
int surfaceY = getEngineMantle().getEngine().getHeight(sx, sz, true) + getEngineMantle().getEngine().getMinHeight();
|
||||
if (surfaceY < placement.getMinHeight() || surfaceY > placement.getMaxHeight()) {
|
||||
return;
|
||||
int baseY;
|
||||
if (placement.isUnderground()) {
|
||||
int worldMinY = getEngineMantle().getEngine().getMinHeight() + 1;
|
||||
int worldMaxY = getEngineMantle().getEngine().getMinHeight() + getEngineMantle().getEngine().getHeight() - 1;
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
baseY = bandMin == bandMax ? bandMin : rng.i(bandMin, bandMax);
|
||||
} else {
|
||||
int surfaceY = getEngineMantle().getEngine().getHeight(sx, sz, true) + getEngineMantle().getEngine().getMinHeight();
|
||||
if (surfaceY < placement.getMinHeight() || surfaceY > placement.getMaxHeight()) {
|
||||
return;
|
||||
}
|
||||
baseY = surfaceY;
|
||||
}
|
||||
|
||||
String key = placement.getStructures().get(rng.i(0, placement.getStructures().size() - 1));
|
||||
@@ -97,14 +110,14 @@ public class IrisStructureComponent extends IrisMantleComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
StructureAssembler assembler = new StructureAssembler(getData(), structure, sx, surfaceY, sz);
|
||||
StructureAssembler assembler = new StructureAssembler(getData(), structure, sx, baseY, sz);
|
||||
KList<PlacedStructurePiece> pieces = assembler.assemble(rng);
|
||||
if (pieces == null || pieces.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectPlaceMode mode = structure.getPlaceMode();
|
||||
if (mode == ObjectPlaceMode.STRUCTURE_PIECE || mode == ObjectPlaceMode.FLOATING) {
|
||||
if (placement.isUnderground() || mode == ObjectPlaceMode.STRUCTURE_PIECE || mode == ObjectPlaceMode.FLOATING) {
|
||||
for (PlacedStructurePiece p : pieces) {
|
||||
placeObject(writer, structure, p, ObjectPlaceMode.STRUCTURE_PIECE, p.getY(), rng);
|
||||
}
|
||||
@@ -115,7 +128,7 @@ public class IrisStructureComponent extends IrisMantleComponent {
|
||||
for (PlacedStructurePiece p : pieces) {
|
||||
lowest = Math.min(lowest, p.getMinY());
|
||||
}
|
||||
int shift = surfaceY - lowest;
|
||||
int shift = baseY - lowest;
|
||||
for (PlacedStructurePiece p : pieces) {
|
||||
placeObject(writer, structure, p, ObjectPlaceMode.STRUCTURE_PIECE, p.getY() + shift, rng);
|
||||
}
|
||||
|
||||
+8
-1
@@ -507,6 +507,9 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
int density = objectPlacement.getDensity(rng, minX, minZ, getData());
|
||||
KMap<Long, KList<Integer>> anchorCache = new KMap<>();
|
||||
IrisCaveAnchorMode anchorMode = resolveAnchorMode(objectPlacement, caveProfile);
|
||||
if (objectPlacement.getMode() == ObjectPlaceMode.CEILING_HANG) {
|
||||
anchorMode = IrisCaveAnchorMode.CEILING;
|
||||
}
|
||||
int anchorScanStep = resolveAnchorScanStep(caveProfile);
|
||||
int objectMinDepthBelowSurface = resolveObjectMinDepthBelowSurface(caveProfile);
|
||||
int anchorSearchAttempts = resolveAnchorSearchAttempts(caveProfile);
|
||||
@@ -595,7 +598,11 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
try {
|
||||
int caveCeiling = findCaveCeiling(writer, x, y, z);
|
||||
IObjectPlacer clampedPlacer = new CeilingClampedPlacer(writer, caveCeiling);
|
||||
int result = object.place(x, y, z, clampedPlacer, effectivePlacement, rng, (b, data) -> {
|
||||
int placeY = y;
|
||||
if (effectivePlacement.getMode() == ObjectPlaceMode.CEILING_HANG) {
|
||||
placeY = Math.max(1, caveCeiling - 1 - Math.floorDiv(object.getH(), 2));
|
||||
}
|
||||
int result = object.place(x, placeY, z, clampedPlacer, effectivePlacement, rng, (b, data) -> {
|
||||
wrotePlacementData.set(true);
|
||||
String marker = placementMarker(object, id, "cave");
|
||||
if (marker != null) {
|
||||
|
||||
@@ -692,9 +692,12 @@ public class IrisObject extends IrisRegistrant {
|
||||
|
||||
boolean warped = !config.getWarp().isFlat();
|
||||
boolean rawStructurePiece = config.getMode() == ObjectPlaceMode.STRUCTURE_PIECE;
|
||||
boolean organicFloor = config.getMode() == ObjectPlaceMode.ORGANIC_STILT;
|
||||
boolean ceilingHang = config.getMode() == ObjectPlaceMode.CEILING_HANG;
|
||||
boolean organic = organicFloor || ceilingHang;
|
||||
boolean stilting = (config.getMode().equals(ObjectPlaceMode.STILT) || config.getMode().equals(ObjectPlaceMode.FAST_STILT) ||
|
||||
config.getMode() == ObjectPlaceMode.MIN_STILT || config.getMode() == ObjectPlaceMode.FAST_MIN_STILT ||
|
||||
config.getMode() == ObjectPlaceMode.CENTER_STILT || config.getMode() == ObjectPlaceMode.ERODE_STILT);
|
||||
config.getMode() == ObjectPlaceMode.CENTER_STILT || config.getMode() == ObjectPlaceMode.ERODE_STILT || organic);
|
||||
boolean eroding = config.getMode() == ObjectPlaceMode.ERODE_STILT;
|
||||
KMap<Position2, Integer> heightmap = config.getSnow() > 0 ? new KMap<>() : null;
|
||||
int spinx = rng.imax() / 1000;
|
||||
@@ -717,7 +720,8 @@ public class IrisObject extends IrisRegistrant {
|
||||
}
|
||||
}
|
||||
} else if (yv < 0) {
|
||||
if (config.getMode().equals(ObjectPlaceMode.CENTER_HEIGHT) || config.getMode() == ObjectPlaceMode.CENTER_STILT) {
|
||||
if (config.getMode().equals(ObjectPlaceMode.CENTER_HEIGHT) || config.getMode() == ObjectPlaceMode.CENTER_STILT
|
||||
|| organic) {
|
||||
y = (c != null ? c.getSurface() : placer.getHighest(x, z, getLoader(), config.isUnderwater())) + rty;
|
||||
if (!config.isForcePlace()) {
|
||||
if (shouldBailForCarvingAnchor(placer, config, x, y, z)) {
|
||||
@@ -923,6 +927,7 @@ public class IrisObject extends IrisRegistrant {
|
||||
}
|
||||
|
||||
int lowest = Integer.MAX_VALUE;
|
||||
int topLayer = Integer.MIN_VALUE;
|
||||
y += yrand;
|
||||
readLock.lock();
|
||||
|
||||
@@ -990,10 +995,18 @@ public class IrisObject extends IrisRegistrant {
|
||||
BlockVector i = g.clone();
|
||||
BlockData data = d.clone();
|
||||
i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone();
|
||||
if (ceilingHang) {
|
||||
i.setY(-i.getBlockY());
|
||||
}
|
||||
i = config.getTranslate().translate(i.clone(), config.getRotation(), spinx, spiny, spinz).clone();
|
||||
|
||||
if (stilting && i.getBlockY() < lowest && shouldStilt(data)) {
|
||||
lowest = i.getBlockY();
|
||||
if (stilting && shouldStilt(data)) {
|
||||
if (i.getBlockY() < lowest) {
|
||||
lowest = i.getBlockY();
|
||||
}
|
||||
if (i.getBlockY() > topLayer) {
|
||||
topLayer = i.getBlockY();
|
||||
}
|
||||
}
|
||||
|
||||
if (placer.isPreventingDecay() && (data) instanceof Leaves && !((Leaves) (data)).isPersistent()) {
|
||||
@@ -1162,10 +1175,14 @@ public class IrisObject extends IrisRegistrant {
|
||||
|
||||
BlockVector i = g.clone();
|
||||
i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone();
|
||||
if (ceilingHang) {
|
||||
i.setY(-i.getBlockY());
|
||||
}
|
||||
i = config.getTranslate().translate(i.clone(), config.getRotation(), spinx, spiny, spinz).clone();
|
||||
d = config.getRotation().rotate(d, spinx, spiny, spinz);
|
||||
|
||||
if (i.getBlockY() != lowest)
|
||||
int targetLayer = ceilingHang ? topLayer : lowest;
|
||||
if (i.getBlockY() != targetLayer)
|
||||
continue;
|
||||
|
||||
for (IrisObjectReplace j : config.getEdit()) {
|
||||
@@ -1195,14 +1212,67 @@ public class IrisObject extends IrisRegistrant {
|
||||
zz += config.warp(rng, i.getZ() + z, i.getY() + y, i.getX() + x, getLoader());
|
||||
}
|
||||
|
||||
if (organic) {
|
||||
int startY = targetLayer + y;
|
||||
int maxScan = settings != null ? Math.max(1, settings.getOrganicMaxScan()) : 48;
|
||||
int jitterMax = settings != null ? Math.max(0, settings.getOrganicJitter()) : 3;
|
||||
double scratch = settings != null ? Math.max(0, Math.min(1, settings.getOrganicScratch())) : 0.55;
|
||||
long colHash = ((long) xx * 341873128712L) ^ ((long) zz * 132897987541L);
|
||||
int jitter = jitterMax > 0 ? (int) (Math.abs(colHash) % (jitterMax + 1)) : 0;
|
||||
|
||||
if (ceilingHang) {
|
||||
int scan = 0;
|
||||
int solidY = startY + 1;
|
||||
while (scan < maxScan && !placer.isSolid(xx, solidY, zz)) {
|
||||
solidY++;
|
||||
scan++;
|
||||
}
|
||||
int topBound = (scan < maxScan ? solidY - 1 : startY + Math.min(maxScan, 8)) - jitter;
|
||||
int total = topBound - startY;
|
||||
for (int j = startY; j <= topBound; j++) {
|
||||
if (scratch > 0 && total > 0) {
|
||||
double ratio = (double) (j - startY) / total;
|
||||
if (ratio > (1.0 - scratch)) {
|
||||
long sh = ((long) xx * 341873128712L) ^ ((long) j * 132897987541L) ^ ((long) zz * 735791245321L);
|
||||
double skipChance = (ratio - (1.0 - scratch)) / scratch;
|
||||
if ((Math.abs(sh) % 1000) / 1000.0 < skipChance * 0.7) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
placer.set(xx, j, zz, d);
|
||||
}
|
||||
} else {
|
||||
int scan = 0;
|
||||
int solidY = startY - 1;
|
||||
while (scan < maxScan && !placer.isSolid(xx, solidY, zz)) {
|
||||
solidY--;
|
||||
scan++;
|
||||
}
|
||||
int bottomBound = (scan < maxScan ? solidY + 1 : startY - Math.min(maxScan, 8)) + jitter;
|
||||
int total = startY - bottomBound;
|
||||
for (int j = startY; j >= bottomBound; j--) {
|
||||
if (scratch > 0 && total > 0) {
|
||||
double ratio = (double) (startY - j) / total;
|
||||
if (ratio > (1.0 - scratch)) {
|
||||
long sh = ((long) xx * 341873128712L) ^ ((long) j * 132897987541L) ^ ((long) zz * 735791245321L);
|
||||
double skipChance = (ratio - (1.0 - scratch)) / scratch;
|
||||
if ((Math.abs(sh) % 1000) / 1000.0 < skipChance * 0.7) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
placer.set(xx, j, zz, d);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int highest = placer.getHighest(xx, zz, getLoader(), true);
|
||||
|
||||
if (d instanceof Waterlogged && shouldAutoWaterlogBlock(placer, config, yv, xx, highest, zz))
|
||||
((Waterlogged) d).setWaterlogged(true);
|
||||
|
||||
if (yv >= 0 && config.isBottom())
|
||||
y += Math.floorDiv(h, 2);
|
||||
|
||||
int lowerBound = highest - 1;
|
||||
if (settings != null) {
|
||||
lowerBound -= config.getStiltSettings().getOverStilt() - rng.i(0, config.getStiltSettings().getYRand());
|
||||
@@ -1221,6 +1291,9 @@ public class IrisObject extends IrisRegistrant {
|
||||
}
|
||||
|
||||
for (int j = lowest + y; j > lowerBound; j--) {
|
||||
if (B.isFluid(placer.get(xx, j, zz))) {
|
||||
break;
|
||||
}
|
||||
if (eroding) {
|
||||
int depth = (lowest + y) - j;
|
||||
int totalDepth = (lowest + y) - lowerBound;
|
||||
|
||||
@@ -30,5 +30,17 @@ public class IrisStiltSettings {
|
||||
private int overStilt;
|
||||
@Desc("If defined, stilting will be done using this block palette rather than the last layer of the object.")
|
||||
private IrisMaterialPalette palette;
|
||||
@MinNumber(1)
|
||||
@MaxNumber(256)
|
||||
@Desc("For ORGANIC_STILT / CEILING_HANG: the maximum number of blocks to scan toward the cave floor (or ceiling) looking for solid ground before giving up.")
|
||||
private int organicMaxScan = 48;
|
||||
@MinNumber(0)
|
||||
@MaxNumber(32)
|
||||
@Desc("For ORGANIC_STILT / CEILING_HANG: the maximum number of blocks each column's stilt is randomly shortened by, giving the underside a ragged organic edge instead of a flat disc.")
|
||||
private int organicJitter = 3;
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("For ORGANIC_STILT / CEILING_HANG: in the deepest fraction of each stilt column, blocks are randomly skipped to break the tip up. Higher = more broken and scratchy. 0 disables.")
|
||||
private double organicScratch = 0.55;
|
||||
|
||||
}
|
||||
|
||||
@@ -90,12 +90,15 @@ public class IrisStructurePlacement {
|
||||
@Desc("IRIS_PLACED only: scale applied to the placed structure.")
|
||||
private IrisObjectScale scale = new IrisObjectScale();
|
||||
|
||||
@Desc("The minimum world Y this structure may start at.")
|
||||
@Desc("IRIS_PLACED: when underground=false this is the minimum surface Y the placement is allowed at (a gate); when underground=true this is the lower bound of the Y band the structure is placed within.")
|
||||
private int minHeight = -2032;
|
||||
|
||||
@Desc("The maximum world Y this structure may start at.")
|
||||
@Desc("IRIS_PLACED: when underground=false this is the maximum surface Y the placement is allowed at (a gate); when underground=true this is the upper bound of the Y band the structure is placed within.")
|
||||
private int maxHeight = 2032;
|
||||
|
||||
@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("If false, this placement is skipped underwater.")
|
||||
private boolean underwater = false;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,14 @@ public enum ObjectPlaceMode {
|
||||
|
||||
ERODE_STILT,
|
||||
|
||||
@Desc("Organic stilting scans straight down from the bottom of the object to the first solid block (the cave floor, or terrain) and fills the gap with the object's own bottom block. Each column stops at a slightly different, noise-varied depth and the lower portion is randomly broken up, so the underside reads as rough organic roots rather than a flat disc. Designed for objects placed inside caves so they connect to the floor instead of floating. Tune with stilt-settings (organicMaxScan, organicJitter, organicScratch).")
|
||||
|
||||
ORGANIC_STILT,
|
||||
|
||||
@Desc("Hangs the object upside-down from a cave ceiling. The object is flipped vertically (its tip points down like a stalactite) and its top is anchored to the ceiling, while an organic, noise-varied stilt grows up into the ceiling so it never floats below the roof. Use inside caves; tune with stilt-settings (organicMaxScan, organicJitter, organicScratch).")
|
||||
|
||||
CEILING_HANG,
|
||||
|
||||
@Desc("Samples the height of the terrain at every x,z position of your object and pushes it down to the surface. It's pretty much like a melt function over the terrain.")
|
||||
|
||||
PAINT,
|
||||
|
||||
Reference in New Issue
Block a user