diff --git a/README.md b/README.md index 5569c60..29b2a62 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ A full suite of Armor Stand tools for CraftBukkit/Spigot Spigot resource page with plugin download: http://www.spigotmc.org/resources/armor-stand-tools.2237/ -Inspiration ------------ +About +----- I wanted to create an armor stand for each kit in my mini-game, and I quickly became frustrated with trying to use commands and numeric values to position the legs, arms, body and head of each armor stand, so I created this plugin which allows you to do all of this with ease. Among other features you can create any pose you wish just by holding right click on the tools and moving your cursor up and down on the armor stand. The plugin can also generate a summon command that will re-create the armor stand at any time. Compatibility @@ -17,61 +17,66 @@ Features -------- - Summon armor stands. - Name armor stands. -- Toggle: Gravity, Visibility, Arms, Base, Size, Invulnerability, Equipment Lock. +- Toggle: Gravity, Visibility, Arms, Base, Size, Invulnerability, Equipment Lock, Glow. - Manipulate the x/y/z rotations of the Head, Body, Arms and Legs. The value depends on how high up the armor stand's body you click with the tool (i.e. click near the feet is one extreme, near the top of the head is the other extreme). - Full control over armor stand's inventory (armor & items in hands). - Pick up and move armor stands. - Armor stand cloning tool. -- Save tool: Automatically generate a command block with the command to summon that armor stand in its current state. +- Save tool: Automatically generate a summon command to summon the armor stand in its current state. This can be saved to a command block or logged. +- Pick up as item: Convert an armor stand into an inventory item that when placed like a normal armor stand retains its inventory, pose and settings. - Player head tool: Give an armor stand the head of a specific player. -- WorldGuard region support, including a custom WorldGuard flag. Set the 'ast' flag for a region to deny to turn off AST use in that region. +- WorldGuard region support, including a custom WorldGuard flag. Setting the 'ast' flag for a region to deny turns off AST use in that region. - Customizable language config file. - Assign commands to armor stands that are run when a player right clicks that armor stand (see below). -Assigning Commands ------------------- -- Use /ascmd to assign a command to an armor stand (see usage and permissions below) -- When using /ascmd assign player , the command will be run by the player -- When using /ascmd assign console , the command will be run by the console (see Warning below!) -- When a player with the astools.ascmd.execute permission right-clicks on an armor stand, it's command is run. -- If a player is crouching when they right-click the armor stand, the command will not be run. -- Warning: Make sure you are careful when assigning console commands. Any player with the astools.ascmd.execute permission will be able to execute the command. -- By default, any command assigned to an armor stand will use the default cooldown set in config.yml. This can be set on an individual basis using the /ascmd cooldown command. Commands -------- - /astools or /ast : Give yourself all the armor stand tools (Note: Saves & clears your inventory which is restored by running this command again) - /astools reload : Reload the config file -- /ascmd assign console : Assign a console command to the nearest armor stand (within 4 blocks) -- /ascmd assign player : Assign a player command to the nearest armor stand -- /ascmd remove : Remove the command assigned to the nearest armor stand -- /ascmd view : View the command assigned to the nearest armor stand +- /ascmd add \ \ \ \ : Add an assigned command to the nearest armor stand. See the assigning commands section below for more info. +- /ascmd remove \ : Remove a command from to the nearest armor stand (use /ascmd list to find the command number) +- /ascmd list : List the commands assigned to the nearest armor stand - /ascmd cooldown : Sets the cooldown (in ticks) for the command on nearest armor stand (Setting this overrides the default cooldown from config.yml) - /ascmd cooldown remove : Removes the cooldown for the command on nearest armor stand (Default cooldown set in config.yml will be used) +Permissions +----------- +- astools.use : Permission for using any of the tools +- astools.command : Permission for the /astools command +- astools.reload : Permission to reload the plugin with /astools reload +- astools.clone: Permission to use the clone tool +- astools.head: Permission to use the player head tool (Ability to specify a player head for an armor stand) +- astools.summon: Permission to use the summon tool (Summons an armor stand without requiring the materials) +- astools.cmdblock: Permission to use the save tool (Create a summon command) +- astools.glow: Permission to use the glow tool (Glow effect on armor stands) +- astools.ascmd.add.console: Permission to add a console command to an armor stand (Previously astools.ascmd.assign.console) +- astools.ascmd.add.player: Permission to add a player command to an armor stand (Previously astools.ascmd.assign.player) +- astools.ascmd.add.bungee: Permission to add a bungee command to an armor stand (See below for details) +- astools.ascmd.remove: Permission to remove a command from an armor stand +- astools.ascmd.list: Permission to list the commands assigned to an armor stand (Previously astools.ascmd.view) +- astools.ascmd.cooldown: Permission to add/remove a cooldown to commands assigned to an armor stand +- astools.ascmd.execute: Permission to execute commands assigned to an armor stand by (on right click) +- astools.bypass-wg-flag: Permission to bypass the WorldGuard ast flag, allowing the player to use AST even in regions that ast flag is set to deny. + +Assigning Commands to Armor Stands +---------------------------------- +- Stand close to the armor stand (The nearest armor stand within 4 blocks is selected) and use the command: /ascmd add \ \ \ \ +- \: When multiple commands are assigned, commands with the lowest priority number are executed first. Command with the same priority can be executed in any order. +- \: Delay in ticks before the command is executed. +- \: Player commands are executed as if they were typed by the player. Console commands are executed by the console. Bungee commands are a special case (see below). +- \: The command to be executed. To use the executing player's name in the command, use the placeholder %player% - it will be replaced with the players name at time of execution. +- When a player with the astools.ascmd.execute permission right-clicks on an armor stand, commands assigned to that armor stand are executed. +- Warning: Make sure you are careful when assigning console commands. Any player with the astools.ascmd.execute permission will be able to execute the command. +- By default, any command assigned to an armor stand will use the default cooldown set in config.yml. This can be set on an individual basis using the /ascmd cooldown command. +- Bungee commands: These are used to send the player to a different BungeeCord server. E.g. This will add a command to send the player to a server called server1: /ascmd add 0 0 bungee server1 + WorldGuard Integration ---------------------- - If a player does not have build permission for the region, that player will also be denied the use of AST in that region. - - Additionally, there is a custom region flag named 'ast' which defaults to Allow, this can be changed to Deny if you wish to have a region in which players have build permission, but not permission to use AST. - - The ast worldguard flag is ignored for any player with the permission node 'astools.bypass-wg-flag'. This means that a player with this permission can use AST in worldguard regions even if the ast flag for that region is set to Deny. + - Additionally, there is a custom region flag named 'ast' which defaults to Allow, this can be changed to Deny if you wish to have a region in which players have build permission, but not permission to use AST. + - The ast worldguard flag is ignored for any player with the permission node 'astools.bypass-wg-flag'. This means that a player with this permission can use AST in worldguard regions even if the ast flag for that region is set to Deny. - The ast worldguard flag is also ignored for players that have op. -Permissions ------------ -- astools.command : Permission for the /astools command -- astools.reload : Permission to reload the plugin with /astools reload -- astools.use : Permission for using any of the tools -- astools.clone: Permission to use the clone tool -- astools.head: Permission to use the player head tool (Ability to specify a player head for an armor stand) -- astools.summon: Permission to use the summon tool (Summons an armor stand without requiring the materials) -- astools.cmdblock: Permission to use the save tool (Creates a command block) -- astools.ascmd.assign.console: Permission to assign a console command to an armor stand -- astools.ascmd.assign.player: Permission to assign a player command to an armor stand -- astools.ascmd.remove: Permission to remove a command from an armor stand -- astools.ascmd.view: Permission to view the command assigned to an armor stand -- astools.ascmd.cooldown: Permission to add a cooldown to commands assigned to an armor stand -- astools.ascmd.execute: Permission to execute a command assigned to an armor stand by (on right click) -- astools.bypass-wg-flag: Permission to bypass the WorldGuard ast flag, allowing the player to use AST even in regions that ast flag is set to deny. - Config ------ - config.yml - The main config file allows you to set the default starting settings for newly summoned armor stands. This is useful if you plan on creating a lot of armor stands with similar equipment. diff --git a/pom.xml b/pom.xml index ec83f5a..1bbc4c0 100644 --- a/pom.xml +++ b/pom.xml @@ -3,9 +3,9 @@ xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 -com.gmail.St3venAU.plugins +com.gmail.st3venau.plugins ArmorStandTools -4.2.0 +4.3.0 ArmorStandTools diff --git a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/AST.java b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/AST.java index 98c08b8..e35cc01 100644 --- a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/AST.java +++ b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/AST.java @@ -35,6 +35,7 @@ public class AST extends JavaPlugin { public static final HashMap savedInventories = new HashMap<>(); static AST plugin; + static String nmsVersion; @Override public void onLoad() { @@ -56,7 +57,7 @@ public class AST extends JavaPlugin { @Override public void onEnable() { plugin = this; - String nmsVersion = getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3]; + nmsVersion = getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3]; if( nmsVersion.startsWith("v1_4") || nmsVersion.startsWith("v1_5") || nmsVersion.startsWith("v1_6") || @@ -75,6 +76,7 @@ public class AST extends JavaPlugin { return; } getServer().getPluginManager().registerEvents(new MainListener(), this); + getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); Commands cmds = new Commands(); PluginCommand command = getCommand("astools"); if(command != null) { @@ -103,6 +105,7 @@ public class AST extends JavaPlugin { @Override public void onDisable() { + getServer().getMessenger().unregisterOutgoingPluginChannel(this); for(UUID uuid : activeTool.keySet()) { if(ArmorStandTool.MOVE != activeTool.get(uuid)) continue; ArmorStand as = selectedArmorStand.get(uuid); @@ -237,7 +240,9 @@ public class AST extends JavaPlugin { if(!Utils.hasPermissionNode(p, "astools.bypass-wg-flag") && !getWorldGuardAstFlag(b.getLocation())) { return false; } - return Config.worldGuardPlugin.createProtectionQuery().testBlockBreak(p, b); + if(!Config.worldGuardPlugin.createProtectionQuery().testBlockBreak(p, b)) { + return false; + } } BlockBreakEvent breakEvent = new BlockBreakEvent(b, p); Bukkit.getServer().getPluginManager().callEvent(breakEvent); diff --git a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandCmd.java b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandCmd.java index 87e2494..0744031 100644 --- a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandCmd.java +++ b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandCmd.java @@ -1,164 +1,109 @@ package com.gmail.st3venau.plugins.armorstandtools; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; +import org.bukkit.command.CommandException; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Player; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.scheduler.BukkitRunnable; +import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.List; +import java.io.Serializable; -class ArmorStandCmd { +public record ArmorStandCmd(String command, CommandType type, Integer priority, Integer delay) implements Comparable, Serializable { - private final ArmorStand armorStand; - private String command; - private boolean console; - - ArmorStandCmd(ArmorStand as, String command, boolean console) { - this.armorStand = as; - this.command = command; - this.console = console; + String getTag() { + return "ascmd::v2::" + priority + "::" + delay + "::" + type.getTag() + "::" + command; } - ArmorStandCmd(ArmorStand as) { - this.armorStand = as; - this.command = null; - for(String tag : armorStand.getScoreboardTags()) { - if(tag.startsWith("ast-cmd-")) { - String cmd = tag.substring(8); - if(cmd.startsWith("con:")) { - cmd = cmd.substring(4); - if(cmd.charAt(0) == '/') { - cmd = cmd.substring(1); - } - if(cmd.length() == 0) return; - this.command = cmd; - this.console = true; - return; - } else if(cmd.startsWith("plr:")) { - cmd = cmd.substring(4); - if(cmd.charAt(0) == '/') { - cmd = cmd.substring(1); - } - if(cmd.length() == 0) return; - this.command = cmd; - this.console = false; - return; - } - } - } + void saveTo(ArmorStand as) { + as.addScoreboardTag(getTag()); } - private String getTag() { - return "ast-cmd-" + (console ? "con" : "plr") + ":" + command; - } - - String getCommand() { - return this.command; + void removeFrom(ArmorStand as) { + as.removeScoreboardTag(getTag()); } boolean execute(Player p) { - if(command == null) return true; - if(isOnCooldown()) { - p.sendMessage(ChatColor.RED + Config.cmdOnCooldown); - return true; - } - setOnCooldown(); - String cmd = command.contains("%player%") ? command.replaceAll("%player%", p.getName()) : command; - if(console) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd); - } else { - return p.performCommand(cmd); - } - } - - private void cleanUpCommand() { - if(command.charAt(0) == '/') { - command = command.substring(1); - } - } - - boolean save() { - removeAssignedCommand(armorStand); - if (command == null) return false; - cleanUpCommand(); - return command.length() != 0 && armorStand.addScoreboardTag(getTag()); - } - - void cloneTo(ArmorStand clone) { - if(command == null) return; - ArmorStandCmd asCmd = new ArmorStandCmd(clone, command, console); - asCmd.save(); - } - - String getType() { - return console ? "Console" : "Player"; - } - - static boolean removeAssignedCommand(ArmorStand as) { - List tags = new ArrayList<>(); - for(String tag : as.getScoreboardTags()) { - if(tag.startsWith("ast-cmd-")) { - tags.add(tag); + if (command == null) return true; + String cmd = command.replaceAll("%player%", p.getName()); + switch (type) { + case PLAYER -> { + return p.performCommand(cmd); } - } - for(String tag : tags) { - as.removeScoreboardTag(tag); - } - return tags.size() > 0; - } - - private void setOnCooldown() { - int cooldownTime = getCooldownTime(); - if(cooldownTime == -1) { - cooldownTime = Config.defaultASCmdCooldownTicks; - } - if(cooldownTime < 1) return; - armorStand.setMetadata("ast-cmd-cooldown", new FixedMetadataValue(AST.plugin, true)); - new BukkitRunnable() { - @Override - public void run() { - armorStand.removeMetadata("ast-cmd-cooldown", AST.plugin); - } - }.runTaskLater(AST.plugin, cooldownTime); - } - - private boolean isOnCooldown() { - return armorStand.hasMetadata("ast-cmd-cooldown"); - } - - // Positive cooldown: Set cooldown time, Negative cooldown: Remove cooldown time - void setCooldownTime(int cooldown) { - if(armorStand == null) return; - List tags = new ArrayList<>(); - for(String tag : armorStand.getScoreboardTags()) { - if(tag.startsWith("ast-cdn-")) { - tags.add(tag); - } - } - for(String tag : tags) { - armorStand.removeScoreboardTag(tag); - } - if(cooldown < 0) return; - armorStand.addScoreboardTag("ast-cdn-" + cooldown); - } - - private int getCooldownTime() { - if(armorStand == null) return -1; - for(String tag : armorStand.getScoreboardTags()) { - if(tag.startsWith("ast-cdn-")) { - String[] split = tag.split("ast-cdn-"); - if(split.length < 2 || split[1].length() < 1) return -1; + case CONSOLE -> { + boolean ok; try { - return Integer.parseInt(split[1]); - } catch (NumberFormatException e) { - return -1; + ok = Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd); + } catch (CommandException e) { + return false; } + return ok; + } + case BUNGEE -> { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("Connect"); + out.writeUTF(cmd); + p.sendPluginMessage(AST.plugin, "BungeeCord", out.toByteArray()); + return true; } } - return -1; + return true; } + static ArmorStandCmd fromTag(String tag) { + //[0] [1] [2] [3] [4] [5 - ...] + //ascmd::v2::priority::delay::type::command + String[] split = tag.split("::"); + if (split.length < 6) return null; + if (!split[0].equals("ascmd")) return null; + int priority; + try { + priority = Integer.parseInt(split[2]); + } catch (NumberFormatException e) { + return null; + } + int delay; + try { + delay = Integer.parseInt(split[3]); + } catch (NumberFormatException e) { + return null; + } + if(delay < 0) return null; + CommandType type = CommandType.fromTag(split[4]); + if (type == null) return null; + String cmd; + if (split.length == 6) { + cmd = split[5]; + } else { + StringBuilder sb = new StringBuilder(); + for (int i = 5; i < split.length; i++) { + sb.append(split[i]).append("::"); + } + cmd = sb.substring(0, sb.length() - 2); + } + if (cmd.charAt(0) == '/') { + cmd = cmd.substring(1); + } + if (cmd.length() == 0) return null; + return new ArmorStandCmd(cmd, type, priority, delay); + } + + static ArmorStandCmd fromLegacyTag(String tag) { + //ast-cmd-type-command + if (!tag.startsWith("ast-cmd-")) return null; + CommandType type = CommandType.fromTag(tag.substring(8, 11)); + if (type == null) return null; + String cmd = tag.substring(12); + if (cmd.charAt(0) == '/') { + cmd = cmd.substring(1); + } + if (cmd.length() == 0) return null; + return new ArmorStandCmd(cmd, type, 0, 0); + } + + @Override + public int compareTo(@NotNull ArmorStandCmd o) { + return Integer.compare(priority, o.priority); + } } diff --git a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandCmdManager.java b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandCmdManager.java new file mode 100644 index 0000000..2e0096a --- /dev/null +++ b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandCmdManager.java @@ -0,0 +1,147 @@ +package com.gmail.st3venau.plugins.armorstandtools; + +import org.bukkit.ChatColor; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +class ArmorStandCmdManager { + + static private final String ON_COOLDOWN_TAG = "AstCmdCooldown"; + + private final ArmorStand armorStand; + private final List commands = new ArrayList<>(); + + ArmorStandCmdManager(ArmorStand as) { + this.armorStand = as; + getCommandsFromScoreboardTags(); + commands.sort(null); + } + + private void getCommandsFromScoreboardTags() { + Set tags = new HashSet<>(armorStand.getScoreboardTags()); + for(String tag : tags) { + if(tag.startsWith("ast-cmd-")) { + ArmorStandCmd command = ArmorStandCmd.fromLegacyTag(tag); + armorStand.removeScoreboardTag(tag); + if(command != null) { + addCommand(command, true); + } + } else if (tag.startsWith("ascmd::v2::")) { + ArmorStandCmd command = ArmorStandCmd.fromTag(tag); + if(command != null) { + addCommand(command, false); + } + } + } + } + + public List getCommands() { + commands.sort(null); + return commands; + } + + boolean hasCommands() { + return !commands.isEmpty(); + } + + void addCommand(ArmorStandCmd command, boolean saveToArmorStand) { + commands.add(command); + if(saveToArmorStand) { + command.saveTo(armorStand); + } + } + + boolean removeCommand(int index) { + ArmorStandCmd cmd; + try { + cmd = commands.get(index); + } catch (IndexOutOfBoundsException e) { + return false; + } + cmd.removeFrom(armorStand); + commands.remove(index); + return true; + } + + void executeCommands(Player p) { + if(isOnCooldown()) { + p.sendMessage(ChatColor.RED + Config.cmdOnCooldown); + return; + } + setOnCooldown(); + long cumulativeDelay = 0; + for(int n = 0; n < commands.size(); n++) { + ArmorStandCmd asCmd = commands.get(n); + cumulativeDelay += asCmd.delay(); + final int num = n + 1; + new BukkitRunnable() { + @Override + public void run() { + if(!p.isOnline()) return; + if(!asCmd.execute(p)) { + p.sendMessage(ChatColor.RED + Config.errorExecutingCmd + " #" + num); + } + } + }.runTaskLater(AST.plugin, cumulativeDelay); + } + } + + private void setOnCooldown() { + int cooldownTime = getCooldownTime(); + if(cooldownTime == -1) { + cooldownTime = Config.defaultASCmdCooldownTicks; + } + if(cooldownTime < 1) return; + armorStand.setMetadata(ON_COOLDOWN_TAG, new FixedMetadataValue(AST.plugin, true)); + new BukkitRunnable() { + @Override + public void run() { + armorStand.removeMetadata(ON_COOLDOWN_TAG, AST.plugin); + } + }.runTaskLater(AST.plugin, cooldownTime); + } + + private boolean isOnCooldown() { + return armorStand.hasMetadata(ON_COOLDOWN_TAG); + } + + // Positive cooldown: Set cooldown time, Negative cooldown: Remove cooldown time + void setCooldownTime(int cooldown) { + if(armorStand == null) return; + List tags = new ArrayList<>(); + for(String tag : armorStand.getScoreboardTags()) { + if(tag.startsWith("ast-cdn-")) { + tags.add(tag); + } + } + for(String tag : tags) { + armorStand.removeScoreboardTag(tag); + } + if(cooldown < 0) return; + armorStand.addScoreboardTag("ast-cdn-" + cooldown); + } + + private int getCooldownTime() { + if(armorStand == null) return -1; + for(String tag : armorStand.getScoreboardTags()) { + if(tag.startsWith("ast-cdn-")) { + String[] split = tag.split("ast-cdn-"); + if(split.length < 2 || split[1].length() < 1) return -1; + try { + return Integer.parseInt(split[1]); + } catch (NumberFormatException e) { + return -1; + } + } + } + return -1; + } + +} diff --git a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandGUI.java b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandGUI.java index 1bc28d8..96adca8 100644 --- a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandGUI.java +++ b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandGUI.java @@ -6,6 +6,7 @@ import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.HandlerList; @@ -18,6 +19,7 @@ import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.scheduler.BukkitRunnable; import java.util.HashSet; @@ -197,7 +199,10 @@ class ArmorStandGUI implements Listener { break; case CLONE: p.closeInventory(); - AST.pickUpArmorStand(Utils.cloneArmorStand(as), p, true); + ArmorStand clone = (ArmorStand) as.getWorld().spawnEntity(as.getLocation().add(1, 0, 0), EntityType.ARMOR_STAND); + (new ArmorStandMeta(as)).applyToArmorStand(clone); + clone.setMetadata("clone", new FixedMetadataValue(AST.plugin, true)); + AST.pickUpArmorStand(clone, p, true); Utils.title(p, Config.carrying); break; case GEN_CMD: @@ -258,6 +263,18 @@ class ArmorStandGUI implements Listener { as.setGlowing(glowing); Utils.title(p, Config.glow + ": " + (glowing ? Config.isOn : Config.isOff)); break; + case ITEM: + ArmorStandMeta asm = new ArmorStandMeta(as); + ItemStack item = asm.convertToItem(); + if(item != null) { + p.closeInventory(); + if(p.getInventory().addItem(item).size() > 0) { + p.sendMessage(ChatColor.RED + Config.inventoryFull); + } else if(p.getGameMode() != GameMode.CREATIVE) { + as.remove(); + } + } + break; default: return; } diff --git a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandMeta.java b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandMeta.java new file mode 100644 index 0000000..d5b7a44 --- /dev/null +++ b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandMeta.java @@ -0,0 +1,259 @@ +package com.gmail.st3venau.plugins.armorstandtools; + +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.ArmorStand; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.EulerAngle; +import org.bukkit.util.io.BukkitObjectInputStream; +import org.bukkit.util.io.BukkitObjectOutputStream; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class ArmorStandMeta implements Serializable { + + final String name; + final float yaw; + ItemStack helmet, chest, leggings, boots, mainHand, offHand; + final double[] headPose, bodyPose, leftArmPose, rightArmPose, leftLegPose, rightLegPose; + final boolean gravity, visible, basePlate, arms, small, invulnerable, glowing, nameVisible, marker; + final HashMap> equipmentLocks; + final List commandTags = new ArrayList<>(); + String cooldownTag; + + ArmorStandMeta(ArmorStand as) { + name = as.getCustomName(); + yaw = as.getLocation().getYaw(); + EntityEquipment equipment = as.getEquipment(); + if(equipment != null) { + helmet = equipment.getHelmet(); + chest = equipment.getChestplate(); + leggings = equipment.getLeggings(); + boots = equipment.getBoots(); + mainHand = equipment.getItemInMainHand(); + offHand = equipment.getItemInOffHand(); + } + headPose = new double[3]; + headPose[0] = as.getHeadPose().getX(); + headPose[1] = as.getHeadPose().getY(); + headPose[2] = as.getHeadPose().getZ(); + bodyPose = new double[3]; + bodyPose[0] = as.getBodyPose().getX(); + bodyPose[1] = as.getBodyPose().getY(); + bodyPose[2] = as.getBodyPose().getZ(); + leftArmPose = new double[3]; + leftArmPose[0] = as.getLeftArmPose().getX(); + leftArmPose[1] = as.getLeftArmPose().getY(); + leftArmPose[2] = as.getLeftArmPose().getZ(); + rightArmPose = new double[3]; + rightArmPose[0] = as.getRightArmPose().getX(); + rightArmPose[1] = as.getRightArmPose().getY(); + rightArmPose[2] = as.getRightArmPose().getZ(); + leftLegPose = new double[3]; + leftLegPose[0] = as.getLeftLegPose().getX(); + leftLegPose[1] = as.getLeftLegPose().getX(); + leftLegPose[2] = as.getLeftLegPose().getZ(); + rightLegPose = new double[3]; + rightLegPose[0] = as.getRightLegPose().getX(); + rightLegPose[1] = as.getRightLegPose().getY(); + rightLegPose[2] = as.getRightLegPose().getZ(); + gravity = as.hasGravity(); + visible = as.isVisible(); + basePlate = as.hasBasePlate(); + arms = as.hasArms(); + small = as.isSmall(); + invulnerable = as.isInvulnerable(); + glowing = as.isGlowing(); + nameVisible = as.isCustomNameVisible(); + marker = as.isMarker(); + equipmentLocks = new HashMap<>(); + for (EquipmentSlot slot : EquipmentSlot.values()) { + ArrayList lockTypes = new ArrayList<>(); + for (ArmorStand.LockType lockType : ArmorStand.LockType.values()) { + if (as.hasEquipmentLock(slot, lockType)) { + lockTypes.add(lockType); + } + } + equipmentLocks.put(slot, lockTypes); + } + for(ArmorStandCmd cmd : new ArmorStandCmdManager(as).getCommands()) { + commandTags.add(cmd.getTag()); + } + for(String tag : as.getScoreboardTags()) { + if(tag.startsWith("ast-cdn-")) { + cooldownTag = tag; + } + } + } + + void applyToArmorStand(ArmorStand as) { + as.setCustomName(name); + EntityEquipment equipment = as.getEquipment(); + if(equipment != null) { + equipment.setHelmet(helmet); + equipment.setChestplate(chest); + equipment.setLeggings(leggings); + equipment.setBoots(boots); + equipment.setItemInMainHand(mainHand); + equipment.setItemInOffHand(offHand); + } + as.setHeadPose( new EulerAngle( headPose[0], headPose[1], headPose[2])); + as.setBodyPose( new EulerAngle( bodyPose[0], bodyPose[1], bodyPose[2])); + as.setLeftArmPose( new EulerAngle( leftArmPose[0], leftArmPose[1], leftArmPose[2])); + as.setRightArmPose(new EulerAngle(rightArmPose[0], rightArmPose[1], rightArmPose[2])); + as.setLeftLegPose( new EulerAngle( leftLegPose[0], leftLegPose[1], leftLegPose[2])); + as.setRightLegPose(new EulerAngle(rightLegPose[0], rightLegPose[1], rightLegPose[2])); + as.setGravity(gravity); + as.setVisible(visible); + as.setBasePlate(basePlate); + as.setArms(arms); + as.setSmall(small); + as.setInvulnerable(invulnerable); + as.setGlowing(glowing); + as.setCustomNameVisible(nameVisible); + as.setMarker(marker); + for (EquipmentSlot slot : equipmentLocks.keySet()) { + for (ArmorStand.LockType lockType : equipmentLocks.get(slot)) { + as.addEquipmentLock(slot, lockType); + } + } + for(String tag : commandTags) { + as.addScoreboardTag(tag); + } + if(cooldownTag != null) { + as.addScoreboardTag(cooldownTag); + } + new BukkitRunnable() { + @Override + public void run() { + Location l = as.getLocation(); + l.setYaw(yaw); + as.teleport(l); + } + }.runTaskLater(AST.plugin, 2L); + } + + ItemStack convertToItem() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + BukkitObjectOutputStream objectStream = new BukkitObjectOutputStream(outputStream); + objectStream.writeObject(this); + objectStream.close(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + ItemStack is = new ItemStack(Material.ARMOR_STAND, 1); + is.addUnsafeEnchantment(Enchantment.DURABILITY, 1); + ItemMeta meta = is.getItemMeta(); + if(meta != null) { + meta.setDisplayName(ChatColor.AQUA + Config.configuredArmorStand); + meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); + meta.setLore(createLore()); + is.setItemMeta(meta); + } + try { + Class craftItemStackClass = Class.forName("org.bukkit.craftbukkit." + AST.nmsVersion + ".inventory.CraftItemStack"); + Object nmsStack = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class) + .invoke(null, is); + Object tag = nmsStack.getClass().getMethod("getTag") + .invoke(nmsStack); + Object nbtArray = Class.forName("net.minecraft.nbt.NBTTagByteArray") + .getConstructor(byte[].class) + .newInstance((Object) outputStream.toByteArray()); + tag.getClass().getMethod("set", String.class, Class.forName("net.minecraft.nbt.NBTBase")) + .invoke(tag, "ArmorStandMeta", nbtArray); + nmsStack.getClass().getMethod("setTag", tag.getClass()) + .invoke(nmsStack, tag); + return (ItemStack) craftItemStackClass.getMethod("asBukkitCopy", nmsStack.getClass()) + .invoke(null, nmsStack); + } catch (Exception e) { + if(Config.showDebugMessages) { + e.printStackTrace(); + AST.debug("Failed to convert armor stand into an item"); + } + return null; + } + } + + static ArmorStandMeta fromItem(ItemStack is) { + if(is == null) return null; + Object armorStandMetaObject; + try { + Class craftItemStackClass = Class.forName("org.bukkit.craftbukkit." + AST.nmsVersion + ".inventory.CraftItemStack"); + Object nmsStack = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class) + .invoke(null, is); + Object tag = nmsStack.getClass().getMethod("getTag") + .invoke(nmsStack); + if(tag == null) return null; + byte[] byteArray = (byte[]) tag.getClass().getMethod("getByteArray", String.class) + .invoke(tag, "ArmorStandMeta"); + if(byteArray == null) return null; + ByteArrayInputStream inputStream = new ByteArrayInputStream(byteArray); + BukkitObjectInputStream objectStream = new BukkitObjectInputStream(inputStream); + armorStandMetaObject = objectStream.readObject(); + objectStream.close(); + } catch (Exception e) { + return null; + } + return armorStandMetaObject instanceof ArmorStandMeta ? (ArmorStandMeta) armorStandMetaObject : null; + } + + List createLore() { + List lore = new ArrayList<>(); + if(name != null && name.length() > 0) { + lore.add(Config.name + ": " + ChatColor.YELLOW + name); + } + int stacks = 0; + int items = 0; + if(helmet != null) { stacks++; items += helmet.getAmount(); } + if(chest != null) { stacks++; items += chest.getAmount(); } + if(leggings != null) { stacks++; items += leggings.getAmount(); } + if(boots != null) { stacks++; items += boots.getAmount(); } + if(mainHand != null) { stacks++; items += mainHand.getAmount(); } + if(offHand != null) { stacks++; items += offHand.getAmount(); } + if(stacks > 0) { + lore.add(Config.inventory + ": " + ChatColor.YELLOW + items + " " + Config.items + " (" + stacks + " " + Config.stacks + ")"); + } + List attribs = new ArrayList<>(); + if(commandTags.size() > 0) attribs.add(commandTags.size() + " " + Config.cmdsAssigned); + if(equipmentLocks.size() > 0) attribs.add(Config.equip + " " + Config.locked); + if(!gravity) attribs.add(Config.gravity + " " + Config.isOff); + if(!visible) attribs.add(Config.invisible); + if(arms) attribs.add(Config.arms); + if(small) attribs.add(Config.small); + if(invulnerable) attribs.add(Config.invuln); + if(glowing) attribs.add(Config.glowing); + if(attribs.size() > 0) { + StringBuilder sb = new StringBuilder(Config.attributes + ": " + ChatColor.YELLOW); + for (String attrib : attribs) { + sb.append(attrib).append(", "); + if (sb.length() >= 40) { + lore.add(sb.toString()); + sb = new StringBuilder("" + ChatColor.YELLOW); + } + } + if (sb.length() < 3) { + String last = lore.get(lore.size() - 1); + lore.add(last.substring(0, last.length() - 2)); + } else { + lore.add(sb.substring(0, sb.length() - 2)); + } + } + return lore; + } + +} diff --git a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandTool.java b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandTool.java index 69cfad7..b4fbcec 100644 --- a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandTool.java +++ b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/ArmorStandTool.java @@ -6,6 +6,7 @@ import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Player; import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.ItemMeta; @@ -59,7 +60,8 @@ public enum ArmorStandTool { RARM ("gui_rarm", Material.REDSTONE_TORCH, 15, true, "astools.use", true), LARM ("gui_larm", Material.TORCH, 17, true, "astools.use", true), RLEG ("gui_rleg", Material.BLAZE_ROD, 24, true, "astools.use", true), - LLEG ("gui_lleg", Material.BONE, 26, true, "astools.use", true); + LLEG ("gui_lleg", Material.BONE, 26, true, "astools.use", true), + ITEM ("gui_item", Material.ARMOR_STAND, 34, true, "astools.use", false); private final ItemStack item; private final String config_id; @@ -73,6 +75,12 @@ public enum ArmorStandTool { ArmorStandTool(String config_id, Material m, int slot, boolean forGui, String permission, boolean reverseSneaking) { item = new ItemStack(m); + ItemMeta meta = item.getItemMeta(); + if(meta != null) { + meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES); + item.setItemMeta(meta); + } this.config_id = config_id; this.slot = slot; this.forGui = forGui; diff --git a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/CommandType.java b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/CommandType.java new file mode 100644 index 0000000..dae4991 --- /dev/null +++ b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/CommandType.java @@ -0,0 +1,49 @@ +package com.gmail.st3venau.plugins.armorstandtools; + +enum CommandType { + + PLAYER ("Player", "plr", "astools.ascmd.add.player"), + CONSOLE("Console", "con", "astools.ascmd.add.console"), + BUNGEE ("Bungee", "bun", "astools.ascmd.add.bungee"); + + private final String name; + private final String tag; + private final String addPermission; + + CommandType(String name, String tag, String addPermission) { + this.name = name; + this.tag = tag; + this.addPermission = addPermission; + } + + String getTag() { + return tag; + } + + String getName() { + return name; + } + + String getAddPermission() { + return addPermission; + } + + static CommandType fromTag(String tag) { + for(CommandType type : values()) { + if(type.tag.equalsIgnoreCase(tag)) { + return type; + } + } + return null; + } + + static CommandType fromName(String name) { + for(CommandType type : values()) { + if(type.name.equalsIgnoreCase(name)) { + return type; + } + } + return null; + } + +} diff --git a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/Commands.java b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/Commands.java index 8d868e7..b4cc4e5 100644 --- a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/Commands.java +++ b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/Commands.java @@ -59,61 +59,71 @@ class Commands implements CommandExecutor, TabCompleter { } String name = " "; if(as.getName().length() > 0 && !as.getName().equalsIgnoreCase("armor stand")) { - name = " (" + ChatColor.AQUA + as.getName() + ChatColor.RESET + ") "; + name = ChatColor.RESET + " (" + ChatColor.AQUA + as.getName() + ChatColor.RESET + ") "; } - if(args.length > 0 && args[0].equalsIgnoreCase("view")) { - // ascmd view - if (!Utils.hasPermissionNode(p, "astools.ascmd.view")) { + ArmorStandCmdManager asCmdManager = new ArmorStandCmdManager(as); + if(args.length >= 1 && args[0].equalsIgnoreCase("list")) { + // [0] + // ascmd list + if (!Utils.hasPermissionNode(p, "astools.ascmd.list")) { p.sendMessage(ChatColor.RED + Config.noCommandPerm); return true; } - ArmorStandCmd asCmd = new ArmorStandCmd(as); - if(asCmd.getCommand() == null) { - p.sendMessage("\n" + Config.closestAS + name + Config.hasNoCmd); - } else { - p.sendMessage("\n" + Config.closestAS + name + Config.hasCmd); - p.sendMessage(Config.type + ": " + ChatColor.YELLOW + asCmd.getType()); - p.sendMessage(Config.command + ": " + ChatColor.YELLOW + asCmd.getCommand()); - } - } else if(args.length > 0 && args[0].equalsIgnoreCase("remove")) { - // ascmd remove + listAssignedCommands(asCmdManager, name, p); + return true; + } else if(args.length >= 2 && args[0].equalsIgnoreCase("remove")) { + // [0] [1] + // ascmd remove if (!Utils.hasPermissionNode(p, "astools.ascmd.remove")) { p.sendMessage(ChatColor.RED + Config.noCommandPerm); return true; } - if(ArmorStandCmd.removeAssignedCommand(as)) { - p.sendMessage("\n" + Config.unassignedCmd + name); - } else { - p.sendMessage("\n" + Config.closestAS + name + Config.hasNoCmd); - } - } else if(args.length >= 3 && args[0].equalsIgnoreCase("assign")) { - // ascmd assign (command) - ArmorStandCmd asCmd = new ArmorStandCmd(as); - if(asCmd.getCommand() != null) { - p.sendMessage("\n" + Config.closestAS + name + Config.hasCmd); - p.sendMessage(Config.removeCmd + ": " + ChatColor.YELLOW + " /ascmd remove"); + int n; + try { + n = Integer.parseInt(args[1]); + } catch (NumberFormatException e) { + p.sendMessage(ChatColor.RED + args[1] + " " + Config.isNotValidNumber); return true; } - Boolean isConsole = null; - if(args[1].equalsIgnoreCase("console")) { - isConsole = true; - if (!Utils.hasPermissionNode(p, "astools.ascmd.assign.console")) { - p.sendMessage(ChatColor.RED + Config.noCommandPerm); - return true; - } - } else if(args[1].equalsIgnoreCase("player")) { - isConsole = false; - if (!Utils.hasPermissionNode(p, "astools.ascmd.assign.player")) { - p.sendMessage(ChatColor.RED + Config.noCommandPerm); - return true; - } + if(asCmdManager.removeCommand(n - 1)) { + p.sendMessage("\n" + Config.command + ChatColor.GREEN + " #" + n + ChatColor.RESET + " " + Config.removedFromAs + name); + } else { + p.sendMessage(ChatColor.RED + args[1] + " " + Config.isNotValidNumber); } - if(isConsole == null) { + listAssignedCommands(asCmdManager, name, p); + return true; + } else if(args.length >= 5 && args[0].equalsIgnoreCase("add")) { + // [0] [1] [2] [3] [4 - ...] + // ascmd add + CommandType type = CommandType.fromName(args[3]); + if(type == null) { ascmdHelp(p); return true; } + if (!Utils.hasPermissionNode(p, type.getAddPermission())) { + p.sendMessage(ChatColor.RED + Config.noCommandPerm); + return true; + } + int priority; + try { + priority = Integer.parseInt(args[1]); + } catch (NumberFormatException e) { + p.sendMessage(ChatColor.RED + args[1] + " " + Config.isNotValidNumber); + return true; + } + int delay; + try { + delay = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { + p.sendMessage(ChatColor.RED + args[2] + " " + Config.isNotValidNumber); + return true; + } + if(delay < 0) { + p.sendMessage(ChatColor.RED + args[2] + " " + Config.isNotValidNumber); + return true; + } StringBuilder sb = new StringBuilder(); - for(int i = 2; i < args.length; i++) { + for(int i = 4; i < args.length; i++) { sb.append(args[i]).append(" "); } int startAt = sb.charAt(0) == '/' ? 1 : 0; @@ -122,26 +132,20 @@ class Commands implements CommandExecutor, TabCompleter { ascmdHelp(p); return true; } - asCmd = new ArmorStandCmd(as, c, isConsole); - if(asCmd.save()) { - p.sendMessage("\n" + Config.assignedCmdToAS + name); - p.sendMessage(Config.type + ": " + ChatColor.YELLOW + asCmd.getType()); - p.sendMessage(Config.command + ": " + ChatColor.YELLOW + asCmd.getCommand()); - } else { - p.sendMessage("\n" + Config.assignCmdError + name); - } + asCmdManager.addCommand(new ArmorStandCmd(c, type, priority, delay), true); + listAssignedCommands(asCmdManager, name, p); + return true; } else if(args.length >= 2 && args[0].equalsIgnoreCase("cooldown")) { //ascmd cooldown /remove if (!Utils.hasPermissionNode(p, "astools.ascmd.cooldown")) { p.sendMessage(ChatColor.RED + Config.noCommandPerm); return true; } - ArmorStandCmd asCmd = new ArmorStandCmd(as); - if(asCmd.getCommand() == null) { - p.sendMessage(Config.closestAS + name + Config.hasNoCmd); + if(!asCmdManager.hasCommands()) { + p.sendMessage(Config.closestAS + name + Config.hasNoCmds); return true; } if(args[1].equalsIgnoreCase("remove")) { - asCmd.setCooldownTime(-1); + asCmdManager.setCooldownTime(-1); p.sendMessage(Config.cooldownRemovedFrom + " " + Config.closestAS + name); } else { int ticks; @@ -155,7 +159,7 @@ class Commands implements CommandExecutor, TabCompleter { p.sendMessage(args[1] + " " + Config.isAnInvalidCooldown); return true; } - asCmd.setCooldownTime(ticks); + asCmdManager.setCooldownTime(ticks); p.sendMessage(Config.cooldownSetTo + " " + ticks + " " + Config.ticksFor + " " + Config.closestAS + name); } return true; @@ -163,19 +167,35 @@ class Commands implements CommandExecutor, TabCompleter { ascmdHelp(p); return true; } - return true; } return false; } + private void listAssignedCommands(ArmorStandCmdManager asCmdManager, String name, Player p) { + if(asCmdManager.hasCommands()) { + p.sendMessage("\n" + Config.closestAS + name + Config.hasTheseCmdsAssigned); + List list = asCmdManager.getCommands(); + for(int n = 0; n < list.size(); n++) { + ArmorStandCmd asCmd = list.get(n); + // #1 Priority:0 Delay:0 Type:Player Command:cmd + p.sendMessage( + ChatColor.GREEN + "#" + (n + 1) + " " + + ChatColor.LIGHT_PURPLE + Config.priority + ":" + ChatColor.RESET + asCmd.priority() + " " + + ChatColor.YELLOW + Config.delay + ":" + ChatColor.RESET + asCmd.delay() + " " + + ChatColor.GOLD + Config.type + ":" + ChatColor.RESET + asCmd.type().getName() + " " + + ChatColor.AQUA + Config.command + ":" + ChatColor.RESET + asCmd.command() + ); + } + } else { + p.sendMessage("\n" + Config.closestAS + name + Config.hasNoCmds); + } + } + private void ascmdHelp(Player p) { - p.sendMessage("\n" + ChatColor.AQUA + Config.ascmdHelp); - p.sendMessage(Config.viewCmd + ": " + ChatColor.YELLOW + "/ascmd view"); - p.sendMessage(Config.removeCmd + ": " + ChatColor.YELLOW + "/ascmd remove"); - p.sendMessage(Config.assignConsole + ":"); - p.sendMessage(ChatColor.YELLOW + "/ascmd assign console "); - p.sendMessage(Config.assignPlayer + ":"); - p.sendMessage(ChatColor.YELLOW + "/ascmd assign player "); + p.sendMessage("\n" + ChatColor.AQUA + Config.cmdHelp); + p.sendMessage(Config.listAssignedCmds + ": " + ChatColor.YELLOW + "/ascmd list"); + p.sendMessage(Config.addACmd + ":" + ChatColor.YELLOW + "/ascmd add "); + p.sendMessage(Config.removeACmd + ": " + ChatColor.YELLOW + "/ascmd remove "); p.sendMessage(Config.setCooldown + ":"); p.sendMessage(ChatColor.YELLOW + "/ascmd cooldown "); p.sendMessage(Config.removeCooldown + ":"); @@ -183,14 +203,17 @@ class Commands implements CommandExecutor, TabCompleter { } private ArmorStand getNearbyArmorStand(Player p) { - ArmorStand closest = null; - double dist = 1000000; + ArmorStand closestAs = null; + double closestDist = 1000000; for(Entity e : p.getNearbyEntities(4, 4, 4)) { - if(e instanceof ArmorStand && e.getLocation().distanceSquared(p.getLocation()) < dist) { - closest = (ArmorStand) e; + if(!(e instanceof ArmorStand)) continue; + double dist = e.getLocation().distanceSquared(p.getLocation()); + if(dist < closestDist) { + closestDist = dist; + closestAs = (ArmorStand) e; } } - return closest; + return closestAs; } public List onTabComplete(@NotNull CommandSender sender, Command command, @NotNull String alias, String[] args) { @@ -202,15 +225,21 @@ class Commands implements CommandExecutor, TabCompleter { } if (cmd.equals("ascmd")) { if (args.length == 1) { - for(String s : Arrays.asList("view", "remove", "assign", "cooldown")) { + for(String s : Arrays.asList("list", "remove", "add", "cooldown")) { if(s.startsWith(typed)) { list.add(s); } } - } else if (args.length == 2 && args[0].equalsIgnoreCase("assign")) { - for(String s : Arrays.asList("player", "console")) { - if(s.startsWith(typed)) { - list.add(s); + } else if (args[0].equalsIgnoreCase("add")) { + if(args.length == 2 && typed.length() == 0) { + list.add("priority"); + } if(args.length == 3 && typed.length() == 0) { + list.add("delay"); + } else if(args.length == 4) { + for (String s : Arrays.asList("player", "console", "bungee")) { + if (s.startsWith(typed)) { + list.add(s); + } } } } else if (args.length == 2 && args[0].equalsIgnoreCase("cooldown")) { diff --git a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/Config.java b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/Config.java index 7804ec1..8405b6b 100644 --- a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/Config.java +++ b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/Config.java @@ -35,22 +35,23 @@ class Config { static ItemStack helmet, chest, pants, boots, itemInHand, itemInOffHand; - static boolean isVisible = true; - static boolean isSmall = false; - static boolean hasArms = true; - static boolean hasBasePlate = false; - static boolean hasGravity = false; - static String defaultName = ""; - static boolean invulnerable = false; - static boolean equipmentLock = false; - static boolean allowMoveWorld = false; - static boolean deactivateOnWorldChange = true; - static boolean showDebugMessages = false; - static boolean requireCreative = false; - static int defaultASCmdCooldownTicks = 0; - static boolean ignoreWGForASCmdExecution = false; + static boolean isVisible = true; + static boolean isSmall = false; + static boolean hasArms = true; + static boolean hasBasePlate = false; + static boolean hasGravity = false; + static String defaultName = ""; + static boolean invulnerable = false; + static boolean equipmentLock = false; + static boolean allowMoveWorld = false; + static boolean deactivateOnWorldChange = true; + static boolean showDebugMessages = false; + static boolean requireCreative = false; + static int defaultASCmdCooldownTicks = 0; + static boolean ignoreWGForASCmdExecution = false; static boolean saveToolCreatesCommandBlock = true; static boolean logGeneratedSummonCommands = false; + static boolean crouchRightClickOpensGUI = false; static final ArrayList deniedCommands = new ArrayList<>(); @@ -60,14 +61,16 @@ class Config { isOn, isOff, gravity, arms, invul, equip, locked, unLocked, notConsole, giveMsg1, giveMsg2, conReload, noRelPerm, noAirError, invalidName, wgNoPerm, currently, - noCommandPerm, generalNoPerm, armorStand, none, - guiInUse, noASNearBy, closestAS, creativeRequired, - hasNoCmd, hasCmd, type, command, unassignedCmd, - assignedCmdToAS, assignCmdError, ascmdHelp, viewCmd, - removeCmd, assignConsole, assignPlayer, executeCmdError, + noCommandPerm, generalNoPerm, armorStand, none, guiInUse, + noASNearBy, closestAS, creativeRequired, type, command, cmdOnCooldown, cooldownRemovedFrom, isAnInvalidCooldown, cooldownSetTo, ticksFor, setCooldown, removeCooldown, - cmdNotAllowed, glow, crouch, click, finish; + cmdNotAllowed, glow, crouch, click, finish, inventoryFull, + configuredArmorStand, name, inventory, cmdsAssigned, + invisible, invuln, glowing, attributes, items, stacks, + hasTheseCmdsAssigned, hasNoCmds, priority, delay, + errorExecutingCmd, isNotValidNumber, removedFromAs, + listAssignedCmds, addACmd, removeACmd, cmdHelp; static void reload() { reloadMainConfig(); @@ -103,6 +106,7 @@ class Config { showDebugMessages = config.getBoolean("showDebugMessages", false); saveToolCreatesCommandBlock = config.getBoolean("saveToolCreatesCommandBlock", true); logGeneratedSummonCommands = config.getBoolean("logGeneratedSummonCommands", false); + crouchRightClickOpensGUI = config.getBoolean("crouchRightClickOpensGUI", false); AST.activeTool.clear(); AST.selectedArmorStand.clear(); @@ -207,19 +211,8 @@ class Config { guiInUse = languageConfig.getString("guiInUse"); noASNearBy = languageConfig.getString("noASNearBy"); closestAS = languageConfig.getString("closestAS"); - hasNoCmd = languageConfig.getString("hasNoCmd"); - hasCmd = languageConfig.getString("hasCmd"); type = languageConfig.getString("type"); command = languageConfig.getString("command"); - unassignedCmd = languageConfig.getString("unassignedCmd"); - assignedCmdToAS = languageConfig.getString("assignedCmdToAS"); - assignCmdError = languageConfig.getString("assignCmdError"); - ascmdHelp = languageConfig.getString("ascmdHelp"); - viewCmd = languageConfig.getString("viewCmd"); - removeCmd = languageConfig.getString("removeCmd"); - assignConsole = languageConfig.getString("assignConsole"); - assignPlayer = languageConfig.getString("assignPlayer"); - executeCmdError = languageConfig.getString("executeCmdError"); creativeRequired = languageConfig.getString("creativeRequired"); cmdOnCooldown = languageConfig.getString("cmdOnCooldown"); cooldownRemovedFrom = languageConfig.getString("cooldownRemovedFrom"); @@ -234,6 +227,28 @@ class Config { crouch = languageConfig.getString("crouch"); click = languageConfig.getString("click"); finish = languageConfig.getString("finish"); + inventoryFull = languageConfig.getString("inventoryFull"); + configuredArmorStand = languageConfig.getString("configuredArmorStand"); + name = languageConfig.getString("name"); + inventory = languageConfig.getString("inventory"); + cmdsAssigned = languageConfig.getString("cmdsAssigned"); + invisible = languageConfig.getString("invisible"); + invuln = languageConfig.getString("invuln"); + glowing = languageConfig.getString("glowing"); + attributes = languageConfig.getString("attributes"); + items = languageConfig.getString("items"); + stacks = languageConfig.getString("stacks"); + hasTheseCmdsAssigned = languageConfig.getString("hasTheseCmdsAssigned"); + hasNoCmds = languageConfig.getString("hasNoCmds"); + priority = languageConfig.getString("priority"); + delay = languageConfig.getString("delay"); + errorExecutingCmd = languageConfig.getString("errorExecutingCmd"); + isNotValidNumber = languageConfig.getString("isNotValidNumber"); + removedFromAs = languageConfig.getString("removedFromAs"); + listAssignedCmds = languageConfig.getString("listAssignedCmds"); + addACmd = languageConfig.getString("addACmd"); + removeACmd = languageConfig.getString("removeACmd"); + cmdHelp = languageConfig.getString("cmdHelp"); } private static ItemStack toItemStack(String s) { diff --git a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/MainListener.java b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/MainListener.java index 6e0a400..3d83e16 100644 --- a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/MainListener.java +++ b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/MainListener.java @@ -2,12 +2,14 @@ package com.gmail.st3venau.plugins.armorstandtools; import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.GameMode; import org.bukkit.GameRule; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.World; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.EntityType; import org.bukkit.entity.ItemFrame; @@ -56,12 +58,21 @@ public class MainListener implements Listener { public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent event) { if(!(event.getRightClicked() instanceof ArmorStand as)) return; Player p = event.getPlayer(); - if(!event.isCancelled() && ArmorStandGUI.isInUse(as)) { + if (ArmorStandGUI.isInUse(as)) { Utils.title(p, Config.guiInUse); event.setCancelled(true); return; } - if(!event.isCancelled() && stopEditing(p, false)) { + if(stopEditing(p, false)) { + event.setCancelled(true); + return; + } + if(Config.crouchRightClickOpensGUI && p.isSneaking() && Utils.hasPermissionNode(p, "astools.use")) { + if (!AST.playerHasPermission(p, as.getLocation().getBlock(), null)) { + p.sendMessage(ChatColor.RED + Config.generalNoPerm); + return; + } + new ArmorStandGUI(as, p); event.setCancelled(true); return; } @@ -116,14 +127,10 @@ public class MainListener implements Listener { return; } if((Config.ignoreWGForASCmdExecution || !event.isCancelled()) && !p.isSneaking()) { - ArmorStandCmd asCmd = new ArmorStandCmd(as); - if (asCmd.getCommand() != null) { + ArmorStandCmdManager asCmdManager = new ArmorStandCmdManager(as); + if (asCmdManager.hasCommands() && Utils.hasPermissionNode(p, "astools.ascmd.execute")) { event.setCancelled(true); - if (Utils.hasPermissionNode(p, "astools.ascmd.execute")) { - if (!asCmd.execute(p)) { - p.sendMessage(Config.executeCmdError); - } - } + asCmdManager.executeCommands(p); } } } @@ -267,10 +274,36 @@ public class MainListener implements Listener { event.setCancelled(true); return; } - ArmorStandTool tool = ArmorStandTool.get(event.getItem()); + Action action = event.getAction(); + BlockFace blockFace = event.getBlockFace(); + ItemStack inHand = event.getItem(); + Block b = event.getClickedBlock(); + ArmorStandTool tool = ArmorStandTool.get(inHand); + if(inHand != null && tool == null && b != null && action == Action.RIGHT_CLICK_BLOCK && blockFace != BlockFace.DOWN && inHand.getType() == Material.ARMOR_STAND) { + b = b.getRelative(blockFace); + if (!AST.playerHasPermission(p, b, ArmorStandTool.ITEM)) { + p.sendMessage(ChatColor.RED + Config.generalNoPerm); + return; + } + ArmorStandMeta asm = ArmorStandMeta.fromItem(inHand); + if(asm != null) { + event.setCancelled(true); + Location l = b.getLocation().add(0.5, 0, 0.5); + ArmorStand as = (ArmorStand) b.getWorld().spawnEntity(l, EntityType.ARMOR_STAND); + asm.applyToArmorStand(as); + if(p.getGameMode() != GameMode.CREATIVE) { + if(inHand.getAmount() == 1) { + p.getInventory().setItemInMainHand(null); + } else { + inHand.setAmount(inHand.getAmount() - 1); + p.getInventory().setItemInMainHand(inHand); + } + } + return; + } + } if(tool == null) return; event.setCancelled(true); - Action action = event.getAction(); if(action == Action.LEFT_CLICK_AIR || action == Action.LEFT_CLICK_BLOCK) { Utils.cycleInventory(p); } else if((action == Action.RIGHT_CLICK_BLOCK || action == Action.RIGHT_CLICK_AIR) && tool == ArmorStandTool.SUMMON) { @@ -453,10 +486,10 @@ public class MainListener implements Listener { attachment.setPermission("astools.head", true); attachment.setPermission("astools.reload", true); attachment.setPermission("astools.cmdblock", true); - attachment.setPermission("astools.ascmd.view", true); + attachment.setPermission("astools.ascmd.list", true); attachment.setPermission("astools.ascmd.remove", true); - attachment.setPermission("astools.ascmd.assign.player", true); - attachment.setPermission("astools.ascmd.assign.console", true); + attachment.setPermission("astools.ascmd.add.player", true); + attachment.setPermission("astools.ascmd.add.console", true); attachment.setPermission("astools.ascmd.execute", true); attachment.setPermission("astools.ascmd.cooldown", true); //attachment.setPermission("astools.bypass-wg-flag", true); diff --git a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/Utils.java b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/Utils.java index f0385d9..2a11010 100644 --- a/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/Utils.java +++ b/src/main/java/com/gmail/St3venAU/plugins/ArmorStandTools/Utils.java @@ -7,7 +7,6 @@ import org.bukkit.block.CommandBlock; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.EquipmentSlot; @@ -99,48 +98,6 @@ class Utils { return slotsDisabled; } - static ArmorStand cloneArmorStand(ArmorStand as) { - ArmorStand clone = (ArmorStand) as.getWorld().spawnEntity(as.getLocation().add(1, 0, 0), EntityType.ARMOR_STAND); - EntityEquipment asEquipment = as.getEquipment(); - EntityEquipment cloneEquipment = clone.getEquipment(); - if(asEquipment != null && cloneEquipment != null) { - cloneEquipment.setHelmet(asEquipment.getHelmet()); - cloneEquipment.setChestplate(asEquipment.getChestplate()); - cloneEquipment.setLeggings(asEquipment.getLeggings()); - cloneEquipment.setBoots(asEquipment.getBoots()); - cloneEquipment.setItemInMainHand(asEquipment.getItemInMainHand()); - cloneEquipment.setItemInOffHand(asEquipment.getItemInOffHand()); - } - clone.setHeadPose(as.getHeadPose()); - clone.setBodyPose(as.getBodyPose()); - clone.setLeftArmPose(as.getLeftArmPose()); - clone.setRightArmPose(as.getRightArmPose()); - clone.setLeftLegPose(as.getLeftLegPose()); - clone.setRightLegPose(as.getRightLegPose()); - clone.setGravity(as.hasGravity()); - clone.setVisible(as.isVisible()); - clone.setBasePlate(as.hasBasePlate()); - clone.setArms(as.hasArms()); - clone.setCustomName(as.getCustomName()); - clone.setCustomNameVisible(as.isCustomNameVisible()); - clone.setSmall(as.isSmall()); - clone.setInvulnerable(as.isInvulnerable()); - clone.setGlowing(as.isGlowing()); - for(EquipmentSlot slot : EquipmentSlot.values()) { - for(ArmorStand.LockType lockType : ArmorStand.LockType.values()) { - if(as.hasEquipmentLock(slot, lockType)) { - clone.addEquipmentLock(slot, lockType); - } - } - } - ArmorStandCmd asCmd = new ArmorStandCmd(as); - if(asCmd.getCommand() != null) { - asCmd.cloneTo(clone); - } - clone.setMetadata("clone", new FixedMetadataValue(AST.plugin, true)); - return clone; - } - static boolean hasPermissionNode(Player p, String perm) { if ((p == null) || p.isOp()) { return true; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 9f8add3..e3e77d7 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -25,10 +25,10 @@ # /astools Give yourself all of the armor stand tools (Note: Clears your inventory) # /astools reload Reload the armor stand tools plugin config file # /ast Alias for the /astools -# /ascmd assign console Assign a console command to the nearest armor stand (within 4 blocks) -# /ascmd assign player Assign a player command to the nearest armor stand -# /ascmd remove Remove the command assigned to the nearest armor stand -# /ascmd view View the command assigned to the nearest armor stand +# /ascmd add +# Add an assigned command to the nearest armor stand. See the assigning commands section below for more info. +# /ascmd remove Remove the command assigned to the nearest armor stand +# /ascmd list List the commands assigned to the nearest armor stand # /ascmd cooldown Set the cooldown for the command on nearest armor stand (Setting this overrides the default cooldown from config.yml) # /ascmd cooldown remove Remove the cooldown for the command on nearest armor stand (Default cooldown set in config.yml will be used) # Note: Commands may include a %player% placeholder, which will get replaced by the executing player's name at time of execution. @@ -41,20 +41,42 @@ # astools.summon Permission to use the summon tool (Summons an armor stand without requiring the materials) # astools.head Permission to use the player head tool (Ability to specify a player head for an armor stand) # astools.cmdblock Permission to use the save tool (Creates a command block) -# astools.ascmd.assign.console Permission to assign a console command to an armor stand -# astools.ascmd.assign.player Permission to assign a player command to an armor stand +# astools.glow Permission to use the glow tool (Glow effect on armor stands) +# astools.ascmd.add.console Permission to add a console command to an armor stand (Previously astools.ascmd.assign.console) +# astools.ascmd.add.player Permission to add a player command to an armor stand (Previously astools.ascmd.assign.player) +# astools.ascmd.add.bungee Permission to add a bungee command to an armor stand (See below for details) # astools.ascmd.remove Permission to remove a command from an armor stand -# astools.ascmd.view Permission to view the command assigned to an armor stand -# astools.ascmd.execute Permission to execute a command assigned to an armor stand by (on right click) -# astools.ascmd.cooldown Permission to add a cooldown to commands assigned to an armor stand +# astools.ascmd.list Permission to list the commands assigned to an armor stand (Previously astools.ascmd.view) +# astools.ascmd.cooldown Permission to add/remove a cooldown to commands assigned to an armor stand +# astools.ascmd.execute Permission to execute commands assigned to an armor stand by (on right click) # astools.bypass-wg-flag Permission to bypass AST's custom worldguard flag (see below for more details) # +# Assigning Commands to Armor Stands: +# - Stand close to the armor stand (The nearest armor stand within 4 blocks is selected) and use the command: +# /ascmd add +# - : When multiple commands are assigned, commands with the lowest priority number are executed first. +# Command with the same priority can be executed in any order. +# - : Delay in ticks before the command is executed. +# - : Player commands are executed as if they were typed by the player. +# Console commands are executed by the console. +# Bungee commands are a special case (see below). +# - : The command to be executed. To use the executing player's name in the command, use the +# placeholder %player% - it will be replaced with the players name at time of execution. +# - When a player with the astools.ascmd.execute permission right-clicks on an armor stand, commands assigned to that +# armor stand are executed. +# - Warning: Make sure you are careful when assigning console commands. Any player with the astools.ascmd.execute +# permission will be able to execute the command. +# - By default, any command assigned to an armor stand will use the default cooldown set in config.yml. This can be +# set on an individual basis using the /ascmd cooldown command. +# - Bungee commands: These are used to send the player to a different BungeeCord server. E.g. This will add a command +# to send the player to a server called server1: /ascmd add 0 0 bungee server1 +# # WorldGuard Integration: # - If a player does not have build permission for the region, that player will also be denied the use of AST in that region. -# - Additionally, there is a custom region flag named 'ast' which defaults to Allow, this can be changed to Deny if you wish to have a -# region in which players have build permission, but not permission to use AST. -# - The ast worldguard flag is ignored for any player with the permission node 'astools.bypass-wg-flag'. This means that a player with -# this permission can use AST in worldguard regions even if the ast flag for that region is set to Deny. +# - Additionally, there is a custom region flag named 'ast' which defaults to Allow, this can be changed to Deny if you wish +# to have a region in which players have build permission, but not permission to use AST. +# - The ast worldguard flag is ignored for any player with the permission node 'astools.bypass-wg-flag'. This means that a +# player with this permission can use AST in worldguard regions even if the ast flag for that region is set to Deny. # - The ast worldguard flag is also ignored for players that have op. # @@ -63,6 +85,7 @@ integrateWithWorldGuard: true allowMovingStandsBetweenWorlds: false deactivateToolsOnWorldChange: true requireCreativeForSaveAsCmdBlock: false +crouchRightClickOpensGUI: false defaultASCmdCooldownTicks: 0 bypassWorldguardForASCmdExecution: false saveToolCreatesCommandBlock: true @@ -135,4 +158,5 @@ enableTool: gui_larm: true gui_rarm: true gui_lleg: true - gui_rleg: true \ No newline at end of file + gui_rleg: true + gui_item: true \ No newline at end of file diff --git a/src/main/resources/language.yml b/src/main/resources/language.yml index f540308..e2b7457 100644 --- a/src/main/resources/language.yml +++ b/src/main/resources/language.yml @@ -63,19 +63,8 @@ guiInUse: 'This armor stands GUI is in use' # New since v2.4.0: noASNearBy: 'No armor stand found near by' closestAS: 'Closest armor stand' -hasNoCmd: 'has no command assigned' -hasCmd: 'has an assigned a command' type: 'Type' command: 'Command' -unassignedCmd: 'Unassigned command from armor stand' -assignedCmdToAS: 'Assigned command to closest armor stand' -assignCmdError: 'An error occured while assigning command to closest armor stand' -ascmdHelp: 'This command is used to assign a command to the nearest armor stand (within 4 blocks). The command is executed when the armor stand is right clicked.' -viewCmd: 'View assigned command' -removeCmd: 'Remove assigned command' -assignConsole: 'Assign a command to be executed by the console' -assignPlayer: 'Assign a command to be executed by the player' -executeCmdError: 'An error occured executing the command assigned to this armor stand' # New since v3.2.0 creativeRequired: 'Creative mode is required to save an armor stand as a command block' # New since v3.3.0 @@ -93,7 +82,30 @@ cmdNotAllowed: 'That command is not allowed while using Armor Stand Tools' crouch: 'Crouch' click: 'Click' finish: 'Finish' -# +#New since v4.3.0 +configuredArmorStand: 'Configured Armor Stand' +inventoryFull: 'Inventory full. Try again.' +name: 'Name' +inventory: 'Inventory' +items: 'Items' +stacks: 'Stacks' +cmdsAssigned: 'commands assigned' +invisible: 'Invisible' +invuln: 'Invulnerable' +glowing: 'Glowing' +attributes: 'Attributes' +hasTheseCmdsAssigned: 'has these commands assigned' +hasNoCmds: 'has no commands assigned' +priority: 'Priority' +delay: 'Delay' +errorExecutingCmd: 'An error occured executing command' +isNotValidNumber: 'is not a valid number' +removedFromAs: 'removed from armor stand' +listAssignedCmds: 'List assigned commands' +addACmd: 'Add a command' +removeACmd: 'Remove a command' +cmdHelp: 'This command is used to add/remove commands to the nearest armor stand (within 4 blocks). The commands are executed when the armor stand is right clicked, lowest priority first.' + ############################# # Tool names & descriptions # ############################# @@ -264,19 +276,31 @@ tool: lore: gui_head: name: 'Move Head' - lore: 'Change the head position' + lore: + - 'Change the head position' gui_body: name: 'Move Body' - lore: 'Change the body position' + lore: + - 'Change the body position' gui_larm: name: 'Move Left Arm' - lore: 'Change the left arm position' + lore: + - 'Change the left arm position' gui_rarm: name: 'Move Right Arm' - lore: 'Change the right arm position' + lore: + - 'Change the right arm position' gui_lleg: name: 'Move Left Leg' - lore: 'Change the left leg position' + lore: + - 'Change the left leg position' gui_rleg: name: 'Move Right Leg' - lore: 'Change the right leg position' \ No newline at end of file + lore: + - 'Change the right leg position' + gui_item: + name: 'Pick Up As Item' + lore: + - 'Create armor stand item for this armor stand' + - 'Creative mode: Armor stand stays' + - 'Survival mode: Armor stand is deleted' diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 5fa27f8..c4cb5be 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -11,5 +11,5 @@ commands: aliases: ast usage: Usage /astools or /astools reload ascmd: - description: View/Remove/Assign the command assigned to the nearest armor stand - usage: Usage /ascmd view, /ascmd remove, /ascmd assign \ No newline at end of file + description: List/Add/Remove commands assigned to the nearest armor stand + usage: Usage /ascmd list, /ascmd remove , /ascmd add \ No newline at end of file