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 extends ItemMeta> 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);