From 41e24e8f91b297bdc313a2dea6457ad0514b2aa4 Mon Sep 17 00:00:00 2001 From: Helvetica Volubi <88063803+Suisuroru@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:12:47 +0800 Subject: [PATCH] Fakeplayer Action Gui - Stage 1 (#161) Still needs some tweaks to fit Leaves' changes * pre add gui (not implemented) * fallback one change in Actions * pre for new structure of gui register * Enhance GUI node structure with item support and command handling * feat: update GUI node handling in bot actions * feat: enhance command building logic in GUI nodes * fix a bug * feat: implement GUI navigation and command execution in BotAction GUI * feat: refactor GuiNode and related classes to use Item instead of ItemStack * feat: update command building logic to include additional action parameters * feat: simplify command syntax in BotAction GUI and update inventory view handling * feat: add ActionType enum and implement action confirmation in BotAction GUI * feat: enhance ActionType handling and selection in BotAction GUI * feat: integrate GUI data handling in bot actions and enhance navigation * fix up * because of tmp move to new org, rebrand * Implement pagination for bot action GUI and enhance action stop functionality * ci updates * replace boarder item * fix https://github.com/LophineCraft/Lophine/pull/161#issuecomment-5012127501 issues - 1 * fix https://github.com/LophineCraft/Lophine/pull/161#issuecomment-5012127501 issues - 2 * fix index * replace some items * feat: use the same item shown for stop & start action * remove 2 todos, we wouldn't provide them * refactor: simplify rendering methods in BotActionGuiContainer * feat: add empty action placeholder and simplify item rendering * fix: remove extra suggestions * fix: update GUI icons for jump, move, and swap actions * fix: update comment in open action gui * set release mode to true --------- Co-authored-by: xiaoxijun --- .../event/bot/BotActionGuiOpenEvent.java | 46 ++ .../bm/lophine/bot/BotActionGuiContainer.java | 759 ++++++++++++++++++ .../fun/bm/lophine/bot/BotActionGuiMenu.java | 279 +++++++ .../bm/lophine/bot/action/gui/ActionType.java | 115 +++ .../bm/lophine/bot/action/gui/GuiNode.java | 52 ++ .../lophine/bot/action/gui/GuiRootNode.java | 78 ++ .../bm/lophine/bot/action/gui/GuiSubNode.java | 37 + .../modules/function/FakeplayerConfig.java | 5 + .../org/leavesmc/leaves/bot/ServerBot.java | 21 +- .../leavesmc/leaves/bot/agent/Actions.java | 6 + .../bot/agent/actions/AbstractBotAction.java | 8 + .../agent/actions/AbstractTimerBotAction.java | 32 +- .../agent/actions/AbstractUseBotAction.java | 18 +- .../bot/agent/actions/ServerAttackAction.java | 5 +- .../agent/actions/ServerBreakBlockAction.java | 5 +- .../bot/agent/actions/ServerDropAction.java | 5 +- .../bot/agent/actions/ServerFishAction.java | 5 +- .../bot/agent/actions/ServerJumpAction.java | 5 +- .../bot/agent/actions/ServerMountAction.java | 4 + .../bot/agent/actions/ServerMoveAction.java | 7 + .../bot/agent/actions/ServerSneakAction.java | 3 + .../bot/agent/actions/ServerSwapAction.java | 4 + .../bot/agent/actions/ServerSwimAction.java | 4 + .../agent/actions/ServerUseItemAction.java | 2 +- .../actions/ServerUseItemAutoAction.java | 5 +- .../actions/ServerUseItemOffhandAction.java | 2 +- .../agent/actions/ServerUseItemOnAction.java | 2 +- .../actions/ServerUseItemOnOffhandAction.java | 2 +- .../agent/actions/ServerUseItemToAction.java | 2 +- .../actions/ServerUseItemToOffhandAction.java | 2 +- .../bot/subcommands/action/StopCommand.java | 43 + 31 files changed, 1543 insertions(+), 20 deletions(-) create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionGuiOpenEvent.java create mode 100644 lophine-server/src/main/java/fun/bm/lophine/bot/BotActionGuiContainer.java create mode 100644 lophine-server/src/main/java/fun/bm/lophine/bot/BotActionGuiMenu.java create mode 100644 lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/ActionType.java create mode 100644 lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/GuiNode.java create mode 100644 lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/GuiRootNode.java create mode 100644 lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/GuiSubNode.java diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionGuiOpenEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionGuiOpenEvent.java new file mode 100644 index 0000000..bfcfb24 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionGuiOpenEvent.java @@ -0,0 +1,46 @@ +package org.leavesmc.leaves.event.bot; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.entity.bot.Bot; + +public class BotActionGuiOpenEvent extends BotEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private final Player player; + private boolean cancel = false; + + public BotActionGuiOpenEvent(@NotNull Bot who, @Nullable Player player) { + super(who); + this.player = player; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Nullable + public Player getOpenPlayer() { + return player; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlers; + } +} diff --git a/lophine-server/src/main/java/fun/bm/lophine/bot/BotActionGuiContainer.java b/lophine-server/src/main/java/fun/bm/lophine/bot/BotActionGuiContainer.java new file mode 100644 index 0000000..ff590db --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/bot/BotActionGuiContainer.java @@ -0,0 +1,759 @@ +package fun.bm.lophine.bot; + +import fun.bm.lophine.bot.action.gui.ActionType; +import fun.bm.lophine.bot.action.gui.GuiNode; +import fun.bm.lophine.bot.action.gui.GuiRootNode; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.item.component.ItemLore; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction; +import org.leavesmc.leaves.entity.bot.CraftBot; +import org.leavesmc.leaves.entity.bot.action.BotAction; +import org.leavesmc.leaves.entity.bot.actions.CraftBotAction; + +import java.util.*; + +public class BotActionGuiContainer extends SimpleContainer { + private static final Map GUI_ROOT_NODE_MAP = new LinkedHashMap<>(); + + private static final int CONTAINER_SIZE = 54; + + private static final int[] BORDER_SLOTS = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 17, + 18, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53 + }; + + public static final int[] CONTENT_SLOTS = { + 10, 11, 12, 13, 14, 15, 16, + 19, 20, 21, 22, 23, 24, 25 + }; + + private static final int BACK_BUTTON_SLOT = 38; + private static final int COMMAND_BUILDER_SLOT = 40; + private static final int HOME_BUTTON_SLOT = 42; + private static final int PREV_PAGE_SLOT = 39; + private static final int NEXT_PAGE_SLOT = 41; + + private static final ItemStack boarder = new ItemStack(Items.WHITE_STAINED_GLASS_PANE); + private static final ItemStack emptyActionPlaceholder = createEmptyActionPlaceholder(); + + private final CraftBot bot; + private final CraftPlayer player; + private final Deque navigationStack = new ArrayDeque<>(); + private final Deque fromRootLevelStack = new ArrayDeque<>(); // Track if we came from root level + private GuiNode currentNode = null; + + // New state for action type selection flow + private ActionType selectedActionType = null; + private boolean isSelectingActionType = false; + + // Pagination state + private int currentPage = 0; + + public static void registerGuiRootNode(GuiRootNode guiRootNode) { + if (!GUI_ROOT_NODE_MAP.containsKey(guiRootNode.getName())) { + GUI_ROOT_NODE_MAP.put(guiRootNode.getName(), guiRootNode); + } + } + + public static void unregisterGuiRootNode(String name) { + GUI_ROOT_NODE_MAP.remove(name); + } + + public static GuiRootNode getGuiRootNode(String name) { + return GUI_ROOT_NODE_MAP.get(name); + } + + public static Map getGuiRootNodes() { + return new LinkedHashMap<>(GUI_ROOT_NODE_MAP); + } + + public BotActionGuiContainer(@NotNull CraftBot bot, CraftPlayer player) { + super(CONTAINER_SIZE, player); + this.bot = bot; + this.player = player; + this.showActionTypes(); + } + + public boolean isCommandBuilderSlot(int id) { + return id == COMMAND_BUILDER_SLOT; + } + + private void fillBorder() { + for (int slot : BORDER_SLOTS) { + this.setItem(slot, boarder); + } + } + + private void showActionTypes() { + this.clearContent(); + this.fillBorder(); + this.currentNode = null; + this.navigationStack.clear(); + this.fromRootLevelStack.clear(); + this.selectedActionType = null; + this.isSelectingActionType = true; + this.currentPage = 0; + + this.renderActionTypes(); + this.addNavigationButtons(); + } + + private void showRootNodes() { + this.clearContent(); + this.fillBorder(); + this.currentNode = null; + this.navigationStack.clear(); + this.fromRootLevelStack.clear(); + this.isSelectingActionType = false; + this.currentPage = 0; + + List allNodes = new ArrayList<>(GUI_ROOT_NODE_MAP.values()); + if (allNodes.isEmpty()) { + // No root nodes, go back to action type selection + this.showActionTypes(); + return; + } + + this.renderRootNodes(); + this.addNavigationButtons(); + } + + public void navigateToChild(GuiNode node) { + if (node == null) { + return; + } + + // If we're at root level (currentNode is null but not selecting action type), + // we need to track that we came from root nodes level + if (this.currentNode != null) { + this.navigationStack.push(this.currentNode); + this.fromRootLevelStack.push(false); + } else if (!this.isSelectingActionType && this.selectedActionType != null) { + // We're at root nodes level, mark that we should return to root nodes + this.fromRootLevelStack.push(true); + } + + this.currentNode = node; + this.currentPage = 0; + this.refreshContainer(); + } + + public boolean navigateBack() { + // If we're at root node level (no navigation stack), go back to action type selection + if (this.navigationStack.isEmpty() && this.fromRootLevelStack.isEmpty()) { + // If we have a selected action type, we're at the root nodes level + if (!this.isSelectingActionType && this.selectedActionType != null) { + this.showActionTypes(); + return true; + } + // Already at action type selection + return false; + } + + // Check if we should return to root nodes level + boolean fromRootLevel = !this.fromRootLevelStack.isEmpty() && this.fromRootLevelStack.pop(); + + if (fromRootLevel) { + // Return to root nodes level + this.currentNode = null; + this.showRootNodes(); + } else if (!this.navigationStack.isEmpty()) { + // Pop from navigation stack and refresh + this.currentNode = this.navigationStack.pop(); + this.refreshContainer(); + } + return true; + } + + public void navigateHome() { + this.showActionTypes(); + } + + private void refreshContainer() { + this.clearContent(); + this.fillBorder(); + + Set children = this.getChildrenOfCurrentNode(); + if (children == null || children.isEmpty()) { + // Empty page: auto-navigate back + this.navigateBack(); + return; + } + + this.renderCurrentNodeChildren(); + this.addNavigationButtons(); + } + + @Nullable + private Set getChildrenOfCurrentNode() { + if (this.currentNode == null) { + return null; + } + if (this.currentNode instanceof GuiRootNode rootNode) { + return rootNode.getChildren(); + } + return null; + } + + private void renderActionTypes() { + List allTypes = List.of(ActionType.values()); + this.renderPagedItems(allTypes, (actionType, slot) -> { + ItemStack item = actionType.toConfirmNode(null).getItemStack(); + if (item != null && !item.isEmpty()) { + this.setItem(slot, item); + } + }); + } + + private void renderRootNodes() { + List allNodes = new ArrayList<>(GUI_ROOT_NODE_MAP.values()); + this.renderPagedItems(allNodes, (node, slot) -> { + ItemStack item = createNodeItemWithRunHint(node); + if (item != null && !item.isEmpty()) { + this.setItem(slot, item); + } + }); + } + + private void renderCurrentNodeChildren() { + Set children = this.getChildrenOfCurrentNode(); + if (children != null && !children.isEmpty()) { + List childList = new ArrayList<>(children); + this.renderPagedItems(childList, (node, slot) -> { + ItemStack item = createNodeItemWithRunHint(node); + if (item != null && !item.isEmpty()) { + this.setItem(slot, item); + } + }); + } + } + + private void renderBotActions() { + int actionSize = this.bot.getActionSize(); + if (actionSize > 0) { + List actionIndices = new ArrayList<>(); + for (int i = 0; i < actionSize; i++) { + actionIndices.add(i); + } + this.renderPagedItems(actionIndices, (index, slot) -> { + BotAction action = this.bot.getAction(index); + ItemStack item = createActionItem(action, index); + if (!item.isEmpty()) { + this.setItem(slot, item); + } + }); + } else { + // Show empty placeholder when no actions are active + this.setItem(CONTENT_SLOTS[0], emptyActionPlaceholder); + } + } + + private static ItemStack createEmptyActionPlaceholder() { + ItemStack item = new ItemStack(Items.STRUCTURE_VOID); + item.set(DataComponents.CUSTOM_NAME, Component.literal("§cNo active actions")); + item.set(DataComponents.LORE, new ItemLore(List.of( + Component.literal("§cThis bot has no running actions"), + Component.literal("§cUse START to add new actions") + ))); + return item; + } + + @Nullable + public GuiNode getGuiNodeAtSlot(int slot) { + if (slot == BACK_BUTTON_SLOT && this.canNavigateBack()) { + return null; // back button is handled separately + } + + // Check if slot is a border slot + for (int borderSlot : BORDER_SLOTS) { + if (borderSlot == slot) { + return null; + } + } + + Set nodes = this.getCurrentDisplayNodes(); + if (nodes == null) { + return null; + } + + // Find the content index for this slot + int contentIndex = -1; + for (int i = 0; i < CONTENT_SLOTS.length; i++) { + if (CONTENT_SLOTS[i] == slot) { + contentIndex = i; + break; + } + } + + if (contentIndex == -1) { + return null; + } + + // Apply pagination offset + int actualIndex = this.currentPage * CONTENT_SLOTS.length + contentIndex; + int index = 0; + for (GuiNode node : nodes) { + if (index == actualIndex) { + return node; + } + index++; + } + return null; + } + + public boolean isBackButtonSlot(int slot) { + return slot == BACK_BUTTON_SLOT && this.canNavigateBack(); + } + + public boolean isHomeButtonSlot(int slot) { + return slot == HOME_BUTTON_SLOT && this.canNavigateHome(); + } + + private static ItemStack createBackButtonItem() { + ItemStack item = new ItemStack(Items.RED_WOOL); + item.set(DataComponents.CUSTOM_NAME, Component.literal("§cBack")); + item.set(DataComponents.LORE, new ItemLore(List.of(Component.literal("Return to previous page")))); + return item; + } + + private static ItemStack createHomeButtonItem() { + ItemStack item = new ItemStack(Items.RED_BED); + item.set(DataComponents.CUSTOM_NAME, Component.literal("§aHome")); + item.set(DataComponents.LORE, new ItemLore(List.of(Component.literal("Return to main menu")))); + return item; + } + + private ItemStack createNodeItemWithRunHint(GuiNode node) { + ItemStack itemStack = node.getItemStack(); + + // Check if this is a last-level node (no children) + boolean isLastLevel; + if (node instanceof GuiRootNode rootNode) { + isLastLevel = rootNode.getChildren().isEmpty(); + } else { + // Non-RootNode is always considered last level + isLastLevel = true; + } + + // If it's the last level and we have an action type selected, add run hint + if (isLastLevel && this.selectedActionType != null && !this.isSelectingActionType) { + // Get existing lore or create new one + ItemLore existingLore = itemStack.get(DataComponents.LORE); + List loreLines = new ArrayList<>(); + + if (existingLore != null) { + loreLines.addAll(existingLore.lines()); + } + + // Add golden run hint + loreLines.add(Component.literal("§6Click to execute")); + + itemStack.set(DataComponents.LORE, new ItemLore(loreLines)); + } + + return itemStack; + } + + /** + * Render items with pagination support. + * + * @param items the full list of items + * @param renderer callback to render each item at a specific slot + */ + private void renderPagedItems(List items, PagedItemRenderer renderer) { + int pageSize = CONTENT_SLOTS.length; + int totalPages = Math.max(1, (items.size() + pageSize - 1) / pageSize); + + // Clamp current page + if (this.currentPage >= totalPages) { + this.currentPage = totalPages - 1; + } + if (this.currentPage < 0) { + this.currentPage = 0; + } + + int start = this.currentPage * pageSize; + int end = Math.min(start + pageSize, items.size()); + + for (int i = start; i < end; i++) { + int slotIndex = i - start; + renderer.render(items.get(i), CONTENT_SLOTS[slotIndex]); + } + } + + @FunctionalInterface + private interface PagedItemRenderer { + void render(T item, int slot); + } + + /** + * Add navigation buttons including pagination controls. + */ + private void addNavigationButtons() { + if (this.canNavigateBack()) { + this.setItem(BACK_BUTTON_SLOT, createBackButtonItem()); + } + + this.setItem(COMMAND_BUILDER_SLOT, createCommandBuilderItem()); + + if (this.canNavigateHome()) { + this.setItem(HOME_BUTTON_SLOT, createHomeButtonItem()); + } + + // Pagination buttons + if (this.hasPrevPage()) { + this.setItem(PREV_PAGE_SLOT, createPrevPageItem()); + } + if (this.hasNextPage()) { + this.setItem(NEXT_PAGE_SLOT, createNextPageItem()); + } + } + + private boolean hasPrevPage() { + return this.currentPage > 0; + } + + private boolean hasNextPage() { + int totalItems = this.getCurrentTotalItemCount(); + int totalPages = Math.max(1, (totalItems + CONTENT_SLOTS.length - 1) / CONTENT_SLOTS.length); + return this.currentPage < totalPages - 1; + } + + private int getCurrentTotalItemCount() { + if (this.isSelectingActionType) { + return ActionType.values().length; + } + if (this.selectedActionType == ActionType.ACTION_STOP) { + return this.bot.getActionSize(); + } + if (this.currentNode == null) { + return GUI_ROOT_NODE_MAP.size(); + } + Set children = this.getChildrenOfCurrentNode(); + return children != null ? children.size() : 0; + } + + public void prevPage() { + if (this.hasPrevPage()) { + this.currentPage--; + this.redisplayCurrentViewWithoutReset(); + } + } + + public void nextPage() { + if (this.hasNextPage()) { + this.currentPage++; + this.redisplayCurrentViewWithoutReset(); + } + } + + public boolean isPrevPageSlot(int slot) { + return slot == PREV_PAGE_SLOT && this.hasPrevPage(); + } + + public boolean isNextPageSlot(int slot) { + return slot == NEXT_PAGE_SLOT && this.hasNextPage(); + } + + /** + * Re-render the current view without resetting pagination state. + * Used for page navigation to preserve currentPage value. + */ + private void redisplayCurrentViewWithoutReset() { + this.clearContent(); + this.fillBorder(); + + if (this.isSelectingActionType) { + this.renderActionTypes(); + } else if (this.selectedActionType == ActionType.ACTION_STOP) { + this.renderBotActions(); + } else if (this.currentNode != null) { + this.renderCurrentNodeChildren(); + } else { + this.renderRootNodes(); + } + + this.addNavigationButtons(); + } + + private static ItemStack createPrevPageItem() { + ItemStack item = new ItemStack(Items.ARROW); + item.set(DataComponents.CUSTOM_NAME, Component.literal("§ePrevious Page")); + return item; + } + + private static ItemStack createNextPageItem() { + ItemStack item = new ItemStack(Items.ARROW); + item.set(DataComponents.CUSTOM_NAME, Component.literal("§eNext Page")); + return item; + } + + private ItemStack createCommandBuilderItem() { + ItemStack item = new ItemStack(Items.BOOK); + + // Build the complete command preview + String commandPreview = buildCommandPreview(); + + // Check if current node is runnable (confirmable) + boolean canRun = this.currentNode != null && this.currentNode.isConfirmable(); + + List loreLines = new ArrayList<>(); + loreLines.add(Component.literal("§eCommand:")); + loreLines.add(Component.literal("§f" + commandPreview)); + + // Add run hint if the node is confirmable + if (canRun) { + loreLines.add(Component.literal("§6Click to execute")); + } + + item.set(DataComponents.CUSTOM_NAME, Component.literal("§6Command Builder")); + item.set(DataComponents.LORE, new ItemLore(loreLines)); + return item; + } + + private String buildCommandPreview() { + if (this.selectedActionType == null) { + return "Select an action type first"; + } + + // For STOP action type, show different preview + if (this.selectedActionType == ActionType.ACTION_STOP) { + int actionSize = this.bot.getActionSize(); + if (actionSize == 0) { + return "No actions to stop"; + } + return "Click an action to stop it (" + actionSize + " active)"; + } + + try { + String actionPrefix = this.selectedActionType.getCommandActionPrefix(); + String actionSuffix = this.selectedActionType.getCommandActionSuffix(); + if (!actionSuffix.isEmpty()) { + actionSuffix = actionSuffix + " "; + } + + String extra = actionPrefix + " " + this.bot.getName() + " " + actionSuffix; + + // If we have a current node and it's a GuiRootNode, build the command + if (this.currentNode instanceof GuiRootNode rootNode) { + String command = rootNode.buildCommand(extra); + + // Apply parameter limit based on ActionType + if (this.selectedActionType.getMaxAllowedParameters() == 0) { + command = actionPrefix + " " + this.bot.getName(); + } + + return command; + } else if (!this.isSelectingActionType) { + // At root nodes level or selecting action type + return extra.trim() + " "; + } else { + return "Select a command node"; + } + } catch (Exception e) { + return "Error building command"; + } + } + + @Nullable + private Set getCurrentDisplayNodes() { + // If selecting action type, return null (handled separately) + if (this.isSelectingActionType) { + return null; + } + if (this.currentNode == null) { + return new LinkedHashSet<>(GUI_ROOT_NODE_MAP.values()); + } + return this.getChildrenOfCurrentNode(); + } + + /** + * Called when user selects an ActionType (START/STOP) + */ + public void selectActionType(ActionType actionType) { + this.selectedActionType = actionType; + this.isSelectingActionType = false; + + // If STOP action type is selected, show current bot actions instead of root nodes + if (actionType == ActionType.ACTION_STOP) { + this.showCurrentBotActions(); + } else { + this.showRootNodes(); + } + } + + /** + * Show the current bot's scheduled actions for stopping + */ + public void showCurrentBotActions() { + this.clearContent(); + this.fillBorder(); + this.currentNode = null; + this.navigationStack.clear(); + this.fromRootLevelStack.clear(); + this.isSelectingActionType = false; + this.currentPage = 0; + + this.renderBotActions(); + this.addNavigationButtons(); + } + + /** + * Create an ItemStack representing a bot action for display in the GUI + */ + private ItemStack createActionItem(BotAction action, int index) { + // Use the same item model as registered in GuiRootNode (start action display) + AbstractBotAction handle = ((CraftBotAction) action).getHandle(); + GuiRootNode guiData = handle.getGuiData(); + ItemStack item = guiData != null ? guiData.getItemStack() : new ItemStack(Items.PAPER); + String actionName = action.getName(); + String actionHash = action.getUUID().toString(); + + item.set(DataComponents.CUSTOM_NAME, Component.literal("§c" + actionName)); + + // Store action hash in CUSTOM_DATA for later retrieval + CompoundTag customTag = new CompoundTag(); + customTag.putString("action_hash", actionHash); + item.set(DataComponents.CUSTOM_DATA, CustomData.of(customTag)); + + List loreLines = new ArrayList<>(); + CompoundTag nbt = new CompoundTag(); + ((CraftBotAction) action).getHandle().save(nbt); + nbt.forEach((key, tag) -> loreLines.add(Component.literal("§7" + key + ": §f" + tag))); + loreLines.add(Component.literal("§7Index: §f" + index)); + loreLines.add(Component.literal("§7Hash: §f" + actionHash.substring(0, 8))); + loreLines.add(Component.literal("§6Click to stop this action")); + + item.set(DataComponents.LORE, new ItemLore(loreLines)); + return item; + } + + /** + * Get the action hash (UUID string) at a specific slot (for STOP action type) + * Returns null if not found or not in STOP mode + */ + @Nullable + public String getActionHashAtSlot(int slot) { + if (this.selectedActionType != ActionType.ACTION_STOP) { + return null; + } + + // Read the action hash directly from the ItemStack's CUSTOM_DATA + ItemStack item = this.getItem(slot); + if (item.isEmpty()) { + return null; + } + + CustomData customData = item.get(DataComponents.CUSTOM_DATA); + if (customData == null) { + return null; + } + + CompoundTag tag = customData.copyTag(); + if (tag.contains("action_hash")) { + return tag.getString("action_hash").get(); + } + + return null; + } + + /** + * Get the ActionType at a specific slot, accounting for pagination offset. + * Returns null if the slot is not a valid content slot or the index is out of bounds. + */ + @Nullable + public ActionType getActionTypeAtSlot(int slot) { + if (!this.isSelectingActionType) { + return null; + } + + int contentIndex = -1; + for (int i = 0; i < CONTENT_SLOTS.length; i++) { + if (CONTENT_SLOTS[i] == slot) { + contentIndex = i; + break; + } + } + if (contentIndex == -1) { + return null; + } + + ActionType[] actionTypes = ActionType.values(); + int actualIndex = this.currentPage * CONTENT_SLOTS.length + contentIndex; + if (actualIndex >= 0 && actualIndex < actionTypes.length) { + return actionTypes[actualIndex]; + } + return null; + } + + /** + * Get the currently selected ActionType + */ + @Nullable + public ActionType getSelectedActionType() { + return this.selectedActionType; + } + + /** + * Check if we're in action type selection mode + */ + public boolean isSelectingActionType() { + return this.isSelectingActionType; + } + + public boolean canNavigateBack() { + return !this.navigationStack.isEmpty() || (!this.isSelectingActionType && this.selectedActionType != null); + } + + public boolean canNavigateHome() { + return !this.isSelectingActionType && (this.currentNode != null || this.selectedActionType != null); + } + + /** + * Check if the command builder can be executed. + * Returns true if there is a current node and it is confirmable. + */ + public boolean canExecuteCommandBuilder() { + return this.currentNode != null && this.currentNode.isConfirmable(); + } + + @Nullable + public GuiNode getCurrentNode() { + return this.currentNode; + } + + /** + * Get the current parameter count (number of selected command nodes). + * This equals the navigation stack size plus 1 if currentNode is not null. + */ + public int getCurrentParameterCount() { + return this.navigationStack.size() + (this.currentNode != null ? 1 : 0); + } + + public CraftBot getBot() { + return this.bot; + } + + public CraftPlayer getPlayer() { + return this.player; + } + + @Override + public boolean stillValid(@NotNull Player player) { + return this.player.getHandle() == player && this.bot.isValid(); + } +} diff --git a/lophine-server/src/main/java/fun/bm/lophine/bot/BotActionGuiMenu.java b/lophine-server/src/main/java/fun/bm/lophine/bot/BotActionGuiMenu.java new file mode 100644 index 0000000..886c15f --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/bot/BotActionGuiMenu.java @@ -0,0 +1,279 @@ +package fun.bm.lophine.bot; + +import com.mojang.logging.LogUtils; +import fun.bm.lophine.bot.action.gui.ActionType; +import fun.bm.lophine.bot.action.gui.GuiNode; +import fun.bm.lophine.bot.action.gui.GuiRootNode; +import fun.bm.lophine.carpet.config.modules.FakePlayerCompatConfig; +import fun.bm.lophine.config.modules.function.FakeplayerConfig; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerInput; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.craftbukkit.inventory.CraftInventoryView; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.entity.bot.CraftBot; + +import java.rmi.UnexpectedException; + +public class BotActionGuiMenu extends AbstractContainerMenu { + private final BotActionGuiContainer container; + private final CraftBot bot; + private final CraftPlayer player; + private CraftInventoryView view = null; + + public BotActionGuiMenu(int containerId, Inventory inventory, BotActionGuiContainer container) { + super(MenuType.GENERIC_9x6, containerId); + this.container = container; + this.bot = container.getBot(); + this.player = container.getPlayer(); + + for (int row = 0; row < 6; row++) { + for (int col = 0; col < 9; col++) { + this.addSlot(new Slot(container, col + row * 9, 8 + col * 18, 18 + row * 18) { + @Override + public boolean mayPickup(Player player) { + return false; + } + + @Override + public boolean mayPlace(ItemStack stack) { + return false; + } + }); + } + } + + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 9; col++) { + this.addSlot(new Slot(inventory, col + row * 9 + 9, 8 + col * 18, 140 + row * 18)); + } + } + + for (int col = 0; col < 9; col++) { + this.addSlot(new Slot(inventory, col, 8 + col * 18, 198)); + } + } + + @Override + public org.bukkit.inventory.InventoryView getBukkitView() { + if (this.view == null) { + CraftInventory inventory = new CraftInventory(this.container); + this.view = new CraftInventoryView( + this.player, + inventory, + this + ); + } + return this.view; + } + + @Override + public boolean stillValid(@NotNull Player player) { + return this.container.stillValid(player); + } + + @Override + public void clicked(int slotIndex, int buttonNum, ContainerInput containerInput, Player player) { + if (slotIndex >= 0 && slotIndex < 54) { + if (this.container.isBackButtonSlot(slotIndex)) { + this.container.navigateBack(); + this.refreshSlots(); + return; + } + + if (this.container.isHomeButtonSlot(slotIndex)) { + this.container.navigateHome(); + this.refreshSlots(); + return; + } + + // Handle pagination buttons + if (this.container.isPrevPageSlot(slotIndex)) { + this.container.prevPage(); + this.refreshSlots(); + return; + } + if (this.container.isNextPageSlot(slotIndex)) { + this.container.nextPage(); + this.refreshSlots(); + return; + } + + // Handle command builder click + if (this.container.isCommandBuilderSlot(slotIndex) && this.container.canExecuteCommandBuilder()) { + this.executeCommandBuilder(player); + this.refreshSlots(); + return; + } + + // Handle ActionType selection (with pagination offset) + if (this.container.isSelectingActionType()) { + ActionType clickedType = this.container.getActionTypeAtSlot(slotIndex); + if (clickedType != null) { + this.container.selectActionType(clickedType); + this.refreshSlots(); + } + return; + } + + // Handle action stop click (when STOP action type is selected) + if (this.container.getSelectedActionType() == ActionType.ACTION_STOP) { + String actionHash = this.container.getActionHashAtSlot(slotIndex); + if (actionHash != null) { + this.stopActionByHash(actionHash, player); + this.refreshSlots(); + } + return; + } + + GuiNode node = this.container.getGuiNodeAtSlot(slotIndex); + if (node != null) { + this.handleNodeClick(node, player); + this.refreshSlots(); + } + return; + } + + super.clicked(slotIndex, buttonNum, containerInput, player); + } + + private void handleNodeClick(GuiNode node, Player player) { + if (node instanceof GuiRootNode rootNode) { + // Get the selected action type + ActionType actionType = this.container.getSelectedActionType(); + if (actionType == null) { + return; + } + + int maxAllowedParameters = actionType.getMaxAllowedParameters(); + + // If the node has children, check parameter limit before navigating + if (!rootNode.getChildren().isEmpty()) { + // Calculate current parameter count (navigation stack size + 1 for current node if exists) + int currentParamCount = this.container.getCurrentParameterCount(); + + // If adding this node would exceed the limit, execute command instead of navigating + if (currentParamCount + 1 > maxAllowedParameters) { + this.executeCommand(rootNode, actionType, player); + // Close GUI after START action execution + if (actionType == ActionType.ACTION_START && player instanceof ServerPlayer serverPlayer) { + serverPlayer.closeContainer(); + } + } else { + this.container.navigateToChild(rootNode); + } + } else { + // Execute command with t`he selected action type + this.executeCommand(rootNode, actionType, player); + // Close GUI after START action execution + if (actionType == ActionType.ACTION_START && player instanceof ServerPlayer serverPlayer) { + serverPlayer.closeContainer(); + } + } + } + } + + private void executeCommand(GuiRootNode node, ActionType actionType, Player player) { + try { + String actionPrefix = actionType.getCommandActionPrefix(); + String actionSuffix = actionType.getCommandActionSuffix(); + if (!actionSuffix.isEmpty()) { + actionSuffix = actionSuffix + " "; + } + + String extra = actionPrefix + " " + bot.getName() + " " + actionSuffix; + String command = node.buildCommand(extra); + + // Apply parameter limit based on ActionType + if (actionType.getMaxAllowedParameters() == 0) { + // For actions like STOP that don't allow parameters, trim to just "action botName" + command = actionPrefix + " " + bot.getName(); + } + + if (player instanceof ServerPlayer serverPlayer) { + MinecraftServer.getServer().getCommands().performPrefixedCommand( + serverPlayer.createCommandSourceStack(), + command + ); + } + } catch (Exception e) { + LogUtils.getLogger().warn("Error executing command: ", e); + } + } + + /** + * Execute the command from the command builder (book item). + * Uses the current node and selected action type. + */ + private void executeCommandBuilder(Player player) { + GuiNode currentNode = this.container.getCurrentNode(); + ActionType actionType = this.container.getSelectedActionType(); + + if (currentNode instanceof GuiRootNode rootNode && actionType != null) { + this.executeCommand(rootNode, actionType, player); + // Close GUI after START action execution + if (actionType != ActionType.ACTION_STOP && player instanceof ServerPlayer serverPlayer) { + serverPlayer.closeContainer(); + } + } + } + + /** + * Stop a bot action by its hash (UUID string) + */ + private void stopActionByHash(String actionHash, Player player) { + try { + if (player instanceof ServerPlayer serverPlayer) { + String command = getStopActionCommand(actionHash); + MinecraftServer.getServer().getCommands().performPrefixedCommand( + serverPlayer.createCommandSourceStack(), + command + ); + } + } catch (Exception e) { + LogUtils.getLogger().warn("Error stopping action with hash {}: ", actionHash, e); + } + // Always refresh to show updated action list after stop attempt + this.container.showCurrentBotActions(); + } + + private String getStopActionCommand(String actionHash) throws UnexpectedException { + boolean botCommand = FakeplayerConfig.enable; + boolean playerCommand = FakePlayerCompatConfig.commandPlayer; + String command; + if (botCommand) { + command = "bot "; + } else if (playerCommand) { + command = "player "; + } else { + throw new UnexpectedException("Unable to build String from commandNode."); + } + command = command + "action " + this.bot.getName() + " stop " + actionHash; + return command; + } + + private void refreshSlots() { + for (int i = 0; i < 54; i++) { + Slot slot = this.slots.get(i); + ItemStack item = this.container.getItem(i); + slot.set(item); + } + } + + @Override + public ItemStack quickMoveStack(Player player, int index) { + return ItemStack.EMPTY; + } + + public BotActionGuiContainer getContainer() { + return this.container; + } +} diff --git a/lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/ActionType.java b/lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/ActionType.java new file mode 100644 index 0000000..085e69d --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/ActionType.java @@ -0,0 +1,115 @@ +package fun.bm.lophine.bot.action.gui; + +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.component.ItemLore; + +import java.util.ArrayList; +import java.util.List; + +public enum ActionType { + ACTION_START("Start", "Start a action", Items.LIME_DYE, "action", "start"), + ACTION_STOP("Stop", "Stop a action or see this bot scheduled tasks", Items.RED_DYE, "action", "stop", 0); + + private final String displayName; + private final String description; + private final Item item; + private final String commandActionPrefix; + private final String commandActionSuffix; + private final int maxAllowedParameters; + + ActionType(String displayName, String description, Item item, String commandActionPrefix) { + this(displayName, description, item, commandActionPrefix, "", Integer.MAX_VALUE); + } + + ActionType(String displayName, String description, Item item, String commandActionPrefix, String commandActionSuffix) { + this(displayName, description, item, commandActionPrefix, commandActionSuffix, Integer.MAX_VALUE); + } + + ActionType(String displayName, String description, Item item, String commandActionPrefix, String commandActionSuffix, int maxAllowedParameters) { + this.displayName = displayName; + this.description = description; + this.item = item; + this.commandActionPrefix = commandActionPrefix; + this.commandActionSuffix = commandActionSuffix; + this.maxAllowedParameters = maxAllowedParameters; + + } + + public String getDisplayName() { + return this.displayName; + } + + public String getDescription() { + return this.description; + } + + public Item getItem() { + return this.item; + } + + public String getCommandActionPrefix() { + return this.commandActionPrefix; + } + + public String getCommandActionSuffix() { + return commandActionSuffix; + } + + public GuiNode toConfirmNode(GuiRootNode targetNode) { + return new ActionConfirmNode(this.displayName, this.description, this.item, targetNode, this.commandActionPrefix, this.maxAllowedParameters); + } + + public int getMaxAllowedParameters() { + return maxAllowedParameters; + } + + /** + * A special node used in the action type confirmation UI. + * It holds a reference to the target GuiRootNode and the selected action string. + */ + public static class ActionConfirmNode extends GuiNode { + private final GuiRootNode targetNode; + private final String action; + private final int maxAllowedParameters; + + public ActionConfirmNode(String name, String description, Item item, GuiRootNode targetNode, String action, int maxAllowedParameters) { + super(name, description, item); + this.targetNode = targetNode; + this.action = action; + this.maxAllowedParameters = maxAllowedParameters; + } + + public GuiRootNode getTargetNode() { + return this.targetNode; + } + + public String getAction() { + return this.action; + } + + public int getMaxAllowedParameters() { + return this.maxAllowedParameters; + } + + @Override + public ItemStack getItemStack() { + ItemStack itemStack = super.getItemStack(); + + // Add parameter limit info to lore + List loreLines = new ArrayList<>(); + if (this.description != null && !this.description.isEmpty()) { + loreLines.add(Component.literal(this.description)); + } + + if (!loreLines.isEmpty()) { + itemStack.set(DataComponents.LORE, new ItemLore(loreLines)); + } + + return itemStack; + } + } +} diff --git a/lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/GuiNode.java b/lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/GuiNode.java new file mode 100644 index 0000000..a77a6fe --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/GuiNode.java @@ -0,0 +1,52 @@ +package fun.bm.lophine.bot.action.gui; + +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.component.ItemLore; + +import java.util.List; + +public class GuiNode { + protected final String name; + protected final String description; + protected final Item item; + protected final boolean confirmable; + + private static final Item defaultItem = Items.PAPER; + + public GuiNode(String name, String description, Item item, boolean confirmable) { + this.name = name; + this.description = description; + this.item = item; + this.confirmable = confirmable; + } + + public GuiNode(String name, String description, Item item) { + this(name, description, item, true); + } + + public String getName() { + return this.name; + } + + public String getDescription() { + return this.description; + } + + public boolean isConfirmable() { + return this.confirmable; + } + + public ItemStack getItemStack() { + ItemStack itemStack = new ItemStack(this.item == null ? defaultItem : this.item).copy(); + itemStack.set(DataComponents.CUSTOM_NAME, Component.literal(this.name)); + if (this.description != null && !this.description.isEmpty()) { + ItemLore lore = new ItemLore(List.of(Component.literal(this.description))); + itemStack.set(DataComponents.LORE, lore); + } + return itemStack; + } +} diff --git a/lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/GuiRootNode.java b/lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/GuiRootNode.java new file mode 100644 index 0000000..d97fd04 --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/GuiRootNode.java @@ -0,0 +1,78 @@ +package fun.bm.lophine.bot.action.gui; + +import fun.bm.lophine.carpet.config.modules.FakePlayerCompatConfig; +import fun.bm.lophine.config.modules.function.FakeplayerConfig; +import net.minecraft.world.item.Item; + +import java.rmi.UnexpectedException; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public class GuiRootNode extends GuiNode { + protected final Set children; + protected final String commandNode; + + public GuiRootNode(String name, String description, Item item, String commandNode) { + this(name, description, item, commandNode, true); + } + + public GuiRootNode(String name, String description, Item item, String commandNode, boolean confirmable) { + this(name, description, item, new LinkedHashSet<>(), commandNode, confirmable); + } + + public GuiRootNode(String name, String description, Item item, Set children, String commandNode) { + this(name, description, item, children, commandNode, true); + } + + public GuiRootNode(String name, String description, Item item, Set children, String commandNode, boolean confirmable) { + super(name, description, item, confirmable); + this.children = children; + this.commandNode = commandNode; + } + + public final void child(GuiNode... node) { + this.children.addAll(List.of(node)); + } + + @SafeVarargs + public final void child(Supplier... node) { + this.children.addAll(Stream.of(node).map(Supplier::get).toList()); + } + + public Set getChildren() { + return this.children; + } + + public Set getAllFurthestChildren() { + if (this.children.isEmpty()) { + Set result = new LinkedHashSet<>(); + result.add(this); + return result; + } + + Set furthestChildren = new LinkedHashSet<>(); + for (GuiNode child : this.children) { + furthestChildren.addAll(((GuiRootNode) child).getAllFurthestChildren()); + } + return furthestChildren; + } + + public String getCommandNode() { + return this.commandNode; + } + + public String buildCommand(String extra) throws UnexpectedException { + boolean botCommand = FakeplayerConfig.enable; + boolean playerCommand = FakePlayerCompatConfig.commandPlayer; + if (botCommand) { + return "bot " + extra + this.getCommandNode(); + } + if (playerCommand) { + return "player " + extra + this.getCommandNode(); + } + throw new UnexpectedException("Unable to build String from commandNode."); + } +} diff --git a/lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/GuiSubNode.java b/lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/GuiSubNode.java new file mode 100644 index 0000000..b53859c --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/bot/action/gui/GuiSubNode.java @@ -0,0 +1,37 @@ +package fun.bm.lophine.bot.action.gui; + +import net.minecraft.world.item.Item; + +import java.rmi.UnexpectedException; +import java.util.LinkedHashSet; +import java.util.Set; + +public class GuiSubNode extends GuiRootNode { + protected final GuiNode parent; + + public GuiSubNode(String name, String description, Item item, GuiNode parent, Set children, String commandNode) { + this(name, description, item, parent, children, commandNode, true); + } + + public GuiSubNode(String name, String description, Item item, GuiNode parent, Set children, String commandNode, boolean confirmable) { + super(name, description, item, children, commandNode, confirmable); + this.parent = parent; + } + + public GuiSubNode(String name, String description, Item item, GuiNode parent, String commandNode) { + this(name, description, item, parent, commandNode, true); + } + + public GuiSubNode(String name, String description, Item item, GuiNode parent, String commandNode, boolean confirmable) { + this(name, description, item, parent, new LinkedHashSet<>(), commandNode, confirmable); + } + + public GuiNode getParent() { + return this.parent; + } + + @Override + public String buildCommand(String extra) throws UnexpectedException { + return ((GuiRootNode) parent).buildCommand(extra) + " " + getCommandNode(); + } +} diff --git a/lophine-server/src/main/java/fun/bm/lophine/config/modules/function/FakeplayerConfig.java b/lophine-server/src/main/java/fun/bm/lophine/config/modules/function/FakeplayerConfig.java index 251a1c4..d4ed728 100644 --- a/lophine-server/src/main/java/fun/bm/lophine/config/modules/function/FakeplayerConfig.java +++ b/lophine-server/src/main/java/fun/bm/lophine/config/modules/function/FakeplayerConfig.java @@ -40,6 +40,11 @@ public class FakeplayerConfig implements IConfigModule { Regeneration amount for fakeplayers""") public static double regenAmount = 0.0; + @ConfigInfo(name = "open-action-gui", comments = """ + Allow opening fakeplayer action gui, + need sneak to open if you enabled inventory open gui""") + public static boolean canOpenActionGui = false; + @ConfigInfo(name = "use-action", comments = """ Allow fakeplayers to use actions""") public static boolean canUseAction = true; diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java index 12aaa72..6c92ac8 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java @@ -20,6 +20,8 @@ package org.leavesmc.leaves.bot; import com.google.common.collect.ImmutableMap; import com.mojang.authlib.GameProfile; import fun.bm.lophine.LophineLogger; +import fun.bm.lophine.bot.BotActionGuiContainer; +import fun.bm.lophine.bot.BotActionGuiMenu; import fun.bm.lophine.carpet.config.modules.FakePlayerCompatConfig; import fun.bm.lophine.config.modules.function.FakeplayerConfig; import io.papermc.paper.adventure.PaperAdventure; @@ -357,15 +359,28 @@ public class ServerBot extends ServerPlayer { } @Override - public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand, @NotNull net.minecraft.world.phys.Vec3 location) { // Leaves - Paper 26.1: Entity#interact now takes Vec3 - if (FakePlayerCompatConfig.openFakePlayerInventory) { - if (player instanceof ServerPlayer player1 && player.getMainHandItem().isEmpty()) { + public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand, @NotNull net.minecraft.world.phys.Vec3 location) { + if (player instanceof ServerPlayer player1 && player.getMainHandItem().isEmpty()) { + boolean isSneaking = player.isShiftKeyDown(); + boolean enabled1Only = FakePlayerCompatConfig.openFakePlayerInventory ^ FakeplayerConfig.canOpenActionGui; + boolean openInventory = enabled1Only ? FakePlayerCompatConfig.openFakePlayerInventory : FakePlayerCompatConfig.openFakePlayerInventory && !isSneaking; + boolean openActionGui = enabled1Only ? FakeplayerConfig.canOpenActionGui : FakeplayerConfig.canOpenActionGui && isSneaking; + + if (openInventory) { BotInventoryOpenEvent event = new BotInventoryOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity()); getServer().server.getPluginManager().callEvent(event); if (!event.isCancelled()) { player.openMenu(new SimpleMenuProvider((i, inventory, p) -> ChestMenu.sixRows(i, inventory, this.container), this.getDisplayName())); return InteractionResult.SUCCESS; } + } else if (openActionGui) { + BotActionGuiOpenEvent event = new BotActionGuiOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity()); + getServer().server.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + BotActionGuiContainer container = new BotActionGuiContainer(this.getBukkitEntity(), player1.getBukkitEntity()); + player.openMenu(new SimpleMenuProvider((i, inventory, p) -> new BotActionGuiMenu(i, inventory, container), this.getDisplayName())); + return InteractionResult.SUCCESS; + } } } return super.interact(player, hand, location); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java index 6a89c59..8aa51a6 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java @@ -17,6 +17,7 @@ package org.leavesmc.leaves.bot.agent; +import fun.bm.lophine.bot.BotActionGuiContainer; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -59,6 +60,9 @@ public class Actions { if (!actionsByName.containsKey(action.getName())) { actionsByName.put(action.getName(), action); actionsByClass.put(type, action); + if (action.getGuiData() != null) { + BotActionGuiContainer.registerGuiRootNode(action.getGuiData()); + } return true; } return false; @@ -70,6 +74,8 @@ public class Actions { public static boolean unregister(@NotNull String name) { AbstractBotAction action = actionsByName.remove(name); + BotActionGuiContainer.unregisterGuiRootNode(name); + if (action != null) { actionsByClass.remove(action.getClass()); return true; diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractBotAction.java index b7e0912..5adef1b 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractBotAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractBotAction.java @@ -20,10 +20,12 @@ package org.leavesmc.leaves.bot.agent.actions; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.exceptions.CommandSyntaxException; import fun.bm.lophine.LophineLogger; +import fun.bm.lophine.bot.action.gui.GuiRootNode; import net.minecraft.core.UUIDUtil; import net.minecraft.nbt.CompoundTag; import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.bot.agent.ExtraData; import org.leavesmc.leaves.command.CommandContext; @@ -57,6 +59,8 @@ public abstract class AbstractBotAction> { private Consumer onSuccess; private Consumer onStop; + protected GuiRootNode guiData; + public AbstractBotAction(String name, Supplier creator) { this.name = name; this.uuid = UUID.randomUUID(); @@ -261,4 +265,8 @@ public abstract class AbstractBotAction> { public void setOnStop(Consumer onStop) { this.onStop = onStop; } + + public @Nullable GuiRootNode getGuiData() { + return this.guiData; + } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerBotAction.java index 6d30aeb..3512ed2 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerBotAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerBotAction.java @@ -17,6 +17,8 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiRootNode; +import fun.bm.lophine.bot.action.gui.GuiSubNode; import net.minecraft.network.chat.Component; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.agent.ExtraData; @@ -29,13 +31,37 @@ import static org.leavesmc.leaves.command.ArgumentNode.ArgumentSuggestions.strin public abstract class AbstractTimerBotAction> extends AbstractBotAction { - public AbstractTimerBotAction(String name, Supplier creator) { + public AbstractTimerBotAction(String name, Supplier creator, GuiRootNode guiData) { super(name, creator); - this.addArgument("delay", integer(0)).suggests(strings("0", "5", "10", "20")).setOptional(true); - this.addArgument("interval", integer(0)).suggests(strings("20", "0", "5", "10")).setOptional(true); + String[] delaySuggestions = {"0", "5", "10", "20"}; + String[] intervalSuggestions = {"20", "0", "5", "10"}; + this.addArgument("delay", integer(0)).suggests(strings(delaySuggestions)).setOptional(true); + this.addArgument("interval", integer(0)).suggests(strings(intervalSuggestions)).setOptional(true); this.addArgument("do_number", integer(-1)) .suggests(((context, builder) -> builder.suggest("-1", Component.literal("do infinite times")))) .setOptional(true); + + if (guiData == null) return; + + this.guiData = guiData; + GuiSubNode[] node1 = new GuiSubNode[delaySuggestions.length]; + for (int i = 0; i < delaySuggestions.length; i++) { + node1[i] = new GuiSubNode(delaySuggestions[i], "Delay for a few ticks", null, guiData, delaySuggestions[i]); + guiData.child(node1[i]); + } + GuiSubNode[] node2 = new GuiSubNode[delaySuggestions.length * intervalSuggestions.length]; + for (int i = 0; i < delaySuggestions.length; i++) { + for (int j = 0; j < intervalSuggestions.length; j++) { + int id = i * intervalSuggestions.length + j; + node2[id] = new GuiSubNode(intervalSuggestions[j], "Interval for a few ticks", null, node1[i], intervalSuggestions[j]); + node1[i].child(node2[id]); + } + } + + for (GuiSubNode node : node2) { + GuiSubNode subNode = new GuiSubNode("-1", "Number of times to do", null, node, "-1"); + node.child(subNode); + } } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractUseBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractUseBotAction.java index 00996cd..1b4b752 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractUseBotAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractUseBotAction.java @@ -17,6 +17,9 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiNode; +import fun.bm.lophine.bot.action.gui.GuiRootNode; +import fun.bm.lophine.bot.action.gui.GuiSubNode; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.world.InteractionResult; @@ -35,8 +38,8 @@ public abstract class AbstractUseBotAction> ex private int alreadyUsedTick = 0; private int useItemRemainingTicks = 0; - public AbstractUseBotAction(String name, Supplier supplier) { - super(name, supplier); + public AbstractUseBotAction(String name, Supplier supplier, GuiRootNode guiData) { + super(name, supplier, guiData); this.addArgument("use_timeout", integer(-1)) .suggests((context, builder) -> { builder.suggest("-1", Component.literal("no use timeout")); @@ -44,6 +47,17 @@ public abstract class AbstractUseBotAction> ex builder.suggest("10", Component.literal("minimum trident shoot time")); }) .setOptional(true); + + if (guiData == null) return; + + for (GuiNode node : guiData.getAllFurthestChildren()) { + GuiSubNode node0 = (GuiSubNode) node; + node0.child( + new GuiSubNode("-1", "no use timeout", null, node0, "-1"), + new GuiSubNode("3", "minimum bow shoot time", null, node0, "3"), + new GuiSubNode("10", "minimum trident shoot time", null, node0, "10") + ); + } } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerAttackAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerAttackAction.java index ffa24b8..1c83fcf 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerAttackAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerAttackAction.java @@ -17,6 +17,8 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiRootNode; +import net.minecraft.world.item.Items; import net.minecraft.world.phys.EntityHitResult; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; @@ -25,7 +27,8 @@ import org.leavesmc.leaves.entity.bot.actions.CraftAttackAction; public class ServerAttackAction extends AbstractTimerBotAction { public ServerAttackAction() { - super("attack", ServerAttackAction::new); + GuiRootNode guiData = new GuiRootNode("Attack", "Attack an entity", Items.DIAMOND_SWORD, "attack"); + super("attack", ServerAttackAction::new, guiData); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBreakBlockAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBreakBlockAction.java index b37c2c5..9dd5b22 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBreakBlockAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBreakBlockAction.java @@ -17,10 +17,12 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiRootNode; import net.minecraft.core.BlockPos; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; @@ -33,7 +35,8 @@ import org.leavesmc.leaves.entity.bot.actions.CraftBreakBlockAction; public class ServerBreakBlockAction extends AbstractTimerBotAction { public ServerBreakBlockAction() { - super("break", ServerBreakBlockAction::new); + GuiRootNode guiRootNode = new GuiRootNode("Break", "Break a block", Items.DIAMOND_PICKAXE, "break"); + super("break", ServerBreakBlockAction::new, guiRootNode); } private ItemStack lastItem = null; diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerDropAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerDropAction.java index 6a22074..e297769 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerDropAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerDropAction.java @@ -17,6 +17,8 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiRootNode; +import net.minecraft.world.item.Items; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftDropAction; @@ -24,7 +26,8 @@ import org.leavesmc.leaves.entity.bot.actions.CraftDropAction; public class ServerDropAction extends AbstractTimerBotAction { public ServerDropAction() { - super("drop", ServerDropAction::new); + GuiRootNode guiRootNode = new GuiRootNode("Drop", "Drop all items", Items.BARRIER, "drop"); + super("drop", ServerDropAction::new, guiRootNode); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerFishAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerFishAction.java index 6f0464d..fa1a47e 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerFishAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerFishAction.java @@ -17,11 +17,13 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiRootNode; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.projectile.FishingHook; import net.minecraft.world.item.FishingRodItem; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftFishAction; @@ -29,7 +31,8 @@ import org.leavesmc.leaves.entity.bot.actions.CraftFishAction; public class ServerFishAction extends AbstractTimerBotAction { public ServerFishAction() { - super("fish", ServerFishAction::new); + GuiRootNode guiRootNode = new GuiRootNode("Fish", "Fish", Items.FISHING_ROD, "fish"); + super("fish", ServerFishAction::new, guiRootNode); } private static final int CATCH_ENTITY_DELAY = 20; diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerJumpAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerJumpAction.java index 6ac04d8..7bc5798 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerJumpAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerJumpAction.java @@ -17,6 +17,8 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiRootNode; +import net.minecraft.world.item.Items; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftJumpAction; @@ -24,7 +26,8 @@ import org.leavesmc.leaves.entity.bot.actions.CraftJumpAction; public class ServerJumpAction extends AbstractTimerBotAction { public ServerJumpAction() { - super("jump", ServerJumpAction::new); + GuiRootNode guiRootNode = new GuiRootNode("Jump", "Jump", Items.ELYTRA, "jump"); + super("jump", ServerJumpAction::new, guiRootNode); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMountAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMountAction.java index 058704d..5ceefae 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMountAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMountAction.java @@ -17,6 +17,8 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiRootNode; +import net.minecraft.world.item.Items; import org.bukkit.Location; import org.bukkit.craftbukkit.entity.CraftVehicle; import org.bukkit.entity.Vehicle; @@ -31,6 +33,8 @@ public class ServerMountAction extends AbstractBotAction { public ServerMountAction() { super("mount", ServerMountAction::new); + + this.guiData = new GuiRootNode("Mount", "Mount", Items.SADDLE, "mount"); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMoveAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMoveAction.java index 8775a92..3daee30 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMoveAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMoveAction.java @@ -17,6 +17,8 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiRootNode; +import net.minecraft.world.item.Items; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.bot.agent.ExtraData; @@ -32,6 +34,11 @@ public class ServerMoveAction extends AbstractStateBotAction { public ServerMoveAction() { super("move", ServerMoveAction::new); this.addArgument("direction", EnumArgumentType.fromEnum(MoveDirection.class)); + + this.guiData = new GuiRootNode("Move", "Move", null, "move", false); + for (MoveDirection direction : MoveDirection.values()) { + this.guiData.child(new GuiRootNode(direction.name, direction.name, Items.LEATHER_BOOTS, "move " + direction.name)); + } } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSneakAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSneakAction.java index fbe007e..26423c9 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSneakAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSneakAction.java @@ -17,6 +17,7 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiRootNode; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftSneakAction; @@ -26,6 +27,8 @@ public class ServerSneakAction extends AbstractStateBotAction public ServerSneakAction() { super("sneak", ServerSneakAction::new); + + this.guiData = new GuiRootNode("Sneak", "Sneak", null, "sneak"); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwapAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwapAction.java index b7aafff..4730ccd 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwapAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwapAction.java @@ -17,8 +17,10 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiRootNode; import net.minecraft.world.InteractionHand; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftSwapAction; @@ -27,6 +29,8 @@ public class ServerSwapAction extends AbstractBotAction { public ServerSwapAction() { super("swap", ServerSwapAction::new); + + this.guiData = new GuiRootNode("Swap", "Swap", Items.SHIELD, "swap"); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwimAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwimAction.java index eb9aad8..9bbee7f 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwimAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwimAction.java @@ -17,6 +17,8 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiRootNode; +import net.minecraft.world.item.Items; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; @@ -26,6 +28,8 @@ public class ServerSwimAction extends AbstractStateBotAction { public ServerSwimAction() { super("swim", ServerSwimAction::new); + + this.guiData = new GuiRootNode("Swim", "Swim", Items.WATER_BUCKET, "swim"); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAction.java index 6d03d6c..66799f0 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAction.java @@ -26,7 +26,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemAction; public class ServerUseItemAction extends AbstractUseBotAction { public ServerUseItemAction() { - super("use", ServerUseItemAction::new); + super("use", ServerUseItemAction::new, null); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAutoAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAutoAction.java index b2ed2cc..5e9f572 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAutoAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAutoAction.java @@ -17,10 +17,12 @@ package org.leavesmc.leaves.bot.agent.actions; +import fun.bm.lophine.bot.action.gui.GuiRootNode; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.HitResult; @@ -37,7 +39,8 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemToAction.useIte public class ServerUseItemAutoAction extends AbstractUseBotAction { public ServerUseItemAutoAction() { - super("use_auto", ServerUseItemAutoAction::new); + GuiRootNode guiRootNode = new GuiRootNode("Use", "Use Item", Items.BOW, "use_auto"); + super("use_auto", ServerUseItemAutoAction::new, guiRootNode); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOffhandAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOffhandAction.java index 20d496f..0bdb97d 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOffhandAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOffhandAction.java @@ -27,7 +27,7 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemAction.useItem; public class ServerUseItemOffhandAction extends AbstractUseBotAction { public ServerUseItemOffhandAction() { - super("use_offhand", ServerUseItemOffhandAction::new); + super("use_offhand", ServerUseItemOffhandAction::new, null); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnAction.java index bf0ccf0..fe51c54 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnAction.java @@ -28,7 +28,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemOnAction; public class ServerUseItemOnAction extends AbstractUseBotAction { public ServerUseItemOnAction() { - super("use_on", ServerUseItemOnAction::new); + super("use_on", ServerUseItemOnAction::new, null); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnOffhandAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnOffhandAction.java index ce9924b..4a0b811 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnOffhandAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnOffhandAction.java @@ -28,7 +28,7 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemOnAction.useIte public class ServerUseItemOnOffhandAction extends AbstractUseBotAction { public ServerUseItemOnOffhandAction() { - super("use_on_offhand", ServerUseItemOnOffhandAction::new); + super("use_on_offhand", ServerUseItemOnOffhandAction::new, null); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToAction.java index 263207f..d118c43 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToAction.java @@ -32,7 +32,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemToAction; public class ServerUseItemToAction extends AbstractUseBotAction { public ServerUseItemToAction() { - super("use_to", ServerUseItemToAction::new); + super("use_to", ServerUseItemToAction::new, null); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToOffhandAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToOffhandAction.java index c4bedf1..63a2c9f 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToOffhandAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToOffhandAction.java @@ -28,7 +28,7 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemToAction.useIte public class ServerUseItemToOffhandAction extends AbstractUseBotAction { public ServerUseItemToOffhandAction() { - super("use_to_offhand", ServerUseItemToOffhandAction::new); + super("use_to_offhand", ServerUseItemToOffhandAction::new, null); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StopCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StopCommand.java index c236a0f..5f92706 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StopCommand.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StopCommand.java @@ -18,6 +18,7 @@ package org.leavesmc.leaves.command.bot.subcommands.action; import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; @@ -49,6 +50,7 @@ public class StopCommand extends LiteralNode { public StopCommand() { super("stop"); children(StopIndexArgument::new); + children(StopHashArgument::new); children(StopAll::new); } @@ -105,6 +107,47 @@ public class StopCommand extends LiteralNode { } } + private static class StopHashArgument extends ArgumentNode { + + private StopHashArgument() { + super("hash", StringArgumentType.string()); + } + + @Override + protected boolean execute(CommandContext context) throws CommandSyntaxException { + ServerBot bot = ActionCommand.BotArgument.getBot(context); + CommandSender sender = context.getSender(); + + String hash = context.getArgument(StopHashArgument.class); + + AbstractBotAction action = null; + for (AbstractBotAction action1 : bot.getBotActions()) { + if (action1.getUUID().toString().equals(hash)) { + action = action1; + break; + } + } + if (action == null) throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); + BotActionStopEvent event = new BotActionStopEvent( + bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender + ); + event.callEvent(); + if (!event.isCancelled()) { + action.stop(bot, BotActionStopEvent.Reason.COMMAND); + bot.getBotActions().remove(action); + sender.sendMessage(join(spaces(), + text("Already stopped", GRAY), + asAdventure(bot.getDisplayName()).append(text("'s", GRAY)), + text("action", GRAY), + text(action.getName(), AQUA).hoverEvent(showText(text(action.getActionDataString()))) + )); + } else { + sender.sendMessage(text("Action stop cancelled by a plugin", RED)); + } + return true; + } + } + private static class StopAll extends LiteralNode { private StopAll() {