mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-28 21:11:16 +00:00
Vwoop
This commit is contained in:
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2026 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.commands;
|
||||
|
||||
final class BukkitNativeStructureLocatePolicy {
|
||||
private static final String MONUMENT_KEY = "minecraft:monument";
|
||||
private static final String UNAVAILABLE_MESSAGE = "Native monument locating is unavailable on Paper, Purpur, and Folia because a cold search can stall the server thread. The monument lookup was skipped; monument generation is unaffected.";
|
||||
|
||||
private BukkitNativeStructureLocatePolicy() {
|
||||
}
|
||||
|
||||
static boolean isUnavailable(String structureKey) {
|
||||
return structureKey != null && MONUMENT_KEY.equalsIgnoreCase(structureKey.trim());
|
||||
}
|
||||
|
||||
static String unavailableMessage() {
|
||||
return UNAVAILABLE_MESSAGE;
|
||||
}
|
||||
}
|
||||
+53
-26
@@ -21,12 +21,16 @@ package art.arcane.iris.core.commands;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.platform.bukkit.BukkitPlatform;
|
||||
import art.arcane.iris.core.service.ObjectStudioSaveService;
|
||||
import art.arcane.iris.core.structure.NativeStructureLocateCapability;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
|
||||
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.IrisNativeStructureDecision;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.ObjectHandler;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.StructureHandler;
|
||||
@@ -117,13 +121,34 @@ public class CommandFind implements DirectorExecutor {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IrisStructureLocator.isPlaced(e, structure)) {
|
||||
locateIrisStructure(e, structure, commandSender);
|
||||
String structureKey = structure == null ? "" : structure.trim();
|
||||
Structure nativeStructure = resolveNativeStructure(structureKey);
|
||||
if (nativeStructure != null) {
|
||||
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(
|
||||
e, structureKey, false);
|
||||
if (!decision.generate()
|
||||
&& decision.status() != NativeStructureGenerationStatus.REPLACED_BY_IRIS) {
|
||||
commandSender.sendMessage(C.RED + NativeStructureGenerationPolicy.generationStatusMessage(
|
||||
structureKey, decision.status()));
|
||||
return;
|
||||
}
|
||||
if (decision.status() == NativeStructureGenerationStatus.REPLACED_BY_IRIS) {
|
||||
locateIrisStructure(e, structureKey, commandSender);
|
||||
return;
|
||||
}
|
||||
if (NativeStructureLocateCapability.isPaperUnavailable(structureKey)) {
|
||||
commandSender.sendMessage(C.RED + NativeStructureLocateCapability.unavailableMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (nativeStructure == null && IrisStructureLocator.isPlaced(e, structureKey)) {
|
||||
locateIrisStructure(e, structureKey, commandSender);
|
||||
return;
|
||||
}
|
||||
|
||||
if (BukkitNativeStructureLocatePolicy.isUnavailable(structure)) {
|
||||
commandSender.sendMessage(C.RED + BukkitNativeStructureLocatePolicy.unavailableMessage());
|
||||
if (nativeStructure == null) {
|
||||
commandSender.sendMessage(C.RED + "Unknown structure: " + structureKey);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -135,42 +160,44 @@ public class CommandFind implements DirectorExecutor {
|
||||
|
||||
World targetWorld = target.getWorld();
|
||||
Location origin = target.getLocation();
|
||||
commandSender.sendMessage(C.GRAY + "Locating " + structure + "...");
|
||||
commandSender.sendMessage(C.GRAY + "Locating " + structureKey + "...");
|
||||
J.s(() -> {
|
||||
try {
|
||||
Registry<Structure> structureRegistry = RegistryAccess.registryAccess().getRegistry(RegistryKey.STRUCTURE);
|
||||
Structure match = null;
|
||||
for (Structure candidate : structureRegistry) {
|
||||
NamespacedKey key = structureRegistry.getKey(candidate);
|
||||
if (key != null && key.toString().equalsIgnoreCase(structure)) {
|
||||
match = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match == null) {
|
||||
sendStructureMessage(target, commandSender, C.RED + "Unknown structure: " + structure);
|
||||
return;
|
||||
}
|
||||
if (!StructureReachability.isReachable(e, structure)) {
|
||||
KList<String> miss = StructureReachability.missingBiomeKeys(e, structure);
|
||||
if (!StructureReachability.isReachable(e, structureKey)) {
|
||||
KList<String> miss = StructureReachability.missingBiomeKeys(e, structureKey);
|
||||
sendStructureMessage(target, commandSender,
|
||||
C.YELLOW + structure + " cannot generate in this world (its required biomes are not produced by this pack"
|
||||
C.YELLOW + structureKey + " cannot generate in this world (its required biomes are not produced by this pack"
|
||||
+ (miss.isEmpty() ? "" : ": needs " + String.join("/", miss)) + ").");
|
||||
return;
|
||||
}
|
||||
StructureSearchResult result = targetWorld.locateNearestStructure(origin, match, 100, false);
|
||||
StructureSearchResult result = targetWorld.locateNearestStructure(
|
||||
origin, nativeStructure, 100, false);
|
||||
if (result == null || result.getLocation() == null) {
|
||||
sendStructureMessage(target, commandSender, C.YELLOW + "No " + structure + " found within range of you.");
|
||||
sendStructureMessage(target, commandSender,
|
||||
C.YELLOW + "No " + structureKey + " found within range of you.");
|
||||
return;
|
||||
}
|
||||
prepareStructureTeleport(target, targetWorld, commandSender, structure, result.getLocation(), false);
|
||||
prepareStructureTeleport(
|
||||
target, targetWorld, commandSender, structureKey, result.getLocation(), false);
|
||||
} catch (Throwable t) {
|
||||
sendStructureMessage(target, commandSender, C.RED + "Could not locate " + structure + ": " + t.getClass().getSimpleName());
|
||||
Iris.reportError("Could not locate structure '" + structure + "'.", t);
|
||||
sendStructureMessage(target, commandSender,
|
||||
C.RED + "Could not locate " + structureKey + ": " + t.getClass().getSimpleName());
|
||||
Iris.reportError("Could not locate structure '" + structureKey + "'.", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Structure resolveNativeStructure(String structureKey) {
|
||||
Registry<Structure> structureRegistry = RegistryAccess.registryAccess().getRegistry(RegistryKey.STRUCTURE);
|
||||
for (Structure candidate : structureRegistry) {
|
||||
NamespacedKey key = structureRegistry.getKey(candidate);
|
||||
if (key != null && key.toString().equalsIgnoreCase(structureKey)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void locateIrisStructure(Engine engine, String structure, VolmitSender commandSender) {
|
||||
Player target = player();
|
||||
if (target == null) {
|
||||
|
||||
+34
-172
@@ -22,15 +22,13 @@ import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.platform.bukkit.BukkitPlatform;
|
||||
import art.arcane.iris.core.link.WorldEditLink;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.loader.ResourceLoader;
|
||||
import art.arcane.iris.core.runtime.ObjectStudioActivation;
|
||||
import art.arcane.iris.core.runtime.WorldRuntimeControlService;
|
||||
import art.arcane.iris.core.service.ObjectSVC;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
import art.arcane.iris.core.service.WandSVC;
|
||||
import art.arcane.iris.core.tools.IrisConverter;
|
||||
import art.arcane.iris.core.tools.PlausibilizeMode;
|
||||
import art.arcane.iris.core.tools.TreePlausibilizer;
|
||||
import art.arcane.iris.core.tools.TreePlausibilizeBatch;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IObjectPlacer;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
@@ -353,199 +351,63 @@ public class CommandObject implements DirectorExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Make tree leaves vanilla-decay-plausible (every leaf within 6 blocks of a log)",
|
||||
@Director(description = "Grow organic branches through the canopy so every leaf survives vanilla decay",
|
||||
origin = DirectorOrigin.BOTH)
|
||||
public void plausibilize(
|
||||
@Param(description = "Object key, prefix (trees/), or filesystem path",
|
||||
customHandler = ObjectTargetHandler.class)
|
||||
String target,
|
||||
@Param(description = "DEFAULT: tentacle logs, delete orphans. NORMALIZE: + flip persistent=false. FOLIAGE_OVERATURE: add leaves to bridge orphans, no deletions. SMOKE: wipe & repaint canopy shell.",
|
||||
defaultValue = "DEFAULT")
|
||||
PlausibilizeMode mode,
|
||||
@Param(description = "Analyze only, do not write", defaultValue = "false")
|
||||
@Param(name = "dryrun", description = "dryrun=true analyzes only, writes nothing", defaultValue = "false")
|
||||
boolean dryRun,
|
||||
@Param(description = "Canopy shell radius (SMOKE only), clamped [0,5]", defaultValue = "2")
|
||||
int radius
|
||||
@Param(name = "reach", description = "reach=N max branch length in blocks from existing wood; farther leaf clusters are pinned persistent instead. reach=0 grows unlimited", defaultValue = "12")
|
||||
int reach
|
||||
) {
|
||||
List<Target> targets = resolveTargets(target);
|
||||
IrisData nearest = data();
|
||||
List<TreePlausibilizeBatch.Target> targets = TreePlausibilizeBatch.resolve(target, nearest);
|
||||
if (targets.isEmpty() && nearest == null) {
|
||||
targets = resolveFromPacks(target);
|
||||
}
|
||||
if (targets.isEmpty()) {
|
||||
sender().sendMessage(C.RED + "No objects matched: " + target);
|
||||
return;
|
||||
}
|
||||
|
||||
sender().sendMessage(C.IRIS + "Plausibilize [" + mode.name()
|
||||
+ (dryRun ? " DRY" : "")
|
||||
+ (mode == PlausibilizeMode.SMOKE ? " r=" + radius : "")
|
||||
sender().sendMessage(C.IRIS + "Plausibilize [reach=" + reach + (dryRun ? ", DRY" : "")
|
||||
+ "] queued " + targets.size() + " object(s)");
|
||||
|
||||
org.bukkit.command.CommandSender s = sender();
|
||||
J.a(() -> runPlausibilize(targets, dryRun, mode, radius, s));
|
||||
List<TreePlausibilizeBatch.Target> queued = targets;
|
||||
J.a(() -> TreePlausibilizeBatch.run(queued, dryRun, reach, nearest, (String line) ->
|
||||
s.sendMessage(line.startsWith("Done:") || line.startsWith("Totals:") || line.startsWith("[")
|
||||
? C.IRIS + line
|
||||
: C.GRAY + " " + line)));
|
||||
}
|
||||
|
||||
private List<Target> resolveTargets(String target) {
|
||||
List<Target> out = new ArrayList<>();
|
||||
if (target == null || target.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
|
||||
File direct = new File(target);
|
||||
if (direct.isFile() && target.toLowerCase().endsWith(".iob")) {
|
||||
out.add(new Target(direct.getName().replaceAll("\\.iob$", ""), direct));
|
||||
return out;
|
||||
}
|
||||
if (direct.isDirectory()) {
|
||||
walkIob(direct, direct, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
IrisData irisData = data();
|
||||
if (irisData != null) {
|
||||
ResourceLoader<IrisObject> loader = irisData.getObjectLoader();
|
||||
if (!target.endsWith("/") && loader.findFile(target) != null) {
|
||||
out.add(new Target(target, null));
|
||||
return out;
|
||||
}
|
||||
String prefix = target.endsWith("/") ? target : target + "/";
|
||||
for (String k : loader.getPossibleKeys()) {
|
||||
if (k.startsWith(prefix)) {
|
||||
out.add(new Target(k, null));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static List<TreePlausibilizeBatch.Target> resolveFromPacks(String target) {
|
||||
List<TreePlausibilizeBatch.Target> out = new ArrayList<>();
|
||||
File packsFolder = Iris.instance.getDataFolder("packs");
|
||||
File[] packs = packsFolder.listFiles(File::isDirectory);
|
||||
if (packs != null) {
|
||||
for (File pack : packs) {
|
||||
File objectsRoot = new File(pack, "objects");
|
||||
if (!objectsRoot.isDirectory()) continue;
|
||||
File candidate = new File(objectsRoot, target + ".iob");
|
||||
if (candidate.isFile()) {
|
||||
out.add(new Target(pack.getName() + "/" + target, candidate));
|
||||
continue;
|
||||
}
|
||||
File candidateDir = new File(objectsRoot, target);
|
||||
if (candidateDir.isDirectory()) {
|
||||
walkIob(candidateDir, objectsRoot, out);
|
||||
}
|
||||
if (packs == null) {
|
||||
return out;
|
||||
}
|
||||
for (File pack : packs) {
|
||||
File objectsRoot = new File(pack, "objects");
|
||||
if (!objectsRoot.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
File candidate = new File(objectsRoot, target + ".iob");
|
||||
if (candidate.isFile()) {
|
||||
out.add(new TreePlausibilizeBatch.Target(pack.getName() + "/" + target, candidate));
|
||||
continue;
|
||||
}
|
||||
File candidateDir = new File(objectsRoot, target);
|
||||
if (candidateDir.isDirectory()) {
|
||||
TreePlausibilizeBatch.walkIob(candidateDir, objectsRoot, out);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void walkIob(File root, File keyRoot, List<Target> out) {
|
||||
File[] kids = root.listFiles();
|
||||
if (kids == null) return;
|
||||
for (File f : kids) {
|
||||
if (f.isDirectory()) {
|
||||
walkIob(f, keyRoot, out);
|
||||
} else if (f.getName().toLowerCase().endsWith(".iob")) {
|
||||
String rel = keyRoot.toPath().relativize(f.toPath()).toString()
|
||||
.replace(File.separatorChar, '/')
|
||||
.replaceAll("\\.iob$", "");
|
||||
out.add(new Target(rel, f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record Target(String key, File file) {
|
||||
}
|
||||
|
||||
private static void runPlausibilize(
|
||||
List<Target> targets,
|
||||
boolean dryRun,
|
||||
PlausibilizeMode mode,
|
||||
int radius,
|
||||
org.bukkit.command.CommandSender s
|
||||
) {
|
||||
int processed = 0;
|
||||
int skipped = 0;
|
||||
int failed = 0;
|
||||
int changed = 0;
|
||||
long totalLogsAdded = 0L;
|
||||
long totalLeavesAdded = 0L;
|
||||
long totalLeavesRemoved = 0L;
|
||||
long totalNormalized = 0L;
|
||||
long totalUnreachableAfter = 0L;
|
||||
|
||||
int progressStep = Math.max(1, targets.size() / 20);
|
||||
int index = 0;
|
||||
|
||||
for (Target t : targets) {
|
||||
index++;
|
||||
try {
|
||||
IrisObject o = loadTarget(t);
|
||||
if (o == null) {
|
||||
s.sendMessage(C.YELLOW + " skip " + t.key() + ": failed to load");
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
TreePlausibilizer.Result r = dryRun
|
||||
? TreePlausibilizer.analyze(o, mode, radius)
|
||||
: TreePlausibilizer.apply(o, mode, radius);
|
||||
|
||||
if (r.skipReason() != null) {
|
||||
s.sendMessage(C.YELLOW + " skip " + t.key() + ": " + r.skipReason());
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean touched = r.logsAdded() > 0 || r.leavesAdded() > 0
|
||||
|| r.leavesRemoved() > 0 || r.leavesNormalized() > 0;
|
||||
if (!dryRun && touched) {
|
||||
File dest = o.getLoadFile() != null ? o.getLoadFile() : t.file();
|
||||
if (dest != null) {
|
||||
o.write(dest);
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
|
||||
processed++;
|
||||
totalLogsAdded += r.logsAdded();
|
||||
totalLeavesAdded += r.leavesAdded();
|
||||
totalLeavesRemoved += r.leavesRemoved();
|
||||
totalNormalized += r.leavesNormalized();
|
||||
totalUnreachableAfter += r.unreachableAfter();
|
||||
|
||||
if (touched || targets.size() == 1) {
|
||||
s.sendMessage(C.GRAY + " " + t.key()
|
||||
+ C.WHITE + " +" + r.logsAdded() + " logs"
|
||||
+ C.WHITE + " +" + r.leavesAdded() + " leaves"
|
||||
+ C.WHITE + " -" + r.leavesRemoved() + " removed"
|
||||
+ (r.leavesNormalized() > 0 ? C.WHITE + " ~" + r.leavesNormalized() + " normalized" : "")
|
||||
+ (r.unreachableAfter() > 0 ? C.YELLOW + " " + r.unreachableAfter() + " unreachable" : ""));
|
||||
}
|
||||
|
||||
if (targets.size() > 1 && index % progressStep == 0) {
|
||||
s.sendMessage(C.IRIS + " [" + index + "/" + targets.size() + "]");
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
s.sendMessage(C.RED + " fail " + t.key() + ": " + e.getClass().getSimpleName()
|
||||
+ ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
s.sendMessage(C.IRIS + "Done: " + processed + " processed, " + changed + " changed, "
|
||||
+ skipped + " skipped, " + failed + " failed");
|
||||
s.sendMessage(C.IRIS + "Totals: +" + totalLogsAdded + " logs, +" + totalLeavesAdded + " leaves, -"
|
||||
+ totalLeavesRemoved + " removed, ~" + totalNormalized + " normalized, "
|
||||
+ totalUnreachableAfter + " unreachable");
|
||||
}
|
||||
|
||||
private static IrisObject loadTarget(Target t) throws IOException {
|
||||
if (t.file() != null) {
|
||||
IrisObject o = new IrisObject();
|
||||
o.read(t.file());
|
||||
o.setLoadFile(t.file());
|
||||
return o;
|
||||
}
|
||||
return IrisData.loadAnyObject(t.key(), null);
|
||||
}
|
||||
|
||||
@Director(description = "Convert .schem files in the 'convert' folder to .iob files.")
|
||||
public void convert () {
|
||||
try {
|
||||
|
||||
+36
-20
@@ -21,18 +21,23 @@ package art.arcane.iris.core.commands;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.structure.BulkStructureImporter;
|
||||
import art.arcane.iris.core.structure.NativeStructureLocateCapability;
|
||||
import art.arcane.iris.core.structure.StructureCaptureImporter;
|
||||
import art.arcane.iris.core.structure.StructureImporter;
|
||||
import art.arcane.iris.core.structure.StructureIndexService;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
|
||||
import art.arcane.iris.engine.framework.PlacedStructurePiece;
|
||||
import art.arcane.iris.engine.framework.StructureAssembler;
|
||||
import art.arcane.iris.engine.framework.StructureReachability;
|
||||
import art.arcane.iris.engine.object.IObjectPlacer;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
|
||||
import art.arcane.iris.engine.object.IrisObjectPlacement;
|
||||
import art.arcane.iris.engine.object.IrisPosition;
|
||||
import art.arcane.iris.engine.object.IrisStructure;
|
||||
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
|
||||
import art.arcane.iris.engine.object.ObjectPlaceMode;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
@@ -158,14 +163,23 @@ public class CommandStructure implements DirectorExecutor {
|
||||
private void runVerification(Engine engine, KList<String> structureKeys, int centerX, int centerZ,
|
||||
int searchRadius, VolmitSender commandSender, Player target) {
|
||||
KList<String> messages = new KList<>();
|
||||
Set<String> reachable;
|
||||
try {
|
||||
reachable = StructureReachability.reachableKeys(engine);
|
||||
} catch (Throwable error) {
|
||||
messages.add(C.RED + "Structure verification could not resolve native biome reachability.");
|
||||
sendVerificationMessages(commandSender, target, messages);
|
||||
Iris.reportError("Could not resolve native structure biome reachability for verification.", error);
|
||||
return;
|
||||
Map<String, IrisNativeStructureDecision> decisions = new HashMap<>(structureKeys.size());
|
||||
boolean requiresNativeReachability = false;
|
||||
for (String keyName : structureKeys) {
|
||||
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(engine, keyName, false);
|
||||
decisions.put(keyName, decision);
|
||||
requiresNativeReachability |= decision.generate();
|
||||
}
|
||||
Set<String> reachable = Set.of();
|
||||
if (requiresNativeReachability) {
|
||||
try {
|
||||
reachable = StructureReachability.reachableKeys(engine);
|
||||
} catch (Throwable error) {
|
||||
messages.add(C.RED + "Structure verification could not resolve native biome reachability.");
|
||||
sendVerificationMessages(commandSender, target, messages);
|
||||
Iris.reportError("Could not resolve native structure biome reachability for verification.", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
int located = 0;
|
||||
int nativeEligible = 0;
|
||||
@@ -175,8 +189,8 @@ public class CommandStructure implements DirectorExecutor {
|
||||
int searchLimited = 0;
|
||||
int errors = 0;
|
||||
for (String keyName : structureKeys) {
|
||||
boolean irisPlaced = IrisStructureLocator.isPlaced(engine, keyName);
|
||||
if (irisPlaced) {
|
||||
IrisNativeStructureDecision decision = decisions.get(keyName);
|
||||
if (decision.status() == NativeStructureGenerationStatus.REPLACED_BY_IRIS) {
|
||||
try {
|
||||
IrisStructureLocator.LocateResult result =
|
||||
IrisStructureLocator.locate(engine, keyName, centerX, centerZ, searchRadius);
|
||||
@@ -201,7 +215,7 @@ public class CommandStructure implements DirectorExecutor {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!engine.getDimension().getImportedStructures().shouldGenerate(keyName)) {
|
||||
if (!decision.generate()) {
|
||||
disabled++;
|
||||
messages.add(C.GRAY + "[disabled] " + C.WHITE + keyName);
|
||||
continue;
|
||||
@@ -213,20 +227,20 @@ public class CommandStructure implements DirectorExecutor {
|
||||
+ (missing.isEmpty() ? "" : C.YELLOW + " needs " + String.join("/", missing)));
|
||||
continue;
|
||||
}
|
||||
if (BukkitNativeStructureLocatePolicy.isUnavailable(keyName)) {
|
||||
nativeEligible++;
|
||||
if (NativeStructureLocateCapability.isPaperUnavailable(keyName)) {
|
||||
unavailable++;
|
||||
messages.add(C.RED + "[unavailable] " + C.WHITE + keyName + C.RED + ": "
|
||||
+ BukkitNativeStructureLocatePolicy.unavailableMessage());
|
||||
messages.add(C.YELLOW + "[native-eligible/locate-unavailable] " + C.WHITE + keyName + C.YELLOW + ": "
|
||||
+ NativeStructureLocateCapability.unavailableMessage());
|
||||
continue;
|
||||
}
|
||||
nativeEligible++;
|
||||
messages.add(C.GREEN + "[native-eligible] " + C.WHITE + keyName);
|
||||
}
|
||||
messages.add(C.GREEN + "Structure verify: " + C.WHITE + located + C.GREEN + " Iris placements located, "
|
||||
+ C.WHITE + nativeEligible + C.GREEN + " native structures eligible, "
|
||||
+ C.WHITE + disabled + C.GREEN + " disabled by policy, "
|
||||
+ C.WHITE + unreachable + C.GREEN + " biome-unreachable, "
|
||||
+ C.WHITE + unavailable + C.GREEN + " unavailable on this platform, "
|
||||
+ C.WHITE + unavailable + C.GREEN + " generation-eligible but unavailable to synchronous locate, "
|
||||
+ C.WHITE + searchLimited + C.GREEN + " density searches safety-limited, "
|
||||
+ C.WHITE + errors + C.GREEN + " errors. Native eligibility is checked without running blocking live locates.");
|
||||
sendVerificationMessages(commandSender, target, messages);
|
||||
@@ -285,12 +299,13 @@ public class CommandStructure implements DirectorExecutor {
|
||||
sender().sendMessage(C.RED + "Could not resolve the pack for dimension " + dimension.getLoadKey());
|
||||
return;
|
||||
}
|
||||
IrisStructure s = IrisData.loadAnyStructure(structure, data);
|
||||
IrisStructure s = data.load(IrisStructure.class, structure, false);
|
||||
if (s == null) {
|
||||
sender().sendMessage(C.RED + "No iris structure '" + structure + "' in this pack");
|
||||
return;
|
||||
}
|
||||
StructureAssembler assembler = new StructureAssembler(data, s, 0, 64, 0);
|
||||
StructureAssembler assembler = StructureAssembler.forData(
|
||||
data, s, new IrisPosition(0, 64, 0));
|
||||
KList<PlacedStructurePiece> pieces = assembler.assemble(new RNG(1234));
|
||||
if (pieces == null || pieces.isEmpty()) {
|
||||
sender().sendMessage(C.RED + "Structure '" + structure + "' assembled 0 pieces (check startPool '" + s.getStartPool() + "')");
|
||||
@@ -321,13 +336,14 @@ public class CommandStructure implements DirectorExecutor {
|
||||
sender().sendMessage(C.RED + "Could not resolve the pack for dimension " + dimension.getLoadKey());
|
||||
return;
|
||||
}
|
||||
IrisStructure s = IrisData.loadAnyStructure(structure, data);
|
||||
IrisStructure s = data.load(IrisStructure.class, structure, false);
|
||||
if (s == null) {
|
||||
sender().sendMessage(C.RED + "No iris structure '" + structure + "' in this pack");
|
||||
return;
|
||||
}
|
||||
Location loc = player().getLocation();
|
||||
StructureAssembler assembler = new StructureAssembler(data, s, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
||||
StructureAssembler assembler = StructureAssembler.forData(
|
||||
data, s, new IrisPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
|
||||
RNG rng = new RNG((long) loc.getBlockX() * 341873128712L + loc.getBlockZ());
|
||||
KList<PlacedStructurePiece> pieces = assembler.assemble(rng);
|
||||
if (pieces == null || pieces.isEmpty()) {
|
||||
|
||||
+23
-3
@@ -26,6 +26,7 @@ import art.arcane.iris.core.gui.NoiseExplorerGUI;
|
||||
import art.arcane.iris.core.gui.VisionGUI;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.project.IrisProject;
|
||||
import art.arcane.iris.core.service.BoardSVC;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
import art.arcane.iris.core.structure.BulkStructureImporter;
|
||||
import art.arcane.iris.core.structure.FeatureImporter;
|
||||
@@ -193,6 +194,25 @@ public class CommandStudio implements DirectorExecutor {
|
||||
}));
|
||||
}
|
||||
|
||||
@Director(description = "Toggle your Studio debug scoreboard", aliases = {"board", "sidebar", "sb"}, origin = DirectorOrigin.PLAYER)
|
||||
public void scoreboard() {
|
||||
Player target = player();
|
||||
VolmitSender commandSender = sender();
|
||||
if (!J.runEntity(target, () -> {
|
||||
PlatformChunkGenerator generator = IrisToolbelt.access(target.getWorld());
|
||||
if (generator == null || !generator.isStudio()) {
|
||||
commandSender.sendMessage(C.RED + "You must be in a Studio world to toggle the debug scoreboard.");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean visible = Iris.service(BoardSVC.class).toggle(target);
|
||||
commandSender.sendMessage((visible ? C.GREEN : C.YELLOW)
|
||||
+ "Studio debug scoreboard " + (visible ? "enabled." : "disabled."));
|
||||
})) {
|
||||
commandSender.sendMessage(C.RED + "Could not update the Studio debug scoreboard right now.");
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Create a new studio project", aliases = "+", sync = true)
|
||||
public void create(
|
||||
@Param(description = "The name of this new Iris Project.", defaultValue = "studio")
|
||||
@@ -259,7 +279,7 @@ public class CommandStudio implements DirectorExecutor {
|
||||
Inventory inv = Bukkit.createInventory(null, 27 * 2);
|
||||
|
||||
try {
|
||||
EngineBukkitOps.addItems(engine(), true, inv, RNG.r, tables, InventorySlotType.STORAGE, player().getWorld(), player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1);
|
||||
EngineBukkitOps.addItems(engine(), true, inv, tables, InventorySlotType.STORAGE, player().getWorld(), player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ());
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
sender().sendMessage(C.RED + "Cannot add items to virtual inventory because of: " + e.getMessage());
|
||||
@@ -286,7 +306,7 @@ public class CommandStudio implements DirectorExecutor {
|
||||
inv.clear();
|
||||
}
|
||||
|
||||
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);
|
||||
EngineBukkitOps.addItems(engine, true, inv, tables, InventorySlotType.STORAGE, player.getWorld(), player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ());
|
||||
}, fast ? 5 : 35));
|
||||
|
||||
sender().sendMessage(C.GREEN + "Opening inventory now!");
|
||||
@@ -829,7 +849,7 @@ public class CommandStudio implements DirectorExecutor {
|
||||
sender().sendMessage(C.RED + "No studio world is open!");
|
||||
return true;
|
||||
}
|
||||
if (!engine().isStudio()) {
|
||||
if (!IrisToolbelt.isStudio(world())) {
|
||||
sender().sendMessage(C.RED + "You must be in a studio world!");
|
||||
return true;
|
||||
}
|
||||
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class BukkitNativeStructureLocatePolicyTest {
|
||||
@Test
|
||||
public void nativeMonumentLocateIsUnavailable() {
|
||||
assertTrue(BukkitNativeStructureLocatePolicy.isUnavailable("minecraft:monument"));
|
||||
assertTrue(BukkitNativeStructureLocatePolicy.isUnavailable(" MINECRAFT:MONUMENT "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unrelatedAndNonCanonicalKeysRemainAvailable() {
|
||||
assertFalse(BukkitNativeStructureLocatePolicy.isUnavailable(null));
|
||||
assertFalse(BukkitNativeStructureLocatePolicy.isUnavailable(""));
|
||||
assertFalse(BukkitNativeStructureLocatePolicy.isUnavailable("minecraft:stronghold"));
|
||||
assertFalse(BukkitNativeStructureLocatePolicy.isUnavailable("minecraft:ocean_monument"));
|
||||
assertFalse(BukkitNativeStructureLocatePolicy.isUnavailable("minecraft:ocean_monuments"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectionExplainsSafetyAndGenerationBehavior() {
|
||||
String message = BukkitNativeStructureLocatePolicy.unavailableMessage().toLowerCase();
|
||||
|
||||
assertTrue(message.contains("unavailable"));
|
||||
assertTrue(message.contains("stall the server thread"));
|
||||
assertTrue(message.contains("generation is unaffected"));
|
||||
}
|
||||
}
|
||||
+61
@@ -1,11 +1,14 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
|
||||
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -21,6 +24,47 @@ public class IrisStructureLocateCommandContractTest {
|
||||
assertFalse(source.contains("at[2] + 8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findRoutesOnlyExplicitNativeReplacementThroughIrisLocate() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.commandFindSource")));
|
||||
int methodStart = source.indexOf("public void structure(");
|
||||
int methodEnd = source.indexOf("private static Structure resolveNativeStructure", methodStart);
|
||||
String method = source.substring(methodStart, methodEnd);
|
||||
int nativeResolution = method.indexOf("resolveNativeStructure(structureKey)");
|
||||
int policyResolution = method.indexOf("NativeStructureGenerationPolicy.resolve(", nativeResolution);
|
||||
int replacementCheck = method.indexOf(
|
||||
"decision.status() == NativeStructureGenerationStatus.REPLACED_BY_IRIS", policyResolution);
|
||||
int replacementLocate = method.indexOf("locateIrisStructure(e, structureKey, commandSender)", replacementCheck);
|
||||
int capabilityCheck = method.indexOf("NativeStructureLocateCapability.isPaperUnavailable(structureKey)", replacementLocate);
|
||||
int genericIrisLookup = method.indexOf(
|
||||
"nativeStructure == null && IrisStructureLocator.isPlaced(e, structureKey)", capabilityCheck);
|
||||
int nativeLocate = method.indexOf("targetWorld.locateNearestStructure(", genericIrisLookup);
|
||||
assertTrue(nativeResolution >= 0);
|
||||
assertTrue(policyResolution > nativeResolution);
|
||||
assertTrue(replacementCheck > policyResolution);
|
||||
assertTrue(replacementLocate > replacementCheck);
|
||||
assertTrue(capabilityCheck > replacementLocate);
|
||||
assertTrue(genericIrisLookup > capabilityCheck);
|
||||
assertTrue(nativeLocate > policyResolution);
|
||||
assertTrue(method.contains("decision.status() != NativeStructureGenerationStatus.REPLACED_BY_IRIS"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNativePolicyMessagesMatchModdedDiagnostics() {
|
||||
assertEquals(
|
||||
"Native structure minecraft:village_plains is disabled by this dimension's importedStructures settings.",
|
||||
NativeStructureGenerationPolicy.generationStatusMessage(
|
||||
"minecraft:village_plains", NativeStructureGenerationStatus.DISABLED_BY_PACK));
|
||||
assertEquals(
|
||||
"Native structure minecraft:ancient_city is replaced by an Iris placement in this pack and locates through that explicit replacement.",
|
||||
NativeStructureGenerationPolicy.generationStatusMessage(
|
||||
"minecraft:ancient_city", NativeStructureGenerationStatus.REPLACED_BY_IRIS));
|
||||
assertEquals(
|
||||
"Native structure minecraft:stronghold generates natively.",
|
||||
NativeStructureGenerationPolicy.generationStatusMessage(
|
||||
"minecraft:stronghold", NativeStructureGenerationStatus.GENERATE_NATIVE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void structureVerifyReportsDensityLimitAndUsesExactLocatedOrigin() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.commandStructureSource")));
|
||||
@@ -31,4 +75,21 @@ public class IrisStructureLocateCommandContractTest {
|
||||
assertFalse(source.contains("at[0] + 8"));
|
||||
assertFalse(source.contains("at[2] + 8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void structureVerifyPartitionsPolicyBeforeNativeReachability() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.commandStructureSource")));
|
||||
int methodStart = source.indexOf("private void runVerification(");
|
||||
int methodEnd = source.indexOf("private void sendVerificationMessages(", methodStart);
|
||||
String method = source.substring(methodStart, methodEnd);
|
||||
int policyResolution = method.indexOf("NativeStructureGenerationPolicy.resolve(engine, keyName, false)");
|
||||
int nativeRequirement = method.indexOf("requiresNativeReachability |= decision.generate()", policyResolution);
|
||||
int reachabilityGuard = method.indexOf("if (requiresNativeReachability)", nativeRequirement);
|
||||
int reachabilityLookup = method.indexOf("StructureReachability.reachableKeys(engine)", reachabilityGuard);
|
||||
|
||||
assertTrue(policyResolution >= 0);
|
||||
assertTrue(nativeRequirement > policyResolution);
|
||||
assertTrue(reachabilityGuard > nativeRequirement);
|
||||
assertTrue(reachabilityLookup > reachabilityGuard);
|
||||
}
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class StudioScoreboardCommandTest {
|
||||
@Test
|
||||
public void scoreboardCommandIsPlayerScopedAndEntityScheduled() throws NoSuchMethodException {
|
||||
Method command = CommandStudio.class.getDeclaredMethod("scoreboard");
|
||||
Director director = command.getAnnotation(Director.class);
|
||||
|
||||
assertEquals(DirectorOrigin.PLAYER, director.origin());
|
||||
assertFalse(director.sync());
|
||||
assertTrue(List.of(director.aliases()).contains("board"));
|
||||
assertTrue(List.of(director.aliases()).contains("sidebar"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user