diff --git a/docs/permissions.md b/docs/permissions.md index 7ae600329..ab6d9fae8 100644 --- a/docs/permissions.md +++ b/docs/permissions.md @@ -39,6 +39,9 @@ The following permissions can be used to restrict functionality within the plugi * **coreprotect.consumer** *(default: op)* Allows access to the CoreProtect consumer command.   +* **coreprotect.give** *(default: false)* + Allows access to the CoreProtect give command. +   * **coreprotect.networking** *(default: op)* Allows access to the CoreProtect networking API. diff --git a/src/main/java/net/coreprotect/command/CommandHandler.java b/src/main/java/net/coreprotect/command/CommandHandler.java index 03faf4e93..340f92a29 100755 --- a/src/main/java/net/coreprotect/command/CommandHandler.java +++ b/src/main/java/net/coreprotect/command/CommandHandler.java @@ -76,6 +76,9 @@ else if (user.hasPermission("coreprotect.consumer") && corecommand.equals("consu else if (user.hasPermission("coreprotect.networking") && corecommand.equals("network-debug")) { permission = true; } + else if (user.hasPermission("coreprotect.give") && corecommand.equals("give")) { + permission = true; + } } if (corecommand.equals("rollback") || corecommand.equals("restore") || corecommand.equals("rb") || corecommand.equals("rs") || corecommand.equals("ro") || corecommand.equals("re")) { @@ -120,6 +123,9 @@ else if (corecommand.equals("consumer")) { else if (corecommand.equals("network-debug")) { NetworkDebugCommand.runCommand(user, permission, argumentArray); } + else if (corecommand.equals("give")) { + GiveCommand.runCommand(user, command, permission, argumentArray); + } else if (corecommand.equals("migrate-db")) { if (!VersionUtils.validDonationKey()) { Chat.sendMessage(user, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.DONATION_KEY_REQUIRED)); diff --git a/src/main/java/net/coreprotect/command/CommandParser.java b/src/main/java/net/coreprotect/command/CommandParser.java index 56e855df9..84e0145d9 100644 --- a/src/main/java/net/coreprotect/command/CommandParser.java +++ b/src/main/java/net/coreprotect/command/CommandParser.java @@ -5,17 +5,11 @@ import java.util.Map; import java.util.Set; +import net.coreprotect.command.parser.*; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.command.CommandSender; -import net.coreprotect.command.parser.ActionParser; -import net.coreprotect.command.parser.LocationParser; -import net.coreprotect.command.parser.MaterialParser; -import net.coreprotect.command.parser.TimeParser; -import net.coreprotect.command.parser.UserParser; -import net.coreprotect.command.parser.WorldParser; - /** * Main parser class for CoreProtect commands. * Delegates to specialized parser classes for specific functionality. @@ -326,4 +320,7 @@ private static String timeString(BigDecimal input) { return input.stripTrailingZeros().toPlainString(); } + protected static Integer parseGivableItemId(String[] inputArguments) { + return GivableItemIdParser.parseGivableItemId(inputArguments); + } } diff --git a/src/main/java/net/coreprotect/command/GiveCommand.java b/src/main/java/net/coreprotect/command/GiveCommand.java new file mode 100644 index 000000000..0ab2d2b4c --- /dev/null +++ b/src/main/java/net/coreprotect/command/GiveCommand.java @@ -0,0 +1,40 @@ +package net.coreprotect.command; + +import net.coreprotect.language.Phrase; +import net.coreprotect.utility.Chat; +import net.coreprotect.utility.ChatMessage; +import net.coreprotect.utility.Color; +import net.coreprotect.utility.ItemUtils; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class GiveCommand { + public static void runCommand(CommandSender sender, Command command, boolean permission, String[] args) { + if (!permission) { + Chat.sendMessage(sender, new ChatMessage(Phrase.build(Phrase.NO_PERMISSION)).build()); + return; + } + + Integer itemId = CommandParser.parseGivableItemId(args); + if (itemId == null) { + Chat.sendMessage(sender, new ChatMessage(Phrase.build(Phrase.MISSING_PARAMETERS, Color.WHITE, "/" + command.getName() + " give ")).build()); + return; + } + + ItemStack item = ItemUtils.getGivableItem(itemId); + if (item == null) { + Chat.sendMessage(sender, new ChatMessage(Phrase.build(Phrase.INVALID_ITEM_ID)).build()); + return; + } + + if (!(sender instanceof Player)) { + Chat.sendMessage(sender, new ChatMessage(Phrase.build(Phrase.ACTION_NOT_SUPPORTED)).build()); + return; + } + + Player player = (Player) sender; + player.getInventory().addItem(item); + } +} diff --git a/src/main/java/net/coreprotect/command/lookup/StandardLookupThread.java b/src/main/java/net/coreprotect/command/lookup/StandardLookupThread.java index cbeba9209..b95387229 100644 --- a/src/main/java/net/coreprotect/command/lookup/StandardLookupThread.java +++ b/src/main/java/net/coreprotect/command/lookup/StandardLookupThread.java @@ -292,6 +292,7 @@ else if (LookupActions.isInventoryLookup(actions)) { String dname = StringUtils.nameFilter(blockType.name().toLowerCase(Locale.ROOT), ddata); byte[] metadata = data[11] == null ? null : data[11].getBytes(StandardCharsets.ISO_8859_1); String tooltip = ItemUtils.getEnchantments(metadata, dtype, amount); + Integer itemId = ItemUtils.makeGivableItem(ItemUtils.getItemStack(metadata, dtype, amount)); String selector = Selector.FIRST; String tag = Color.WHITE + "-"; @@ -320,7 +321,7 @@ else if (daction == ItemTransactionActions.SELL || daction == ItemTransactionAct tag = (daction == ItemTransactionActions.REMOVE ? Color.GREEN + "+" : Color.RED + "-"); } - Chat.sendComponent(player, timeago + " " + tag + " " + Phrase.build(Phrase.LOOKUP_CONTAINER, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, "x" + amount, ChatUtils.createTooltip(Color.DARK_AQUA + rbd + dname, tooltip) + Color.WHITE, selector)); + Chat.sendComponent(player, timeago + " " + tag + " " + Phrase.build(Phrase.LOOKUP_CONTAINER, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, "x" + amount, ChatUtils.createTooltip(Color.DARK_AQUA + rbd + dname, tooltip) + ChatUtils.filterComponent(player.hasPermission("coreprotect.give"), ChatUtils.createGiveItemComponent(Color.GREY + "(↓)", command.getName(), itemId)) + Color.WHITE, selector)); PluginChannelListener.getInstance().sendData(player, Integer.parseInt(time), Phrase.LOOKUP_CONTAINER, selector, dplayer, dname, amount, dataX, dataY, dataZ, wid, rbd, true, tag.contains("+")); } } @@ -389,6 +390,7 @@ else if (daction == ItemTransactionActions.SELL || daction == ItemTransactionAct if (actions.contains(LookupActions.CONTAINER) || actions.contains(5) || actions.contains(LookupActions.ITEM) || amount > -1) { byte[] metadata = data[11] == null ? null : data[11].getBytes(StandardCharsets.ISO_8859_1); String tooltip = ItemUtils.getEnchantments(metadata, dtype, amount); + Integer itemId = ItemUtils.makeGivableItem(ItemUtils.getItemStack(metadata, dtype, amount)); if (daction == ItemTransactionActions.DROP || daction == ItemTransactionActions.PICKUP) { phrase = Phrase.LOOKUP_ITEM; // {picked up|dropped} @@ -415,7 +417,7 @@ else if (daction == ItemTransactionActions.THROW || daction == ItemTransactionAc action = "a:container"; } - Chat.sendComponent(player, timeago + " " + tag + " " + Phrase.build(phrase, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, "x" + amount, ChatUtils.createTooltip(Color.DARK_AQUA + rbd + dname, tooltip) + Color.WHITE, selector)); + Chat.sendComponent(player, timeago + " " + tag + " " + Phrase.build(phrase, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, "x" + amount, ChatUtils.createTooltip(Color.DARK_AQUA + rbd + dname, tooltip) + ChatUtils.filterComponent(player.hasPermission("coreprotect.give"), ChatUtils.createGiveItemComponent(Color.GREY + "(↓)", command.getName(), itemId)) + Color.WHITE, selector)); PluginChannelListener.getInstance().sendData(player, Integer.parseInt(time), phrase, selector, dplayer, dname, (tag.contains("+") ? 1 : -1), dataX, dataY, dataZ, wid, rbd, action.contains("container"), tag.contains("+")); } else { diff --git a/src/main/java/net/coreprotect/command/parser/GivableItemIdParser.java b/src/main/java/net/coreprotect/command/parser/GivableItemIdParser.java new file mode 100644 index 000000000..ffa088477 --- /dev/null +++ b/src/main/java/net/coreprotect/command/parser/GivableItemIdParser.java @@ -0,0 +1,19 @@ +package net.coreprotect.command.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class GivableItemIdParser { + + protected static final Pattern PATTERN = Pattern.compile("#([0-9]+)"); + + public static Integer parseGivableItemId(String[] inputArguments) { + for (String argument : inputArguments) { + Matcher matcher = PATTERN.matcher(argument); + if (matcher.find()) { + return Integer.parseInt(matcher.group(1)); + } + } + return null; + } +} diff --git a/src/main/java/net/coreprotect/language/Language.java b/src/main/java/net/coreprotect/language/Language.java index 37e7bda93..1d9ca23af 100644 --- a/src/main/java/net/coreprotect/language/Language.java +++ b/src/main/java/net/coreprotect/language/Language.java @@ -127,6 +127,7 @@ public static void loadPhrases() { phrases.put(Phrase.INVALID_INCLUDE, "\"{0}\" is an invalid block/entity name."); phrases.put(Phrase.INVALID_INCLUDE_COMBO, "That is an invalid block/entity combination."); phrases.put(Phrase.INVALID_PARAMETER, "\"{0}\" is not a supported parameter."); + phrases.put(Phrase.INVALID_ITEM_ID, "Please enter a valid item id."); phrases.put(Phrase.INVALID_RADIUS, "Please enter a valid radius."); phrases.put(Phrase.INVALID_SELECTION, "{0} selection not found."); phrases.put(Phrase.INVALID_USERNAME, "\"{0}\" is an invalid username."); diff --git a/src/main/java/net/coreprotect/language/Phrase.java b/src/main/java/net/coreprotect/language/Phrase.java index d844763e6..ab85c0fca 100644 --- a/src/main/java/net/coreprotect/language/Phrase.java +++ b/src/main/java/net/coreprotect/language/Phrase.java @@ -110,6 +110,7 @@ public enum Phrase { INVALID_INCLUDE, INVALID_INCLUDE_COMBO, INVALID_PARAMETER, + INVALID_ITEM_ID, INVALID_RADIUS, INVALID_SELECTION, INVALID_USERNAME, diff --git a/src/main/java/net/coreprotect/utility/ChatUtils.java b/src/main/java/net/coreprotect/utility/ChatUtils.java index 8dbd9f614..e208d2455 100644 --- a/src/main/java/net/coreprotect/utility/ChatUtils.java +++ b/src/main/java/net/coreprotect/utility/ChatUtils.java @@ -185,8 +185,20 @@ public static String createTooltip(String phrase, String tooltip) { return message.append(Chat.COMPONENT_TAG_CLOSE).toString(); } + public static String createGiveItemComponent(String phrase, String command, Integer itemId) { + if (itemId == null) { + return ""; + } + + return Chat.COMPONENT_TAG_OPEN + Chat.COMPONENT_COMMAND + "|/" + command + " give #" + itemId + "|" + phrase + Chat.COMPONENT_TAG_CLOSE; + } + // This theoretically initializes the component code, to prevent gson adapter errors public static void sendConsoleComponentStartup(ConsoleCommandSender consoleSender, String string) { Chat.sendComponent(consoleSender, Color.RESET + "[CoreProtect] " + string + Chat.COMPONENT_TAG_OPEN + Chat.COMPONENT_POPUP + "| | " + Chat.COMPONENT_TAG_CLOSE); } + + public static String filterComponent(boolean condition, String component) { + return condition ? component : ""; + } } \ No newline at end of file diff --git a/src/main/java/net/coreprotect/utility/ItemUtils.java b/src/main/java/net/coreprotect/utility/ItemUtils.java index 97e2fa97a..1a2d05633 100644 --- a/src/main/java/net/coreprotect/utility/ItemUtils.java +++ b/src/main/java/net/coreprotect/utility/ItemUtils.java @@ -2,16 +2,7 @@ import java.io.ByteArrayOutputStream; import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -37,6 +28,7 @@ import net.coreprotect.utility.serialize.ItemMetaHandler; public class ItemUtils { + private static final Map GIVABLE_ITEMS = Collections.synchronizedMap(new LinkedHashMap<>()); private static final Object UNSERIALIZABLE_VALUE = new Object(); private static final Logger LOGGER = Logger.getLogger("CoreProtect"); @@ -70,6 +62,19 @@ private ItemUtils() { throw new IllegalStateException("Utility class"); } + public static ItemStack getGivableItem(int id) { + //we can use skip here because it's a linked map from which elements are never removed + return GIVABLE_ITEMS.keySet().stream().skip(id).findFirst().orElse(null); + } + + public static Integer makeGivableItem(ItemStack item) { + if (item == null) { + return null; + } + + return GIVABLE_ITEMS.computeIfAbsent(item, k -> GIVABLE_ITEMS.size()); + } + public static void mergeItems(Material material, ItemStack[] items) { if (material != null && (material.equals(Material.ARMOR_STAND) || BukkitAdapter.ADAPTER.isItemFrame(material))) { return; @@ -646,14 +651,21 @@ public static ItemMeta deserializeItemMeta(Class itemMetaCla return null; } - - public static String getEnchantments(byte[] metadata, int type, int amount) { + + public static ItemStack getItemStack(byte[] metadata, int type, int amount) { if (metadata == null) { - return ""; + return null; } ItemStack item = new ItemStack(MaterialUtils.getType(type), amount); item = (ItemStack) net.coreprotect.database.rollback.Rollback.populateItemStack(item, metadata)[2]; + return item; + } + + public static String getEnchantments(byte[] metadata, int type, int amount) { + var item = getItemStack(metadata, type, amount); + if (item == null) return ""; + String displayName = item.hasItemMeta() && item.getItemMeta().hasDisplayName() ? item.getItemMeta().getDisplayName() : ""; StringBuilder message = new StringBuilder(Color.ITALIC + displayName + Color.GREY); @@ -675,7 +687,7 @@ else if (!enchantments.isEmpty()) { return message.toString(); } - + public static Map serializeItemStackLegacy(ItemStack itemStack, String faceData, int slot) { Map result = new HashMap<>(); Map itemMap = serializeItemStack(itemStack, faceData, slot); @@ -730,4 +742,4 @@ public static ItemStack unserializeItemStack(Object value) { return result; } -} +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 25c5ad3a3..037eb1617 100755 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -53,6 +53,7 @@ permissions: coreprotect.status: true coreprotect.consumer: true coreprotect.networking: true + coreprotect.give: true coreprotect.co: description: Has permission to access the CoreProtect /co command default: true @@ -144,3 +145,6 @@ permissions: coreprotect.networking: description: Has permission to use the networking API default: op + coreproctect.give: + description: Has permission to use the give command + default: false