mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
Split jigsaw studio menus
- Move each jigsaw menu kind into its own renderer class - Move workcell capacity staging into JigsawStudioWorkcellMenu - Move menu presentation helpers and adjustment math into JigsawStudioMenuFormat - Promote the menu action contract to JigsawStudioMenuActions - Point the menu controller test at the relocated menu types
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioToolAction;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioToolPayload;
|
||||
import art.arcane.volmlib.util.inventorygui.UIElement;
|
||||
import art.arcane.volmlib.util.inventorygui.UIWindow;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.CHANCE_STEP_PERCENTAGE_POINTS;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.GRID_POSITIONS;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.MEMBERSHIPS_PER_PAGE;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.chance;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.clampPage;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.dimensions;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.displayKey;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.element;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.evaluationElement;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.maximumPlacements;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.page;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.pageCount;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.safe;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.themes;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.yesNo;
|
||||
|
||||
final class JigsawStudioDetailsMenu {
|
||||
private final JigsawStudioMenuController controller;
|
||||
|
||||
JigsawStudioDetailsMenu(JigsawStudioMenuController controller) {
|
||||
this.controller = Objects.requireNonNull(controller, "Jigsaw Studio menu controller");
|
||||
}
|
||||
|
||||
void render(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
JigsawStudioMenuState.Variant variant,
|
||||
int requestedPage
|
||||
) {
|
||||
int page = clampPage(requestedPage, variant.memberships().size(), MEMBERSHIPS_PER_PAGE);
|
||||
controller.purgeExpiredConfirmations(window.getViewer().getUniqueId());
|
||||
window.batch(() -> {
|
||||
window.clearElements();
|
||||
addDetailsHeader(window, state, workcell, variant, page);
|
||||
addMemberships(window, state, workcell, variant, page);
|
||||
addDetailsFooter(window, state, workcell, variant, page);
|
||||
});
|
||||
}
|
||||
|
||||
private void addDetailsHeader(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
JigsawStudioMenuState.Variant variant,
|
||||
int page
|
||||
) {
|
||||
UIElement back = element("back", Material.ARROW, ChatColor.YELLOW + "Back to Variants");
|
||||
back.onLeftClick(clicked -> controller.refreshMain(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), 0));
|
||||
window.setElement(-4, 0, back);
|
||||
|
||||
UIElement identity = element("identity", Material.NAME_TAG, ChatColor.AQUA + safe(variant.displayName()));
|
||||
identity.addLore(ChatColor.DARK_GRAY + safe(variant.pieceKey()));
|
||||
identity.addLore(variant.owned()
|
||||
? ChatColor.GREEN + "Owned variant"
|
||||
: ChatColor.GRAY + "Read-only variant");
|
||||
identity.addLore(variant.active()
|
||||
? ChatColor.GREEN + "Loaded in this workcell"
|
||||
: ChatColor.GRAY + "Not currently loaded");
|
||||
identity.addLore(ChatColor.GRAY + "Size: " + variant.dimensions()
|
||||
.map(JigsawStudioMenuFormat::dimensions)
|
||||
.orElse("Object missing"));
|
||||
identity.addLore(ChatColor.GRAY + "Themes: " + themes(variant.themes()));
|
||||
identity.addLore(ChatColor.GRAY + "Depth: " + variant.rules().minimumDepth()
|
||||
+ "-" + variant.rules().maximumDepth());
|
||||
identity.addLore(ChatColor.GRAY + "Placements: " + variant.rules().minimumPlacements()
|
||||
+ "-" + maximumPlacements(variant.rules().maximumPlacements()));
|
||||
identity.addLore(ChatColor.GRAY + "Terminal: " + yesNo(variant.rules().terminal()));
|
||||
identity.addLore(ChatColor.GRAY + "Pool entries: " + variant.memberships().size());
|
||||
if (variant.owned()) {
|
||||
identity.addLore(ChatColor.YELLOW + "Left-click for a rename stick");
|
||||
identity.addLore(ChatColor.GRAY + "Rename that stick in an anvil, then right-click it.");
|
||||
identity.addLore(ChatColor.GRAY + "Sneak-right-click the stick to reset this label.");
|
||||
identity.onLeftClick(clicked -> controller.actions.giveTool(
|
||||
window.getViewer(),
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.RENAME_VARIANT,
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey())));
|
||||
}
|
||||
window.setElement(0, 0, identity);
|
||||
|
||||
boolean activeOwned = variant.active() && variant.owned();
|
||||
boolean irisRuleEditing = activeOwned && state.irisExtended();
|
||||
UIElement settings = element(
|
||||
"variant-settings",
|
||||
irisRuleEditing ? Material.COMPARATOR : Material.GRAY_DYE,
|
||||
irisRuleEditing
|
||||
? ChatColor.GOLD + "Themes & Piece Rules"
|
||||
: ChatColor.GRAY + "Themes & Rules Unavailable");
|
||||
settings.addLore(irisRuleEditing
|
||||
? ChatColor.GRAY + "Edit this loaded variant's theme membership and placement rules."
|
||||
: state.irisExtended()
|
||||
? ChatColor.GRAY + "Load an owned variant before editing its rules."
|
||||
: ChatColor.GRAY + "Vanilla-portable pieces cannot encode Iris theme or rule metadata.");
|
||||
if (irisRuleEditing) {
|
||||
settings.addLore(ChatColor.YELLOW + "Left-click to edit");
|
||||
settings.onLeftClick(clicked -> controller.openVariantSettings(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), variant.pieceKey(), 0));
|
||||
}
|
||||
window.setElement(-2, 0, settings);
|
||||
|
||||
boolean rotationEditable = variant.active() && variant.rotationEditable();
|
||||
UIElement rotation = element(
|
||||
"rotation",
|
||||
rotationEditable ? Material.REPEATER : Material.BARRIER,
|
||||
rotationEditable
|
||||
? ChatColor.LIGHT_PURPLE + "Toggle Rotation"
|
||||
: ChatColor.GRAY + "Rotation is Read-only");
|
||||
rotation.addLore(ChatColor.GRAY + "Currently: " + (variant.rotatable() ? "Enabled" : "Disabled"));
|
||||
if (rotationEditable) {
|
||||
rotation.addLore(ChatColor.YELLOW + "Left-click to toggle");
|
||||
rotation.onLeftClick(clicked -> controller.toggleRotation(
|
||||
window.getViewer(), state.requestId(), workcell.stableId()));
|
||||
} else if (!variant.active()) {
|
||||
rotation.addLore(ChatColor.GRAY + "Load this variant before editing rotation.");
|
||||
} else if (variant.owned() && variant.rotatable()) {
|
||||
rotation.addLore(ChatColor.GRAY + "Vanilla-portable variants must remain rotatable.");
|
||||
}
|
||||
window.setElement(3, 0, rotation);
|
||||
|
||||
boolean sizeEditable = variant.owned() && variant.dimensions().isPresent();
|
||||
UIElement size = element(
|
||||
"variant-size",
|
||||
sizeEditable ? Material.SCAFFOLDING : Material.BARRIER,
|
||||
sizeEditable
|
||||
? ChatColor.GREEN + "Edit This Variant's Size"
|
||||
: ChatColor.GRAY + "Variant Size is Read-only");
|
||||
size.addLore(ChatColor.GRAY + "Current: " + variant.dimensions()
|
||||
.map(JigsawStudioMenuFormat::dimensions)
|
||||
.orElse("Object missing"));
|
||||
size.addLore(ChatColor.GRAY + "Workcell capacity: " + dimensions(workcell.capacity()));
|
||||
if (sizeEditable) {
|
||||
size.addLore(ChatColor.YELLOW + "Left-click for width, height, and depth controls");
|
||||
size.onLeftClick(clicked -> controller.openVariantSizeSettings(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), variant.pieceKey()));
|
||||
if (variant.resizableToCapacity()) {
|
||||
size.addLore(ChatColor.YELLOW + "Shift-left: resize this variant to capacity");
|
||||
size.onShiftLeftClick(clicked -> controller.resizeVariant(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
workcell.capacity()));
|
||||
}
|
||||
}
|
||||
window.setElement(2, 0, size);
|
||||
|
||||
boolean lastVariant = workcell.variants().size() <= 1;
|
||||
boolean deletionAvailable = variant.owned() && !variant.active() && !lastVariant;
|
||||
boolean confirmed = deletionAvailable && controller.isDeleteConfirmed(
|
||||
window.getViewer().getUniqueId(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey());
|
||||
UIElement delete = element(
|
||||
"delete-variant",
|
||||
deletionAvailable ? confirmed ? Material.REDSTONE : Material.LAVA_BUCKET : Material.BARRIER,
|
||||
deletionAvailable
|
||||
? confirmed ? ChatColor.RED + "Confirm Delete Variant" : ChatColor.RED + "Delete Variant"
|
||||
: variant.active()
|
||||
? ChatColor.GRAY + "Loaded Variant Cannot Be Deleted"
|
||||
: lastVariant
|
||||
? ChatColor.GRAY + "Last Variant Cannot Be Deleted"
|
||||
: ChatColor.GRAY + "Deletion is Read-only")
|
||||
.setEnchanted(confirmed);
|
||||
if (deletionAvailable) {
|
||||
delete.addLore(confirmed
|
||||
? ChatColor.RED + "Click again to permanently delete this owned variant."
|
||||
: ChatColor.RED + "Click twice within 10 seconds to confirm.");
|
||||
delete.onLeftClick(clicked -> controller.confirmOrDeleteVariant(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
page));
|
||||
} else if (variant.active()) {
|
||||
delete.addLore(ChatColor.GRAY + "Load another variant in this workcell first.");
|
||||
} else if (lastVariant) {
|
||||
delete.addLore(ChatColor.GRAY + "Create another variant before deleting this one.");
|
||||
}
|
||||
window.setElement(4, 0, delete);
|
||||
}
|
||||
|
||||
private void addMemberships(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
JigsawStudioMenuState.Variant variant,
|
||||
int page
|
||||
) {
|
||||
List<JigsawStudioMenuState.Membership> memberships = page(
|
||||
variant.memberships(), page, MEMBERSHIPS_PER_PAGE);
|
||||
if (memberships.isEmpty()) {
|
||||
UIElement empty = element("no-memberships", Material.BARRIER, ChatColor.RED + "No Pool Entries");
|
||||
empty.addLore(ChatColor.GRAY + "This variant is not linked from an owned pool.");
|
||||
window.setElement(0, 2, empty);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int index = 0; index < memberships.size(); index++) {
|
||||
JigsawStudioMenuState.Membership membership = memberships.get(index);
|
||||
int position = GRID_POSITIONS[index % GRID_POSITIONS.length];
|
||||
int row = 1 + index / GRID_POSITIONS.length;
|
||||
boolean confirmed = variant.active() && variant.owned() && controller.isUnlinkConfirmed(
|
||||
window.getViewer().getUniqueId(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
membership);
|
||||
UIElement element = element(
|
||||
"membership-" + index,
|
||||
confirmed ? Material.REDSTONE : Material.GOLD_NUGGET,
|
||||
(confirmed ? ChatColor.RED : ChatColor.GOLD)
|
||||
+ safe(displayKey(membership.poolKey()))
|
||||
+ " [" + membership.entryIndex() + "]")
|
||||
.setEnchanted(confirmed);
|
||||
element.addLore(ChatColor.DARK_GRAY + safe(membership.poolKey()));
|
||||
element.addLore(ChatColor.GRAY + "Exact entry: " + membership.entryIndex());
|
||||
element.addLore(ChatColor.WHITE + "Weight: " + membership.weight());
|
||||
element.addLore((state.irisExtended() ? ChatColor.WHITE : ChatColor.GRAY)
|
||||
+ "Chance: " + chance(membership.chance())
|
||||
+ (state.irisExtended() ? "" : " (Iris only)"));
|
||||
if (variant.active() && variant.owned()) {
|
||||
element.addLore(ChatColor.GREEN + "Left-click: weight +1");
|
||||
element.addLore(ChatColor.YELLOW + "Right-click: weight -1");
|
||||
if (state.irisExtended()) {
|
||||
element.addLore(ChatColor.GREEN + "Shift-left: chance +"
|
||||
+ CHANCE_STEP_PERCENTAGE_POINTS + "%");
|
||||
element.addLore(ChatColor.YELLOW + "Shift-right: chance -"
|
||||
+ CHANCE_STEP_PERCENTAGE_POINTS + "%");
|
||||
} else {
|
||||
element.addLore(ChatColor.GRAY + "Vanilla-portable pool entries always have 100% chance.");
|
||||
}
|
||||
element.addLore(confirmed
|
||||
? ChatColor.RED + "Middle-click again to unlink"
|
||||
: ChatColor.RED + "Middle-click twice to unlink");
|
||||
element.onLeftClick(clicked -> controller.adjustMembershipWeight(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), membership, 1));
|
||||
element.onRightClick(clicked -> controller.adjustMembershipWeight(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), membership, -1));
|
||||
if (state.irisExtended()) {
|
||||
element.onShiftLeftClick(clicked -> controller.adjustMembershipChance(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
membership,
|
||||
CHANCE_STEP_PERCENTAGE_POINTS));
|
||||
element.onShiftRightClick(clicked -> controller.adjustMembershipChance(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
membership,
|
||||
-CHANCE_STEP_PERCENTAGE_POINTS));
|
||||
}
|
||||
element.onMiddleClick(clicked -> controller.confirmOrUnlink(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), membership, page));
|
||||
} else {
|
||||
element.addLore(variant.active()
|
||||
? ChatColor.GRAY + "Read-only pool entry"
|
||||
: ChatColor.GRAY + "Load this variant to edit its pool entry");
|
||||
}
|
||||
window.setElement(position, row, element);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDetailsFooter(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
JigsawStudioMenuState.Variant variant,
|
||||
int page
|
||||
) {
|
||||
UIElement back = element("footer-back", Material.ARROW, ChatColor.YELLOW + "Back");
|
||||
back.onLeftClick(clicked -> controller.refreshMain(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), 0));
|
||||
window.setElement(-4, 5, back);
|
||||
|
||||
int pageCount = pageCount(variant.memberships().size(), MEMBERSHIPS_PER_PAGE);
|
||||
if (page > 0) {
|
||||
UIElement previous = element("membership-previous", Material.ARROW, ChatColor.YELLOW + "Previous Page");
|
||||
previous.onLeftClick(clicked -> controller.refreshDetails(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
page - 1));
|
||||
window.setElement(-1, 5, previous);
|
||||
}
|
||||
UIElement indicator = element("membership-page", Material.BOOK, ChatColor.WHITE + "Page "
|
||||
+ (page + 1) + "/" + pageCount);
|
||||
indicator.addLore(ChatColor.GRAY + "" + variant.memberships().size() + " exact pool entries");
|
||||
window.setElement(0, 5, indicator);
|
||||
if (page + 1 < pageCount) {
|
||||
UIElement next = element("membership-next", Material.ARROW, ChatColor.YELLOW + "Next Page");
|
||||
next.onLeftClick(clicked -> controller.refreshDetails(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
page + 1));
|
||||
window.setElement(1, 5, next);
|
||||
}
|
||||
window.setElement(2, 5, evaluationElement(state.evaluation()));
|
||||
|
||||
UIElement toolbox = element("details-toolbox", Material.STICK, ChatColor.AQUA + "Toolbox");
|
||||
toolbox.onLeftClick(clicked -> controller.openToolbox(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), 0));
|
||||
window.setElement(4, 5, toolbox);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.inventorygui.UIElement;
|
||||
import art.arcane.volmlib.util.inventorygui.UIWindow;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.GRID_POSITIONS;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.VARIANTS_PER_PAGE;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.clampPage;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.compatibilityName;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.dimensions;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.element;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.evaluationElement;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.nextThemeSetKey;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.page;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.pageCount;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.safe;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.themes;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.variantMaterial;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.workcellMaterial;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.workcellStatus;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.yesNo;
|
||||
|
||||
final class JigsawStudioMainMenu {
|
||||
private final JigsawStudioMenuController controller;
|
||||
|
||||
JigsawStudioMainMenu(JigsawStudioMenuController controller) {
|
||||
this.controller = Objects.requireNonNull(controller, "Jigsaw Studio menu controller");
|
||||
}
|
||||
|
||||
void render(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell selected,
|
||||
int requestedPage
|
||||
) {
|
||||
int page = clampPage(requestedPage, selected.variants().size(), VARIANTS_PER_PAGE);
|
||||
window.batch(() -> {
|
||||
window.clearElements();
|
||||
addWorkcells(window, state, selected.stableId());
|
||||
addFamilyControl(window, state);
|
||||
addStructureControl(window, state, selected.stableId());
|
||||
addVariants(window, state, selected, page);
|
||||
addMainActions(window, state, selected, page);
|
||||
});
|
||||
}
|
||||
|
||||
private void addFamilyControl(UIWindow window, JigsawStudioMenuState state) {
|
||||
String nextThemeKey = nextThemeSetKey(state.themeSets());
|
||||
boolean available = state.irisExtended() && state.workcells().stream()
|
||||
.filter(JigsawStudioMenuState.Workcell::enabled)
|
||||
.allMatch(workcell -> workcell.activeVariant() != null && workcell.activeVariant().owned());
|
||||
UIElement family = element(
|
||||
"duplicate-family",
|
||||
available ? Material.PURPLE_DYE : Material.GRAY_DYE,
|
||||
available
|
||||
? ChatColor.LIGHT_PURPLE + "Duplicate All Enabled Cells as Family"
|
||||
: ChatColor.GRAY + "Duplicate All Enabled Cells as Family");
|
||||
family.addLore(ChatColor.GRAY + "Copies each enabled cell's loaded variant into one coherent family.");
|
||||
family.addLore(ChatColor.GRAY + "New family: " + safe(nextThemeKey));
|
||||
if (available) {
|
||||
family.addLore(ChatColor.YELLOW + "Left-click to duplicate the complete family atomically");
|
||||
family.onLeftClick(clicked -> controller.duplicateActiveFamily(
|
||||
window.getViewer(), state.requestId(), nextThemeKey));
|
||||
} else if (!state.irisExtended()) {
|
||||
family.addLore(ChatColor.GRAY + "Coherent families require Iris compatibility.");
|
||||
} else {
|
||||
family.addLore(ChatColor.GRAY + "Load an owned variant in every enabled workcell first.");
|
||||
}
|
||||
window.setElement(3, 0, family);
|
||||
}
|
||||
|
||||
private void addStructureControl(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
String selectedWorkcellId
|
||||
) {
|
||||
UIElement structure = element(
|
||||
"structure-rules",
|
||||
state.irisExtended()
|
||||
? state.requireCaps() ? Material.IRON_BARS : Material.TRIPWIRE_HOOK
|
||||
: Material.BARRIER,
|
||||
ChatColor.LIGHT_PURPLE + "Structure Rules");
|
||||
structure.addLore(ChatColor.GRAY + "Mandatory caps: " + yesNo(state.requireCaps()));
|
||||
structure.addLore(ChatColor.GRAY + "Theme sets: " + state.themeSets().size());
|
||||
structure.addLore(ChatColor.GRAY + "Compatibility: " + compatibilityName(state.compatibilityTarget()));
|
||||
structure.addLore(ChatColor.YELLOW + "Left-click for themes and rules");
|
||||
structure.addLore(state.irisExtended()
|
||||
? ChatColor.YELLOW + "Right-click to toggle mandatory caps"
|
||||
: ChatColor.GRAY + "Mandatory caps and themes require Iris compatibility");
|
||||
structure.onLeftClick(clicked -> controller.openStructureSettings(
|
||||
window.getViewer(), state.requestId(), selectedWorkcellId, 0));
|
||||
if (state.irisExtended()) {
|
||||
structure.onRightClick(clicked -> controller.setRequireCaps(
|
||||
window.getViewer(), state.requestId(), !state.requireCaps()));
|
||||
}
|
||||
window.setElement(4, 0, structure);
|
||||
}
|
||||
|
||||
private void addWorkcells(UIWindow window, JigsawStudioMenuState state, String selectedWorkcellId) {
|
||||
int count = state.workcells().size();
|
||||
int start = count == 1 ? 0 : -(count / 2);
|
||||
for (int index = 0; index < count; index++) {
|
||||
JigsawStudioMenuState.Workcell workcell = state.workcells().get(index);
|
||||
boolean selected = workcell.stableId().equals(selectedWorkcellId);
|
||||
UIElement element = element(
|
||||
"workcell-" + index,
|
||||
workcellMaterial(workcell, selected),
|
||||
(selected ? ChatColor.AQUA : workcell.enabled() ? ChatColor.WHITE : ChatColor.GRAY)
|
||||
+ safe(workcell.displayName()))
|
||||
.setEnchanted(selected);
|
||||
element.addLore(workcell.enabled()
|
||||
? ChatColor.GREEN + "Enabled"
|
||||
: ChatColor.RED + "Disabled for assembly and export");
|
||||
if (!workcell.canonicalName().equals(workcell.displayName())) {
|
||||
element.addLore(ChatColor.GRAY + "Solver role: " + safe(workcell.canonicalName()));
|
||||
}
|
||||
element.addLore(ChatColor.GRAY + "Capacity: " + dimensions(workcell.capacity()));
|
||||
element.addLore(ChatColor.GRAY + "Variants: " + workcell.variants().size());
|
||||
JigsawStudioMenuState.Variant active = workcell.activeVariant();
|
||||
element.addLore(ChatColor.GRAY + "Loaded: "
|
||||
+ (active == null ? "None" : safe(active.displayName())));
|
||||
element.addLore(workcellStatus(workcell));
|
||||
element.addLore(ChatColor.GRAY + "Connector blocks: "
|
||||
+ (workcell.connectorsVisible() ? "Visible" : "Hidden"));
|
||||
element.addLore(ChatColor.YELLOW + "Left-click to select and teleport");
|
||||
element.addLore(ChatColor.YELLOW + "Right-click for workcell settings");
|
||||
if (workcell.dirty() && !workcell.saving()) {
|
||||
element.addLore(ChatColor.GOLD + "Shift-left: Flush Autosave Now");
|
||||
element.onShiftLeftClick(clicked -> controller.flushNow(
|
||||
window.getViewer(), state.requestId(), workcell.stableId()));
|
||||
}
|
||||
element.onLeftClick(clicked -> controller.selectWorkcell(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId()));
|
||||
element.onRightClick(clicked -> J.runEntity(
|
||||
window.getViewer(),
|
||||
() -> controller.openWorkcellSettings(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId()),
|
||||
1));
|
||||
window.setElement(start + index, 0, element);
|
||||
}
|
||||
}
|
||||
|
||||
private void addVariants(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
int page
|
||||
) {
|
||||
List<JigsawStudioMenuState.Variant> variants = page(
|
||||
workcell.variants(), page, VARIANTS_PER_PAGE);
|
||||
if (variants.isEmpty()) {
|
||||
UIElement empty = element("no-variants", Material.BARRIER, ChatColor.RED + "No variants");
|
||||
empty.addLore(ChatColor.GRAY + "Create a variant to begin authoring this workcell.");
|
||||
window.setElement(0, 2, empty);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int index = 0; index < variants.size(); index++) {
|
||||
JigsawStudioMenuState.Variant variant = variants.get(index);
|
||||
int position = GRID_POSITIONS[index % GRID_POSITIONS.length];
|
||||
int row = 1 + index / GRID_POSITIONS.length;
|
||||
UIElement element = element(
|
||||
"variant-" + index,
|
||||
variantMaterial(variant),
|
||||
(variant.active() ? ChatColor.GREEN : ChatColor.WHITE)
|
||||
+ safe(variant.displayName())
|
||||
+ (variant.active() ? ChatColor.AQUA + " [Loaded]" : ""))
|
||||
.setEnchanted(variant.active());
|
||||
element.addLore(ChatColor.DARK_GRAY + safe(variant.pieceKey()));
|
||||
element.addLore(variant.owned()
|
||||
? ChatColor.GREEN + "Owned"
|
||||
: ChatColor.GRAY + "Read-only");
|
||||
element.addLore(ChatColor.GRAY + "Rotation: " + (variant.rotatable() ? "Enabled" : "Disabled"));
|
||||
element.addLore(ChatColor.GRAY + "Size: " + variant.dimensions()
|
||||
.map(JigsawStudioMenuFormat::dimensions)
|
||||
.orElse("Object missing"));
|
||||
element.addLore(ChatColor.GRAY + "Themes: " + themes(variant.themes()));
|
||||
element.addLore(ChatColor.GRAY + "Pool entries: " + variant.memberships().size());
|
||||
element.addLore(variant.active()
|
||||
? ChatColor.YELLOW + "Loaded; right-click for details"
|
||||
: ChatColor.YELLOW + "Left-click to load");
|
||||
if (!variant.active()) {
|
||||
element.addLore(ChatColor.YELLOW + "Right-click for details or deletion");
|
||||
}
|
||||
element.onLeftClick(clicked -> controller.switchVariant(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey()));
|
||||
element.onRightClick(clicked -> J.runEntity(
|
||||
window.getViewer(),
|
||||
() -> controller.openDetails(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
0),
|
||||
1));
|
||||
window.setElement(position, row, element);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMainActions(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
int page
|
||||
) {
|
||||
UIElement create = element("create", Material.NETHER_STAR, ChatColor.GREEN + "New Blank Variant");
|
||||
create.addLore(ChatColor.GRAY + "Create an empty owned variant with this cell's current size.");
|
||||
create.onLeftClick(clicked -> controller.createVariant(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), false));
|
||||
window.setElement(-4, 5, create);
|
||||
|
||||
JigsawStudioMenuState.Variant active = workcell.activeVariant();
|
||||
boolean duplicateAvailable = active != null && active.owned();
|
||||
UIElement duplicate = element(
|
||||
"duplicate",
|
||||
duplicateAvailable ? Material.PAPER : Material.GRAY_DYE,
|
||||
duplicateAvailable
|
||||
? ChatColor.AQUA + "Duplicate This Cell's Variant"
|
||||
: active == null
|
||||
? ChatColor.GRAY + "Duplicate This Cell's Variant"
|
||||
: ChatColor.GRAY + "Duplicate This Cell's Variant");
|
||||
duplicate.addLore(duplicateAvailable
|
||||
? ChatColor.GRAY + "Clone the loaded variant into an owned variant."
|
||||
: active == null
|
||||
? ChatColor.GRAY + "Load a variant before duplicating it."
|
||||
: ChatColor.GRAY + "Adopt the read-only graph before duplicating it.");
|
||||
if (duplicateAvailable) {
|
||||
duplicate.onLeftClick(clicked -> controller.createVariant(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), true));
|
||||
}
|
||||
window.setElement(-3, 5, duplicate);
|
||||
|
||||
UIElement settings = element("settings", Material.CRAFTING_TABLE, ChatColor.GOLD + "Workcell Settings");
|
||||
settings.addLore(ChatColor.GRAY + "Enabled: " + yesNo(workcell.enabled()));
|
||||
settings.addLore(ChatColor.GRAY + "Capacity: " + dimensions(workcell.capacity()));
|
||||
settings.onLeftClick(clicked -> controller.openWorkcellSettings(
|
||||
window.getViewer(), state.requestId(), workcell.stableId()));
|
||||
window.setElement(-2, 5, settings);
|
||||
|
||||
int pageCount = pageCount(workcell.variants().size(), VARIANTS_PER_PAGE);
|
||||
if (page > 0) {
|
||||
UIElement previous = element("previous", Material.ARROW, ChatColor.YELLOW + "Previous Page");
|
||||
previous.onLeftClick(clicked -> controller.refreshMain(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), page - 1));
|
||||
window.setElement(-1, 5, previous);
|
||||
}
|
||||
|
||||
UIElement pageIndicator = element("page", Material.BOOK, ChatColor.WHITE + "Page "
|
||||
+ (page + 1) + "/" + pageCount);
|
||||
pageIndicator.addLore(ChatColor.GRAY + "" + workcell.variants().size() + " variants");
|
||||
window.setElement(0, 5, pageIndicator);
|
||||
|
||||
if (page + 1 < pageCount) {
|
||||
UIElement next = element("next", Material.ARROW, ChatColor.YELLOW + "Next Page");
|
||||
next.onLeftClick(clicked -> controller.refreshMain(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), page + 1));
|
||||
window.setElement(1, 5, next);
|
||||
}
|
||||
|
||||
window.setElement(2, 5, evaluationElement(state.evaluation()));
|
||||
|
||||
UIElement preview = element("preview", Material.ENDER_EYE, ChatColor.LIGHT_PURPLE + "Go to Preview");
|
||||
preview.addLore(ChatColor.GRAY + "Open the deterministic assembly preview.");
|
||||
preview.onLeftClick(clicked -> controller.goToPreview(window.getViewer(), state.requestId()));
|
||||
window.setElement(3, 5, preview);
|
||||
|
||||
UIElement toolbox = element("toolbox", Material.STICK, ChatColor.AQUA + "Toolbox");
|
||||
toolbox.addLore(ChatColor.GRAY + "Take named sticks bound to this Studio session.");
|
||||
toolbox.onLeftClick(clicked -> controller.openToolbox(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), 0));
|
||||
window.setElement(4, 5, toolbox);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioCellDimensions;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioPieceRules;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioToolPayload;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface JigsawStudioMenuActions {
|
||||
Optional<JigsawStudioMenuState> menuState(Player player);
|
||||
|
||||
boolean selectWorkcell(Player player, String workcellId);
|
||||
|
||||
boolean teleportToWorkcell(Player player, String workcellId);
|
||||
|
||||
boolean setConnectorBlocksVisible(Player player, String workcellId, boolean visible);
|
||||
|
||||
boolean resetConnectorBlocks(Player player, String workcellId);
|
||||
|
||||
boolean undoAutosave(Player player);
|
||||
|
||||
boolean switchVariant(Player player, String workcellId, String pieceKey, boolean discardDirty);
|
||||
|
||||
boolean createVariant(Player player, String workcellId, boolean duplicateActive);
|
||||
|
||||
boolean setWorkcellEnabled(Player player, String workcellId, boolean enabled);
|
||||
|
||||
boolean updateWorkcellDimensions(
|
||||
Player player,
|
||||
String workcellId,
|
||||
JigsawStudioCellDimensions dimensions
|
||||
);
|
||||
|
||||
boolean updateWorkcellDisplayName(Player player, String workcellId, String displayName);
|
||||
|
||||
boolean setRequireCaps(Player player, boolean requireCaps);
|
||||
|
||||
boolean duplicateActiveFamily(Player player, String themeKey);
|
||||
|
||||
boolean updateThemeSetWeight(Player player, String themeKey, int weight);
|
||||
|
||||
boolean flushAutosave(Player player, String workcellId);
|
||||
|
||||
boolean goToPreview(Player player);
|
||||
|
||||
boolean toggleVariantRotatable(Player player, String workcellId);
|
||||
|
||||
boolean expandVariantToCell(Player player, String workcellId);
|
||||
|
||||
boolean resizeVariant(
|
||||
Player player,
|
||||
String workcellId,
|
||||
String pieceKey,
|
||||
JigsawStudioCellDimensions dimensions
|
||||
);
|
||||
|
||||
boolean updateVariantDisplayName(
|
||||
Player player,
|
||||
String workcellId,
|
||||
String pieceKey,
|
||||
String displayName
|
||||
);
|
||||
|
||||
boolean updateVariantThemes(
|
||||
Player player,
|
||||
String workcellId,
|
||||
String pieceKey,
|
||||
List<String> themes
|
||||
);
|
||||
|
||||
boolean updateVariantRules(
|
||||
Player player,
|
||||
String workcellId,
|
||||
String pieceKey,
|
||||
JigsawStudioPieceRules rules
|
||||
);
|
||||
|
||||
boolean adjustVariantWeight(
|
||||
Player player,
|
||||
String workcellId,
|
||||
String pieceKey,
|
||||
String poolKey,
|
||||
int entryIndex,
|
||||
int delta
|
||||
);
|
||||
|
||||
boolean adjustVariantChance(
|
||||
Player player,
|
||||
String workcellId,
|
||||
String pieceKey,
|
||||
String poolKey,
|
||||
int entryIndex,
|
||||
int deltaPercentagePoints
|
||||
);
|
||||
|
||||
boolean unlinkVariantMembership(
|
||||
Player player,
|
||||
String workcellId,
|
||||
String pieceKey,
|
||||
String poolKey,
|
||||
int entryIndex
|
||||
);
|
||||
|
||||
boolean deleteVariant(Player player, String workcellId, String pieceKey);
|
||||
|
||||
boolean deleteProject(Player player);
|
||||
|
||||
boolean giveTool(Player player, JigsawStudioToolPayload payload);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,477 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioCellDimensions;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioCompatibilityTarget;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioPieceRules;
|
||||
import art.arcane.volmlib.util.data.MaterialBlock;
|
||||
import art.arcane.volmlib.util.inventorygui.UIElement;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.PLACEMENT_RULE_SHIFT_STEP;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.RULE_SHIFT_STEP;
|
||||
|
||||
final class JigsawStudioMenuFormat {
|
||||
private JigsawStudioMenuFormat() {
|
||||
}
|
||||
|
||||
static boolean hasMembership(
|
||||
JigsawStudioMenuState state,
|
||||
String workcellId,
|
||||
JigsawStudioMenuState.Membership expected
|
||||
) {
|
||||
JigsawStudioMenuState.Variant active = activeVariant(state, workcellId);
|
||||
if (active == null) {
|
||||
return false;
|
||||
}
|
||||
for (JigsawStudioMenuState.Membership membership : active.memberships()) {
|
||||
if (membership.poolKey().equals(expected.poolKey())
|
||||
&& membership.entryIndex() == expected.entryIndex()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static JigsawStudioMenuState.Variant activeVariant(
|
||||
JigsawStudioMenuState state,
|
||||
String workcellId
|
||||
) {
|
||||
JigsawStudioMenuState.Workcell workcell = state.workcell(workcellId);
|
||||
return workcell == null ? null : workcell.activeVariant();
|
||||
}
|
||||
|
||||
static JigsawStudioMenuState.Variant variant(
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
String pieceKey
|
||||
) {
|
||||
if (workcell == null || pieceKey == null) {
|
||||
return null;
|
||||
}
|
||||
for (JigsawStudioMenuState.Variant variant : workcell.variants()) {
|
||||
if (variant.pieceKey().equals(pieceKey)) {
|
||||
return variant;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String nextThemeSetKey(List<JigsawStudioMenuState.ThemeSet> themeSets) {
|
||||
List<JigsawStudioMenuState.ThemeSet> values = List.copyOf(Objects.requireNonNull(
|
||||
themeSets,
|
||||
"Jigsaw Studio theme sets"));
|
||||
int candidate = 1;
|
||||
while (candidate > 0) {
|
||||
String key = "variant-" + candidate;
|
||||
boolean present = false;
|
||||
for (JigsawStudioMenuState.ThemeSet themeSet : values) {
|
||||
if (Objects.requireNonNull(themeSet, "Jigsaw Studio theme set").key().equals(key)) {
|
||||
present = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!present) {
|
||||
return key;
|
||||
}
|
||||
candidate = Math.incrementExact(candidate);
|
||||
}
|
||||
throw new IllegalStateException("Jigsaw Studio cannot allocate another numbered theme set");
|
||||
}
|
||||
|
||||
static String themeSelectionPercent(
|
||||
List<JigsawStudioMenuState.ThemeSet> themeSets,
|
||||
JigsawStudioMenuState.ThemeSet target
|
||||
) {
|
||||
List<JigsawStudioMenuState.ThemeSet> activeThemeSets = Objects.requireNonNull(
|
||||
themeSets,
|
||||
"Jigsaw Studio theme sets");
|
||||
JigsawStudioMenuState.ThemeSet activeTarget = Objects.requireNonNull(
|
||||
target,
|
||||
"Jigsaw Studio target theme set");
|
||||
int totalWeight = 0;
|
||||
for (JigsawStudioMenuState.ThemeSet themeSet : activeThemeSets) {
|
||||
totalWeight = Math.addExact(totalWeight, Objects.requireNonNull(
|
||||
themeSet,
|
||||
"Jigsaw Studio theme set").weight());
|
||||
}
|
||||
if (totalWeight < 1) {
|
||||
return "0.0%";
|
||||
}
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"%.1f%%",
|
||||
activeTarget.weight() * 100.0D / totalWeight);
|
||||
}
|
||||
|
||||
static Optional<Integer> adjustedPositiveValue(int value, int delta) {
|
||||
if (value < 1 || delta == 0) {
|
||||
throw new IllegalArgumentException("Jigsaw Studio positive value adjustment is invalid");
|
||||
}
|
||||
try {
|
||||
int adjusted = Math.addExact(value, delta);
|
||||
return adjusted < 1 ? Optional.empty() : Optional.of(adjusted);
|
||||
} catch (ArithmeticException exception) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
static Optional<JigsawStudioPieceRules> adjustedRules(
|
||||
JigsawStudioPieceRules rules,
|
||||
RuleField field,
|
||||
int delta
|
||||
) {
|
||||
JigsawStudioPieceRules current = Objects.requireNonNull(rules, "Jigsaw Studio piece rules");
|
||||
RuleField target = Objects.requireNonNull(field, "Jigsaw Studio piece rule field");
|
||||
if (delta == 0) {
|
||||
throw new IllegalArgumentException("Jigsaw Studio piece rule delta cannot be zero");
|
||||
}
|
||||
try {
|
||||
int minimumDepth = current.minimumDepth();
|
||||
int maximumDepth = current.maximumDepth();
|
||||
int minimumPlacements = current.minimumPlacements();
|
||||
int maximumPlacements = current.maximumPlacements();
|
||||
switch (target) {
|
||||
case MINIMUM_DEPTH -> minimumDepth = Math.addExact(minimumDepth, delta);
|
||||
case MAXIMUM_DEPTH -> maximumDepth = Math.addExact(maximumDepth, delta);
|
||||
case MINIMUM_PLACEMENTS -> minimumPlacements = Math.addExact(minimumPlacements, delta);
|
||||
case MAXIMUM_PLACEMENTS -> {
|
||||
Optional<Integer> adjustedMaximum = adjustedMaximumPlacements(maximumPlacements, delta);
|
||||
if (adjustedMaximum.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
maximumPlacements = adjustedMaximum.get();
|
||||
}
|
||||
}
|
||||
return Optional.of(new JigsawStudioPieceRules(
|
||||
minimumDepth,
|
||||
maximumDepth,
|
||||
minimumPlacements,
|
||||
maximumPlacements,
|
||||
current.terminal()));
|
||||
} catch (ArithmeticException | IllegalArgumentException exception) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
static JigsawStudioPieceRules withTerminal(JigsawStudioPieceRules rules, boolean terminal) {
|
||||
JigsawStudioPieceRules current = Objects.requireNonNull(rules, "Jigsaw Studio piece rules");
|
||||
return new JigsawStudioPieceRules(
|
||||
current.minimumDepth(),
|
||||
current.maximumDepth(),
|
||||
current.minimumPlacements(),
|
||||
current.maximumPlacements(),
|
||||
terminal);
|
||||
}
|
||||
|
||||
private static Optional<Integer> adjustedMaximumPlacements(int value, int delta) {
|
||||
if (value == 0) {
|
||||
if (delta > 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
int adjusted = Math.addExact(513, delta);
|
||||
return adjusted < 1 ? Optional.empty() : Optional.of(adjusted);
|
||||
}
|
||||
int adjusted = Math.addExact(value, delta);
|
||||
if (adjusted > 512) {
|
||||
return Optional.of(0);
|
||||
}
|
||||
return adjusted < 1 ? Optional.empty() : Optional.of(adjusted);
|
||||
}
|
||||
|
||||
static Optional<JigsawStudioCellDimensions> adjustedDimensions(
|
||||
JigsawStudioCellDimensions dimensions,
|
||||
DimensionAxis axis,
|
||||
int delta
|
||||
) {
|
||||
JigsawStudioCellDimensions current = Objects.requireNonNull(
|
||||
dimensions,
|
||||
"Jigsaw Studio workcell dimensions");
|
||||
DimensionAxis target = Objects.requireNonNull(axis, "Jigsaw Studio workcell dimension axis");
|
||||
if (delta == 0) {
|
||||
throw new IllegalArgumentException("Jigsaw Studio workcell dimension delta cannot be zero");
|
||||
}
|
||||
try {
|
||||
int width = target == DimensionAxis.WIDTH
|
||||
? Math.addExact(current.width(), delta)
|
||||
: current.width();
|
||||
int height = target == DimensionAxis.HEIGHT
|
||||
? Math.addExact(current.height(), delta)
|
||||
: current.height();
|
||||
int depth = target == DimensionAxis.DEPTH
|
||||
? Math.addExact(current.depth(), delta)
|
||||
: current.depth();
|
||||
return Optional.of(new JigsawStudioCellDimensions(width, height, depth));
|
||||
} catch (ArithmeticException | IllegalArgumentException exception) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
static JigsawStudioMenuState.Workcell withCapacity(
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
JigsawStudioCellDimensions capacity
|
||||
) {
|
||||
JigsawStudioMenuState.Workcell source = Objects.requireNonNull(
|
||||
workcell,
|
||||
"Jigsaw Studio menu workcell");
|
||||
return new JigsawStudioMenuState.Workcell(
|
||||
source.stableId(),
|
||||
source.canonicalName(),
|
||||
source.displayName(),
|
||||
Objects.requireNonNull(capacity, "Jigsaw Studio staged workcell capacity"),
|
||||
source.enabled(),
|
||||
source.activeVariantKey(),
|
||||
source.dirty(),
|
||||
source.saving(),
|
||||
source.loading(),
|
||||
source.connectorsVisible(),
|
||||
source.variants());
|
||||
}
|
||||
|
||||
static int pageCount(int itemCount, int pageSize) {
|
||||
if (itemCount < 0 || pageSize < 1) {
|
||||
throw new IllegalArgumentException("Jigsaw Studio menu page bounds are invalid");
|
||||
}
|
||||
return Math.max(1, (itemCount + pageSize - 1) / pageSize);
|
||||
}
|
||||
|
||||
static int clampPage(int requestedPage, int itemCount, int pageSize) {
|
||||
int maximum = pageCount(itemCount, pageSize) - 1;
|
||||
return Math.max(0, Math.min(requestedPage, maximum));
|
||||
}
|
||||
|
||||
static <T> List<T> page(List<T> items, int requestedPage, int pageSize) {
|
||||
List<T> values = List.copyOf(Objects.requireNonNull(items, "Jigsaw Studio menu page items"));
|
||||
int page = clampPage(requestedPage, values.size(), pageSize);
|
||||
int start = page * pageSize;
|
||||
int end = Math.min(values.size(), start + pageSize);
|
||||
return values.subList(start, end);
|
||||
}
|
||||
|
||||
static UIElement evaluationElement(JigsawStudioMenuState.Evaluation evaluation) {
|
||||
UIElement element = element(
|
||||
"evaluation",
|
||||
evaluationMaterial(evaluation.state()),
|
||||
evaluationColor(evaluation.state()) + evaluationName(evaluation.state()));
|
||||
element.addLore(ChatColor.GRAY + "Evaluation updates automatically.");
|
||||
element.addLore(ChatColor.GRAY + "Seed: " + evaluation.seed());
|
||||
if (evaluation.generation() > 0L) {
|
||||
element.addLore(ChatColor.GRAY + "Generation: " + evaluation.generation());
|
||||
}
|
||||
if (!evaluation.selectedTheme().isEmpty()) {
|
||||
element.addLore(ChatColor.GRAY + "Theme: " + safe(evaluation.selectedTheme()));
|
||||
}
|
||||
element.addLore(ChatColor.GRAY + "Pieces: " + evaluation.pieceCount());
|
||||
if (!evaluation.detail().isEmpty()) {
|
||||
element.addLore(ChatColor.GRAY + safe(evaluation.detail()));
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
static Material evaluationMaterial(JigsawStudioEvaluationState state) {
|
||||
return switch (state) {
|
||||
case PENDING -> Material.CLOCK;
|
||||
case VALID -> Material.EMERALD;
|
||||
case WARNING -> Material.YELLOW_DYE;
|
||||
case INVALID -> Material.RED_DYE;
|
||||
case STALE -> Material.GRAY_DYE;
|
||||
};
|
||||
}
|
||||
|
||||
private static ChatColor evaluationColor(JigsawStudioEvaluationState state) {
|
||||
return switch (state) {
|
||||
case PENDING -> ChatColor.AQUA;
|
||||
case VALID -> ChatColor.GREEN;
|
||||
case WARNING -> ChatColor.YELLOW;
|
||||
case INVALID -> ChatColor.RED;
|
||||
case STALE -> ChatColor.GRAY;
|
||||
};
|
||||
}
|
||||
|
||||
private static String evaluationName(JigsawStudioEvaluationState state) {
|
||||
return switch (state) {
|
||||
case PENDING -> "Evaluation Pending";
|
||||
case VALID -> "Ready to Assemble";
|
||||
case WARNING -> "Ready with Warnings";
|
||||
case INVALID -> "Assembly Blocked";
|
||||
case STALE -> "Evaluation Stale";
|
||||
};
|
||||
}
|
||||
|
||||
static Material workcellMaterial(JigsawStudioMenuState.Workcell workcell, boolean selected) {
|
||||
if (!workcell.enabled()) {
|
||||
return Material.GRAY_WOOL;
|
||||
}
|
||||
if (workcell.loading() || workcell.saving()) {
|
||||
return Material.YELLOW_WOOL;
|
||||
}
|
||||
if (workcell.dirty()) {
|
||||
return Material.ORANGE_WOOL;
|
||||
}
|
||||
return selected ? Material.LIME_WOOL : Material.LIGHT_GRAY_WOOL;
|
||||
}
|
||||
|
||||
static Material variantMaterial(JigsawStudioMenuState.Variant variant) {
|
||||
if (variant.active()) {
|
||||
return Material.JIGSAW;
|
||||
}
|
||||
return variant.owned() ? Material.PAPER : Material.GRAY_DYE;
|
||||
}
|
||||
|
||||
static String workcellStatus(JigsawStudioMenuState.Workcell workcell) {
|
||||
if (workcell.loading()) {
|
||||
return ChatColor.YELLOW + "Loading variant";
|
||||
}
|
||||
if (workcell.saving()) {
|
||||
return ChatColor.YELLOW + "Autosave in progress";
|
||||
}
|
||||
if (workcell.dirty()) {
|
||||
return ChatColor.GOLD + "Autosave pending";
|
||||
}
|
||||
return ChatColor.GREEN + "Autosaved";
|
||||
}
|
||||
|
||||
static String dimensions(JigsawStudioCellDimensions dimensions) {
|
||||
return dimensions.width() + "x" + dimensions.height() + "x" + dimensions.depth();
|
||||
}
|
||||
|
||||
static String themes(List<String> themes) {
|
||||
return themes.isEmpty() ? "None" : safe(String.join(", ", themes));
|
||||
}
|
||||
|
||||
static String maximumPlacements(int maximum) {
|
||||
return maximum == 0 ? "Unlimited" : Integer.toString(maximum);
|
||||
}
|
||||
|
||||
static String chance(double chance) {
|
||||
return String.format(Locale.ROOT, "%.1f%%", chance * 100D);
|
||||
}
|
||||
|
||||
static String yesNo(boolean value) {
|
||||
return value ? "Yes" : "No";
|
||||
}
|
||||
|
||||
static String compatibilityName(JigsawStudioCompatibilityTarget target) {
|
||||
return switch (target) {
|
||||
case IRIS_EXTENDED -> "Iris Extended";
|
||||
case VANILLA_PORTABLE -> "Vanilla Portable";
|
||||
};
|
||||
}
|
||||
|
||||
static UIElement element(String id, Material material, String name) {
|
||||
return new UIElement(id)
|
||||
.setMaterial(new MaterialBlock(material))
|
||||
.setName(name);
|
||||
}
|
||||
|
||||
static String title(String structureKey) {
|
||||
String value = "Iris Jigsaw: " + safe(displayKey(structureKey));
|
||||
return value.length() > 32 ? value.substring(0, 32) : value;
|
||||
}
|
||||
|
||||
static String displayKey(String resourceKey) {
|
||||
int separator = resourceKey.lastIndexOf('/');
|
||||
return separator < 0 ? resourceKey : resourceKey.substring(separator + 1);
|
||||
}
|
||||
|
||||
static String safe(String value) {
|
||||
return BoardSVC.untrustedBoardValue(value);
|
||||
}
|
||||
|
||||
enum RuleField {
|
||||
MINIMUM_DEPTH("Minimum Depth", Material.LIGHT_BLUE_DYE, RULE_SHIFT_STEP) {
|
||||
@Override
|
||||
int value(JigsawStudioPieceRules rules) {
|
||||
return rules.minimumDepth();
|
||||
}
|
||||
},
|
||||
MAXIMUM_DEPTH("Maximum Depth", Material.BLUE_DYE, RULE_SHIFT_STEP) {
|
||||
@Override
|
||||
int value(JigsawStudioPieceRules rules) {
|
||||
return rules.maximumDepth();
|
||||
}
|
||||
},
|
||||
MINIMUM_PLACEMENTS("Minimum Placements", Material.TARGET, PLACEMENT_RULE_SHIFT_STEP) {
|
||||
@Override
|
||||
int value(JigsawStudioPieceRules rules) {
|
||||
return rules.minimumPlacements();
|
||||
}
|
||||
},
|
||||
MAXIMUM_PLACEMENTS("Maximum Placements", Material.GREEN_DYE, PLACEMENT_RULE_SHIFT_STEP) {
|
||||
@Override
|
||||
int value(JigsawStudioPieceRules rules) {
|
||||
return rules.maximumPlacements();
|
||||
}
|
||||
|
||||
@Override
|
||||
String displayValue(int value) {
|
||||
return maximumPlacements(value);
|
||||
}
|
||||
};
|
||||
|
||||
private final String displayName;
|
||||
private final Material material;
|
||||
private final int shiftStep;
|
||||
|
||||
RuleField(String displayName, Material material, int shiftStep) {
|
||||
this.displayName = displayName;
|
||||
this.material = material;
|
||||
this.shiftStep = shiftStep;
|
||||
}
|
||||
|
||||
String displayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
Material material() {
|
||||
return material;
|
||||
}
|
||||
|
||||
int shiftStep() {
|
||||
return shiftStep;
|
||||
}
|
||||
|
||||
String displayValue(int value) {
|
||||
return Integer.toString(value);
|
||||
}
|
||||
|
||||
abstract int value(JigsawStudioPieceRules rules);
|
||||
}
|
||||
|
||||
enum DimensionAxis {
|
||||
WIDTH("Width") {
|
||||
@Override
|
||||
int value(JigsawStudioCellDimensions dimensions) {
|
||||
return dimensions.width();
|
||||
}
|
||||
},
|
||||
HEIGHT("Height") {
|
||||
@Override
|
||||
int value(JigsawStudioCellDimensions dimensions) {
|
||||
return dimensions.height();
|
||||
}
|
||||
},
|
||||
DEPTH("Depth") {
|
||||
@Override
|
||||
int value(JigsawStudioCellDimensions dimensions) {
|
||||
return dimensions.depth();
|
||||
}
|
||||
};
|
||||
|
||||
private final String displayName;
|
||||
|
||||
DimensionAxis(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
String displayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
abstract int value(JigsawStudioCellDimensions dimensions);
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public final class JigsawStudioService implements IrisService, JigsawStudioMenuController.Actions {
|
||||
public final class JigsawStudioService implements IrisService, JigsawStudioMenuActions {
|
||||
private static final int AUTOSAVE_DEBOUNCE_TICKS = 40;
|
||||
private static final int AUTOSAVE_RETRY_TICKS = 5;
|
||||
private static final int REPLACEMENT_CLOSE_WAIT_TICKS = 2_400;
|
||||
@@ -3185,14 +3185,14 @@ public final class JigsawStudioService implements IrisService, JigsawStudioMenuC
|
||||
}
|
||||
return duplicateActiveFamily(
|
||||
player,
|
||||
JigsawStudioMenuController.nextThemeSetKey(state.get().themeSets()));
|
||||
JigsawStudioMenuFormat.nextThemeSetKey(state.get().themeSets()));
|
||||
}
|
||||
|
||||
private boolean duplicateFamilyFromTool(Player player) {
|
||||
Optional<JigsawStudioMenuState> state = menuState(player);
|
||||
return state.isPresent() && duplicateActiveFamily(
|
||||
player,
|
||||
JigsawStudioMenuController.nextThemeSetKey(state.get().themeSets()));
|
||||
JigsawStudioMenuFormat.nextThemeSetKey(state.get().themeSets()));
|
||||
}
|
||||
|
||||
private boolean toggleRequireCapsFromTool(Player player) {
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.volmlib.util.inventorygui.UIElement;
|
||||
import art.arcane.volmlib.util.inventorygui.UIWindow;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.GRID_POSITIONS;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.THEME_SETS_PER_PAGE;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.clampPage;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.compatibilityName;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.element;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.evaluationElement;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.nextThemeSetKey;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.page;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.pageCount;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.safe;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.themeSelectionPercent;
|
||||
|
||||
final class JigsawStudioStructureMenu {
|
||||
private final JigsawStudioMenuController controller;
|
||||
|
||||
JigsawStudioStructureMenu(JigsawStudioMenuController controller) {
|
||||
this.controller = Objects.requireNonNull(controller, "Jigsaw Studio menu controller");
|
||||
}
|
||||
|
||||
void render(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
String selectedWorkcellId,
|
||||
int requestedPage
|
||||
) {
|
||||
int page = clampPage(requestedPage, state.themeSets().size(), THEME_SETS_PER_PAGE);
|
||||
controller.purgeExpiredConfirmations(window.getViewer().getUniqueId());
|
||||
window.batch(() -> {
|
||||
window.clearElements();
|
||||
|
||||
UIElement back = element("structure-back", Material.ARROW, ChatColor.YELLOW + "Back to Variants");
|
||||
back.onLeftClick(clicked -> controller.refreshMain(
|
||||
window.getViewer(), state.requestId(), selectedWorkcellId, 0));
|
||||
window.setElement(-4, 0, back);
|
||||
|
||||
UIElement identity = element(
|
||||
"structure-identity",
|
||||
Material.JIGSAW,
|
||||
ChatColor.LIGHT_PURPLE + "Structure Themes & Caps");
|
||||
identity.addLore(ChatColor.DARK_GRAY + safe(state.structureKey()));
|
||||
identity.addLore(ChatColor.GRAY + "Compatibility: " + compatibilityName(state.compatibilityTarget()));
|
||||
identity.addLore(ChatColor.GRAY + "Theme sets select one coherent variant family per assembly.");
|
||||
identity.addLore(ChatColor.GRAY + "Empty piece themes remain available to every family.");
|
||||
window.setElement(0, 0, identity);
|
||||
window.setElement(4, 0, evaluationElement(state.evaluation()));
|
||||
|
||||
UIElement requireCaps = element(
|
||||
"require-caps",
|
||||
state.irisExtended()
|
||||
? state.requireCaps() ? Material.IRON_BARS : Material.TRIPWIRE_HOOK
|
||||
: Material.BARRIER,
|
||||
!state.irisExtended()
|
||||
? ChatColor.GRAY + "Mandatory Caps Require Iris Compatibility"
|
||||
: state.requireCaps()
|
||||
? ChatColor.GREEN + "Mandatory Caps Enabled"
|
||||
: ChatColor.YELLOW + "Mandatory Caps Disabled");
|
||||
requireCaps.addLore(!state.irisExtended()
|
||||
? ChatColor.GRAY + "Vanilla-portable export rejects Iris mandatory-cap metadata."
|
||||
: state.requireCaps()
|
||||
? ChatColor.GRAY + "Every open connector must close with a terminal piece."
|
||||
: ChatColor.GRAY + "Assembly may leave an open connector when no cap can fit.");
|
||||
if (state.irisExtended()) {
|
||||
requireCaps.addLore(ChatColor.YELLOW + "Left-click to toggle");
|
||||
requireCaps.onLeftClick(clicked -> controller.setRequireCaps(
|
||||
window.getViewer(), state.requestId(), !state.requireCaps()));
|
||||
}
|
||||
window.setElement(-3, 1, requireCaps);
|
||||
|
||||
String nextThemeKey = nextThemeSetKey(state.themeSets());
|
||||
boolean themeEditingAvailable = state.irisExtended();
|
||||
UIElement createTheme = element(
|
||||
"new-theme-set",
|
||||
themeEditingAvailable ? Material.NETHER_STAR : Material.BARRIER,
|
||||
themeEditingAvailable
|
||||
? ChatColor.GREEN + "Duplicate All Enabled Cells as Family: " + safe(nextThemeKey)
|
||||
: ChatColor.GRAY + "Theme Sets Require Iris Compatibility");
|
||||
if (themeEditingAvailable) {
|
||||
createTheme.addLore(ChatColor.GRAY + "Creates one new owned variant for every enabled workcell.");
|
||||
createTheme.addLore(ChatColor.GRAY + "All created variants join " + safe(nextThemeKey) + ".");
|
||||
createTheme.addLore(ChatColor.GRAY + "One family is selected for the complete assembly.");
|
||||
createTheme.addLore(ChatColor.YELLOW + "Left-click to create the complete set");
|
||||
createTheme.onLeftClick(clicked -> controller.duplicateActiveFamily(
|
||||
window.getViewer(), state.requestId(), nextThemeKey));
|
||||
} else {
|
||||
createTheme.addLore(ChatColor.GRAY + "Vanilla jigsaw resources cannot encode Iris theme metadata.");
|
||||
}
|
||||
window.setElement(0, 1, createTheme);
|
||||
|
||||
boolean deleteConfirmed = controller.isProjectDeleteConfirmed(
|
||||
window.getViewer().getUniqueId(), state.requestId());
|
||||
UIElement deleteProject = element(
|
||||
"delete-project",
|
||||
deleteConfirmed ? Material.REDSTONE : Material.LAVA_BUCKET,
|
||||
deleteConfirmed
|
||||
? ChatColor.RED + "Confirm Delete Jigsaw"
|
||||
: ChatColor.RED + "Delete Jigsaw")
|
||||
.setEnchanted(deleteConfirmed);
|
||||
deleteProject.addLore(deleteConfirmed
|
||||
? ChatColor.RED + "Click again to delete this entire managed project."
|
||||
: ChatColor.RED + "Click twice within 10 seconds to confirm.");
|
||||
deleteProject.onLeftClick(clicked -> controller.confirmOrDeleteProject(
|
||||
window.getViewer(), state.requestId(), selectedWorkcellId, page));
|
||||
window.setElement(3, 1, deleteProject);
|
||||
|
||||
addThemeSets(window, state, page);
|
||||
|
||||
UIElement footerBack = element("structure-footer-back", Material.ARROW, ChatColor.YELLOW + "Back");
|
||||
footerBack.onLeftClick(clicked -> controller.refreshMain(
|
||||
window.getViewer(), state.requestId(), selectedWorkcellId, 0));
|
||||
window.setElement(-4, 5, footerBack);
|
||||
|
||||
int pageCount = pageCount(state.themeSets().size(), THEME_SETS_PER_PAGE);
|
||||
if (page > 0) {
|
||||
UIElement previous = element(
|
||||
"theme-previous",
|
||||
Material.ARROW,
|
||||
ChatColor.YELLOW + "Previous Page");
|
||||
previous.onLeftClick(clicked -> controller.refreshStructureSettings(
|
||||
window.getViewer(), state.requestId(), selectedWorkcellId, page - 1));
|
||||
window.setElement(-1, 5, previous);
|
||||
}
|
||||
UIElement indicator = element(
|
||||
"theme-page",
|
||||
Material.BOOK,
|
||||
ChatColor.WHITE + "Page " + (page + 1) + "/" + pageCount);
|
||||
indicator.addLore(ChatColor.GRAY + "" + state.themeSets().size() + " theme sets");
|
||||
window.setElement(0, 5, indicator);
|
||||
if (page + 1 < pageCount) {
|
||||
UIElement next = element("theme-next", Material.ARROW, ChatColor.YELLOW + "Next Page");
|
||||
next.onLeftClick(clicked -> controller.refreshStructureSettings(
|
||||
window.getViewer(), state.requestId(), selectedWorkcellId, page + 1));
|
||||
window.setElement(1, 5, next);
|
||||
}
|
||||
window.setElement(4, 5, evaluationElement(state.evaluation()));
|
||||
});
|
||||
}
|
||||
|
||||
private void addThemeSets(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
int page
|
||||
) {
|
||||
List<JigsawStudioMenuState.ThemeSet> themeSets = page(
|
||||
state.themeSets(), page, THEME_SETS_PER_PAGE);
|
||||
if (themeSets.isEmpty()) {
|
||||
UIElement empty = element(
|
||||
"no-theme-sets",
|
||||
Material.GRAY_DYE,
|
||||
ChatColor.GRAY + "Implicit Unthemed Assembly");
|
||||
empty.addLore(ChatColor.GRAY + "Create variant-1 to begin coherent theme selection.");
|
||||
window.setElement(0, 3, empty);
|
||||
return;
|
||||
}
|
||||
for (int index = 0; index < themeSets.size(); index++) {
|
||||
JigsawStudioMenuState.ThemeSet themeSet = themeSets.get(index);
|
||||
int position = GRID_POSITIONS[index % GRID_POSITIONS.length];
|
||||
int row = 2 + index / GRID_POSITIONS.length;
|
||||
UIElement element = element(
|
||||
"theme-set-" + index,
|
||||
state.irisExtended() ? Material.PURPLE_DYE : Material.GRAY_DYE,
|
||||
(state.irisExtended() ? ChatColor.LIGHT_PURPLE : ChatColor.GRAY) + safe(themeSet.key()));
|
||||
element.addLore(ChatColor.WHITE + "Selection weight: " + themeSet.weight());
|
||||
element.addLore(ChatColor.WHITE + "Whole-assembly chance: "
|
||||
+ themeSelectionPercent(state.themeSets(), themeSet));
|
||||
if (state.irisExtended()) {
|
||||
element.addLore(ChatColor.GREEN + "Left-click: weight +1");
|
||||
element.addLore(ChatColor.YELLOW + "Right-click: weight -1");
|
||||
element.addLore(ChatColor.GREEN + "Shift-left: weight +8");
|
||||
element.addLore(ChatColor.YELLOW + "Shift-right: weight -8");
|
||||
element.onLeftClick(clicked -> controller.adjustThemeSetWeight(
|
||||
window.getViewer(), state.requestId(), themeSet, 1));
|
||||
element.onRightClick(clicked -> controller.adjustThemeSetWeight(
|
||||
window.getViewer(), state.requestId(), themeSet, -1));
|
||||
element.onShiftLeftClick(clicked -> controller.adjustThemeSetWeight(
|
||||
window.getViewer(), state.requestId(), themeSet, 8));
|
||||
element.onShiftRightClick(clicked -> controller.adjustThemeSetWeight(
|
||||
window.getViewer(), state.requestId(), themeSet, -8));
|
||||
} else {
|
||||
element.addLore(ChatColor.GRAY + "Theme metadata is unavailable for vanilla-portable graphs.");
|
||||
}
|
||||
window.setElement(position, row, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioMode;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioToolAction;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioToolPayload;
|
||||
import art.arcane.volmlib.util.inventorygui.UIElement;
|
||||
import art.arcane.volmlib.util.inventorygui.UIWindow;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.CHANCE_STEP_PERCENTAGE_POINTS;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.GRID_POSITIONS;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.TOOLS_PER_PAGE;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.clampPage;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.displayKey;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.element;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.evaluationElement;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.nextThemeSetKey;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.page;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.pageCount;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.safe;
|
||||
|
||||
final class JigsawStudioToolboxMenu {
|
||||
private final JigsawStudioMenuController controller;
|
||||
|
||||
JigsawStudioToolboxMenu(JigsawStudioMenuController controller) {
|
||||
this.controller = Objects.requireNonNull(controller, "Jigsaw Studio menu controller");
|
||||
}
|
||||
|
||||
void render(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
int requestedPage
|
||||
) {
|
||||
List<ToolboxTool> allTools = toolboxTools(state, workcell);
|
||||
int page = clampPage(requestedPage, allTools.size(), TOOLS_PER_PAGE);
|
||||
List<ToolboxTool> tools = page(allTools, page, TOOLS_PER_PAGE);
|
||||
window.batch(() -> {
|
||||
window.clearElements();
|
||||
|
||||
UIElement back = element("toolbox-back", Material.ARROW, ChatColor.YELLOW + "Back to Variants");
|
||||
back.onLeftClick(clicked -> controller.refreshMain(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), 0));
|
||||
window.setElement(-4, 0, back);
|
||||
|
||||
UIElement heading = element("toolbox-heading", Material.CHEST, ChatColor.AQUA + "Bound Tool Sticks");
|
||||
heading.addLore(ChatColor.GRAY + "Every stick is bound to request " + state.requestId());
|
||||
heading.addLore(ChatColor.GRAY + "Stale sticks stop working when this Studio closes.");
|
||||
window.setElement(0, 0, heading);
|
||||
window.setElement(4, 0, evaluationElement(state.evaluation()));
|
||||
|
||||
for (int index = 0; index < tools.size(); index++) {
|
||||
ToolboxTool tool = tools.get(index);
|
||||
int position = GRID_POSITIONS[index % GRID_POSITIONS.length];
|
||||
int row = 1 + index / GRID_POSITIONS.length;
|
||||
UIElement element = element(
|
||||
"tool-" + index,
|
||||
Material.STICK,
|
||||
ChatColor.AQUA + safe(tool.displayName()));
|
||||
element.addLore(ChatColor.GRAY + "Action: " + tool.payload().action().displayName());
|
||||
addToolBindingLore(element, tool.payload());
|
||||
element.addLore(tool.payload().action().destructive()
|
||||
? ChatColor.RED + "Bound tools require confirmation when used."
|
||||
: ChatColor.YELLOW + "Left-click to receive this named stick.");
|
||||
element.onLeftClick(clicked -> controller.giveTool(
|
||||
window.getViewer(), state.requestId(), tool.payload()));
|
||||
window.setElement(position, row, element);
|
||||
}
|
||||
|
||||
UIElement footerBack = element("toolbox-footer-back", Material.ARROW, ChatColor.YELLOW + "Back");
|
||||
footerBack.onLeftClick(clicked -> controller.refreshMain(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), 0));
|
||||
window.setElement(-4, 5, footerBack);
|
||||
|
||||
int pageCount = pageCount(allTools.size(), TOOLS_PER_PAGE);
|
||||
if (page > 0) {
|
||||
UIElement previous = element("toolbox-previous", Material.ARROW, ChatColor.YELLOW + "Previous Page");
|
||||
previous.onLeftClick(clicked -> controller.refreshToolbox(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), page - 1));
|
||||
window.setElement(-1, 5, previous);
|
||||
}
|
||||
UIElement indicator = element("toolbox-page", Material.BOOK, ChatColor.WHITE + "Page "
|
||||
+ (page + 1) + "/" + pageCount);
|
||||
indicator.addLore(ChatColor.GRAY + "" + allTools.size() + " bound tools");
|
||||
window.setElement(0, 5, indicator);
|
||||
if (page + 1 < pageCount) {
|
||||
UIElement next = element("toolbox-next", Material.ARROW, ChatColor.YELLOW + "Next Page");
|
||||
next.onLeftClick(clicked -> controller.refreshToolbox(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), page + 1));
|
||||
window.setElement(1, 5, next);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static List<ToolboxTool> toolboxTools(
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell
|
||||
) {
|
||||
JigsawStudioMenuState menu = Objects.requireNonNull(state, "Jigsaw Studio toolbox state");
|
||||
JigsawStudioMenuState.Workcell selected = Objects.requireNonNull(
|
||||
workcell,
|
||||
"Jigsaw Studio toolbox workcell");
|
||||
UUID requestId = menu.requestId();
|
||||
List<ToolboxTool> tools = new ArrayList<>();
|
||||
tools.add(new ToolboxTool(
|
||||
"Open Control Menu",
|
||||
JigsawStudioToolPayload.request(JigsawStudioToolAction.OPEN_MENU, requestId)));
|
||||
tools.add(new ToolboxTool(
|
||||
"Select " + selected.displayName(),
|
||||
JigsawStudioToolPayload.workcell(
|
||||
JigsawStudioToolAction.SELECT_WORKCELL,
|
||||
requestId,
|
||||
selected.stableId())));
|
||||
if (menu.mode() == JigsawStudioMode.PLANAR_JIGSAW) {
|
||||
tools.add(new ToolboxTool(
|
||||
"Toggle " + selected.displayName(),
|
||||
JigsawStudioToolPayload.workcell(
|
||||
JigsawStudioToolAction.TOGGLE_WORKCELL,
|
||||
requestId,
|
||||
selected.stableId())));
|
||||
}
|
||||
tools.add(new ToolboxTool(
|
||||
"Resize " + selected.displayName() + " Capacity",
|
||||
JigsawStudioToolPayload.workcell(
|
||||
JigsawStudioToolAction.RESIZE_WORKCELL,
|
||||
requestId,
|
||||
selected.stableId())));
|
||||
tools.add(new ToolboxTool(
|
||||
"Rename " + selected.displayName(),
|
||||
JigsawStudioToolPayload.workcell(
|
||||
JigsawStudioToolAction.RENAME_WORKCELL,
|
||||
requestId,
|
||||
selected.stableId())));
|
||||
tools.add(new ToolboxTool(
|
||||
"New Blank " + selected.displayName() + " Variant",
|
||||
JigsawStudioToolPayload.workcell(
|
||||
JigsawStudioToolAction.CREATE_VARIANT,
|
||||
requestId,
|
||||
selected.stableId())));
|
||||
tools.add(new ToolboxTool(
|
||||
"Go to Preview",
|
||||
JigsawStudioToolPayload.request(JigsawStudioToolAction.PREVIEW_GRAPH, requestId)));
|
||||
tools.add(new ToolboxTool(
|
||||
"Flush " + selected.displayName() + " Autosave",
|
||||
JigsawStudioToolPayload.workcell(
|
||||
JigsawStudioToolAction.FLUSH_AUTOSAVE,
|
||||
requestId,
|
||||
selected.stableId())));
|
||||
if (menu.irisExtended()) {
|
||||
tools.add(new ToolboxTool(
|
||||
"Duplicate All Enabled Cells as Family: " + nextThemeSetKey(menu.themeSets()),
|
||||
JigsawStudioToolPayload.request(JigsawStudioToolAction.DUPLICATE_FAMILY, requestId)));
|
||||
}
|
||||
|
||||
JigsawStudioMenuState.Variant active = selected.activeVariant();
|
||||
if (active != null) {
|
||||
if (active.owned()) {
|
||||
tools.add(new ToolboxTool(
|
||||
"Duplicate This Cell's Variant: " + active.displayName(),
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.DUPLICATE_VARIANT,
|
||||
requestId,
|
||||
selected.stableId(),
|
||||
active.pieceKey())));
|
||||
}
|
||||
if (active.owned()) {
|
||||
tools.add(new ToolboxTool(
|
||||
"Rename This Variant: " + active.displayName(),
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.RENAME_VARIANT,
|
||||
requestId,
|
||||
selected.stableId(),
|
||||
active.pieceKey())));
|
||||
tools.add(new ToolboxTool(
|
||||
"Resize This Variant: " + active.displayName(),
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.RESIZE_VARIANT,
|
||||
requestId,
|
||||
selected.stableId(),
|
||||
active.pieceKey())));
|
||||
}
|
||||
if (active.rotationEditable()) {
|
||||
tools.add(new ToolboxTool(
|
||||
"Toggle Rotation: " + active.displayName(),
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.TOGGLE_ROTATION,
|
||||
requestId,
|
||||
selected.stableId(),
|
||||
active.pieceKey())));
|
||||
}
|
||||
if (active.owned() && active.resizableToCapacity()) {
|
||||
tools.add(new ToolboxTool(
|
||||
"Resize " + active.displayName() + " to Capacity",
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.EXPAND_TO_CELL,
|
||||
requestId,
|
||||
selected.stableId(),
|
||||
active.pieceKey())));
|
||||
}
|
||||
if (active.owned() && menu.irisExtended()) {
|
||||
tools.add(new ToolboxTool(
|
||||
"Open Theme Memberships: " + active.displayName(),
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.SET_THEME,
|
||||
requestId,
|
||||
selected.stableId(),
|
||||
active.pieceKey())));
|
||||
tools.add(new ToolboxTool(
|
||||
"Open Piece Rules: " + active.displayName(),
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.SET_PIECE_RULES,
|
||||
requestId,
|
||||
selected.stableId(),
|
||||
active.pieceKey())));
|
||||
}
|
||||
if (active.owned()) {
|
||||
for (JigsawStudioMenuState.Membership membership : active.memberships()) {
|
||||
addMembershipTools(
|
||||
tools,
|
||||
requestId,
|
||||
selected,
|
||||
active,
|
||||
membership,
|
||||
menu.irisExtended());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (JigsawStudioMenuState.Variant variant : selected.variants()) {
|
||||
if (variant.active()) {
|
||||
continue;
|
||||
}
|
||||
tools.add(new ToolboxTool(
|
||||
"Load " + variant.displayName(),
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.LOAD_VARIANT,
|
||||
requestId,
|
||||
selected.stableId(),
|
||||
variant.pieceKey())));
|
||||
if (variant.owned()) {
|
||||
tools.add(new ToolboxTool(
|
||||
"Rename " + variant.displayName(),
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.RENAME_VARIANT,
|
||||
requestId,
|
||||
selected.stableId(),
|
||||
variant.pieceKey())));
|
||||
tools.add(new ToolboxTool(
|
||||
"Resize " + variant.displayName(),
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.RESIZE_VARIANT,
|
||||
requestId,
|
||||
selected.stableId(),
|
||||
variant.pieceKey())));
|
||||
}
|
||||
if (variant.owned() && selected.variants().size() > 1) {
|
||||
tools.add(new ToolboxTool(
|
||||
"Delete " + variant.displayName(),
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.DELETE_VARIANT,
|
||||
requestId,
|
||||
selected.stableId(),
|
||||
variant.pieceKey())));
|
||||
}
|
||||
}
|
||||
if (menu.irisExtended()) {
|
||||
tools.add(new ToolboxTool(
|
||||
menu.requireCaps() ? "Disable Mandatory Caps" : "Enable Mandatory Caps",
|
||||
JigsawStudioToolPayload.request(JigsawStudioToolAction.TOGGLE_REQUIRE_CAPS, requestId)));
|
||||
}
|
||||
tools.add(new ToolboxTool(
|
||||
"Delete Project",
|
||||
JigsawStudioToolPayload.request(JigsawStudioToolAction.DELETE_PROJECT, requestId)));
|
||||
return List.copyOf(tools);
|
||||
}
|
||||
|
||||
private static void addMembershipTools(
|
||||
List<ToolboxTool> tools,
|
||||
UUID requestId,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
JigsawStudioMenuState.Variant variant,
|
||||
JigsawStudioMenuState.Membership membership,
|
||||
boolean chanceEditable
|
||||
) {
|
||||
String membershipName = displayKey(membership.poolKey()) + " [" + membership.entryIndex() + "]";
|
||||
tools.add(new ToolboxTool(
|
||||
"Weight +1: " + membershipName,
|
||||
JigsawStudioToolPayload.membership(
|
||||
JigsawStudioToolAction.ADJUST_VARIANT_WEIGHT,
|
||||
requestId,
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
membership.poolKey(),
|
||||
membership.entryIndex(),
|
||||
1)));
|
||||
tools.add(new ToolboxTool(
|
||||
"Weight -1: " + membershipName,
|
||||
JigsawStudioToolPayload.membership(
|
||||
JigsawStudioToolAction.ADJUST_VARIANT_WEIGHT,
|
||||
requestId,
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
membership.poolKey(),
|
||||
membership.entryIndex(),
|
||||
-1)));
|
||||
if (chanceEditable) {
|
||||
tools.add(new ToolboxTool(
|
||||
"Chance +" + CHANCE_STEP_PERCENTAGE_POINTS + "%: " + membershipName,
|
||||
JigsawStudioToolPayload.membership(
|
||||
JigsawStudioToolAction.ADJUST_VARIANT_CHANCE,
|
||||
requestId,
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
membership.poolKey(),
|
||||
membership.entryIndex(),
|
||||
CHANCE_STEP_PERCENTAGE_POINTS)));
|
||||
tools.add(new ToolboxTool(
|
||||
"Chance -" + CHANCE_STEP_PERCENTAGE_POINTS + "%: " + membershipName,
|
||||
JigsawStudioToolPayload.membership(
|
||||
JigsawStudioToolAction.ADJUST_VARIANT_CHANCE,
|
||||
requestId,
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
membership.poolKey(),
|
||||
membership.entryIndex(),
|
||||
-CHANCE_STEP_PERCENTAGE_POINTS)));
|
||||
}
|
||||
tools.add(new ToolboxTool(
|
||||
"Unlink " + membershipName,
|
||||
JigsawStudioToolPayload.membership(
|
||||
JigsawStudioToolAction.UNLINK_MEMBERSHIP,
|
||||
requestId,
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
membership.poolKey(),
|
||||
membership.entryIndex(),
|
||||
0)));
|
||||
}
|
||||
|
||||
private static void addToolBindingLore(UIElement element, JigsawStudioToolPayload payload) {
|
||||
if (!payload.workcellId().isEmpty()) {
|
||||
element.addLore(ChatColor.GRAY + "Workcell: " + safe(payload.workcellId()));
|
||||
}
|
||||
if (!payload.pieceKey().isEmpty()) {
|
||||
element.addLore(ChatColor.GRAY + "Variant: " + safe(payload.pieceKey()));
|
||||
}
|
||||
if (!payload.poolKey().isEmpty()) {
|
||||
element.addLore(ChatColor.GRAY + "Pool: " + safe(payload.poolKey())
|
||||
+ (payload.entryIndex() < 0 ? "" : " [" + payload.entryIndex() + "]"));
|
||||
}
|
||||
if (payload.amount() != 0) {
|
||||
element.addLore(ChatColor.GRAY + "Amount: " + payload.amount());
|
||||
}
|
||||
}
|
||||
|
||||
record ToolboxTool(String displayName, JigsawStudioToolPayload payload) {
|
||||
ToolboxTool {
|
||||
displayName = Objects.requireNonNull(displayName, "Jigsaw Studio toolbox display name").trim();
|
||||
if (displayName.isEmpty()) {
|
||||
throw new IllegalArgumentException("Jigsaw Studio toolbox display name cannot be blank");
|
||||
}
|
||||
payload = Objects.requireNonNull(payload, "Jigsaw Studio toolbox payload");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.core.service.JigsawStudioMenuFormat.RuleField;
|
||||
import art.arcane.volmlib.util.inventorygui.UIElement;
|
||||
import art.arcane.volmlib.util.inventorygui.UIWindow;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.GRID_POSITIONS;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.THEME_SETS_PER_PAGE;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.clampPage;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.element;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.evaluationElement;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.page;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.pageCount;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.safe;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.themes;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.withTerminal;
|
||||
|
||||
final class JigsawStudioVariantMenu {
|
||||
private final JigsawStudioMenuController controller;
|
||||
|
||||
JigsawStudioVariantMenu(JigsawStudioMenuController controller) {
|
||||
this.controller = Objects.requireNonNull(controller, "Jigsaw Studio menu controller");
|
||||
}
|
||||
|
||||
void render(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
JigsawStudioMenuState.Variant variant,
|
||||
int requestedPage
|
||||
) {
|
||||
int page = clampPage(requestedPage, state.themeSets().size(), THEME_SETS_PER_PAGE);
|
||||
window.batch(() -> {
|
||||
window.clearElements();
|
||||
|
||||
UIElement back = element("variant-settings-back", Material.ARROW, ChatColor.YELLOW + "Back to Details");
|
||||
back.onLeftClick(clicked -> controller.openDetails(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), variant.pieceKey(), 0));
|
||||
window.setElement(-4, 0, back);
|
||||
|
||||
UIElement identity = element(
|
||||
"variant-settings-identity",
|
||||
Material.COMPARATOR,
|
||||
ChatColor.GOLD + safe(variant.displayName()) + " Rules");
|
||||
identity.addLore(ChatColor.DARK_GRAY + safe(variant.pieceKey()));
|
||||
identity.addLore(ChatColor.GRAY + "Theme memberships: " + themes(variant.themes()));
|
||||
identity.addLore(ChatColor.GRAY + "Use Duplicate All Enabled Cells as Family for coherent sets.");
|
||||
window.setElement(0, 0, identity);
|
||||
window.setElement(4, 0, evaluationElement(state.evaluation()));
|
||||
|
||||
if (!variant.active() || !variant.owned() || !state.irisExtended()) {
|
||||
UIElement unavailable = element(
|
||||
"variant-settings-unavailable",
|
||||
Material.BARRIER,
|
||||
state.irisExtended()
|
||||
? ChatColor.RED + "Load an Owned Variant First"
|
||||
: ChatColor.GRAY + "Iris Metadata Unavailable");
|
||||
unavailable.addLore(state.irisExtended()
|
||||
? ChatColor.GRAY + "Rules and themes can only change for the loaded owned variant."
|
||||
: ChatColor.GRAY + "Vanilla-portable pieces use vanilla placement behavior.");
|
||||
window.setElement(0, 2, unavailable);
|
||||
} else {
|
||||
window.setElement(-4, 1, ruleElement(
|
||||
window, state, workcell, variant, RuleField.MINIMUM_DEPTH));
|
||||
window.setElement(-2, 1, ruleElement(
|
||||
window, state, workcell, variant, RuleField.MAXIMUM_DEPTH));
|
||||
window.setElement(0, 1, ruleElement(
|
||||
window, state, workcell, variant, RuleField.MINIMUM_PLACEMENTS));
|
||||
window.setElement(2, 1, ruleElement(
|
||||
window, state, workcell, variant, RuleField.MAXIMUM_PLACEMENTS));
|
||||
|
||||
UIElement terminal = element(
|
||||
"rule-terminal",
|
||||
variant.rules().terminal() ? Material.REDSTONE_TORCH : Material.LEVER,
|
||||
variant.rules().terminal()
|
||||
? ChatColor.GREEN + "Terminal Piece: Yes"
|
||||
: ChatColor.YELLOW + "Terminal Piece: No");
|
||||
terminal.addLore(ChatColor.GRAY + "Terminal pieces consume a connector without expanding.");
|
||||
terminal.addLore(ChatColor.YELLOW + "Left-click to toggle");
|
||||
terminal.onLeftClick(clicked -> controller.updateVariantRules(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
withTerminal(variant.rules(), !variant.rules().terminal())));
|
||||
window.setElement(4, 1, terminal);
|
||||
|
||||
addVariantThemeMemberships(window, state, workcell, variant, page);
|
||||
}
|
||||
|
||||
UIElement footerBack = element("variant-rules-footer-back", Material.ARROW, ChatColor.YELLOW + "Back");
|
||||
footerBack.onLeftClick(clicked -> controller.openDetails(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), variant.pieceKey(), 0));
|
||||
window.setElement(-4, 5, footerBack);
|
||||
|
||||
int pageCount = pageCount(state.themeSets().size(), THEME_SETS_PER_PAGE);
|
||||
if (page > 0) {
|
||||
UIElement previous = element(
|
||||
"variant-theme-previous",
|
||||
Material.ARROW,
|
||||
ChatColor.YELLOW + "Previous Page");
|
||||
previous.onLeftClick(clicked -> controller.refreshVariantSettings(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
page - 1));
|
||||
window.setElement(-1, 5, previous);
|
||||
}
|
||||
UIElement indicator = element(
|
||||
"variant-theme-page",
|
||||
Material.BOOK,
|
||||
ChatColor.WHITE + "Theme Page " + (page + 1) + "/" + pageCount);
|
||||
indicator.addLore(ChatColor.GRAY + "" + state.themeSets().size() + " declared themes");
|
||||
window.setElement(0, 5, indicator);
|
||||
if (page + 1 < pageCount) {
|
||||
UIElement next = element(
|
||||
"variant-theme-next",
|
||||
Material.ARROW,
|
||||
ChatColor.YELLOW + "Next Page");
|
||||
next.onLeftClick(clicked -> controller.refreshVariantSettings(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
page + 1));
|
||||
window.setElement(1, 5, next);
|
||||
}
|
||||
window.setElement(4, 5, evaluationElement(state.evaluation()));
|
||||
});
|
||||
}
|
||||
|
||||
private UIElement ruleElement(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
JigsawStudioMenuState.Variant variant,
|
||||
RuleField field
|
||||
) {
|
||||
int value = field.value(variant.rules());
|
||||
UIElement element = element(
|
||||
"rule-" + field.name().toLowerCase(Locale.ROOT),
|
||||
field.material(),
|
||||
ChatColor.AQUA + field.displayName() + ": " + field.displayValue(value));
|
||||
boolean unlimitedMaximum = field == RuleField.MAXIMUM_PLACEMENTS && value == 0;
|
||||
if (unlimitedMaximum) {
|
||||
element.addLore(ChatColor.GRAY + "Increase is already unlimited.");
|
||||
} else {
|
||||
element.addLore(ChatColor.GREEN + "Left-click: +1");
|
||||
element.addLore(ChatColor.GREEN + "Shift-left: +" + field.shiftStep());
|
||||
element.onLeftClick(clicked -> controller.adjustVariantRule(
|
||||
window.getViewer(), state.requestId(), workcell, variant, field, 1));
|
||||
element.onShiftLeftClick(clicked -> controller.adjustVariantRule(
|
||||
window.getViewer(), state.requestId(), workcell, variant, field, field.shiftStep()));
|
||||
}
|
||||
element.addLore(unlimitedMaximum
|
||||
? ChatColor.YELLOW + "Right-click: set 512"
|
||||
: ChatColor.YELLOW + "Right-click: -1");
|
||||
element.addLore(unlimitedMaximum
|
||||
? ChatColor.YELLOW + "Shift-right: set 497"
|
||||
: ChatColor.YELLOW + "Shift-right: -" + field.shiftStep());
|
||||
element.onRightClick(clicked -> controller.adjustVariantRule(
|
||||
window.getViewer(), state.requestId(), workcell, variant, field, -1));
|
||||
element.onShiftRightClick(clicked -> controller.adjustVariantRule(
|
||||
window.getViewer(), state.requestId(), workcell, variant, field, -field.shiftStep()));
|
||||
return element;
|
||||
}
|
||||
|
||||
private void addVariantThemeMemberships(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
JigsawStudioMenuState.Variant variant,
|
||||
int page
|
||||
) {
|
||||
List<JigsawStudioMenuState.ThemeSet> themeSets = page(
|
||||
state.themeSets(), page, THEME_SETS_PER_PAGE);
|
||||
if (themeSets.isEmpty()) {
|
||||
UIElement empty = element(
|
||||
"variant-no-themes",
|
||||
Material.GRAY_DYE,
|
||||
ChatColor.GRAY + "Available to Every Theme");
|
||||
empty.addLore(ChatColor.GRAY + "Create a coherent theme set from Structure Rules first.");
|
||||
window.setElement(0, 3, empty);
|
||||
return;
|
||||
}
|
||||
for (int index = 0; index < themeSets.size(); index++) {
|
||||
JigsawStudioMenuState.ThemeSet themeSet = themeSets.get(index);
|
||||
boolean member = variant.themes().contains(themeSet.key());
|
||||
int position = GRID_POSITIONS[index % GRID_POSITIONS.length];
|
||||
int row = 2 + index / GRID_POSITIONS.length;
|
||||
UIElement element = element(
|
||||
"variant-theme-" + index,
|
||||
member ? Material.LIME_DYE : Material.GRAY_DYE,
|
||||
(member ? ChatColor.GREEN : ChatColor.GRAY) + safe(themeSet.key()))
|
||||
.setEnchanted(member);
|
||||
element.addLore(member
|
||||
? ChatColor.GREEN + "This variant belongs to the theme."
|
||||
: ChatColor.GRAY + "This variant does not belong to the theme.");
|
||||
element.addLore(ChatColor.GRAY + "Only variants in the selected family are eligible.");
|
||||
element.addLore(ChatColor.YELLOW + "Left-click to toggle membership");
|
||||
element.onLeftClick(clicked -> controller.toggleVariantTheme(
|
||||
window.getViewer(), state.requestId(), workcell, variant, themeSet.key()));
|
||||
window.setElement(position, row, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,536 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.core.service.JigsawStudioMenuFormat.DimensionAxis;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioCellDimensions;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioMode;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioToolAction;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioToolPayload;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.inventorygui.UIElement;
|
||||
import art.arcane.volmlib.util.inventorygui.UIWindow;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
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;
|
||||
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuController.send;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.adjustedDimensions;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.dimensions;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.element;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.evaluationElement;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.safe;
|
||||
import static art.arcane.iris.core.service.JigsawStudioMenuFormat.withCapacity;
|
||||
|
||||
final class JigsawStudioWorkcellMenu {
|
||||
private static final int WORKCELL_RESIZE_REFRESH_TICKS = 2;
|
||||
private static final int WORKCELL_RESIZE_REFRESH_ATTEMPTS = 200;
|
||||
private final Map<UUID, PendingWorkcellResize> pendingWorkcellResizes = new ConcurrentHashMap<>();
|
||||
|
||||
private final JigsawStudioMenuController controller;
|
||||
|
||||
JigsawStudioWorkcellMenu(JigsawStudioMenuController controller) {
|
||||
this.controller = Objects.requireNonNull(controller, "Jigsaw Studio menu controller");
|
||||
}
|
||||
|
||||
void render(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell
|
||||
) {
|
||||
PendingWorkcellResize pendingResize = pendingWorkcellResize(
|
||||
window.getViewer().getUniqueId(),
|
||||
state.requestId(),
|
||||
workcell.stableId());
|
||||
JigsawStudioMenuState.Workcell renderedWorkcell = pendingResize == null
|
||||
? workcell
|
||||
: withCapacity(workcell, pendingResize.dimensions());
|
||||
window.batch(() -> {
|
||||
window.clearElements();
|
||||
|
||||
UIElement back = element("settings-back", Material.ARROW, ChatColor.YELLOW + "Back to Variants");
|
||||
back.onLeftClick(clicked -> leaveWorkcellSettings(
|
||||
window.getViewer(), state.requestId(), renderedWorkcell.stableId()));
|
||||
window.setElement(-4, 0, back);
|
||||
|
||||
UIElement identity = element(
|
||||
"settings-identity",
|
||||
renderedWorkcell.enabled() ? Material.LIME_WOOL : Material.GRAY_WOOL,
|
||||
ChatColor.AQUA + safe(renderedWorkcell.displayName()));
|
||||
identity.addLore(ChatColor.DARK_GRAY + safe(renderedWorkcell.stableId()));
|
||||
identity.addLore(renderedWorkcell.enabled()
|
||||
? ChatColor.GREEN + "Enabled"
|
||||
: ChatColor.RED + "Disabled for assembly and export");
|
||||
if (!renderedWorkcell.canonicalName().equals(renderedWorkcell.displayName())) {
|
||||
identity.addLore(ChatColor.GRAY + "Solver role: " + safe(renderedWorkcell.canonicalName()));
|
||||
}
|
||||
identity.addLore(ChatColor.GRAY + "Capacity: " + dimensions(renderedWorkcell.capacity()));
|
||||
if (pendingResize != null) {
|
||||
identity.addLore(pendingResize.applying()
|
||||
? ChatColor.YELLOW + "Applying one live relayout"
|
||||
: ChatColor.GOLD + "Pending; apply when all dimensions are ready");
|
||||
}
|
||||
identity.addLore(ChatColor.YELLOW + "Left-click for a rename stick");
|
||||
identity.addLore(ChatColor.GRAY + "Rename that stick in an anvil, then right-click it.");
|
||||
identity.addLore(ChatColor.GRAY + "Sneak-right-click the stick to reset this label.");
|
||||
identity.onLeftClick(clicked -> controller.actions.giveTool(
|
||||
window.getViewer(),
|
||||
JigsawStudioToolPayload.workcell(
|
||||
JigsawStudioToolAction.RENAME_WORKCELL,
|
||||
state.requestId(),
|
||||
renderedWorkcell.stableId())));
|
||||
window.setElement(0, 0, identity);
|
||||
window.setElement(4, 0, evaluationElement(state.evaluation()));
|
||||
|
||||
if (state.mode() == JigsawStudioMode.PLANAR_JIGSAW) {
|
||||
UIElement enabled = element(
|
||||
"settings-enabled",
|
||||
renderedWorkcell.enabled() ? Material.LEVER : Material.REDSTONE_TORCH,
|
||||
renderedWorkcell.enabled()
|
||||
? ChatColor.GREEN + "Workcell Enabled"
|
||||
: ChatColor.RED + "Workcell Disabled");
|
||||
enabled.addLore(ChatColor.GRAY + "Disabled workcells remain editable and keep their size.");
|
||||
enabled.addLore(ChatColor.YELLOW + "Left-click to "
|
||||
+ (renderedWorkcell.enabled() ? "disable" : "enable"));
|
||||
enabled.onLeftClick(clicked -> controller.setWorkcellEnabled(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
renderedWorkcell.stableId(),
|
||||
!renderedWorkcell.enabled()));
|
||||
window.setElement(0, 1, enabled);
|
||||
} else {
|
||||
UIElement spatial = element(
|
||||
"spatial-bounds",
|
||||
Material.SCAFFOLDING,
|
||||
ChatColor.AQUA + "Spatial Workcell Bounds");
|
||||
spatial.addLore(ChatColor.GRAY + "Spatial project bounds are shared by the project.");
|
||||
spatial.addLore(ChatColor.GRAY + "Changing them regenerates the Studio layout.");
|
||||
window.setElement(0, 1, spatial);
|
||||
}
|
||||
|
||||
UIElement connectors = element(
|
||||
"settings-connectors",
|
||||
renderedWorkcell.connectorsVisible() ? Material.JIGSAW : Material.STRUCTURE_VOID,
|
||||
renderedWorkcell.connectorsVisible()
|
||||
? ChatColor.GREEN + "Connector Blocks Visible"
|
||||
: ChatColor.YELLOW + "Connector Blocks Hidden");
|
||||
connectors.addLore(ChatColor.GRAY + "Hidden connectors retain their metadata and final block state.");
|
||||
connectors.addLore(ChatColor.YELLOW + "Left-click to "
|
||||
+ (renderedWorkcell.connectorsVisible() ? "hide" : "show") + " connector blocks");
|
||||
connectors.onLeftClick(clicked -> controller.toggleConnectorBlocks(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
renderedWorkcell.stableId(),
|
||||
!renderedWorkcell.connectorsVisible()));
|
||||
window.setElement(2, 1, connectors);
|
||||
|
||||
UIElement resetConnectors = element(
|
||||
"settings-reset-connectors",
|
||||
Material.RECOVERY_COMPASS,
|
||||
ChatColor.AQUA + "Reset Connector Blocks");
|
||||
resetConnectors.addLore(ChatColor.GRAY + "Restore every connector in this workcell from disk.");
|
||||
resetConnectors.addLore(ChatColor.GRAY + "Other edited blocks are left unchanged.");
|
||||
resetConnectors.onLeftClick(clicked -> controller.resetConnectorBlocks(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
renderedWorkcell.stableId()));
|
||||
window.setElement(4, 1, resetConnectors);
|
||||
|
||||
window.setElement(-2, 2, axisElement(
|
||||
window,
|
||||
state,
|
||||
renderedWorkcell,
|
||||
DimensionAxis.WIDTH,
|
||||
Material.IRON_INGOT));
|
||||
window.setElement(0, 2, axisElement(
|
||||
window,
|
||||
state,
|
||||
renderedWorkcell,
|
||||
DimensionAxis.HEIGHT,
|
||||
Material.GOLD_INGOT));
|
||||
window.setElement(2, 2, axisElement(
|
||||
window,
|
||||
state,
|
||||
renderedWorkcell,
|
||||
DimensionAxis.DEPTH,
|
||||
Material.COPPER_INGOT));
|
||||
|
||||
if (pendingResize != null) {
|
||||
UIElement apply = element(
|
||||
"settings-apply-capacity",
|
||||
pendingResize.applying() ? Material.CLOCK : Material.EMERALD_BLOCK,
|
||||
pendingResize.applying()
|
||||
? ChatColor.YELLOW + "Applying Cell Size"
|
||||
: ChatColor.GREEN + "Apply Cell Size");
|
||||
apply.addLore(ChatColor.WHITE + dimensions(pendingResize.dimensions()));
|
||||
apply.addLore(ChatColor.GRAY + "Regenerates the layout once after all size edits.");
|
||||
if (!pendingResize.applying()) {
|
||||
apply.onLeftClick(clicked -> applyWorkcellResize(
|
||||
window.getViewer(), state.requestId(), renderedWorkcell.stableId()));
|
||||
}
|
||||
window.setElement(0, 3, apply);
|
||||
|
||||
if (!pendingResize.applying()) {
|
||||
UIElement discard = element(
|
||||
"settings-discard-capacity",
|
||||
Material.BARRIER,
|
||||
ChatColor.RED + "Discard Size Changes");
|
||||
discard.onLeftClick(clicked -> discardWorkcellResize(
|
||||
window.getViewer(), state.requestId(), renderedWorkcell.stableId()));
|
||||
window.setElement(2, 3, discard);
|
||||
}
|
||||
}
|
||||
|
||||
UIElement footerBack = element("settings-footer-back", Material.ARROW, ChatColor.YELLOW + "Back");
|
||||
footerBack.onLeftClick(clicked -> leaveWorkcellSettings(
|
||||
window.getViewer(), state.requestId(), renderedWorkcell.stableId()));
|
||||
window.setElement(-4, 5, footerBack);
|
||||
|
||||
UIElement undo = element(
|
||||
"settings-undo",
|
||||
Material.CLOCK,
|
||||
ChatColor.LIGHT_PURPLE + "Undo Last Autosave");
|
||||
undo.addLore(ChatColor.GRAY + "Restore the previous owned graph iteration.");
|
||||
undo.addLore(ChatColor.GRAY + "Up to five autosave iterations are retained on disk.");
|
||||
undo.onLeftClick(clicked -> controller.undoAutosave(
|
||||
window.getViewer(),
|
||||
state.requestId()));
|
||||
window.setElement(0, 5, undo);
|
||||
|
||||
if (renderedWorkcell.dirty() && !renderedWorkcell.saving()) {
|
||||
UIElement saveNow = element(
|
||||
"save-now",
|
||||
Material.EMERALD,
|
||||
ChatColor.GOLD + "Flush Autosave Now");
|
||||
saveNow.addLore(ChatColor.GRAY + "Autosave is automatic.");
|
||||
saveNow.addLore(ChatColor.GRAY + "Use this only to flush pending work or recover immediately.");
|
||||
saveNow.onLeftClick(clicked -> controller.flushNow(
|
||||
window.getViewer(), state.requestId(), renderedWorkcell.stableId()));
|
||||
window.setElement(2, 5, saveNow);
|
||||
}
|
||||
|
||||
UIElement preview = element(
|
||||
"settings-preview",
|
||||
Material.ENDER_EYE,
|
||||
ChatColor.LIGHT_PURPLE + "Go to Preview");
|
||||
preview.onLeftClick(clicked -> controller.goToPreview(window.getViewer(), state.requestId()));
|
||||
window.setElement(3, 5, preview);
|
||||
|
||||
UIElement toolbox = element("settings-toolbox", Material.STICK, ChatColor.AQUA + "Toolbox");
|
||||
toolbox.onLeftClick(clicked -> controller.openToolbox(
|
||||
window.getViewer(), state.requestId(), renderedWorkcell.stableId(), 0));
|
||||
window.setElement(4, 5, toolbox);
|
||||
});
|
||||
}
|
||||
|
||||
void renderSize(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
JigsawStudioMenuState.Variant variant
|
||||
) {
|
||||
JigsawStudioCellDimensions current = variant.dimensions().orElseThrow();
|
||||
window.batch(() -> {
|
||||
window.clearElements();
|
||||
|
||||
UIElement back = element("variant-size-back", Material.ARROW, ChatColor.YELLOW + "Back to Details");
|
||||
back.onLeftClick(clicked -> controller.openDetails(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), variant.pieceKey(), 0));
|
||||
window.setElement(-4, 0, back);
|
||||
|
||||
UIElement identity = element(
|
||||
"variant-size-identity",
|
||||
Material.SCAFFOLDING,
|
||||
ChatColor.AQUA + safe(variant.displayName()) + " Size");
|
||||
identity.addLore(ChatColor.DARK_GRAY + safe(variant.pieceKey()));
|
||||
identity.addLore(ChatColor.WHITE + "Current: " + dimensions(current));
|
||||
identity.addLore(ChatColor.GRAY + "Workcell capacity: " + dimensions(workcell.capacity()));
|
||||
identity.addLore(ChatColor.GRAY + "Only this variant object and its edge connectors change.");
|
||||
window.setElement(0, 0, identity);
|
||||
window.setElement(4, 0, evaluationElement(state.evaluation()));
|
||||
|
||||
window.setElement(-2, 2, variantAxisElement(
|
||||
window, state, workcell, variant, DimensionAxis.WIDTH, Material.IRON_INGOT));
|
||||
window.setElement(0, 2, variantAxisElement(
|
||||
window, state, workcell, variant, DimensionAxis.HEIGHT, Material.GOLD_INGOT));
|
||||
window.setElement(2, 2, variantAxisElement(
|
||||
window, state, workcell, variant, DimensionAxis.DEPTH, Material.COPPER_INGOT));
|
||||
|
||||
UIElement capacity = element(
|
||||
"variant-size-capacity",
|
||||
variant.resizableToCapacity() ? Material.NETHER_STAR : Material.GRAY_DYE,
|
||||
variant.resizableToCapacity()
|
||||
? ChatColor.GREEN + "Resize This Variant to Capacity"
|
||||
: ChatColor.GRAY + "Variant Already Matches Capacity");
|
||||
capacity.addLore(ChatColor.GRAY + dimensions(workcell.capacity()));
|
||||
if (variant.resizableToCapacity()) {
|
||||
capacity.addLore(ChatColor.YELLOW + "Left-click to resize only this variant");
|
||||
capacity.onLeftClick(clicked -> controller.resizeVariant(
|
||||
window.getViewer(),
|
||||
state.requestId(),
|
||||
workcell.stableId(),
|
||||
variant.pieceKey(),
|
||||
workcell.capacity()));
|
||||
}
|
||||
window.setElement(0, 3, capacity);
|
||||
|
||||
UIElement footerBack = element("variant-size-footer-back", Material.ARROW, ChatColor.YELLOW + "Back");
|
||||
footerBack.onLeftClick(clicked -> controller.openDetails(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), variant.pieceKey(), 0));
|
||||
window.setElement(-4, 5, footerBack);
|
||||
});
|
||||
}
|
||||
|
||||
private UIElement variantAxisElement(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
JigsawStudioMenuState.Variant variant,
|
||||
DimensionAxis axis,
|
||||
Material material
|
||||
) {
|
||||
JigsawStudioCellDimensions current = variant.dimensions().orElseThrow();
|
||||
UIElement element = element(
|
||||
"variant-axis-" + axis.name().toLowerCase(Locale.ROOT),
|
||||
material,
|
||||
ChatColor.AQUA + axis.displayName() + ": " + axis.value(current));
|
||||
element.addLore(ChatColor.GREEN + "Left-click: +1");
|
||||
element.addLore(ChatColor.YELLOW + "Right-click: -1");
|
||||
element.addLore(ChatColor.GREEN + "Shift-left: +8");
|
||||
element.addLore(ChatColor.YELLOW + "Shift-right: -8");
|
||||
element.addLore(ChatColor.GRAY + "Maximum: " + axis.value(workcell.capacity()));
|
||||
element.onLeftClick(clicked -> controller.resizeVariantAxis(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), variant.pieceKey(), axis, 1));
|
||||
element.onRightClick(clicked -> controller.resizeVariantAxis(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), variant.pieceKey(), axis, -1));
|
||||
element.onShiftLeftClick(clicked -> controller.resizeVariantAxis(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), variant.pieceKey(), axis, 8));
|
||||
element.onShiftRightClick(clicked -> controller.resizeVariantAxis(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), variant.pieceKey(), axis, -8));
|
||||
return element;
|
||||
}
|
||||
|
||||
private UIElement axisElement(
|
||||
UIWindow window,
|
||||
JigsawStudioMenuState state,
|
||||
JigsawStudioMenuState.Workcell workcell,
|
||||
DimensionAxis axis,
|
||||
Material material
|
||||
) {
|
||||
UIElement element = element(
|
||||
"axis-" + axis.name().toLowerCase(Locale.ROOT),
|
||||
material,
|
||||
ChatColor.AQUA + axis.displayName() + " Capacity: " + axis.value(workcell.capacity()));
|
||||
element.addLore(ChatColor.GREEN + "Left-click: +1");
|
||||
element.addLore(ChatColor.YELLOW + "Right-click: -1");
|
||||
element.addLore(ChatColor.GREEN + "Shift-left: +8");
|
||||
element.addLore(ChatColor.YELLOW + "Shift-right: -8");
|
||||
element.addLore(ChatColor.GRAY + "Changes stay in this menu until Apply Cell Size.");
|
||||
element.onLeftClick(clicked -> resizeWorkcell(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), axis, 1));
|
||||
element.onRightClick(clicked -> resizeWorkcell(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), axis, -1));
|
||||
element.onShiftLeftClick(clicked -> resizeWorkcell(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), axis, 8));
|
||||
element.onShiftRightClick(clicked -> resizeWorkcell(
|
||||
window.getViewer(), state.requestId(), workcell.stableId(), axis, -8));
|
||||
return element;
|
||||
}
|
||||
|
||||
private void resizeWorkcell(
|
||||
Player player,
|
||||
UUID requestId,
|
||||
String workcellId,
|
||||
DimensionAxis axis,
|
||||
int delta
|
||||
) {
|
||||
Optional<JigsawStudioMenuState> current = controller.matchingState(player, requestId, true);
|
||||
if (current.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
JigsawStudioMenuState.Workcell workcell = current.get().workcell(workcellId);
|
||||
if (workcell == null) {
|
||||
controller.stale(player);
|
||||
return;
|
||||
}
|
||||
UUID playerId = player.getUniqueId();
|
||||
PendingWorkcellResize existing = pendingWorkcellResize(playerId, requestId, workcellId);
|
||||
if (existing != null && existing.applying()) {
|
||||
send(player, ChatColor.YELLOW + "That cell size is already being applied.");
|
||||
return;
|
||||
}
|
||||
JigsawStudioCellDimensions base = existing == null
|
||||
? workcell.capacity()
|
||||
: existing.dimensions();
|
||||
Optional<JigsawStudioCellDimensions> adjusted = adjustedDimensions(
|
||||
base, axis, delta);
|
||||
if (adjusted.isEmpty()) {
|
||||
send(player, ChatColor.RED + "That workcell size is outside Iris limits.");
|
||||
return;
|
||||
}
|
||||
PendingWorkcellResize pending = new PendingWorkcellResize(
|
||||
requestId,
|
||||
workcellId,
|
||||
adjusted.get(),
|
||||
false);
|
||||
pendingWorkcellResizes.put(playerId, pending);
|
||||
UIWindow window = controller.windows.get(playerId);
|
||||
if (window != null) {
|
||||
render(window, current.get(), withCapacity(workcell, pending.dimensions()));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyWorkcellResize(Player player, UUID requestId, String workcellId) {
|
||||
Optional<JigsawStudioMenuState> current = controller.matchingState(player, requestId, true);
|
||||
if (current.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
UUID playerId = player.getUniqueId();
|
||||
PendingWorkcellResize pending = pendingWorkcellResize(playerId, requestId, workcellId);
|
||||
JigsawStudioMenuState.Workcell workcell = current.get().workcell(workcellId);
|
||||
if (pending == null || pending.applying() || workcell == null) {
|
||||
return;
|
||||
}
|
||||
if (workcell.capacity().equals(pending.dimensions())) {
|
||||
pendingWorkcellResizes.remove(playerId, pending);
|
||||
refreshWorkcellSettings(player, requestId, workcellId);
|
||||
return;
|
||||
}
|
||||
PendingWorkcellResize applying = new PendingWorkcellResize(
|
||||
requestId,
|
||||
workcellId,
|
||||
pending.dimensions(),
|
||||
true);
|
||||
pendingWorkcellResizes.put(playerId, applying);
|
||||
UIWindow window = controller.windows.get(playerId);
|
||||
if (window != null) {
|
||||
render(window, current.get(), withCapacity(workcell, applying.dimensions()));
|
||||
}
|
||||
if (!controller.actions.updateWorkcellDimensions(player, workcellId, applying.dimensions())) {
|
||||
pendingWorkcellResizes.replace(playerId, applying, pending);
|
||||
refreshWorkcellSettings(player, requestId, workcellId);
|
||||
return;
|
||||
}
|
||||
scheduleWorkcellResizeRefresh(player, applying, WORKCELL_RESIZE_REFRESH_ATTEMPTS);
|
||||
}
|
||||
|
||||
private void scheduleWorkcellResizeRefresh(
|
||||
Player player,
|
||||
PendingWorkcellResize pending,
|
||||
int attemptsRemaining
|
||||
) {
|
||||
boolean scheduled = J.runEntity(
|
||||
player,
|
||||
() -> refreshAppliedWorkcellResize(player, pending, attemptsRemaining),
|
||||
WORKCELL_RESIZE_REFRESH_TICKS);
|
||||
if (!scheduled) {
|
||||
pendingWorkcellResizes.remove(player.getUniqueId(), pending);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshAppliedWorkcellResize(
|
||||
Player player,
|
||||
PendingWorkcellResize pending,
|
||||
int attemptsRemaining
|
||||
) {
|
||||
UUID playerId = player.getUniqueId();
|
||||
if (!pending.equals(pendingWorkcellResizes.get(playerId))) {
|
||||
return;
|
||||
}
|
||||
Optional<JigsawStudioMenuState> current = controller.matchingState(player, pending.requestId(), false);
|
||||
JigsawStudioMenuState.Workcell workcell = current
|
||||
.map(state -> state.workcell(pending.workcellId()))
|
||||
.orElse(null);
|
||||
if (workcell == null) {
|
||||
pendingWorkcellResizes.remove(playerId, pending);
|
||||
return;
|
||||
}
|
||||
if (workcell.capacity().equals(pending.dimensions())) {
|
||||
pendingWorkcellResizes.remove(playerId, pending);
|
||||
UIWindow window = controller.windows.get(playerId);
|
||||
if (window != null) {
|
||||
render(window, current.orElseThrow(), workcell);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (attemptsRemaining > 0) {
|
||||
scheduleWorkcellResizeRefresh(player, pending, attemptsRemaining - 1);
|
||||
return;
|
||||
}
|
||||
PendingWorkcellResize retry = new PendingWorkcellResize(
|
||||
pending.requestId(),
|
||||
pending.workcellId(),
|
||||
pending.dimensions(),
|
||||
false);
|
||||
pendingWorkcellResizes.replace(playerId, pending, retry);
|
||||
UIWindow window = controller.windows.get(playerId);
|
||||
if (window != null) {
|
||||
render(window, current.orElseThrow(), withCapacity(workcell, retry.dimensions()));
|
||||
}
|
||||
send(player, ChatColor.YELLOW
|
||||
+ "Cell resizing is still pending; use Apply Cell Size to retry after the current operation settles.");
|
||||
}
|
||||
|
||||
private void discardWorkcellResize(Player player, UUID requestId, String workcellId) {
|
||||
PendingWorkcellResize pending = pendingWorkcellResize(player.getUniqueId(), requestId, workcellId);
|
||||
if (pending != null && !pending.applying()) {
|
||||
pendingWorkcellResizes.remove(player.getUniqueId(), pending);
|
||||
}
|
||||
refreshWorkcellSettings(player, requestId, workcellId);
|
||||
}
|
||||
|
||||
private void leaveWorkcellSettings(Player player, UUID requestId, String workcellId) {
|
||||
PendingWorkcellResize pending = pendingWorkcellResize(player.getUniqueId(), requestId, workcellId);
|
||||
if (pending != null && !pending.applying()) {
|
||||
pendingWorkcellResizes.remove(player.getUniqueId(), pending);
|
||||
}
|
||||
controller.refreshMain(player, requestId, workcellId, 0);
|
||||
}
|
||||
|
||||
private void refreshWorkcellSettings(Player player, UUID requestId, String workcellId) {
|
||||
Optional<JigsawStudioMenuState> current = controller.matchingState(player, requestId, true);
|
||||
UIWindow window = controller.windows.get(player.getUniqueId());
|
||||
JigsawStudioMenuState.Workcell workcell = current
|
||||
.map(state -> state.workcell(workcellId))
|
||||
.orElse(null);
|
||||
if (window == null || workcell == null) {
|
||||
return;
|
||||
}
|
||||
render(window, current.orElseThrow(), workcell);
|
||||
}
|
||||
|
||||
private PendingWorkcellResize pendingWorkcellResize(UUID playerId, UUID requestId, String workcellId) {
|
||||
PendingWorkcellResize pending = pendingWorkcellResizes.get(playerId);
|
||||
if (pending == null
|
||||
|| !pending.requestId().equals(requestId)
|
||||
|| !pending.workcellId().equals(workcellId)) {
|
||||
return null;
|
||||
}
|
||||
return pending;
|
||||
}
|
||||
|
||||
private record PendingWorkcellResize(
|
||||
UUID requestId,
|
||||
String workcellId,
|
||||
JigsawStudioCellDimensions dimensions,
|
||||
boolean applying
|
||||
) {
|
||||
private PendingWorkcellResize {
|
||||
requestId = Objects.requireNonNull(requestId, "Jigsaw Studio resize request ID");
|
||||
workcellId = Objects.requireNonNull(workcellId, "Jigsaw Studio resize workcell ID");
|
||||
dimensions = Objects.requireNonNull(dimensions, "Jigsaw Studio resize dimensions");
|
||||
}
|
||||
}
|
||||
void clearStagedResize(UUID playerId) {
|
||||
pendingWorkcellResizes.remove(playerId);
|
||||
}
|
||||
|
||||
void clearStagedResizes() {
|
||||
pendingWorkcellResizes.clear();
|
||||
}
|
||||
}
|
||||
+67
-66
@@ -57,13 +57,13 @@ public class JigsawStudioMenuControllerTest {
|
||||
values.add(index);
|
||||
}
|
||||
|
||||
assertEquals(1, JigsawStudioMenuController.pageCount(0, 28));
|
||||
assertEquals(1, JigsawStudioMenuController.pageCount(28, 28));
|
||||
assertEquals(2, JigsawStudioMenuController.pageCount(29, 28));
|
||||
assertEquals(List.of(0, 1, 2), JigsawStudioMenuController.page(values, -10, 3));
|
||||
assertEquals(List.of(54, 55, 56), JigsawStudioMenuController.page(values, 999, 3));
|
||||
assertEquals(1, JigsawStudioMenuFormat.pageCount(0, 28));
|
||||
assertEquals(1, JigsawStudioMenuFormat.pageCount(28, 28));
|
||||
assertEquals(2, JigsawStudioMenuFormat.pageCount(29, 28));
|
||||
assertEquals(List.of(0, 1, 2), JigsawStudioMenuFormat.page(values, -10, 3));
|
||||
assertEquals(List.of(54, 55, 56), JigsawStudioMenuFormat.page(values, 999, 3));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> JigsawStudioMenuController.pageCount(1, 0));
|
||||
() -> JigsawStudioMenuFormat.pageCount(1, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -86,7 +86,7 @@ public class JigsawStudioMenuControllerTest {
|
||||
false,
|
||||
List.of(active));
|
||||
|
||||
assertEquals(ChatColor.GREEN + "Autosaved", JigsawStudioMenuController.workcellStatus(fresh));
|
||||
assertEquals(ChatColor.GREEN + "Autosaved", JigsawStudioMenuFormat.workcellStatus(fresh));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -97,11 +97,11 @@ public class JigsawStudioMenuControllerTest {
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
assertEquals(Material.JIGSAW, JigsawStudioMenuController.variantMaterial(active));
|
||||
assertEquals(Material.JIGSAW, JigsawStudioMenuFormat.variantMaterial(active));
|
||||
assertEquals(Material.EMERALD,
|
||||
JigsawStudioMenuController.evaluationMaterial(JigsawStudioEvaluationState.VALID));
|
||||
JigsawStudioMenuFormat.evaluationMaterial(JigsawStudioEvaluationState.VALID));
|
||||
assertEquals(Material.TARGET,
|
||||
JigsawStudioMenuController.RuleField.MINIMUM_PLACEMENTS.material());
|
||||
JigsawStudioMenuFormat.RuleField.MINIMUM_PLACEMENTS.material());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -217,33 +217,33 @@ public class JigsawStudioMenuControllerTest {
|
||||
|
||||
assertEquals(
|
||||
new JigsawStudioCellDimensions(25, 12, 18),
|
||||
JigsawStudioMenuController.adjustedDimensions(
|
||||
JigsawStudioMenuFormat.adjustedDimensions(
|
||||
dimensions,
|
||||
JigsawStudioMenuController.DimensionAxis.WIDTH,
|
||||
JigsawStudioMenuFormat.DimensionAxis.WIDTH,
|
||||
1).orElseThrow());
|
||||
assertEquals(
|
||||
new JigsawStudioCellDimensions(24, 20, 18),
|
||||
JigsawStudioMenuController.adjustedDimensions(
|
||||
JigsawStudioMenuFormat.adjustedDimensions(
|
||||
dimensions,
|
||||
JigsawStudioMenuController.DimensionAxis.HEIGHT,
|
||||
JigsawStudioMenuFormat.DimensionAxis.HEIGHT,
|
||||
8).orElseThrow());
|
||||
assertEquals(
|
||||
new JigsawStudioCellDimensions(24, 12, 10),
|
||||
JigsawStudioMenuController.adjustedDimensions(
|
||||
JigsawStudioMenuFormat.adjustedDimensions(
|
||||
dimensions,
|
||||
JigsawStudioMenuController.DimensionAxis.DEPTH,
|
||||
JigsawStudioMenuFormat.DimensionAxis.DEPTH,
|
||||
-8).orElseThrow());
|
||||
assertTrue(JigsawStudioMenuController.adjustedDimensions(
|
||||
assertTrue(JigsawStudioMenuFormat.adjustedDimensions(
|
||||
new JigsawStudioCellDimensions(1, 1, 1),
|
||||
JigsawStudioMenuController.DimensionAxis.WIDTH,
|
||||
JigsawStudioMenuFormat.DimensionAxis.WIDTH,
|
||||
-1).isEmpty());
|
||||
assertTrue(JigsawStudioMenuController.adjustedDimensions(
|
||||
assertTrue(JigsawStudioMenuFormat.adjustedDimensions(
|
||||
new JigsawStudioCellDimensions(128, 1, 1),
|
||||
JigsawStudioMenuController.DimensionAxis.WIDTH,
|
||||
JigsawStudioMenuFormat.DimensionAxis.WIDTH,
|
||||
1).isEmpty());
|
||||
assertThrows(IllegalArgumentException.class, () -> JigsawStudioMenuController.adjustedDimensions(
|
||||
assertThrows(IllegalArgumentException.class, () -> JigsawStudioMenuFormat.adjustedDimensions(
|
||||
dimensions,
|
||||
JigsawStudioMenuController.DimensionAxis.WIDTH,
|
||||
JigsawStudioMenuFormat.DimensionAxis.WIDTH,
|
||||
0));
|
||||
}
|
||||
|
||||
@@ -251,21 +251,22 @@ public class JigsawStudioMenuControllerTest {
|
||||
public void stagesMultipleCellSizeEditsAndAppliesOneRelayoutWithoutClosing() throws Exception {
|
||||
UUID playerId = UUID.fromString("55555555-5555-5555-5555-555555555555");
|
||||
Player player = mock(Player.class);
|
||||
JigsawStudioMenuController.Actions actions = mock(JigsawStudioMenuController.Actions.class);
|
||||
JigsawStudioMenuActions actions = mock(JigsawStudioMenuActions.class);
|
||||
JigsawStudioMenuState.Variant active = variant("pieces/corner", true, List.of(), List.of());
|
||||
JigsawStudioMenuState.Workcell workcell = workcell(active);
|
||||
JigsawStudioMenuState state = state(JigsawStudioMenuState.Evaluation.pending(), workcell);
|
||||
JigsawStudioMenuController controller = new JigsawStudioMenuController(
|
||||
mock(JavaPlugin.class),
|
||||
actions);
|
||||
Method resize = JigsawStudioMenuController.class.getDeclaredMethod(
|
||||
JigsawStudioWorkcellMenu workcellMenu = new JigsawStudioWorkcellMenu(controller);
|
||||
Method resize = JigsawStudioWorkcellMenu.class.getDeclaredMethod(
|
||||
"resizeWorkcell",
|
||||
Player.class,
|
||||
UUID.class,
|
||||
String.class,
|
||||
JigsawStudioMenuController.DimensionAxis.class,
|
||||
JigsawStudioMenuFormat.DimensionAxis.class,
|
||||
int.class);
|
||||
Method apply = JigsawStudioMenuController.class.getDeclaredMethod(
|
||||
Method apply = JigsawStudioWorkcellMenu.class.getDeclaredMethod(
|
||||
"applyWorkcellResize",
|
||||
Player.class,
|
||||
UUID.class,
|
||||
@@ -280,18 +281,18 @@ public class JigsawStudioMenuControllerTest {
|
||||
new JigsawStudioCellDimensions(25, 20, 18))).thenReturn(true);
|
||||
|
||||
resize.invoke(
|
||||
controller,
|
||||
workcellMenu,
|
||||
player,
|
||||
REQUEST_ID,
|
||||
workcell.stableId(),
|
||||
JigsawStudioMenuController.DimensionAxis.WIDTH,
|
||||
JigsawStudioMenuFormat.DimensionAxis.WIDTH,
|
||||
1);
|
||||
resize.invoke(
|
||||
controller,
|
||||
workcellMenu,
|
||||
player,
|
||||
REQUEST_ID,
|
||||
workcell.stableId(),
|
||||
JigsawStudioMenuController.DimensionAxis.HEIGHT,
|
||||
JigsawStudioMenuFormat.DimensionAxis.HEIGHT,
|
||||
8);
|
||||
|
||||
verify(actions, never()).updateWorkcellDimensions(
|
||||
@@ -304,7 +305,7 @@ public class JigsawStudioMenuControllerTest {
|
||||
any(Runnable.class),
|
||||
eq(2))).thenReturn(true);
|
||||
|
||||
apply.invoke(controller, player, REQUEST_ID, workcell.stableId());
|
||||
apply.invoke(workcellMenu, player, REQUEST_ID, workcell.stableId());
|
||||
|
||||
verify(actions).updateWorkcellDimensions(
|
||||
player,
|
||||
@@ -319,7 +320,7 @@ public class JigsawStudioMenuControllerTest {
|
||||
JigsawStudioMenuState.Workcell source = workcell(active);
|
||||
JigsawStudioCellDimensions capacity = new JigsawStudioCellDimensions(31, 19, 27);
|
||||
|
||||
JigsawStudioMenuState.Workcell staged = JigsawStudioMenuController.withCapacity(source, capacity);
|
||||
JigsawStudioMenuState.Workcell staged = JigsawStudioMenuFormat.withCapacity(source, capacity);
|
||||
|
||||
assertEquals(capacity, staged.capacity());
|
||||
assertEquals(source.stableId(), staged.stableId());
|
||||
@@ -335,12 +336,12 @@ public class JigsawStudioMenuControllerTest {
|
||||
new JigsawStudioMenuState.ThemeSet("seasonal", 4),
|
||||
new JigsawStudioMenuState.ThemeSet("variant-2", 2));
|
||||
|
||||
assertEquals("variant-3", JigsawStudioMenuController.nextThemeSetKey(themeSets));
|
||||
assertEquals("variant-1", JigsawStudioMenuController.nextThemeSetKey(List.of()));
|
||||
assertEquals(Integer.valueOf(12), JigsawStudioMenuController.adjustedPositiveValue(4, 8).orElseThrow());
|
||||
assertTrue(JigsawStudioMenuController.adjustedPositiveValue(4, -8).isEmpty());
|
||||
assertEquals("variant-3", JigsawStudioMenuFormat.nextThemeSetKey(themeSets));
|
||||
assertEquals("variant-1", JigsawStudioMenuFormat.nextThemeSetKey(List.of()));
|
||||
assertEquals(Integer.valueOf(12), JigsawStudioMenuFormat.adjustedPositiveValue(4, 8).orElseThrow());
|
||||
assertTrue(JigsawStudioMenuFormat.adjustedPositiveValue(4, -8).isEmpty());
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> JigsawStudioMenuController.adjustedPositiveValue(4, 0));
|
||||
() -> JigsawStudioMenuFormat.adjustedPositiveValue(4, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -349,10 +350,10 @@ public class JigsawStudioMenuControllerTest {
|
||||
new JigsawStudioMenuState.ThemeSet("variant-1", 3),
|
||||
new JigsawStudioMenuState.ThemeSet("variant-2", 1));
|
||||
|
||||
assertEquals("75.0%", JigsawStudioMenuController.themeSelectionPercent(
|
||||
assertEquals("75.0%", JigsawStudioMenuFormat.themeSelectionPercent(
|
||||
themes,
|
||||
themes.getFirst()));
|
||||
assertEquals("25.0%", JigsawStudioMenuController.themeSelectionPercent(
|
||||
assertEquals("25.0%", JigsawStudioMenuFormat.themeSelectionPercent(
|
||||
themes,
|
||||
themes.getLast()));
|
||||
}
|
||||
@@ -361,47 +362,47 @@ public class JigsawStudioMenuControllerTest {
|
||||
public void editsPieceRuleFieldsWithinRuntimeBounds() {
|
||||
assertEquals(
|
||||
new JigsawStudioPieceRules(2, 12, 2, 8, false),
|
||||
JigsawStudioMenuController.adjustedRules(
|
||||
JigsawStudioMenuFormat.adjustedRules(
|
||||
RULES,
|
||||
JigsawStudioMenuController.RuleField.MINIMUM_DEPTH,
|
||||
JigsawStudioMenuFormat.RuleField.MINIMUM_DEPTH,
|
||||
1).orElseThrow());
|
||||
assertEquals(
|
||||
new JigsawStudioPieceRules(1, 17, 2, 8, false),
|
||||
JigsawStudioMenuController.adjustedRules(
|
||||
JigsawStudioMenuFormat.adjustedRules(
|
||||
RULES,
|
||||
JigsawStudioMenuController.RuleField.MAXIMUM_DEPTH,
|
||||
JigsawStudioMenuFormat.RuleField.MAXIMUM_DEPTH,
|
||||
5).orElseThrow());
|
||||
assertEquals(
|
||||
new JigsawStudioPieceRules(1, 12, 3, 8, false),
|
||||
JigsawStudioMenuController.adjustedRules(
|
||||
JigsawStudioMenuFormat.adjustedRules(
|
||||
RULES,
|
||||
JigsawStudioMenuController.RuleField.MINIMUM_PLACEMENTS,
|
||||
JigsawStudioMenuFormat.RuleField.MINIMUM_PLACEMENTS,
|
||||
1).orElseThrow());
|
||||
assertEquals(
|
||||
new JigsawStudioPieceRules(1, 12, 2, 24, false),
|
||||
JigsawStudioMenuController.adjustedRules(
|
||||
JigsawStudioMenuFormat.adjustedRules(
|
||||
RULES,
|
||||
JigsawStudioMenuController.RuleField.MAXIMUM_PLACEMENTS,
|
||||
JigsawStudioMenuFormat.RuleField.MAXIMUM_PLACEMENTS,
|
||||
16).orElseThrow());
|
||||
assertEquals(
|
||||
new JigsawStudioPieceRules(0, 30, 0, 0, false),
|
||||
JigsawStudioMenuController.adjustedRules(
|
||||
JigsawStudioMenuFormat.adjustedRules(
|
||||
new JigsawStudioPieceRules(0, 30, 0, 512, false),
|
||||
JigsawStudioMenuController.RuleField.MAXIMUM_PLACEMENTS,
|
||||
JigsawStudioMenuFormat.RuleField.MAXIMUM_PLACEMENTS,
|
||||
1).orElseThrow());
|
||||
assertEquals(
|
||||
new JigsawStudioPieceRules(0, 30, 0, 512, false),
|
||||
JigsawStudioMenuController.adjustedRules(
|
||||
JigsawStudioMenuFormat.adjustedRules(
|
||||
new JigsawStudioPieceRules(0, 30, 0, 0, false),
|
||||
JigsawStudioMenuController.RuleField.MAXIMUM_PLACEMENTS,
|
||||
JigsawStudioMenuFormat.RuleField.MAXIMUM_PLACEMENTS,
|
||||
-1).orElseThrow());
|
||||
assertTrue(JigsawStudioMenuController.adjustedRules(
|
||||
assertTrue(JigsawStudioMenuFormat.adjustedRules(
|
||||
RULES,
|
||||
JigsawStudioMenuController.RuleField.MINIMUM_DEPTH,
|
||||
JigsawStudioMenuFormat.RuleField.MINIMUM_DEPTH,
|
||||
12).isEmpty());
|
||||
assertEquals(
|
||||
new JigsawStudioPieceRules(1, 12, 2, 8, true),
|
||||
JigsawStudioMenuController.withTerminal(RULES, true));
|
||||
JigsawStudioMenuFormat.withTerminal(RULES, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -424,23 +425,23 @@ public class JigsawStudioMenuControllerTest {
|
||||
JigsawStudioMenuState.Workcell corner = workcell(active, List.of(active, inactive));
|
||||
JigsawStudioMenuState state = state(JigsawStudioMenuState.Evaluation.pending(), corner);
|
||||
|
||||
List<JigsawStudioMenuController.ToolboxTool> tools = JigsawStudioMenuController.toolboxTools(
|
||||
List<JigsawStudioToolboxMenu.ToolboxTool> tools = JigsawStudioToolboxMenu.toolboxTools(
|
||||
state,
|
||||
corner);
|
||||
JigsawStudioMenuController.ToolboxTool preview = tool(tools, JigsawStudioToolAction.PREVIEW_GRAPH, 0);
|
||||
JigsawStudioMenuController.ToolboxTool chanceIncrease = tool(
|
||||
JigsawStudioToolboxMenu.ToolboxTool preview = tool(tools, JigsawStudioToolAction.PREVIEW_GRAPH, 0);
|
||||
JigsawStudioToolboxMenu.ToolboxTool chanceIncrease = tool(
|
||||
tools,
|
||||
JigsawStudioToolAction.ADJUST_VARIANT_CHANCE,
|
||||
JigsawStudioMenuController.CHANCE_STEP_PERCENTAGE_POINTS);
|
||||
JigsawStudioMenuController.ToolboxTool delete = tool(
|
||||
JigsawStudioToolboxMenu.ToolboxTool delete = tool(
|
||||
tools,
|
||||
JigsawStudioToolAction.DELETE_VARIANT,
|
||||
0);
|
||||
JigsawStudioMenuController.ToolboxTool newThemeSet = tools.stream()
|
||||
JigsawStudioToolboxMenu.ToolboxTool newThemeSet = tools.stream()
|
||||
.filter(tool -> tool.payload().action() == JigsawStudioToolAction.DUPLICATE_FAMILY)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
JigsawStudioMenuController.ToolboxTool pieceRules = tools.stream()
|
||||
JigsawStudioToolboxMenu.ToolboxTool pieceRules = tools.stream()
|
||||
.filter(tool -> tool.payload().action() == JigsawStudioToolAction.SET_PIECE_RULES)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
@@ -489,7 +490,7 @@ public class JigsawStudioMenuControllerTest {
|
||||
JigsawStudioMenuState.Evaluation.pending(),
|
||||
List.of(room));
|
||||
|
||||
List<JigsawStudioMenuController.ToolboxTool> tools = JigsawStudioMenuController.toolboxTools(state, room);
|
||||
List<JigsawStudioToolboxMenu.ToolboxTool> tools = JigsawStudioToolboxMenu.toolboxTools(state, room);
|
||||
|
||||
assertTrue(tools.stream().anyMatch(tool -> tool.payload().action()
|
||||
== JigsawStudioToolAction.RESIZE_WORKCELL));
|
||||
@@ -519,7 +520,7 @@ public class JigsawStudioMenuControllerTest {
|
||||
JigsawStudioMenuState.Evaluation.pending(),
|
||||
List.of(corner));
|
||||
|
||||
List<JigsawStudioMenuController.ToolboxTool> tools = JigsawStudioMenuController.toolboxTools(state, corner);
|
||||
List<JigsawStudioToolboxMenu.ToolboxTool> tools = JigsawStudioToolboxMenu.toolboxTools(state, corner);
|
||||
|
||||
assertFalse(state.irisExtended());
|
||||
assertTrue(tools.stream().noneMatch(tool -> tool.payload().action() == JigsawStudioToolAction.SET_THEME));
|
||||
@@ -536,7 +537,7 @@ public class JigsawStudioMenuControllerTest {
|
||||
@Test
|
||||
public void openMarshalsToThePlayerRegionBeforeReadingStudioState() {
|
||||
Player player = mock(Player.class);
|
||||
JigsawStudioMenuController.Actions actions = mock(JigsawStudioMenuController.Actions.class);
|
||||
JigsawStudioMenuActions actions = mock(JigsawStudioMenuActions.class);
|
||||
JigsawStudioMenuController controller = new JigsawStudioMenuController(
|
||||
mock(JavaPlugin.class),
|
||||
actions);
|
||||
@@ -649,12 +650,12 @@ public class JigsawStudioMenuControllerTest {
|
||||
memberships);
|
||||
}
|
||||
|
||||
private JigsawStudioMenuController.ToolboxTool tool(
|
||||
List<JigsawStudioMenuController.ToolboxTool> tools,
|
||||
private JigsawStudioToolboxMenu.ToolboxTool tool(
|
||||
List<JigsawStudioToolboxMenu.ToolboxTool> tools,
|
||||
JigsawStudioToolAction action,
|
||||
int amount
|
||||
) {
|
||||
Optional<JigsawStudioMenuController.ToolboxTool> match = tools.stream()
|
||||
Optional<JigsawStudioToolboxMenu.ToolboxTool> match = tools.stream()
|
||||
.filter(tool -> tool.payload().action() == action && tool.payload().amount() == amount)
|
||||
.findFirst();
|
||||
return match.orElseThrow();
|
||||
|
||||
Reference in New Issue
Block a user