mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
Split jigsaw command tree
- Move each jigsaw subgroup executor into its own top-level command class - Move the adoption plan store, handlers and helpers into CommandJigsawAdopt - Move the vanilla export lease and reporting helpers into CommandJigsawExports - Move the studio open lifecycle into CommandJigsawOpen - Point the jigsaw contract test at the relocated command types - Exclude the new jigsaw command sources from the generation revision scope
This commit is contained in:
+18
-1411
File diff suppressed because it is too large
Load Diff
+424
@@ -0,0 +1,424 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.structure.authoring.StructureKey;
|
||||
import art.arcane.iris.core.structure.authoring.StructureOwnershipManifest;
|
||||
import art.arcane.iris.core.structure.authoring.StructureTransactionWriter;
|
||||
import art.arcane.iris.core.structure.conversion.IrisStructureAdoptionDiagnostic;
|
||||
import art.arcane.iris.core.structure.conversion.IrisStructureAdoptionInputKind;
|
||||
import art.arcane.iris.core.structure.conversion.IrisStructureAdoptionPlan;
|
||||
import art.arcane.iris.core.structure.conversion.IrisStructureAdoptionRequest;
|
||||
import art.arcane.iris.core.structure.conversion.IrisStructureAdoptionResult;
|
||||
import art.arcane.iris.core.structure.conversion.IrisStructureAdoptionService;
|
||||
import art.arcane.iris.core.structure.conversion.IrisStructureAdoptionStrategy;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.DirectorParameterHandler;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Director(name = "adopt", description = "Inspect and apply safe ownership adoption plans",
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public class CommandJigsawAdopt implements DirectorExecutor {
|
||||
private static final Map<UUID, AdoptionPlanBinding> ADOPTION_PLANS = new ConcurrentHashMap<>();
|
||||
private static final Map<Path, IrisStructureAdoptionService> ADOPTION_SERVICES = new ConcurrentHashMap<>();
|
||||
|
||||
@Director(description = "Inspect an Iris graph for safe Studio ownership adoption", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void inspect(
|
||||
@Param(description = "Pack dimension") IrisDimension dimension,
|
||||
@Param(description = "Existing Iris structure key") String source,
|
||||
@Param(description = "New owned Iris key, or auto", defaultValue = "auto") String target,
|
||||
@Param(description = "auto, in-place, or clone", defaultValue = "auto",
|
||||
customHandler = JigsawAdoptionStrategyHandler.class) String strategy
|
||||
) {
|
||||
VolmitSender commandSender = sender();
|
||||
IrisData data = CommandJigsaw.requireData(dimension, commandSender);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
String sourceKey;
|
||||
Optional<StructureKey> targetKey;
|
||||
IrisStructureAdoptionStrategy adoptionStrategy;
|
||||
Path root;
|
||||
try {
|
||||
sourceKey = parseAdoptionSource(source);
|
||||
targetKey = parseAdoptionTarget(target);
|
||||
adoptionStrategy = parseAdoptionStrategy(strategy);
|
||||
root = canonicalAdoptionRoot(data.getDataFolder().toPath());
|
||||
} catch (IOException | IllegalArgumentException exception) {
|
||||
CommandJigsaw.sendError(commandSender, "Could not inspect adoption source: " + exception.getMessage());
|
||||
return;
|
||||
}
|
||||
Player targetPlayer = player();
|
||||
UUID ownerId = targetPlayer.getUniqueId();
|
||||
commandSender.sendMessage(C.GRAY + "Inspecting Jigsaw adoption source '" + sourceKey
|
||||
+ "' asynchronously...");
|
||||
try {
|
||||
J.a(() -> runAdoptionInspect(
|
||||
targetPlayer,
|
||||
commandSender,
|
||||
ownerId,
|
||||
dimension,
|
||||
data,
|
||||
root,
|
||||
sourceKey,
|
||||
targetKey,
|
||||
adoptionStrategy));
|
||||
} catch (RuntimeException exception) {
|
||||
Iris.reportError("Failed to schedule Jigsaw adoption inspection for '"
|
||||
+ sourceKey + "'.", exception);
|
||||
CommandJigsaw.sendError(commandSender, "Jigsaw adoption inspection could not be scheduled: "
|
||||
+ exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Apply one inspected adoption plan without overwriting", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void apply(
|
||||
@Param(description = "Plan UUID returned by adopt inspect",
|
||||
customHandler = JigsawAdoptionPlanHandler.class) String planId
|
||||
) {
|
||||
VolmitSender commandSender = sender();
|
||||
UUID parsedPlanId;
|
||||
try {
|
||||
parsedPlanId = UUID.fromString(planId);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
CommandJigsaw.sendError(commandSender, "Adoption plan ID must be a UUID.");
|
||||
return;
|
||||
}
|
||||
if (CommandJigsaw.studioIsActiveOrOpening()) {
|
||||
CommandJigsaw.sendError(commandSender,
|
||||
"Close the active or opening Jigsaw Studio before applying an adoption plan.");
|
||||
return;
|
||||
}
|
||||
pruneAdoptionPlanBindings();
|
||||
AdoptionPlanBinding binding = ADOPTION_PLANS.get(parsedPlanId);
|
||||
UUID playerId = player().getUniqueId();
|
||||
if (binding == null || !binding.ownerId().equals(playerId)) {
|
||||
CommandJigsaw.sendError(commandSender, "No active adoption plan '" + parsedPlanId
|
||||
+ "' belongs to your player session. Run adopt inspect again.");
|
||||
return;
|
||||
}
|
||||
if (binding.service().plan(parsedPlanId).isEmpty()) {
|
||||
ADOPTION_PLANS.remove(parsedPlanId, binding);
|
||||
CommandJigsaw.sendError(commandSender, "Adoption plan '" + parsedPlanId
|
||||
+ "' expired or was already consumed. Run adopt inspect again.");
|
||||
return;
|
||||
}
|
||||
if (!ADOPTION_PLANS.remove(parsedPlanId, binding)) {
|
||||
CommandJigsaw.sendError(commandSender, "Adoption plan '" + parsedPlanId + "' is already being applied.");
|
||||
return;
|
||||
}
|
||||
Player targetPlayer = player();
|
||||
commandSender.sendMessage(C.GRAY + "Applying adoption plan '" + parsedPlanId
|
||||
+ "' asynchronously without overwrite...");
|
||||
try {
|
||||
J.a(() -> runAdoptionApply(targetPlayer, commandSender, parsedPlanId, binding));
|
||||
} catch (RuntimeException exception) {
|
||||
ADOPTION_PLANS.putIfAbsent(parsedPlanId, binding);
|
||||
Iris.reportError("Failed to schedule Jigsaw adoption plan '" + parsedPlanId + "'.", exception);
|
||||
CommandJigsaw.sendError(commandSender, "Jigsaw adoption could not be scheduled: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void pruneAdoptionPlanBindings() {
|
||||
for (Map.Entry<UUID, AdoptionPlanBinding> entry : ADOPTION_PLANS.entrySet()) {
|
||||
AdoptionPlanBinding binding = entry.getValue();
|
||||
if (binding.service().plan(entry.getKey()).isEmpty()) {
|
||||
ADOPTION_PLANS.remove(entry.getKey(), binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void runAdoptionApply(
|
||||
Player targetPlayer,
|
||||
VolmitSender commandSender,
|
||||
UUID planId,
|
||||
AdoptionPlanBinding binding
|
||||
) {
|
||||
if (CommandJigsaw.studioIsActiveOrOpening()) {
|
||||
ADOPTION_PLANS.putIfAbsent(planId, binding);
|
||||
J.runEntity(targetPlayer, () -> CommandJigsaw.sendError(commandSender,
|
||||
"Close the active or opening Jigsaw Studio before applying an adoption plan."));
|
||||
return;
|
||||
}
|
||||
IrisStructureAdoptionResult result;
|
||||
Throwable failure = null;
|
||||
try {
|
||||
result = binding.service().apply(binding.plan());
|
||||
} catch (Throwable throwable) {
|
||||
result = null;
|
||||
failure = throwable;
|
||||
Iris.reportError("Failed to apply Jigsaw adoption plan '" + planId + "'.", throwable);
|
||||
}
|
||||
IrisStructureAdoptionResult completedResult = result;
|
||||
Throwable completedFailure = failure;
|
||||
J.runEntity(targetPlayer, () -> finishAdoptionApply(
|
||||
targetPlayer,
|
||||
commandSender,
|
||||
binding,
|
||||
completedResult,
|
||||
completedFailure));
|
||||
}
|
||||
|
||||
private static void runAdoptionInspect(
|
||||
Player targetPlayer,
|
||||
VolmitSender commandSender,
|
||||
UUID ownerId,
|
||||
IrisDimension dimension,
|
||||
IrisData data,
|
||||
Path root,
|
||||
String sourceKey,
|
||||
Optional<StructureKey> targetKey,
|
||||
IrisStructureAdoptionStrategy strategy
|
||||
) {
|
||||
IrisStructureAdoptionService service = null;
|
||||
IrisStructureAdoptionPlan plan = null;
|
||||
Throwable failure = null;
|
||||
try {
|
||||
IrisStructureAdoptionInputKind inputKind = adoptionInputKind(root, sourceKey);
|
||||
service = ADOPTION_SERVICES.computeIfAbsent(root, IrisStructureAdoptionService::new);
|
||||
plan = service.inspect(new IrisStructureAdoptionRequest(
|
||||
sourceKey,
|
||||
targetKey,
|
||||
strategy,
|
||||
inputKind));
|
||||
} catch (Throwable throwable) {
|
||||
failure = throwable;
|
||||
Iris.reportError("Failed to inspect Jigsaw adoption source '" + sourceKey + "'.", throwable);
|
||||
}
|
||||
IrisStructureAdoptionService completedService = service;
|
||||
IrisStructureAdoptionPlan completedPlan = plan;
|
||||
Throwable completedFailure = failure;
|
||||
J.runEntity(targetPlayer, () -> finishAdoptionInspect(
|
||||
commandSender,
|
||||
ownerId,
|
||||
dimension,
|
||||
data,
|
||||
completedService,
|
||||
completedPlan,
|
||||
completedFailure));
|
||||
}
|
||||
|
||||
private static void finishAdoptionInspect(
|
||||
VolmitSender commandSender,
|
||||
UUID ownerId,
|
||||
IrisDimension dimension,
|
||||
IrisData data,
|
||||
IrisStructureAdoptionService service,
|
||||
IrisStructureAdoptionPlan plan,
|
||||
Throwable failure
|
||||
) {
|
||||
if (failure != null || service == null || plan == null) {
|
||||
CommandJigsaw.sendError(commandSender, "Jigsaw adoption inspection failed: "
|
||||
+ (failure == null ? "no result" : failure.getMessage()));
|
||||
return;
|
||||
}
|
||||
pruneAdoptionPlanBindings();
|
||||
if (service.plan(plan.planId()).isPresent()) {
|
||||
ADOPTION_PLANS.put(plan.planId(), new AdoptionPlanBinding(
|
||||
ownerId, dimension, data, service, plan));
|
||||
}
|
||||
reportAdoptionPlan(commandSender, plan);
|
||||
}
|
||||
|
||||
private static void finishAdoptionApply(
|
||||
Player targetPlayer,
|
||||
VolmitSender commandSender,
|
||||
AdoptionPlanBinding binding,
|
||||
IrisStructureAdoptionResult result,
|
||||
Throwable failure
|
||||
) {
|
||||
if (failure != null || result == null) {
|
||||
CommandJigsaw.sendError(commandSender, "Jigsaw adoption failed: "
|
||||
+ (failure == null ? "no result" : failure.getMessage()));
|
||||
return;
|
||||
}
|
||||
reportAdoptionResult(commandSender, result);
|
||||
if (!result.successful()) {
|
||||
return;
|
||||
}
|
||||
StructureKey target = result.receipt().orElseThrow().targetStructure();
|
||||
binding.data().invalidateStructureResources();
|
||||
CommandJigsawOpen.openProject(targetPlayer, commandSender, binding.dimension(), target.path(), CommandJigsaw.DEFAULT_STUDIO_SEED);
|
||||
}
|
||||
|
||||
private static void reportAdoptionPlan(VolmitSender commandSender, IrisStructureAdoptionPlan plan) {
|
||||
C statusColor = plan.canApply() ? C.GREEN : C.RED;
|
||||
commandSender.sendMessage(statusColor + "Adoption plan " + plan.planId() + " -> "
|
||||
+ plan.disposition() + " for " + plan.targetStructure().value() + ".");
|
||||
commandSender.sendMessage(C.GRAY + "Resources=" + plan.resourceCount()
|
||||
+ ", bytes=" + plan.totalSourceBytes()
|
||||
+ ", errors=" + plan.errorCount()
|
||||
+ ", warnings=" + plan.warningCount() + ".");
|
||||
for (IrisStructureAdoptionDiagnostic diagnostic : plan.diagnostics()) {
|
||||
commandSender.sendMessage(adoptionColor(diagnostic).toString() + diagnostic.summary());
|
||||
}
|
||||
if (plan.canApply()) {
|
||||
commandSender.sendMessage(C.AQUA + "Apply with /iris jigsaw adopt apply " + plan.planId());
|
||||
}
|
||||
}
|
||||
|
||||
private static void reportAdoptionResult(VolmitSender commandSender, IrisStructureAdoptionResult result) {
|
||||
commandSender.sendMessage((result.successful() ? C.GREEN : C.RED)
|
||||
+ "Adoption plan " + result.planId() + ": " + result.status() + ".");
|
||||
for (IrisStructureAdoptionDiagnostic diagnostic : result.diagnostics()) {
|
||||
commandSender.sendMessage(adoptionColor(diagnostic).toString() + diagnostic.summary());
|
||||
}
|
||||
}
|
||||
|
||||
private static C adoptionColor(IrisStructureAdoptionDiagnostic diagnostic) {
|
||||
return switch (diagnostic.severity()) {
|
||||
case ERROR -> C.RED;
|
||||
case WARNING -> C.YELLOW;
|
||||
case INFO -> C.GRAY;
|
||||
};
|
||||
}
|
||||
|
||||
static String parseAdoptionSource(String value) {
|
||||
String normalized = Objects.requireNonNull(value, "Adoption source").trim()
|
||||
.toLowerCase(Locale.ROOT);
|
||||
StructureKey source = StructureKey.parse(normalized, "iris");
|
||||
if (!"iris".equals(source.namespace())) {
|
||||
throw new IllegalArgumentException("Adoption sources must use the iris namespace.");
|
||||
}
|
||||
return source.path();
|
||||
}
|
||||
|
||||
static Optional<StructureKey> parseAdoptionTarget(String value) {
|
||||
String normalized = Objects.requireNonNull(value, "Adoption target").trim()
|
||||
.toLowerCase(Locale.ROOT);
|
||||
if (normalized.isEmpty() || "auto".equals(normalized)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
StructureKey target = StructureKey.parse(normalized, "iris");
|
||||
if (!"iris".equals(target.namespace())) {
|
||||
throw new IllegalArgumentException("Adoption targets must use the iris namespace.");
|
||||
}
|
||||
return Optional.of(target);
|
||||
}
|
||||
|
||||
private static IrisStructureAdoptionStrategy parseAdoptionStrategy(String value) {
|
||||
return switch (Objects.requireNonNull(value, "Adoption strategy").trim().toLowerCase(Locale.ROOT)) {
|
||||
case "auto" -> IrisStructureAdoptionStrategy.AUTO;
|
||||
case "in-place", "in_place", "inplace", "claim" -> IrisStructureAdoptionStrategy.IN_PLACE;
|
||||
case "clone", "copy" -> IrisStructureAdoptionStrategy.CLONE;
|
||||
default -> throw new IllegalArgumentException("Adoption strategy must be auto, in-place, or clone.");
|
||||
};
|
||||
}
|
||||
|
||||
static IrisStructureAdoptionInputKind adoptionInputKind(Path packRoot, String source) throws IOException {
|
||||
Path root = canonicalAdoptionRoot(packRoot);
|
||||
StructureKey sourceKey = new StructureKey("iris", parseAdoptionSource(source));
|
||||
Path manifestPath = new StructureTransactionWriter(root).ownershipManifestPath(sourceKey);
|
||||
if (!Files.exists(manifestPath, LinkOption.NOFOLLOW_LINKS)) {
|
||||
return IrisStructureAdoptionInputKind.UNOWNED_IRIS;
|
||||
}
|
||||
if (!Files.isRegularFile(manifestPath, LinkOption.NOFOLLOW_LINKS)) {
|
||||
throw new IOException("Ownership manifest is not a regular file: " + manifestPath);
|
||||
}
|
||||
StructureOwnershipManifest manifest;
|
||||
try {
|
||||
manifest = StructureOwnershipManifest.fromJson(Files.readAllBytes(manifestPath));
|
||||
} catch (RuntimeException exception) {
|
||||
throw new IOException("Invalid ownership manifest at " + manifestPath, exception);
|
||||
}
|
||||
if (!manifest.structure().equals(sourceKey)) {
|
||||
throw new IOException("Ownership manifest at " + manifestPath
|
||||
+ " belongs to " + manifest.structure().value());
|
||||
}
|
||||
return manifest.provenance().origin() == StructureOwnershipManifest.Origin.MANAGED_DATAPACK
|
||||
? IrisStructureAdoptionInputKind.MANAGED_DATAPACK
|
||||
: IrisStructureAdoptionInputKind.UNOWNED_IRIS;
|
||||
}
|
||||
|
||||
private static Path canonicalAdoptionRoot(Path packRoot) throws IOException {
|
||||
Path normalized = Objects.requireNonNull(packRoot, "Adoption pack root").toAbsolutePath().normalize();
|
||||
return Files.exists(normalized, LinkOption.NOFOLLOW_LINKS) ? normalized.toRealPath() : normalized;
|
||||
}
|
||||
|
||||
private record AdoptionPlanBinding(
|
||||
UUID ownerId,
|
||||
IrisDimension dimension,
|
||||
IrisData data,
|
||||
IrisStructureAdoptionService service,
|
||||
IrisStructureAdoptionPlan plan
|
||||
) {
|
||||
}
|
||||
|
||||
public static final class JigsawAdoptionStrategyHandler implements DirectorParameterHandler<String> {
|
||||
@Override
|
||||
public KList<String> getPossibilities() {
|
||||
return new KList<>("auto", "in-place", "clone");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parse(String input, boolean force) throws DirectorParsingException {
|
||||
try {
|
||||
return switch (parseAdoptionStrategy(input)) {
|
||||
case AUTO -> "auto";
|
||||
case IN_PLACE -> "in-place";
|
||||
case CLONE -> "clone";
|
||||
};
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw new DirectorParsingException(exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> type) {
|
||||
return type == String.class;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class JigsawAdoptionPlanHandler implements DirectorParameterHandler<String> {
|
||||
@Override
|
||||
public KList<String> getPossibilities() {
|
||||
return new KList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parse(String input, boolean force) throws DirectorParsingException {
|
||||
try {
|
||||
return UUID.fromString(input).toString();
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw new DirectorParsingException("Adoption plan ID must be a UUID.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> type) {
|
||||
return type == String.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioBay;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioCellDimensions;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioGraphEditor;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioVariant;
|
||||
import art.arcane.iris.engine.object.IrisObject;
|
||||
import art.arcane.iris.engine.object.IrisPosition;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.type.Jigsaw;
|
||||
|
||||
@Director(name = "connector", description = "Inspect and configure targeted jigsaw connectors",
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public class CommandJigsawConnector implements DirectorExecutor {
|
||||
@Director(description = "Set the Iris-only channel on the targeted jigsaw marker", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void channel(
|
||||
@Param(description = "Exact channel, or none to clear") String channel
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
Block target = player().getTargetBlockExact(8);
|
||||
if (target == null || !(target.getBlockData() instanceof Jigsaw)) {
|
||||
CommandJigsaw.sendError(this, "Look directly at a jigsaw marker within eight blocks.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioBay bay = context.session().layout().findAt(
|
||||
target.getX(), target.getY(), target.getZ());
|
||||
JigsawStudioVariant activeVariant = bay == null
|
||||
? null
|
||||
: context.session().activeVariant(bay.stableId()).orElse(null);
|
||||
if (activeVariant == null) {
|
||||
CommandJigsaw.sendError(this, "The targeted marker must be inside a workcell with an active variant.");
|
||||
return;
|
||||
}
|
||||
if (!activeVariant.owned()) {
|
||||
CommandJigsaw.sendError(this, "The active variant is read-only. Adopt it into this project before editing it.");
|
||||
return;
|
||||
}
|
||||
IrisObject object = context.request().source().getObjectLoader().load(activeVariant.objectKey());
|
||||
if (object == null) {
|
||||
CommandJigsaw.sendError(this, "The active variant object '" + activeVariant.objectKey() + "' is missing.");
|
||||
return;
|
||||
}
|
||||
IrisPosition canonicalPosition = new IrisPosition(
|
||||
target.getX() - bay.bounds().originX(),
|
||||
target.getY() - bay.bounds().originY(),
|
||||
target.getZ() - bay.bounds().originZ());
|
||||
IrisPosition position;
|
||||
try {
|
||||
position = activeVariant.canonicalToSourcePosition(
|
||||
canonicalPosition,
|
||||
new JigsawStudioCellDimensions(object.getW(), object.getH(), object.getD()));
|
||||
} catch (IllegalArgumentException exception) {
|
||||
CommandJigsaw.sendError(this, "Connector channel update failed: " + exception.getMessage());
|
||||
return;
|
||||
}
|
||||
String normalized = channel == null || channel.isBlank() || "none".equalsIgnoreCase(channel)
|
||||
? "none"
|
||||
: channel.trim();
|
||||
CommandJigsaw.runGraphMutation(player(), context, () -> {
|
||||
JigsawStudioGraphEditor.updateConnectorChannel(
|
||||
context.request().source().getDataFolder().toPath(),
|
||||
context.request().structureKey(),
|
||||
activeVariant.pieceKey(),
|
||||
position,
|
||||
channel);
|
||||
return CommandJigsaw.mappedMutationResult(
|
||||
context,
|
||||
"",
|
||||
"",
|
||||
"Persisted connector channel '" + normalized + "' on piece '"
|
||||
+ activeVariant.pieceKey() + "' at " + position.getX() + "," + position.getY()
|
||||
+ "," + position.getZ() + ".");
|
||||
});
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.service.JigsawStudioService;
|
||||
import art.arcane.iris.core.structure.export.VanillaJigsawDatapackExporter;
|
||||
import art.arcane.iris.core.structure.export.VanillaJigsawExportDiagnostic;
|
||||
import art.arcane.iris.core.structure.export.VanillaJigsawExportFormat;
|
||||
import art.arcane.iris.core.structure.export.VanillaJigsawExportRequest;
|
||||
import art.arcane.iris.core.structure.export.VanillaJigsawExportResult;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class CommandJigsawExports {
|
||||
private static final Pattern EXPORT_OUTPUT_NAME = Pattern.compile("[a-zA-Z0-9_-][a-zA-Z0-9._-]{0,127}");
|
||||
private static final Set<UUID> EXPORTING_PLAYERS = ConcurrentHashMap.newKeySet();
|
||||
private static final Set<Path> EXPORT_DESTINATIONS = ConcurrentHashMap.newKeySet();
|
||||
|
||||
private CommandJigsawExports() {
|
||||
}
|
||||
|
||||
static String exportStartError(JigsawStudioService.ExportStart exportStart) {
|
||||
Objects.requireNonNull(exportStart, "Jigsaw Studio export start status");
|
||||
return switch (exportStart) {
|
||||
case NOT_ACTIVE -> "The active Jigsaw Studio is no longer available.";
|
||||
case NOT_OWNER -> "Only the Jigsaw Studio owner can export this project.";
|
||||
case DIRTY -> "Wait for the pending autosave or discard the edits before exporting the on-disk graph.";
|
||||
case CLOSING -> "The active Jigsaw Studio is closing and cannot be exported.";
|
||||
case SAVE_IN_PROGRESS -> "Wait for the current Jigsaw Studio save to finish before exporting.";
|
||||
case OPERATION_IN_PROGRESS -> "Wait for the current Jigsaw Studio operation to finish before exporting.";
|
||||
case IN_PROGRESS -> "A Jigsaw Studio export is already in progress.";
|
||||
case STARTED -> throw new IllegalArgumentException("STARTED is not an export failure");
|
||||
};
|
||||
}
|
||||
|
||||
static boolean beginExport(UUID playerId, Path destination) {
|
||||
Objects.requireNonNull(playerId);
|
||||
Path normalizedDestination = Objects.requireNonNull(destination).toAbsolutePath().normalize();
|
||||
if (!EXPORTING_PLAYERS.add(playerId)) {
|
||||
return false;
|
||||
}
|
||||
if (EXPORT_DESTINATIONS.add(normalizedDestination)) {
|
||||
return true;
|
||||
}
|
||||
EXPORTING_PLAYERS.remove(playerId);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void finishExport(UUID playerId, Path destination) {
|
||||
EXPORTING_PLAYERS.remove(Objects.requireNonNull(playerId));
|
||||
EXPORT_DESTINATIONS.remove(Objects.requireNonNull(destination).toAbsolutePath().normalize());
|
||||
}
|
||||
|
||||
static void runExport(ExportOperation operation) {
|
||||
VanillaJigsawExportResult result = null;
|
||||
Throwable failure = null;
|
||||
try {
|
||||
result = new VanillaJigsawDatapackExporter().export(operation.request());
|
||||
} catch (Throwable throwable) {
|
||||
failure = throwable;
|
||||
Iris.reportError("Vanilla Jigsaw export failed for '" + operation.structureKey() + "'.", throwable);
|
||||
} finally {
|
||||
operation.lease().release();
|
||||
}
|
||||
VanillaJigsawExportResult completedResult = result;
|
||||
Throwable completedFailure = failure;
|
||||
Runnable response = () -> reportExport(operation, completedResult, completedFailure);
|
||||
J.runEntity(operation.player(), response);
|
||||
}
|
||||
|
||||
private static void reportExport(
|
||||
ExportOperation operation,
|
||||
VanillaJigsawExportResult result,
|
||||
Throwable failure
|
||||
) {
|
||||
if (failure != null || result == null) {
|
||||
operation.sender().sendMessage(C.RED + "Background vanilla export for '"
|
||||
+ operation.structureKey() + "' failed: "
|
||||
+ (failure == null ? "no result" : failure.getMessage()));
|
||||
return;
|
||||
}
|
||||
for (VanillaJigsawExportDiagnostic diagnostic : result.diagnostics()) {
|
||||
C color = diagnostic.isBlocking() ? C.RED : C.YELLOW;
|
||||
operation.sender().sendMessage(color.toString() + diagnostic.code() + ": " + diagnostic.message());
|
||||
}
|
||||
CommandJigsaw.ActiveContext current = CommandJigsaw.active(operation.player());
|
||||
boolean sameSession = current != null
|
||||
&& current.request().requestId().equals(operation.requestId());
|
||||
String contextLabel = sameSession ? "Vanilla export" : "Background vanilla export for '"
|
||||
+ operation.structureKey() + "'";
|
||||
operation.sender().sendMessage((result.isSuccess() ? C.GREEN : C.RED) + contextLabel + " "
|
||||
+ result.status() + ": " + result.output());
|
||||
}
|
||||
|
||||
static VanillaJigsawExportFormat parseFormat(String value) {
|
||||
return switch (value.trim().toLowerCase(Locale.ROOT)) {
|
||||
case "zip" -> VanillaJigsawExportFormat.ZIP;
|
||||
case "directory", "dir", "folder" -> VanillaJigsawExportFormat.DIRECTORY;
|
||||
default -> throw new IllegalArgumentException("Export format must be directory or zip.");
|
||||
};
|
||||
}
|
||||
|
||||
static Path resolveExportDestination(
|
||||
Path exportRoot,
|
||||
String output,
|
||||
VanillaJigsawExportFormat format
|
||||
) {
|
||||
Path root = Objects.requireNonNull(exportRoot, "Jigsaw export root").toAbsolutePath().normalize();
|
||||
String requested = Objects.requireNonNull(output, "Jigsaw export output name");
|
||||
String name = requested.trim();
|
||||
if (!name.equals(requested) || !EXPORT_OUTPUT_NAME.matcher(name).matches()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Export output must be one file name using letters, numbers, '.', '_', or '-'.");
|
||||
}
|
||||
String fileName = format == VanillaJigsawExportFormat.ZIP && !name.endsWith(".zip")
|
||||
? name + ".zip" : name;
|
||||
Path destination = root.resolve(fileName).normalize();
|
||||
if (destination.equals(root) || !root.equals(destination.getParent())) {
|
||||
throw new IllegalArgumentException("Export output must be one artifact inside the Studio exports folder.");
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
record ExportOperation(
|
||||
Player player,
|
||||
VolmitSender sender,
|
||||
UUID requestId,
|
||||
String structureKey,
|
||||
VanillaJigsawExportRequest request,
|
||||
ExportLease lease
|
||||
) {
|
||||
}
|
||||
|
||||
static final class ExportLease {
|
||||
private final Runnable release;
|
||||
private final AtomicBoolean released;
|
||||
|
||||
ExportLease(Runnable release) {
|
||||
this.release = Objects.requireNonNull(release, "Jigsaw export lease release action");
|
||||
this.released = new AtomicBoolean();
|
||||
}
|
||||
|
||||
void release() {
|
||||
if (released.compareAndSet(false, true)) {
|
||||
release.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.pack.BrokenPackException;
|
||||
import art.arcane.iris.core.runtime.WorldRuntimeControlService;
|
||||
import art.arcane.iris.core.runtime.StudioOpenCoordinator;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioActivation;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioBay;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioGraphMapper;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioLayout;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioSession;
|
||||
import art.arcane.iris.core.service.JigsawStudioService;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisStructure;
|
||||
import art.arcane.iris.platform.bukkit.BukkitPlatform;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
final class CommandJigsawOpen {
|
||||
private CommandJigsawOpen() {
|
||||
}
|
||||
|
||||
static void openProject(
|
||||
Player targetPlayer,
|
||||
VolmitSender commandSender,
|
||||
IrisDimension dimension,
|
||||
String structure,
|
||||
long seed
|
||||
) {
|
||||
UUID playerId = targetPlayer.getUniqueId();
|
||||
UUID ownerId = JigsawStudioActivation.activeOwnerId();
|
||||
if (ownerId != null && !ownerId.equals(playerId)) {
|
||||
CommandJigsaw.sendError(commandSender, "Jigsaw Studio is already owned by another player session.");
|
||||
return;
|
||||
}
|
||||
UUID openingOwner = JigsawStudioActivation.openingOwnerId();
|
||||
if (openingOwner != null) {
|
||||
CommandJigsaw.sendError(commandSender, openingOwner.equals(playerId)
|
||||
? "Your Jigsaw Studio is already opening."
|
||||
: "Jigsaw Studio is already opening for another player.");
|
||||
return;
|
||||
}
|
||||
CommandJigsaw.ActiveContext previous = CommandJigsaw.active(targetPlayer);
|
||||
if (ownerId != null && previous == null) {
|
||||
CommandJigsaw.sendError(commandSender, "Your active Jigsaw Studio binding is unavailable. Close it before opening another project.");
|
||||
return;
|
||||
}
|
||||
CommandJigsaw.ActiveContext dirty = CommandJigsaw.findDirtyContext();
|
||||
if (dirty != null) {
|
||||
CommandJigsaw.sendError(commandSender,
|
||||
"The current Jigsaw Studio is waiting for autosave. Wait for it to finish or close with discard=true.");
|
||||
return;
|
||||
}
|
||||
IrisData data = CommandJigsaw.requireData(dimension, commandSender);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
IrisStructure irisStructure = data.load(IrisStructure.class, structure, false);
|
||||
if (irisStructure == null) {
|
||||
CommandJigsaw.sendError(commandSender, "No Iris jigsaw structure '" + structure + "' exists in this pack.");
|
||||
return;
|
||||
}
|
||||
|
||||
JigsawStudioLayout layout;
|
||||
try {
|
||||
layout = JigsawStudioGraphMapper.map(data, irisStructure);
|
||||
} catch (RuntimeException exception) {
|
||||
Iris.reportError("Failed to map Jigsaw Studio graph '" + structure + "'.", exception);
|
||||
CommandJigsaw.sendError(commandSender, "Could not map this jigsaw graph: " + exception.getMessage());
|
||||
return;
|
||||
}
|
||||
String packKey = dimension.getLoadKey();
|
||||
if (!JigsawStudioActivation.tryBeginOpen(playerId)) {
|
||||
CommandJigsaw.sendError(commandSender,
|
||||
"Jigsaw Studio began opening for another player. Try again after it finishes.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioService studioService = JigsawStudioService.get();
|
||||
UUID closingRequestId = previous == null ? null : previous.request().requestId();
|
||||
if (closingRequestId != null) {
|
||||
JigsawStudioService.CloseStart closeStart = studioService.tryBeginClose(
|
||||
closingRequestId,
|
||||
playerId,
|
||||
false);
|
||||
if (closeStart != JigsawStudioService.CloseStart.STARTED) {
|
||||
JigsawStudioActivation.finishOpen(playerId);
|
||||
CommandJigsaw.sendError(commandSender, closeStart == JigsawStudioService.CloseStart.SAVE_IN_PROGRESS
|
||||
? "The current Jigsaw Studio is saving. Wait for it to finish before opening another project."
|
||||
: closeStart == JigsawStudioService.CloseStart.OPERATION_IN_PROGRESS
|
||||
? "The current Jigsaw Studio is loading a variant. Wait for it to finish before opening another project."
|
||||
: closeStart == JigsawStudioService.CloseStart.DIRTY
|
||||
? "The current Jigsaw Studio is waiting for autosave. Wait for it to finish before opening another project."
|
||||
: "The current Jigsaw Studio could not be reserved for replacement.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
JigsawStudioActivation.StagedActivation staged;
|
||||
try {
|
||||
staged = JigsawStudioActivation.stage(
|
||||
packKey,
|
||||
structure,
|
||||
layout.mode(),
|
||||
CommandJigsaw.compatibilityOf(irisStructure),
|
||||
layout.cellDimensions(),
|
||||
data,
|
||||
layout,
|
||||
playerId,
|
||||
closingRequestId);
|
||||
} catch (RuntimeException exception) {
|
||||
JigsawStudioActivation.finishOpen(playerId);
|
||||
studioService.cancelClose(closingRequestId);
|
||||
Iris.reportError("Failed to stage Jigsaw Studio for '" + structure + "'.", exception);
|
||||
CommandJigsaw.sendError(commandSender, "Jigsaw Studio staging failed: " + exception.getMessage());
|
||||
return;
|
||||
}
|
||||
commandSender.sendMessage(C.GREEN + "Opening transient Jigsaw Studio for '" + structure + "' with "
|
||||
+ layout.bays().size() + " workcells.");
|
||||
|
||||
try {
|
||||
Iris.service(StudioSVC.class).openTracked(
|
||||
commandSender,
|
||||
seed,
|
||||
packKey,
|
||||
CommandJigsaw.STUDIO_OPEN_KIND,
|
||||
() -> beginStagedOpen(staged),
|
||||
world -> finishOpen(targetPlayer, world, staged, studioService))
|
||||
.whenComplete((result, throwable) -> J.s(() -> finishOpenAttempt(
|
||||
commandSender,
|
||||
playerId,
|
||||
structure,
|
||||
closingRequestId,
|
||||
studioService,
|
||||
staged,
|
||||
result,
|
||||
throwable)));
|
||||
} catch (Throwable exception) {
|
||||
JigsawStudioActivation.finishOpen(playerId);
|
||||
studioService.cancelClose(closingRequestId);
|
||||
JigsawStudioActivation.rollback(staged);
|
||||
Iris.reportError("Failed to open Jigsaw Studio for '" + structure + "'.", exception);
|
||||
CommandJigsaw.sendError(commandSender, "Jigsaw Studio open failed: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void beginStagedOpen(JigsawStudioActivation.StagedActivation staged) {
|
||||
if (!JigsawStudioActivation.beginStagedGeneration(staged)) {
|
||||
throw new IllegalStateException("The staged Jigsaw Studio activation is no longer current");
|
||||
}
|
||||
}
|
||||
|
||||
private static void finishOpenAttempt(
|
||||
VolmitSender commandSender,
|
||||
UUID playerId,
|
||||
String structureKey,
|
||||
UUID closingRequestId,
|
||||
JigsawStudioService studioService,
|
||||
JigsawStudioActivation.StagedActivation staged,
|
||||
StudioOpenCoordinator.StudioOpenResult result,
|
||||
Throwable throwable
|
||||
) {
|
||||
JigsawStudioActivation.finishOpen(playerId);
|
||||
if (throwable == null && result != null) {
|
||||
studioService.cancelClose(closingRequestId);
|
||||
return;
|
||||
}
|
||||
studioService.cancelClose(closingRequestId);
|
||||
JigsawStudioActivation.rollback(staged);
|
||||
Throwable failure = unwrapCompletionFailure(throwable == null
|
||||
? new IllegalStateException("Studio open completed without a result")
|
||||
: throwable);
|
||||
if (isExpectedOpenDenial(failure)) {
|
||||
Iris.warn("Jigsaw Studio open for '%s' was denied: %s",
|
||||
structureKey, failure.getMessage());
|
||||
return;
|
||||
}
|
||||
Iris.reportError("Failed to open Jigsaw Studio for '" + structureKey + "'.", failure);
|
||||
CommandJigsaw.sendError(commandSender, "Jigsaw Studio open failed: " + failure.getMessage());
|
||||
}
|
||||
|
||||
private static void finishOpen(
|
||||
Player targetPlayer,
|
||||
World world,
|
||||
JigsawStudioActivation.StagedActivation staged,
|
||||
JigsawStudioService studioService
|
||||
) {
|
||||
if (world == null) {
|
||||
throw new IllegalStateException("Jigsaw Studio opened without a world");
|
||||
}
|
||||
JigsawStudioActivation.Request request = staged.request();
|
||||
JigsawStudioSession session = staged.session();
|
||||
try {
|
||||
WorldRuntimeControlService.get().applyObjectStudioWorldRules(world);
|
||||
} catch (Throwable exception) {
|
||||
Iris.reportError("Failed to apply Jigsaw Studio world rules for '" + world.getName() + "'.", exception);
|
||||
}
|
||||
JigsawStudioBay destination = initialBay(session.layout());
|
||||
Location target = destination == null
|
||||
? new Location(world, 0.5D, 66D, 0.5D)
|
||||
: new Location(
|
||||
world,
|
||||
destination.bounds().originX() + destination.bounds().dimensions().width() / 2.0D,
|
||||
destination.bounds().maxY() + 2.0D,
|
||||
destination.bounds().originZ() + destination.bounds().dimensions().depth() / 2.0D);
|
||||
if (destination != null) {
|
||||
session.selectBay(destination.stableId());
|
||||
}
|
||||
if (!JigsawStudioActivation.commit(staged)) {
|
||||
throw new IllegalStateException("The staged Jigsaw Studio activation could not be committed");
|
||||
}
|
||||
studioService.activationCommitted(world, request.requestId());
|
||||
CommandJigsaw.PLAYER_PACKS.put(targetPlayer.getUniqueId(), new CommandJigsaw.SessionBinding(
|
||||
request.packKey(), request.requestId()));
|
||||
try {
|
||||
J.runEntity(targetPlayer, () -> BukkitPlatform.teleportAsync(targetPlayer, target)
|
||||
.thenRun(() -> J.runEntity(targetPlayer, () -> targetPlayer.setGameMode(GameMode.CREATIVE))));
|
||||
} catch (RuntimeException exception) {
|
||||
Iris.reportError("Failed to move the player to the committed Jigsaw Studio workcell.", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static JigsawStudioBay initialBay(JigsawStudioLayout layout) {
|
||||
for (JigsawStudioBay bay : layout.bays()) {
|
||||
if (layout.defaultVariant(bay).isPresent()) {
|
||||
return bay;
|
||||
}
|
||||
}
|
||||
return layout.bays().isEmpty() ? null : layout.bays().getFirst();
|
||||
}
|
||||
|
||||
private static Throwable unwrapCompletionFailure(Throwable failure) {
|
||||
Throwable current = failure;
|
||||
while (current instanceof CompletionException && current.getCause() != null) {
|
||||
current = current.getCause();
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
static boolean isExpectedOpenDenial(Throwable failure) {
|
||||
return unwrapCompletionFailure(failure) instanceof BrokenPackException;
|
||||
}
|
||||
}
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawPlanarTopology;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioBay;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioCellDimensions;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioCompatibilityTarget;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioGraphEditor;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioLayout;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioMode;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioPoolEditor;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioVariant;
|
||||
import art.arcane.iris.core.service.JigsawStudioService;
|
||||
import art.arcane.iris.engine.object.IrisJigsawPiece;
|
||||
import art.arcane.iris.engine.object.IrisObject;
|
||||
import art.arcane.iris.engine.object.IrisPosition;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Director(name = "piece", description = "Manage contextual Studio pieces", origin = DirectorOrigin.PLAYER)
|
||||
public class CommandJigsawPiece implements DirectorExecutor {
|
||||
@Director(description = "Create an owned piece from the selected Studio topology", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void create(
|
||||
@Param(description = "Owned pool resource key") String poolKey,
|
||||
@Param(description = "New owned piece and object key") String pieceKey,
|
||||
@Param(description = "Positive selection weight", defaultValue = "1") int weight
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
JigsawPlanarTopology topology = null;
|
||||
JigsawStudioBay targetWorkcell = context.session().layout().get(JigsawStudioLayout.SPATIAL_WORKCELL_ID);
|
||||
if (context.session().layout().mode() == JigsawStudioMode.PLANAR_JIGSAW) {
|
||||
Location location = player().getLocation();
|
||||
JigsawStudioBay contextual = context.session().layout().findAt(
|
||||
location.getBlockX(), location.getBlockY(), location.getBlockZ());
|
||||
if (contextual == null) {
|
||||
contextual = CommandJigsaw.selectedBay(context);
|
||||
} else {
|
||||
context.session().selectBay(contextual.stableId());
|
||||
}
|
||||
if (contextual == null || contextual.topology().isEmpty()) {
|
||||
CommandJigsaw.sendError(this, "Stand in or select a planar topology workcell before creating a variant.");
|
||||
return;
|
||||
}
|
||||
topology = contextual.topology().get();
|
||||
targetWorkcell = contextual;
|
||||
}
|
||||
if (targetWorkcell == null) {
|
||||
CommandJigsaw.sendError(this, "The selected Jigsaw Studio workcell is no longer available.");
|
||||
return;
|
||||
}
|
||||
JigsawPlanarTopology selectedTopology = topology;
|
||||
String activationWorkcellId = targetWorkcell.stableId();
|
||||
JigsawStudioCellDimensions dimensions = targetWorkcell.capacity();
|
||||
String topologyText = selectedTopology == null ? "blank spatial" : selectedTopology.name();
|
||||
CommandJigsaw.runGraphMutation(player(), context, () -> {
|
||||
JigsawStudioGraphEditor.createPiece(
|
||||
context.request().source().getDataFolder().toPath(),
|
||||
context.request().structureKey(),
|
||||
poolKey,
|
||||
pieceKey,
|
||||
weight,
|
||||
dimensions,
|
||||
selectedTopology);
|
||||
return CommandJigsaw.mappedMutationResult(
|
||||
context,
|
||||
activationWorkcellId,
|
||||
pieceKey,
|
||||
"Created owned " + topologyText + " piece '" + pieceKey
|
||||
+ "' and added it to pool '" + poolKey + "' atomically.");
|
||||
});
|
||||
}
|
||||
|
||||
@Director(description = "Add an existing jigsaw piece to an owned pool transactionally", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void add(
|
||||
@Param(description = "Owned pool resource key") String poolKey,
|
||||
@Param(description = "Existing jigsaw piece key") String pieceKey,
|
||||
@Param(description = "Positive selection weight", defaultValue = "1") int weight
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
IrisJigsawPiece piece = context.request().source().getJigsawPieceLoader().load(pieceKey);
|
||||
if (piece == null) {
|
||||
CommandJigsaw.sendError(this, "No jigsaw piece '" + pieceKey + "' exists in this pack.");
|
||||
return;
|
||||
}
|
||||
if (piece.getObject() == null || piece.getObject().isBlank()) {
|
||||
CommandJigsaw.sendError(this, "Jigsaw piece '" + pieceKey + "' does not declare an object.");
|
||||
return;
|
||||
}
|
||||
IrisObject object = context.request().source().getObjectLoader().load(piece.getObject());
|
||||
if (object == null) {
|
||||
CommandJigsaw.sendError(this, "Jigsaw piece '" + pieceKey + "' has no loadable object.");
|
||||
return;
|
||||
}
|
||||
CommandJigsaw.PieceWorkcellResolution resolution;
|
||||
try {
|
||||
resolution = CommandJigsaw.resolvePieceWorkcell(context.session().layout(), piece, object);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
CommandJigsaw.sendError(this, "Jigsaw piece '" + pieceKey + "' cannot be assigned to a Studio workcell: "
|
||||
+ exception.getMessage());
|
||||
return;
|
||||
}
|
||||
if (!resolution.fits()) {
|
||||
IrisPosition required = resolution.requiredDimensions();
|
||||
JigsawStudioCellDimensions capacity = resolution.workcell().capacity();
|
||||
CommandJigsaw.sendError(this, "Jigsaw piece '" + pieceKey + "' requires " + required.getX() + "x"
|
||||
+ required.getY() + "x" + required.getZ() + " in " + resolution.workcell().displayName()
|
||||
+ ", but that workcell capacity is " + capacity.width() + "x" + capacity.height()
|
||||
+ "x" + capacity.depth() + ".");
|
||||
return;
|
||||
}
|
||||
String objectKey = piece.getObject();
|
||||
CommandJigsaw.runGraphMutation(player(), context, () -> {
|
||||
if (!JigsawStudioGraphEditor.ownsPiece(
|
||||
context.request().source().getDataFolder().toPath(),
|
||||
context.request().structureKey(),
|
||||
pieceKey,
|
||||
objectKey)) {
|
||||
throw new IOException("Piece add accepts only piece/object resources already owned by this project; "
|
||||
+ "use piece create for a new owned piece");
|
||||
}
|
||||
JigsawStudioPoolEditor.PoolUpdate update = JigsawStudioPoolEditor.addPiece(
|
||||
context.request().source().getDataFolder().toPath(),
|
||||
context.request().structureKey(),
|
||||
poolKey,
|
||||
pieceKey,
|
||||
weight);
|
||||
if (!update.changed()) {
|
||||
throw new IOException("The owned pool is missing or already references '" + pieceKey + "'");
|
||||
}
|
||||
JigsawStudioLayout layout = CommandJigsaw.loadPersistedLayout(context);
|
||||
String activationWorkcellId = CommandJigsaw.workcellIdForVariant(layout, pieceKey);
|
||||
return new JigsawStudioService.CommandGraphMutationResult(
|
||||
layout,
|
||||
activationWorkcellId,
|
||||
pieceKey,
|
||||
"Added '" + pieceKey + "' to pool '" + poolKey + "' in one graph transaction.");
|
||||
});
|
||||
}
|
||||
|
||||
@Director(description = "Remove the selected piece from an owned pool transactionally", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void remove(
|
||||
@Param(description = "Owned pool resource key") String poolKey
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioVariant selected = CommandJigsaw.selectedVariant(context);
|
||||
if (selected == null) {
|
||||
CommandJigsaw.sendError(this, "Select a workcell with an active variant before removing its pool entry.");
|
||||
return;
|
||||
}
|
||||
String pieceKey = selected.pieceKey();
|
||||
CommandJigsaw.runGraphMutation(player(), context, () -> {
|
||||
JigsawStudioPoolEditor.PoolUpdate update = JigsawStudioPoolEditor.removePiece(
|
||||
context.request().source().getDataFolder().toPath(),
|
||||
context.request().structureKey(),
|
||||
poolKey,
|
||||
pieceKey);
|
||||
if (!update.changed()) {
|
||||
throw new IOException("The owned pool does not reference selected piece '" + pieceKey + "'");
|
||||
}
|
||||
return CommandJigsaw.mappedMutationResult(
|
||||
context,
|
||||
"",
|
||||
"",
|
||||
"Removed '" + pieceKey + "' from pool '" + poolKey + "' in one graph transaction.");
|
||||
});
|
||||
}
|
||||
|
||||
@Director(description = "Allow or forbid cardinal rotation of the selected piece", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void rotatable(
|
||||
@Param(description = "Whether the selected piece may rotate") boolean rotatable
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioVariant selected = CommandJigsaw.selectedVariant(context);
|
||||
if (selected == null) {
|
||||
CommandJigsaw.sendError(this, "Select a workcell with an active variant before changing rotation.");
|
||||
return;
|
||||
}
|
||||
if (!rotatable
|
||||
&& context.request().compatibilityTarget()
|
||||
== JigsawStudioCompatibilityTarget.VANILLA_PORTABLE) {
|
||||
CommandJigsaw.sendError(this, "Vanilla-portable pieces must remain rotatable.");
|
||||
return;
|
||||
}
|
||||
String pieceKey = selected.pieceKey();
|
||||
CommandJigsaw.runGraphMutation(player(), context, () -> {
|
||||
JigsawStudioGraphEditor.updateRotatable(
|
||||
context.request().source().getDataFolder().toPath(),
|
||||
context.request().structureKey(),
|
||||
pieceKey,
|
||||
rotatable);
|
||||
return CommandJigsaw.mappedMutationResult(
|
||||
context,
|
||||
"",
|
||||
"",
|
||||
"Persisted piece '" + pieceKey + "' rotatable=" + rotatable + ".");
|
||||
});
|
||||
}
|
||||
|
||||
@Director(description = "Resize the selected piece object exactly to workcell capacity", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void expand() {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioBay workcell = CommandJigsaw.selectedBay(context);
|
||||
if (workcell == null) {
|
||||
CommandJigsaw.sendError(this, "Select a workcell before expanding its active variant.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioService.get().expandVariantToCell(player(), workcell.stableId());
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioGraphEditor;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
|
||||
@Director(name = "pool", description = "Manage owned jigsaw pools", origin = DirectorOrigin.PLAYER)
|
||||
public class CommandJigsawPool implements DirectorExecutor {
|
||||
@Director(description = "Create an empty owned jigsaw pool", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void create(
|
||||
@Param(description = "New owned pool resource key") String poolKey,
|
||||
@Param(description = "Owned fallback pool key, or none", defaultValue = "none")
|
||||
String fallbackPoolKey
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
String fallback = "none".equalsIgnoreCase(fallbackPoolKey) ? "" : fallbackPoolKey;
|
||||
CommandJigsaw.runGraphMutation(player(), context, () -> {
|
||||
JigsawStudioGraphEditor.createPool(
|
||||
context.request().source().getDataFolder().toPath(),
|
||||
context.request().structureKey(),
|
||||
poolKey,
|
||||
fallback);
|
||||
return CommandJigsaw.mappedMutationResult(
|
||||
context,
|
||||
"",
|
||||
"",
|
||||
"Created owned pool '" + poolKey + "' with fallback '"
|
||||
+ (fallback.isBlank() ? "none" : fallback) + "'.");
|
||||
});
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.core.service.JigsawStudioService;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
|
||||
@Director(name = "preview", description = "Assemble a read-only jigsaw preview",
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public class CommandJigsawPreview implements DirectorExecutor {
|
||||
@Director(name = "goto", aliases = "teleport", description = "Teleport to the live seed-1337 preview",
|
||||
sync = true, origin = DirectorOrigin.PLAYER)
|
||||
public void gotoPreview() {
|
||||
JigsawStudioService.get().goToPreview(player());
|
||||
}
|
||||
|
||||
@Director(description = "Assemble a deterministic preview at your location", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void assemble(
|
||||
@Param(description = "Assembly seed", defaultValue = "1337") long seed
|
||||
) {
|
||||
JigsawStudioService.get().previewStudio(player(), seed);
|
||||
}
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioCompatibilityTarget;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioPoolEditor;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioStructureEditor;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
|
||||
@Director(name = "rules", aliases = "rule", description = "Manage structure-wide jigsaw rules",
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public class CommandJigsawRules implements DirectorExecutor {
|
||||
@Director(description = "Set maximum expansion depth and horizontal radius", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void limits(
|
||||
@Param(description = "Maximum expansion depth") int maxDepth,
|
||||
@Param(description = "Maximum radius in chunks") int maxSizeChunks
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
if (context.request().compatibilityTarget() == JigsawStudioCompatibilityTarget.VANILLA_PORTABLE
|
||||
&& (maxDepth > 20 || maxSizeChunks > 8)) {
|
||||
CommandJigsaw.sendError(this, "Vanilla-portable rules require maxDepth <= 20 and maxSizeChunks <= 8.");
|
||||
return;
|
||||
}
|
||||
CommandJigsaw.runGraphMutation(player(), context, () -> {
|
||||
JigsawStudioStructureEditor.updateLimits(
|
||||
context.request().source().getDataFolder().toPath(),
|
||||
context.request().structureKey(),
|
||||
maxDepth,
|
||||
maxSizeChunks);
|
||||
return CommandJigsaw.mappedMutationResult(
|
||||
context,
|
||||
"",
|
||||
"",
|
||||
"Persisted jigsaw limits: maxDepth=" + maxDepth
|
||||
+ ", maxSizeChunks=" + maxSizeChunks + ".");
|
||||
});
|
||||
}
|
||||
|
||||
@Director(description = "Set or clear one owned pool's direct fallback", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void fallback(
|
||||
@Param(description = "Owned pool resource key") String poolKey,
|
||||
@Param(description = "Owned fallback pool key, or none") String fallbackPoolKey
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
String fallback = "none".equalsIgnoreCase(fallbackPoolKey) ? "" : fallbackPoolKey;
|
||||
CommandJigsaw.runGraphMutation(player(), context, () -> {
|
||||
JigsawStudioPoolEditor.PoolUpdate update = JigsawStudioPoolEditor.updateFallback(
|
||||
context.request().source().getDataFolder().toPath(),
|
||||
context.request().structureKey(),
|
||||
poolKey,
|
||||
fallback);
|
||||
if (!update.changed()) {
|
||||
return CommandJigsaw.mappedMutationResult(context, "", "", "Pool fallback is unchanged.");
|
||||
}
|
||||
return CommandJigsaw.mappedMutationResult(
|
||||
context,
|
||||
"",
|
||||
"",
|
||||
"Persisted pool '" + poolKey + "' fallback as '"
|
||||
+ (fallback.isBlank() ? "none" : fallback) + "'.");
|
||||
});
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioBay;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioCellDimensions;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioPoolEditor;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioVariant;
|
||||
import art.arcane.iris.core.service.JigsawStudioService;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Director(name = "variant", description = "Manage selected jigsaw variants", origin = DirectorOrigin.PLAYER)
|
||||
public class CommandJigsawVariant implements DirectorExecutor {
|
||||
@Director(description = "Set the selected piece weight in a pool", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void weight(
|
||||
@Param(description = "Pool resource key") String poolKey,
|
||||
@Param(description = "Positive selection weight") int weight
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioVariant selected = CommandJigsaw.selectedVariant(context);
|
||||
if (selected == null) {
|
||||
CommandJigsaw.sendError(this, "Select a workcell with an active variant before changing its weight.");
|
||||
return;
|
||||
}
|
||||
String pieceKey = selected.pieceKey();
|
||||
CommandJigsaw.runGraphMutation(player(), context, () -> {
|
||||
JigsawStudioPoolEditor.WeightUpdate update = JigsawStudioPoolEditor.updateWeight(
|
||||
context.request().source().getDataFolder().toPath(),
|
||||
context.request().structureKey(),
|
||||
poolKey,
|
||||
pieceKey,
|
||||
weight);
|
||||
if (!update.changed()) {
|
||||
throw new IOException("Pool '" + poolKey + "' does not reference selected piece '"
|
||||
+ pieceKey + "'");
|
||||
}
|
||||
return CommandJigsaw.mappedMutationResult(
|
||||
context,
|
||||
"",
|
||||
"",
|
||||
"Updated " + update.changedEntries() + " pool entry weight atomically in "
|
||||
+ update.poolPath() + ".");
|
||||
});
|
||||
}
|
||||
|
||||
@Director(description = "Resize only the active variant object within workcell capacity", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void resize(
|
||||
@Param(description = "Variant width") int width,
|
||||
@Param(description = "Variant height") int height,
|
||||
@Param(description = "Variant depth") int depth
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioBay selectedWorkcell = CommandJigsaw.selectedBay(context);
|
||||
JigsawStudioVariant selectedVariant = CommandJigsaw.selectedVariant(context);
|
||||
if (selectedWorkcell == null || selectedVariant == null) {
|
||||
CommandJigsaw.sendError(this, "Select a workcell with an active variant before resizing it.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioCellDimensions dimensions;
|
||||
try {
|
||||
dimensions = new JigsawStudioCellDimensions(width, height, depth);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
CommandJigsaw.sendError(this, exception.getMessage());
|
||||
return;
|
||||
}
|
||||
JigsawStudioService.get().resizeVariant(
|
||||
player(),
|
||||
selectedWorkcell.stableId(),
|
||||
selectedVariant.pieceKey(),
|
||||
dimensions);
|
||||
}
|
||||
|
||||
@Director(description = "Set the active variant's author-facing label", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void label(
|
||||
@Param(description = "Display label; quote labels containing spaces") String displayName
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioBay selectedWorkcell = CommandJigsaw.selectedBay(context);
|
||||
JigsawStudioVariant selectedVariant = CommandJigsaw.selectedVariant(context);
|
||||
if (selectedWorkcell == null || selectedVariant == null) {
|
||||
CommandJigsaw.sendError(this, "Select a workcell with an active variant before renaming it.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioService.get().updateVariantDisplayName(
|
||||
player(),
|
||||
selectedWorkcell.stableId(),
|
||||
selectedVariant.pieceKey(),
|
||||
displayName);
|
||||
}
|
||||
|
||||
@Director(name = "label-reset", aliases = "reset-label",
|
||||
description = "Reset the active variant to its resource-key label", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void labelReset() {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioBay selectedWorkcell = CommandJigsaw.selectedBay(context);
|
||||
JigsawStudioVariant selectedVariant = CommandJigsaw.selectedVariant(context);
|
||||
if (selectedWorkcell == null || selectedVariant == null) {
|
||||
CommandJigsaw.sendError(this, "Select a workcell with an active variant before resetting its label.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioService.get().updateVariantDisplayName(
|
||||
player(),
|
||||
selectedWorkcell.stableId(),
|
||||
selectedVariant.pieceKey(),
|
||||
"");
|
||||
}
|
||||
|
||||
@Director(description = "Duplicate the active variant in only this workcell", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void duplicate() {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioBay selected = CommandJigsaw.selectedBay(context);
|
||||
if (selected == null || CommandJigsaw.selectedVariant(context) == null) {
|
||||
CommandJigsaw.sendError(this, "Select a workcell with an active variant before duplicating it.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioService.get().createVariant(player(), selected.stableId(), true);
|
||||
}
|
||||
|
||||
@Director(name = "duplicate-family", aliases = "family",
|
||||
description = "Duplicate every enabled workcell's active variant as one coherent family",
|
||||
sync = true, origin = DirectorOrigin.PLAYER)
|
||||
public void duplicateFamily(
|
||||
@Param(description = "Family key or next", defaultValue = "next") String themeKey
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
String selectedTheme = themeKey;
|
||||
if ("next".equalsIgnoreCase(themeKey)) {
|
||||
try {
|
||||
selectedTheme = CommandJigsaw.nextThemeKey(context);
|
||||
} catch (IOException | ArithmeticException exception) {
|
||||
CommandJigsaw.sendError(this, exception.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
JigsawStudioService.get().duplicateActiveFamily(player(), selectedTheme);
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioBay;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioCellDimensions;
|
||||
import art.arcane.iris.core.service.JigsawStudioService;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
|
||||
@Director(name = "workcell", aliases = "cell", description = "Manage the selected Studio workcell",
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public class CommandJigsawWorkcell implements DirectorExecutor {
|
||||
@Director(description = "Set capacity without resizing any variant object", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void capacity(
|
||||
@Param(description = "Capacity width") int width,
|
||||
@Param(description = "Capacity height") int height,
|
||||
@Param(description = "Capacity depth") int depth
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioBay selected = CommandJigsaw.selectedBay(context);
|
||||
if (selected == null) {
|
||||
CommandJigsaw.sendError(this, "Select a workcell before changing its capacity.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioCellDimensions dimensions;
|
||||
try {
|
||||
dimensions = new JigsawStudioCellDimensions(width, height, depth);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
CommandJigsaw.sendError(this, exception.getMessage());
|
||||
return;
|
||||
}
|
||||
JigsawStudioService.get().updateWorkcellDimensions(
|
||||
player(),
|
||||
selected.stableId(),
|
||||
dimensions);
|
||||
}
|
||||
|
||||
@Director(description = "Set the selected workcell's author-facing label", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void label(
|
||||
@Param(description = "Display label; quote labels containing spaces") String displayName
|
||||
) {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioBay selected = CommandJigsaw.selectedBay(context);
|
||||
if (selected == null) {
|
||||
CommandJigsaw.sendError(this, "Select a workcell before renaming it.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioService.get().updateWorkcellDisplayName(
|
||||
player(),
|
||||
selected.stableId(),
|
||||
displayName);
|
||||
}
|
||||
|
||||
@Director(name = "label-reset", aliases = "reset-label",
|
||||
description = "Reset the selected workcell to its canonical solver label", sync = true,
|
||||
origin = DirectorOrigin.PLAYER)
|
||||
public void labelReset() {
|
||||
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
|
||||
if (context == null) {
|
||||
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioBay selected = CommandJigsaw.selectedBay(context);
|
||||
if (selected == null) {
|
||||
CommandJigsaw.sendError(this, "Select a workcell before resetting its label.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioService.get().updateWorkcellDisplayName(player(), selected.stableId(), "");
|
||||
}
|
||||
}
|
||||
+62
-62
@@ -108,10 +108,10 @@ public class CommandJigsawContractTest {
|
||||
BrokenPackException failure = new BrokenPackException(
|
||||
"overworld", List.of("Biome 'broken' has no resolvable regions."));
|
||||
|
||||
assertTrue(CommandJigsaw.isExpectedOpenDenial(failure));
|
||||
assertTrue(CommandJigsaw.isExpectedOpenDenial(
|
||||
assertTrue(CommandJigsawOpen.isExpectedOpenDenial(failure));
|
||||
assertTrue(CommandJigsawOpen.isExpectedOpenDenial(
|
||||
new CompletionException(failure)));
|
||||
assertFalse(CommandJigsaw.isExpectedOpenDenial(
|
||||
assertFalse(CommandJigsawOpen.isExpectedOpenDenial(
|
||||
new IllegalStateException("Unexpected Studio failure")));
|
||||
}
|
||||
|
||||
@@ -138,20 +138,20 @@ public class CommandJigsawContractTest {
|
||||
|
||||
@Test
|
||||
public void adoptionCommandsExposeTwoStepPlanContract() throws Exception {
|
||||
Method inspect = CommandJigsaw.CommandJigsawAdopt.class.getDeclaredMethod(
|
||||
Method inspect = CommandJigsawAdopt.class.getDeclaredMethod(
|
||||
"inspect", IrisDimension.class, String.class, String.class, String.class);
|
||||
Method apply = CommandJigsaw.CommandJigsawAdopt.class.getDeclaredMethod("apply", String.class);
|
||||
Method apply = CommandJigsawAdopt.class.getDeclaredMethod("apply", String.class);
|
||||
Parameter[] inspectParameters = inspect.getParameters();
|
||||
|
||||
assertNotNull(inspect.getAnnotation(Director.class));
|
||||
assertNotNull(apply.getAnnotation(Director.class));
|
||||
assertParameter(inspectParameters[2], "auto", null);
|
||||
assertParameter(inspectParameters[3], "auto", CommandJigsaw.JigsawAdoptionStrategyHandler.class);
|
||||
assertEquals(CommandJigsaw.JigsawAdoptionPlanHandler.class,
|
||||
assertParameter(inspectParameters[3], "auto", CommandJigsawAdopt.JigsawAdoptionStrategyHandler.class);
|
||||
assertEquals(CommandJigsawAdopt.JigsawAdoptionPlanHandler.class,
|
||||
apply.getParameters()[0].getAnnotation(Param.class).customHandler());
|
||||
|
||||
CommandJigsaw.JigsawAdoptionStrategyHandler strategyHandler =
|
||||
new CommandJigsaw.JigsawAdoptionStrategyHandler();
|
||||
CommandJigsawAdopt.JigsawAdoptionStrategyHandler strategyHandler =
|
||||
new CommandJigsawAdopt.JigsawAdoptionStrategyHandler();
|
||||
assertEquals(List.of("auto", "in-place", "clone"), strategyHandler.getPossibilities());
|
||||
assertEquals("in-place", strategyHandler.parse("claim", false));
|
||||
assertEquals("clone", strategyHandler.parse("copy", false));
|
||||
@@ -163,14 +163,14 @@ public class CommandJigsawContractTest {
|
||||
Path root = temporaryFolder.newFolder("adoption-provenance").toPath();
|
||||
|
||||
assertEquals(IrisStructureAdoptionInputKind.UNOWNED_IRIS,
|
||||
CommandJigsaw.adoptionInputKind(root, "unowned"));
|
||||
CommandJigsawAdopt.adoptionInputKind(root, "unowned"));
|
||||
writeManifest(root, "datapack-created", StructureSource.Kind.DATAPACK,
|
||||
StructureOwnershipManifest.Provenance.created());
|
||||
assertEquals(IrisStructureAdoptionInputKind.UNOWNED_IRIS,
|
||||
CommandJigsaw.adoptionInputKind(root, "datapack-created"));
|
||||
CommandJigsawAdopt.adoptionInputKind(root, "datapack-created"));
|
||||
writeManifest(root, "managed-provenance", StructureSource.Kind.IRIS, managedProvenance());
|
||||
assertEquals(IrisStructureAdoptionInputKind.MANAGED_DATAPACK,
|
||||
CommandJigsaw.adoptionInputKind(root, "managed-provenance"));
|
||||
CommandJigsawAdopt.adoptionInputKind(root, "managed-provenance"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -275,70 +275,70 @@ public class CommandJigsawContractTest {
|
||||
|
||||
@Test
|
||||
public void exposesNestedPieceVariantAndPreviewCommands() throws Exception {
|
||||
assertNotNull(CommandJigsaw.CommandJigsawPool.class
|
||||
assertNotNull(CommandJigsawPool.class
|
||||
.getDeclaredMethod("create", String.class, String.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawConnector.class
|
||||
assertNotNull(CommandJigsawConnector.class
|
||||
.getDeclaredMethod("channel", String.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawPiece.class
|
||||
assertNotNull(CommandJigsawPiece.class
|
||||
.getDeclaredMethod("create", String.class, String.class, int.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawPiece.class
|
||||
assertNotNull(CommandJigsawPiece.class
|
||||
.getDeclaredMethod("add", String.class, String.class, int.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawPiece.class
|
||||
assertNotNull(CommandJigsawPiece.class
|
||||
.getDeclaredMethod("remove", String.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawPiece.class
|
||||
assertNotNull(CommandJigsawPiece.class
|
||||
.getDeclaredMethod("rotatable", boolean.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawPiece.class
|
||||
assertNotNull(CommandJigsawPiece.class
|
||||
.getDeclaredMethod("expand")
|
||||
.getAnnotation(Director.class));
|
||||
assertEquals("Resize the selected piece object exactly to workcell capacity",
|
||||
CommandJigsaw.CommandJigsawPiece.class
|
||||
CommandJigsawPiece.class
|
||||
.getDeclaredMethod("expand")
|
||||
.getAnnotation(Director.class)
|
||||
.description());
|
||||
assertNotNull(CommandJigsaw.CommandJigsawVariant.class
|
||||
assertNotNull(CommandJigsawVariant.class
|
||||
.getDeclaredMethod("weight", String.class, int.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawVariant.class
|
||||
assertNotNull(CommandJigsawVariant.class
|
||||
.getDeclaredMethod("resize", int.class, int.class, int.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawVariant.class
|
||||
assertNotNull(CommandJigsawVariant.class
|
||||
.getDeclaredMethod("label", String.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawVariant.class
|
||||
assertNotNull(CommandJigsawVariant.class
|
||||
.getDeclaredMethod("labelReset")
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawVariant.class
|
||||
assertNotNull(CommandJigsawVariant.class
|
||||
.getDeclaredMethod("duplicate")
|
||||
.getAnnotation(Director.class));
|
||||
Method duplicateFamily = CommandJigsaw.CommandJigsawVariant.class
|
||||
Method duplicateFamily = CommandJigsawVariant.class
|
||||
.getDeclaredMethod("duplicateFamily", String.class);
|
||||
assertNotNull(duplicateFamily.getAnnotation(Director.class));
|
||||
assertParameter(duplicateFamily.getParameters()[0], "next", null);
|
||||
assertNotNull(CommandJigsaw.CommandJigsawWorkcell.class
|
||||
assertNotNull(CommandJigsawWorkcell.class
|
||||
.getDeclaredMethod("capacity", int.class, int.class, int.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawWorkcell.class
|
||||
assertNotNull(CommandJigsawWorkcell.class
|
||||
.getDeclaredMethod("label", String.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawWorkcell.class
|
||||
assertNotNull(CommandJigsawWorkcell.class
|
||||
.getDeclaredMethod("labelReset")
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawRules.class
|
||||
assertNotNull(CommandJigsawRules.class
|
||||
.getDeclaredMethod("limits", int.class, int.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawRules.class
|
||||
assertNotNull(CommandJigsawRules.class
|
||||
.getDeclaredMethod("fallback", String.class, String.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawPreview.class
|
||||
assertNotNull(CommandJigsawPreview.class
|
||||
.getDeclaredMethod("assemble", long.class)
|
||||
.getAnnotation(Director.class));
|
||||
assertNotNull(CommandJigsaw.CommandJigsawPreview.class
|
||||
assertNotNull(CommandJigsawPreview.class
|
||||
.getDeclaredMethod("gotoPreview")
|
||||
.getAnnotation(Director.class));
|
||||
}
|
||||
@@ -393,14 +393,14 @@ public class CommandJigsawContractTest {
|
||||
public void everyExecutableCommandRejectsNonPlayerOrigins() {
|
||||
Class<?>[] commandTypes = {
|
||||
CommandJigsaw.class,
|
||||
CommandJigsaw.CommandJigsawConnector.class,
|
||||
CommandJigsaw.CommandJigsawPool.class,
|
||||
CommandJigsaw.CommandJigsawPiece.class,
|
||||
CommandJigsaw.CommandJigsawVariant.class,
|
||||
CommandJigsaw.CommandJigsawWorkcell.class,
|
||||
CommandJigsaw.CommandJigsawRules.class,
|
||||
CommandJigsaw.CommandJigsawPreview.class,
|
||||
CommandJigsaw.CommandJigsawAdopt.class
|
||||
CommandJigsawConnector.class,
|
||||
CommandJigsawPool.class,
|
||||
CommandJigsawPiece.class,
|
||||
CommandJigsawVariant.class,
|
||||
CommandJigsawWorkcell.class,
|
||||
CommandJigsawRules.class,
|
||||
CommandJigsawPreview.class,
|
||||
CommandJigsawAdopt.class
|
||||
};
|
||||
|
||||
for (Class<?> commandType : commandTypes) {
|
||||
@@ -417,17 +417,17 @@ public class CommandJigsawContractTest {
|
||||
public void exportOutputIsOneSafeChildArtifact() {
|
||||
Path root = Path.of("build", "jigsaw-exports").toAbsolutePath().normalize();
|
||||
|
||||
assertEquals(root.resolve("village.zip"), CommandJigsaw.resolveExportDestination(
|
||||
assertEquals(root.resolve("village.zip"), CommandJigsawExports.resolveExportDestination(
|
||||
root, "village", VanillaJigsawExportFormat.ZIP));
|
||||
assertEquals(root.resolve("village"), CommandJigsaw.resolveExportDestination(
|
||||
assertEquals(root.resolve("village"), CommandJigsawExports.resolveExportDestination(
|
||||
root, "village", VanillaJigsawExportFormat.DIRECTORY));
|
||||
assertThrows(IllegalArgumentException.class, () -> CommandJigsaw.resolveExportDestination(
|
||||
assertThrows(IllegalArgumentException.class, () -> CommandJigsawExports.resolveExportDestination(
|
||||
root, "", VanillaJigsawExportFormat.DIRECTORY));
|
||||
assertThrows(IllegalArgumentException.class, () -> CommandJigsaw.resolveExportDestination(
|
||||
assertThrows(IllegalArgumentException.class, () -> CommandJigsawExports.resolveExportDestination(
|
||||
root, ".", VanillaJigsawExportFormat.DIRECTORY));
|
||||
assertThrows(IllegalArgumentException.class, () -> CommandJigsaw.resolveExportDestination(
|
||||
assertThrows(IllegalArgumentException.class, () -> CommandJigsawExports.resolveExportDestination(
|
||||
root, "../all-exports", VanillaJigsawExportFormat.DIRECTORY));
|
||||
assertThrows(IllegalArgumentException.class, () -> CommandJigsaw.resolveExportDestination(
|
||||
assertThrows(IllegalArgumentException.class, () -> CommandJigsawExports.resolveExportDestination(
|
||||
root, "nested/export", VanillaJigsawExportFormat.DIRECTORY));
|
||||
}
|
||||
|
||||
@@ -438,42 +438,42 @@ public class CommandJigsawContractTest {
|
||||
Path firstDestination = Path.of("build", "jigsaw-exports", "first.zip");
|
||||
Path secondDestination = Path.of("build", "jigsaw-exports", "second.zip");
|
||||
|
||||
assertEquals(true, CommandJigsaw.beginExport(firstPlayer, firstDestination));
|
||||
assertEquals(true, CommandJigsawExports.beginExport(firstPlayer, firstDestination));
|
||||
try {
|
||||
assertEquals(false, CommandJigsaw.beginExport(firstPlayer, secondDestination));
|
||||
assertEquals(false, CommandJigsaw.beginExport(secondPlayer, firstDestination));
|
||||
assertEquals(false, CommandJigsawExports.beginExport(firstPlayer, secondDestination));
|
||||
assertEquals(false, CommandJigsawExports.beginExport(secondPlayer, firstDestination));
|
||||
} finally {
|
||||
CommandJigsaw.finishExport(firstPlayer, firstDestination);
|
||||
CommandJigsawExports.finishExport(firstPlayer, firstDestination);
|
||||
}
|
||||
|
||||
assertEquals(true, CommandJigsaw.beginExport(secondPlayer, firstDestination));
|
||||
CommandJigsaw.finishExport(secondPlayer, firstDestination);
|
||||
assertEquals(true, CommandJigsawExports.beginExport(secondPlayer, firstDestination));
|
||||
CommandJigsawExports.finishExport(secondPlayer, firstDestination);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exportStartFailuresHavePreciseOperatorMessages() {
|
||||
assertEquals("The active Jigsaw Studio is no longer available.",
|
||||
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.NOT_ACTIVE));
|
||||
CommandJigsawExports.exportStartError(JigsawStudioService.ExportStart.NOT_ACTIVE));
|
||||
assertEquals("Only the Jigsaw Studio owner can export this project.",
|
||||
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.NOT_OWNER));
|
||||
CommandJigsawExports.exportStartError(JigsawStudioService.ExportStart.NOT_OWNER));
|
||||
assertEquals("Wait for the pending autosave or discard the edits before exporting the on-disk graph.",
|
||||
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.DIRTY));
|
||||
CommandJigsawExports.exportStartError(JigsawStudioService.ExportStart.DIRTY));
|
||||
assertEquals("The active Jigsaw Studio is closing and cannot be exported.",
|
||||
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.CLOSING));
|
||||
CommandJigsawExports.exportStartError(JigsawStudioService.ExportStart.CLOSING));
|
||||
assertEquals("Wait for the current Jigsaw Studio save to finish before exporting.",
|
||||
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.SAVE_IN_PROGRESS));
|
||||
CommandJigsawExports.exportStartError(JigsawStudioService.ExportStart.SAVE_IN_PROGRESS));
|
||||
assertEquals("Wait for the current Jigsaw Studio operation to finish before exporting.",
|
||||
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.OPERATION_IN_PROGRESS));
|
||||
CommandJigsawExports.exportStartError(JigsawStudioService.ExportStart.OPERATION_IN_PROGRESS));
|
||||
assertEquals("A Jigsaw Studio export is already in progress.",
|
||||
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.IN_PROGRESS));
|
||||
CommandJigsawExports.exportStartError(JigsawStudioService.ExportStart.IN_PROGRESS));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.STARTED));
|
||||
() -> CommandJigsawExports.exportStartError(JigsawStudioService.ExportStart.STARTED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exportLeaseReleaseActionRunsExactlyOnce() {
|
||||
AtomicInteger releases = new AtomicInteger();
|
||||
CommandJigsaw.ExportLease lease = new CommandJigsaw.ExportLease(releases::incrementAndGet);
|
||||
CommandJigsawExports.ExportLease lease = new CommandJigsawExports.ExportLease(releases::incrementAndGet);
|
||||
|
||||
lease.release();
|
||||
lease.release();
|
||||
|
||||
@@ -35,6 +35,16 @@ public final class GenerationRevisionScope {
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandPregen.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandJigsaw.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandJigsawAdopt.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandJigsawConnector.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandJigsawExports.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandJigsawOpen.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandJigsawPiece.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandJigsawPool.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandJigsawPreview.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandJigsawRules.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandJigsawVariant.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandJigsawWorkcell.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandStudio.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandObject.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/PaperCommandRegistrar.java",
|
||||
|
||||
Reference in New Issue
Block a user