diff --git a/docs/permissions.md b/docs/permissions.md index a70f719c5..9d72d7422 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: op)* + Allows access to the CoreProtect give command. +   * **coreprotect.networking** *(default: op)* Allows access to the CoreProtect networking API. diff --git a/pom.xml b/pom.xml index 8ad0cbec2..0a1497c75 100755 --- a/pom.xml +++ b/pom.xml @@ -2,9 +2,9 @@ 4.0.0 net.coreprotect CoreProtect - 23.0 + 23.1 - + development UTF-8 true 11 diff --git a/src/main/java/net/coreprotect/command/CommandHandler.java b/src/main/java/net/coreprotect/command/CommandHandler.java index 52a407bff..f5ebadfc7 100755 --- a/src/main/java/net/coreprotect/command/CommandHandler.java +++ b/src/main/java/net/coreprotect/command/CommandHandler.java @@ -75,6 +75,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")) { @@ -119,6 +122,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 f6540ff07..e67314713 100644 --- a/src/main/java/net/coreprotect/command/lookup/StandardLookupThread.java +++ b/src/main/java/net/coreprotect/command/lookup/StandardLookupThread.java @@ -289,6 +289,7 @@ else if (actions.contains(4) && actions.contains(11)) { // inventory transaction 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 + "-"; @@ -317,7 +318,7 @@ else if (daction == ItemLogger.ITEM_SELL || daction == ItemLogger.ITEM_BUY) { // tag = (daction == 0 ? 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.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("+")); } } @@ -387,6 +388,7 @@ else if (daction == ItemLogger.ITEM_SELL || daction == ItemLogger.ITEM_BUY) { // if (actions.contains(4) || actions.contains(5) || actions.contains(11) || 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 == 2 || daction == 3) { phrase = Phrase.LOOKUP_ITEM; // {picked up|dropped} @@ -413,7 +415,7 @@ else if (daction == 6 || daction == 7) { 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.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 32ffac5c1..de2ece753 100644 --- a/src/main/java/net/coreprotect/language/Language.java +++ b/src/main/java/net/coreprotect/language/Language.java @@ -123,6 +123,7 @@ public static void loadPhrases() { phrases.put(Phrase.INVALID_DONATION_KEY, "Invalid donation key."); 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_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 51da5f637..b8a1a4efd 100644 --- a/src/main/java/net/coreprotect/language/Phrase.java +++ b/src/main/java/net/coreprotect/language/Phrase.java @@ -106,6 +106,7 @@ public enum Phrase { INVALID_DONATION_KEY, INVALID_INCLUDE, INVALID_INCLUDE_COMBO, + 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..1124ec86c 100644 --- a/src/main/java/net/coreprotect/utility/ChatUtils.java +++ b/src/main/java/net/coreprotect/utility/ChatUtils.java @@ -185,6 +185,14 @@ 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); diff --git a/src/main/java/net/coreprotect/utility/ItemUtils.java b/src/main/java/net/coreprotect/utility/ItemUtils.java index 7a9dc2f9b..873387915 100644 --- a/src/main/java/net/coreprotect/utility/ItemUtils.java +++ b/src/main/java/net/coreprotect/utility/ItemUtils.java @@ -5,6 +5,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -30,11 +31,25 @@ import net.coreprotect.utility.serialize.ItemMetaHandler; public class ItemUtils { + private static final Map GIVABLE_ITEMS = Collections.synchronizedMap(new LinkedHashMap<>()); 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; @@ -372,14 +387,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); @@ -401,7 +423,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);