diff --git a/pom.xml b/pom.xml
index 2c1cb4e..b931547 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
CustomItemsLib
jar
- 1.1.0-SNAPSHOT
+ 1.1.0-JL.10
Rocologo's CustomItems Library
https://github.com/Rocologo/CustomItemsLib
CustomItemsLib is a library with code shared between MobHunting and BagOfGold.
@@ -24,7 +24,7 @@
- 4.5.6-SNAPSHOT
+ 4.5.6-JL.2
8.5.5-SNAPSHOT
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 720dbfa..cd49fa3 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -7,7 +7,7 @@ author: Rocologo
depend: []
loadbefore: [BagOfGold, MobHunting]
softdepend: [Reserve, Vault, PerWorldInventory, Citizens, Essentials, ProtocolLib, PacketListenerApi, ItemFrameShops, Multiverse-Core, TitleManager, CMI, CMILib, BossBarAPI, BarAPI, ActionBar, TitleAPI, TitleManager, ActionBarAPI, ActionAnnouncer]
-api-version: 1.13
+api-version: 1.21
commands:
customitemslib:
diff --git a/src/one/lindegaard/CustomItemsLib/Core.java b/src/one/lindegaard/CustomItemsLib/Core.java
index cf87b03..1324d13 100644
--- a/src/one/lindegaard/CustomItemsLib/Core.java
+++ b/src/one/lindegaard/CustomItemsLib/Core.java
@@ -32,7 +32,9 @@
import one.lindegaard.CustomItemsLib.config.ConfigManager;
import one.lindegaard.CustomItemsLib.messages.Messages;
import one.lindegaard.CustomItemsLib.rewards.CoreRewardManager;
+import one.lindegaard.CustomItemsLib.rewards.RewardSecurity;
import one.lindegaard.CustomItemsLib.rewards.RewardBlockManager;
+import one.lindegaard.CustomItemsLib.rewards.TokenSpendStore;
import one.lindegaard.CustomItemsLib.storage.DataStoreException;
import one.lindegaard.CustomItemsLib.storage.DataStoreManager;
import one.lindegaard.CustomItemsLib.storage.IDataStore;
@@ -54,6 +56,8 @@ public class Core extends JavaPlugin {
private static DataStoreManager mDataStoreManager;
private static PlayerSettingsManager mPlayerSettingsManager;
private static CoreRewardManager mCoreRewardManager;
+ private static RewardSecurity mRewardSecurity;
+ private static TokenSpendStore mTokenSpendStore;
private static CompatibilityManager mCompatibilityManager;
private CommandDispatcher mCommandDispatcher;
private SpigetUpdater mSpigetUpdater;
@@ -117,6 +121,14 @@ public void onEnable() {
mMessages.setLanguage(mConfig.language + ".lang");
mMessages.debug("Loading config.yml file, version %s", config_version);
+ mRewardSecurity = new RewardSecurity(this);
+ try {
+ mRewardSecurity.initialize();
+ } catch (IOException ex) {
+ Bukkit.getConsoleSender().sendMessage(PREFIX_ERROR + "Could not initialize security.yml: " + ex.getMessage());
+ throw new RuntimeException("[CustomItemsLib] Could not initialize security.yml", ex);
+ }
+
List itemtypes = Arrays.asList("SKULL", "ITEM", "KILLER", "KILLED", "GRINGOTTS_STYLE");
if (!itemtypes.contains(mConfig.rewardItemtype)) {
Bukkit.getConsoleSender().sendMessage(PREFIX + ChatColor.RED
@@ -153,6 +165,14 @@ public void onEnable() {
return;
}
+ mTokenSpendStore = new TokenSpendStore(this);
+ try {
+ mTokenSpendStore.initialize();
+ } catch (Exception ex) {
+ Bukkit.getConsoleSender().sendMessage(PREFIX_ERROR + "Could not initialize token spend store: " + ex.getMessage());
+ throw new RuntimeException("[CustomItemsLib] Could not initialize token spend store", ex);
+ }
+
mDataStoreManager = new DataStoreManager(plugin, mStore);
mPlayerSettingsManager = new PlayerSettingsManager(plugin);
mCoreRewardManager = new CoreRewardManager(plugin);
@@ -175,6 +195,7 @@ public void onEnable() {
// Hook into Vault or Reserve
mEconomyManager = new EconomyManager(this);
+ Bukkit.getPluginManager().registerEvents(new EconomyProviderListener(), this);
if (!mEconomyManager.isActive())
return;
@@ -238,6 +259,14 @@ public static CoreRewardManager getCoreRewardManager() {
return mCoreRewardManager;
}
+ public static RewardSecurity getRewardSecurity() {
+ return mRewardSecurity;
+ }
+
+ public static TokenSpendStore getTokenSpendStore() {
+ return mTokenSpendStore;
+ }
+
public SpigetUpdater getSpigetUpdater() {
return mSpigetUpdater;
}
diff --git a/src/one/lindegaard/CustomItemsLib/EconomyManager.java b/src/one/lindegaard/CustomItemsLib/EconomyManager.java
index 6869922..4250a96 100644
--- a/src/one/lindegaard/CustomItemsLib/EconomyManager.java
+++ b/src/one/lindegaard/CustomItemsLib/EconomyManager.java
@@ -1,6 +1,7 @@
package one.lindegaard.CustomItemsLib;
import java.math.BigDecimal;
+import java.util.Collection;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@@ -68,6 +69,27 @@ private void setVersion(String version) {
this.version = version;
}
+ private RegisteredServiceProvider getPreferredVaultProvider() {
+ Collection> providers = plugin.getServer().getServicesManager()
+ .getRegistrations(Economy.class);
+
+ RegisteredServiceProvider bagOfGoldProvider = null;
+ for (RegisteredServiceProvider provider : providers) {
+ if (provider == null || provider.getProvider() == null)
+ continue;
+ if ("BagOfGold".equalsIgnoreCase(provider.getProvider().getName())) {
+ if (bagOfGoldProvider == null
+ || provider.getPriority().compareTo(bagOfGoldProvider.getPriority()) > 0)
+ bagOfGoldProvider = provider;
+ }
+ }
+
+ if (bagOfGoldProvider != null)
+ return bagOfGoldProvider;
+
+ return plugin.getServer().getServicesManager().getRegistration(Economy.class);
+ }
+
/**
* Find and configure a suitable economy provider
*
@@ -75,22 +97,25 @@ private void setVersion(String version) {
*/
public Boolean setupEconomy() {
Plugin economyProvider = null;
+ EcoType previousType = Type;
+ String previousVersion = getVersion();
/*
* Attempt to find Vault for Economy handling
*/
try {
- RegisteredServiceProvider vaultEcoProvider = plugin.getServer().getServicesManager()
- .getRegistration(net.milkbowl.vault.economy.Economy.class);
+ RegisteredServiceProvider vaultEcoProvider = getPreferredVaultProvider();
if (vaultEcoProvider != null) {
/*
* Flag as using Vault hooks
*/
vaultEconomy = vaultEcoProvider.getProvider();
+ reserveEconomy = null;
setVersion(String.format("%s %s", vaultEcoProvider.getProvider().getName(), "via Vault"));
- Bukkit.getConsoleSender().sendMessage(
- Core.PREFIX + "CustomItemsLib is using " + getVersion() + " as Economy Provider");
Type = EcoType.VAULT;
+ if (previousType != Type || !previousVersion.equals(getVersion()))
+ Bukkit.getConsoleSender()
+ .sendMessage(Core.PREFIX + "CustomItemsLib is using " + getVersion() + " as Economy Provider");
return true;
}
} catch (NoClassDefFoundError ex) {
@@ -105,12 +130,18 @@ public Boolean setupEconomy() {
* Flat as using Reserve Hooks.
*/
reserveEconomy = ((Reserve) economyProvider).economy();
+ vaultEconomy = null;
setVersion(String.format("%s %s", reserveEconomy.name(), "via Reserve"));
- Bukkit.getConsoleSender()
- .sendMessage(Core.PREFIX + "CustomItemsLib is using " + getVersion() + " as Economy Provider");
Type = EcoType.RESERVE;
+ if (previousType != Type || !previousVersion.equals(getVersion()))
+ Bukkit.getConsoleSender()
+ .sendMessage(Core.PREFIX + "CustomItemsLib is using " + getVersion() + " as Economy Provider");
return true;
}
+ vaultEconomy = null;
+ reserveEconomy = null;
+ setVersion("");
+ Type = EcoType.NONE;
return false;
}
diff --git a/src/one/lindegaard/CustomItemsLib/EconomyProviderListener.java b/src/one/lindegaard/CustomItemsLib/EconomyProviderListener.java
new file mode 100644
index 0000000..6a604c5
--- /dev/null
+++ b/src/one/lindegaard/CustomItemsLib/EconomyProviderListener.java
@@ -0,0 +1,25 @@
+package one.lindegaard.CustomItemsLib;
+
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.server.PluginEnableEvent;
+import org.bukkit.event.server.ServerLoadEvent;
+
+public class EconomyProviderListener implements Listener {
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onPluginEnable(PluginEnableEvent event) {
+ if (!"BagOfGold".equalsIgnoreCase(event.getPlugin().getName()))
+ return;
+
+ if (Core.getEconomyManager() != null)
+ Core.getEconomyManager().setupEconomy();
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onServerLoad(ServerLoadEvent event) {
+ if (Core.getEconomyManager() != null)
+ Core.getEconomyManager().setupEconomy();
+ }
+}
diff --git a/src/one/lindegaard/CustomItemsLib/compatibility/ProtocolLibHelper.java b/src/one/lindegaard/CustomItemsLib/compatibility/ProtocolLibHelper.java
index 44ee6b1..89bb407 100644
--- a/src/one/lindegaard/CustomItemsLib/compatibility/ProtocolLibHelper.java
+++ b/src/one/lindegaard/CustomItemsLib/compatibility/ProtocolLibHelper.java
@@ -1,88 +1,78 @@
-package one.lindegaard.CustomItemsLib.compatibility;
-
-import java.util.Iterator;
+package one.lindegaard.CustomItemsLib.compatibility;
+
import java.util.List;
-
-import org.bukkit.GameMode;
+import org.bukkit.ChatColor;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
-import com.comphenix.packetwrapper.WrapperPlayServerCollect;
-import com.comphenix.protocol.PacketType;
-import com.comphenix.protocol.ProtocolLibrary;
-import com.comphenix.protocol.ProtocolManager;
-import com.comphenix.protocol.events.ListenerPriority;
-import com.comphenix.protocol.events.PacketAdapter;
-import com.comphenix.protocol.events.PacketContainer;
-import com.comphenix.protocol.events.PacketEvent;
-import com.comphenix.protocol.reflect.StructureModifier;
-
-import one.lindegaard.CustomItemsLib.Core;
-
-public class ProtocolLibHelper {
-
- private static ProtocolManager protocolManager;
-
+import com.comphenix.packetwrapper.WrapperPlayServerCollect;
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.ProtocolManager;
+import com.comphenix.protocol.events.ListenerPriority;
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.events.PacketEvent;
+import com.comphenix.protocol.reflect.StructureModifier;
+
+import one.lindegaard.CustomItemsLib.Core;
+
+public class ProtocolLibHelper {
+
+ private static ProtocolManager protocolManager;
+
public static void enableProtocolLib() {
protocolManager = ProtocolLibrary.getProtocolManager();
- ProtocolLibrary.getProtocolManager()
- .addPacketListener(new PacketAdapter(Core.getInstance(), ListenerPriority.HIGHEST,
- PacketType.Play.Server.SET_SLOT, PacketType.Play.Server.WINDOW_ITEMS,
- PacketType.Play.Server.RECIPES, PacketType.Play.Server.RECIPE_UPDATE) {
+ // Only hook packet types that carry inventory items on modern Paper versions.
+ // Registering every enum value causes ProtocolLib warnings for legacy/unregistered packets.
+ protocolManager.addPacketListener(new PacketAdapter(Core.getInstance(), ListenerPriority.HIGHEST,
+ PacketType.Play.Server.SET_SLOT, PacketType.Play.Server.WINDOW_ITEMS) {
@Override
public void onPacketSending(PacketEvent event) {
- if (event.getPacketType() == PacketType.Play.Server.SET_SLOT) {
- PacketContainer packet = event.getPacket().deepClone();
- StructureModifier sm = packet.getItemModifier();
- for (int i = 0; i < sm.size(); i++) {
- ItemStack is = sm.getValues().get(i);
- if (is.hasItemMeta()) {
- ItemMeta itemMeta = is.getItemMeta();
- if (itemMeta.hasLore()) {
- List lore = itemMeta.getLore();
- Iterator itr = lore.iterator();
- while (itr.hasNext()) {
- String str = itr.next();
- if (str.startsWith("Hidden("))
- if (event.getPlayer().getGameMode() == GameMode.SURVIVAL)
- itr.remove();
- }
- itemMeta.setLore(lore);
- is.setItemMeta(itemMeta);
- }
- }
+ boolean hideInternalLore = shouldHideInternalLore(event);
+ if (!hideInternalLore) {
+ return;
+ }
+
+ PacketContainer packet = event.getPacket().deepClone();
+ boolean changed = false;
+
+ StructureModifier itemModifier = packet.getItemModifier();
+ for (int i = 0; i < itemModifier.size(); i++) {
+ ItemStack itemStack = itemModifier.read(i);
+ ItemStack sanitized = sanitizeHiddenLore(itemStack);
+ if (sanitized != itemStack) {
+ itemModifier.write(i, sanitized);
+ changed = true;
}
- event.setPacket(packet);
}
- else if (event.getPacketType() == PacketType.Play.Server.WINDOW_ITEMS) {
- PacketContainer packet = event.getPacket().deepClone();
- StructureModifier> modifiers = packet.getItemListModifier();
- for (int j = 0; j < modifiers.size(); j++) {
- List itemStackList = modifiers.getValues().get(j);
- for (int i = 0; i < itemStackList.size(); i++) {
- ItemStack is = itemStackList.get(i);
- if (is.hasItemMeta()) {
- ItemMeta itemMeta = is.getItemMeta();
- if (itemMeta.hasLore()) {
- List lore = itemMeta.getLore();
- Iterator itr = lore.iterator();
- while (itr.hasNext()) {
- String str = itr.next();
- if (str.startsWith("Hidden("))
- if (event.getPlayer().getGameMode() == GameMode.SURVIVAL) {
-// BagOfGold.getInstance().getMessages().debug("ProtocolLibHelper:ItemSlots=%s", event.getPacket().getItemSlots().toString());
- itr.remove();
- }
- }
- itemMeta.setLore(lore);
- is.setItemMeta(itemMeta);
- }
- }
+ StructureModifier> listModifier = packet.getItemListModifier();
+ for (int i = 0; i < listModifier.size(); i++) {
+ List itemList = listModifier.read(i);
+ if (itemList == null || itemList.isEmpty()) {
+ continue;
+ }
+
+ boolean listChanged = false;
+ for (int j = 0; j < itemList.size(); j++) {
+ ItemStack original = itemList.get(j);
+ ItemStack sanitized = sanitizeHiddenLore(original);
+ if (sanitized != original) {
+ itemList.set(j, sanitized);
+ listChanged = true;
}
}
+
+ if (listChanged) {
+ listModifier.write(i, itemList);
+ changed = true;
+ }
+ }
+
+ if (changed) {
event.setPacket(packet);
}
}
@@ -91,15 +81,82 @@ else if (event.getPacketType() == PacketType.Play.Server.WINDOW_ITEMS) {
});
}
- public static ProtocolManager getProtocolmanager() {
- return protocolManager;
+ private static boolean shouldHideInternalLore(PacketEvent event) {
+ if (event == null) {
+ return false;
+ }
+
+ Player player = event.getPlayer();
+ if (player == null) {
+ return false;
+ }
+
+ // Hidden(...) lore is internal metadata and must never be visible to clients.
+ // Always strip it for packet-rendered items (Java + Bedrock via Geyser/Floodgate).
+ return true;
+ }
+
+ private static boolean isInternalHiddenLoreLine(String line) {
+ if (line == null || line.isEmpty()) {
+ return false;
+ }
+
+ String plain = ChatColor.stripColor(line);
+ if (plain == null) {
+ return false;
+ }
+
+ return plain.trim().startsWith("Hidden(");
}
- public static void pickupMoney(Player player, Entity ent) {
- WrapperPlayServerCollect wpsc = new WrapperPlayServerCollect();
- wpsc.setCollectedEntityId(ent.getEntityId());
- wpsc.setCollectorEntityId(player.getEntityId());
- wpsc.sendPacket(player);
+ private static ItemStack sanitizeHiddenLore(ItemStack itemStack) {
+ if (itemStack == null || !itemStack.hasItemMeta()) {
+ return itemStack;
+ }
+
+ ItemMeta itemMeta = itemStack.getItemMeta();
+ if (itemMeta == null || !itemMeta.hasLore()) {
+ return itemStack;
+ }
+
+ List lore = itemMeta.getLore();
+ if (lore == null || lore.isEmpty()) {
+ return itemStack;
+ }
+
+ List filtered = new java.util.ArrayList<>(lore.size());
+ boolean removed = false;
+ for (String line : lore) {
+ if (isInternalHiddenLoreLine(line)) {
+ removed = true;
+ continue;
+ }
+ filtered.add(line);
+ }
+
+ if (!removed) {
+ return itemStack;
+ }
+
+ ItemStack cloned = itemStack.clone();
+ ItemMeta clonedMeta = cloned.getItemMeta();
+ if (clonedMeta == null) {
+ return itemStack;
+ }
+ clonedMeta.setLore(filtered.isEmpty() ? null : filtered);
+ cloned.setItemMeta(clonedMeta);
+ return cloned;
}
-}
+ public static ProtocolManager getProtocolmanager() {
+ return protocolManager;
+ }
+
+ public static void pickupMoney(Player player, Entity ent) {
+ WrapperPlayServerCollect wpsc = new WrapperPlayServerCollect();
+ wpsc.setCollectedEntityId(ent.getEntityId());
+ wpsc.setCollectorEntityId(player.getEntityId());
+ wpsc.sendPacket(player);
+ }
+
+}
diff --git a/src/one/lindegaard/CustomItemsLib/rewards/CoreCustomItems.java b/src/one/lindegaard/CustomItemsLib/rewards/CoreCustomItems.java
index 2e14808..02b4e33 100644
--- a/src/one/lindegaard/CustomItemsLib/rewards/CoreCustomItems.java
+++ b/src/one/lindegaard/CustomItemsLib/rewards/CoreCustomItems.java
@@ -1,388 +1,422 @@
-package one.lindegaard.CustomItemsLib.rewards;
-
+package one.lindegaard.CustomItemsLib.rewards;
+
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
+import java.util.Base64;
import java.util.UUID;
-
-import org.bukkit.Bukkit;
-import org.bukkit.Material;
-import org.bukkit.OfflinePlayer;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
-
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.mojang.authlib.GameProfile;
-import com.mojang.authlib.properties.Property;
-
-import one.lindegaard.Core.shared.Skins;
-import one.lindegaard.Core.v1_10_R1.Skins_1_10_R1;
-import one.lindegaard.Core.v1_11_R1.Skins_1_11_R1;
-import one.lindegaard.Core.v1_10_R1.Skins_1_12_R1;
-import one.lindegaard.Core.v1_13_R1.Skins_1_13_R1;
-import one.lindegaard.Core.v1_13_R2.Skins_1_13_R2;
-import one.lindegaard.Core.v1_14_R1.Skins_1_14_R1;
-import one.lindegaard.Core.v1_15_R1.Skins_1_15_R1;
-import one.lindegaard.Core.v1_16_R1.Skins_1_16_R1;
-import one.lindegaard.Core.v1_16_R2.Skins_1_16_R2;
-import one.lindegaard.Core.v1_16_R3.Skins_1_16_R3;
-import one.lindegaard.Core.v1_17_R1.Skins_1_17_R1;
-import one.lindegaard.Core.v1_18_R1.Skins_1_18_R1;
-import one.lindegaard.Core.v1_19_R1.Skins_1_19_R1;
-import one.lindegaard.Core.v1_19_R2.Skins_1_19_R2;
-import one.lindegaard.Core.v1_19_R3.Skins_1_19_R3;
-import one.lindegaard.Core.v1_20_R1.Skins_1_20_R1;
-import one.lindegaard.Core.v1_20_R2.Skins_1_20_R2;
-import one.lindegaard.Core.v1_20_R3.Skins_1_20_R3;
-import one.lindegaard.Core.v1_21_R1.Skins_1_21_R1;
-import one.lindegaard.Core.v1_21_R2.Skins_1_21_R2;
-import one.lindegaard.Core.v1_21_R3.Skins_1_21_R3;
-import one.lindegaard.Core.v1_21_R4.Skins_1_21_R4;
-import one.lindegaard.Core.v1_21_R5.Skins_1_21_R5;
-import one.lindegaard.Core.v1_8_R1.Skins_1_8_R1;
-import one.lindegaard.Core.v1_8_R2.Skins_1_8_R2;
-import one.lindegaard.Core.v1_8_R3.Skins_1_8_R3;
-import one.lindegaard.Core.v1_9_R1.Skins_1_9_R1;
-import one.lindegaard.Core.v1_9_R2.Skins_1_9_R2;
-import one.lindegaard.CustomItemsLib.Core;
-import one.lindegaard.CustomItemsLib.PlayerSettings;
-import one.lindegaard.CustomItemsLib.Strings;
-import one.lindegaard.CustomItemsLib.mobs.MobType;
-import one.lindegaard.CustomItemsLib.server.Servers;
-
-public class CoreCustomItems {
-
- //Plugin plugin;
-
- //public CoreCustomItems(Plugin plugin) {
- // this.plugin = plugin;
- //}
-
- // How to get Playerskin
- // https://www.spigotmc.org/threads/how-to-get-a-players-texture.244966/
-
- /**
- * Return an ItemStack with the Players head texture.
- *
- * @param name
- * @param money
- * @return
- */
- public static Skins getSkinsClass() {
- String version;
- Skins sk = null;
- try {
- version = Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3];
- } catch (ArrayIndexOutOfBoundsException e) {
- // MC 1.21.5+ dropped versioned package names — fall back to Mojang API for skin retrieval
- Bukkit.getLogger().warning("[CustomItemsLib] Unversioned CraftBukkit package detected (MC 1.21.5+) — skin lookup will use Mojang API instead of NMS.");
- return null;
- }
- // https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-1-16/
- if (version.equals("v1_21_R5")) {
- sk = new Skins_1_21_R5();
- } else if (version.equals("v1_21_R4")) {
- sk = new Skins_1_21_R4();
- } else if (version.equals("v1_21_R3")) {
- sk = new Skins_1_21_R3();
- } else if (version.equals("v1_21_R2")) {
- sk = new Skins_1_21_R2();
- } else if (version.equals("v1_21_R1")) {
- sk = new Skins_1_21_R1();
- } else if (version.equals("v1_20_R3")) {
- sk = new Skins_1_20_R3();
- } else if (version.equals("v1_20_R2")) {
- sk = new Skins_1_20_R2();
- } else if (version.equals("v1_20_R1")) {
- sk = new Skins_1_20_R1();
- } else if (version.equals("v1_19_R3")) {
- sk = new Skins_1_19_R3();
- } else if (version.equals("v1_19_R2")) {
- sk = new Skins_1_19_R2();
- } else if (version.equals("v1_19_R1")) {
- sk = new Skins_1_19_R1();
- } else if (version.equals("v1_18_R1")) {
- sk = new Skins_1_18_R1();
- } else if (version.equals("v1_17_R1")) {
- sk = new Skins_1_17_R1();
- } else if (version.equals("v1_16_R3")) {
- sk = new Skins_1_16_R3();
- } else if (version.equals("v1_16_R2")) {
- sk = new Skins_1_16_R2();
- } else if (version.equals("v1_16_R1")) {
- sk = new Skins_1_16_R1();
- } else if (version.equals("v1_15_R1")) {
- sk = new Skins_1_15_R1();
- } else if (version.equals("v1_14_R1")) {
- sk = new Skins_1_14_R1();
- } else if (version.equals("v1_13_R2")) {
- sk = new Skins_1_13_R2();
- } else if (version.equals("v1_13_R1")) {
- sk = new Skins_1_13_R1();
- } else if (version.equals("v1_12_R1")) {
- sk = new Skins_1_12_R1();
- } else if (version.equals("v1_11_R1")) {
- sk = new Skins_1_11_R1();
- } else if (version.equals("v1_10_R1")) {
- sk = new Skins_1_10_R1();
- } else if (version.equals("v1_9_R2")) {
- sk = new Skins_1_9_R2();
- } else if (version.equals("v1_9_R1")) {
- sk = new Skins_1_9_R1();
- } else if (version.equals("v1_8_R3")) {
- sk = new Skins_1_8_R3();
- } else if (version.equals("v1_8_R2")) {
- sk = new Skins_1_8_R2();
- } else if (version.equals("v1_8_R1")) {
- sk = new Skins_1_8_R1();
- }
- return sk;
- }
-
- /**
- * Return an ItemStack with a custom texture. If Mojang changes the way they
- * calculate Signatures this method will stop working.
- *
- * @param mPlayerUUID
- * @param mDisplayName
- * @param mTextureValue
- * @param mTextureSignature
- * @param money
- * @return ItemStack with custom texture.
- */
+import org.bukkit.profile.PlayerProfile;
+import org.bukkit.profile.PlayerTextures;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.authlib.GameProfile;
+import com.mojang.authlib.properties.Property;
+
+import one.lindegaard.Core.shared.Skins;
+import one.lindegaard.Core.v1_10_R1.Skins_1_10_R1;
+import one.lindegaard.Core.v1_11_R1.Skins_1_11_R1;
+import one.lindegaard.Core.v1_10_R1.Skins_1_12_R1;
+import one.lindegaard.Core.v1_13_R1.Skins_1_13_R1;
+import one.lindegaard.Core.v1_13_R2.Skins_1_13_R2;
+import one.lindegaard.Core.v1_14_R1.Skins_1_14_R1;
+import one.lindegaard.Core.v1_15_R1.Skins_1_15_R1;
+import one.lindegaard.Core.v1_16_R1.Skins_1_16_R1;
+import one.lindegaard.Core.v1_16_R2.Skins_1_16_R2;
+import one.lindegaard.Core.v1_16_R3.Skins_1_16_R3;
+import one.lindegaard.Core.v1_17_R1.Skins_1_17_R1;
+import one.lindegaard.Core.v1_18_R1.Skins_1_18_R1;
+import one.lindegaard.Core.v1_19_R1.Skins_1_19_R1;
+import one.lindegaard.Core.v1_19_R2.Skins_1_19_R2;
+import one.lindegaard.Core.v1_19_R3.Skins_1_19_R3;
+import one.lindegaard.Core.v1_20_R1.Skins_1_20_R1;
+import one.lindegaard.Core.v1_20_R2.Skins_1_20_R2;
+import one.lindegaard.Core.v1_20_R3.Skins_1_20_R3;
+import one.lindegaard.Core.v1_21_R1.Skins_1_21_R1;
+import one.lindegaard.Core.v1_21_R2.Skins_1_21_R2;
+import one.lindegaard.Core.v1_21_R3.Skins_1_21_R3;
+import one.lindegaard.Core.v1_21_R4.Skins_1_21_R4;
+import one.lindegaard.Core.v1_21_R5.Skins_1_21_R5;
+import one.lindegaard.Core.v1_8_R1.Skins_1_8_R1;
+import one.lindegaard.Core.v1_8_R2.Skins_1_8_R2;
+import one.lindegaard.Core.v1_8_R3.Skins_1_8_R3;
+import one.lindegaard.Core.v1_9_R1.Skins_1_9_R1;
+import one.lindegaard.Core.v1_9_R2.Skins_1_9_R2;
+import one.lindegaard.CustomItemsLib.Core;
+import one.lindegaard.CustomItemsLib.PlayerSettings;
+import one.lindegaard.CustomItemsLib.mobs.MobType;
+import one.lindegaard.CustomItemsLib.server.Servers;
+
+public class CoreCustomItems {
+
+ //Plugin plugin;
+
+ //public CoreCustomItems(Plugin plugin) {
+ // this.plugin = plugin;
+ //}
+
+ // How to get Playerskin
+ // https://www.spigotmc.org/threads/how-to-get-a-players-texture.244966/
+
+ /**
+ * Return an ItemStack with the Players head texture.
+ *
+ * @param name
+ * @param money
+ * @return
+ */
+ public static Skins getSkinsClass() {
+ String version;
+ Skins sk = null;
+ try {
+ version = Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // MC 1.21.5+ dropped versioned package names — fall back to Mojang API for skin retrieval
+ Bukkit.getLogger().warning("[CustomItemsLib] Unversioned CraftBukkit package detected (MC 1.21.5+) — skin lookup will use Mojang API instead of NMS.");
+ return null;
+ }
+ // https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-1-16/
+ if (version.equals("v1_21_R5")) {
+ sk = new Skins_1_21_R5();
+ } else if (version.equals("v1_21_R4")) {
+ sk = new Skins_1_21_R4();
+ } else if (version.equals("v1_21_R3")) {
+ sk = new Skins_1_21_R3();
+ } else if (version.equals("v1_21_R2")) {
+ sk = new Skins_1_21_R2();
+ } else if (version.equals("v1_21_R1")) {
+ sk = new Skins_1_21_R1();
+ } else if (version.equals("v1_20_R3")) {
+ sk = new Skins_1_20_R3();
+ } else if (version.equals("v1_20_R2")) {
+ sk = new Skins_1_20_R2();
+ } else if (version.equals("v1_20_R1")) {
+ sk = new Skins_1_20_R1();
+ } else if (version.equals("v1_19_R3")) {
+ sk = new Skins_1_19_R3();
+ } else if (version.equals("v1_19_R2")) {
+ sk = new Skins_1_19_R2();
+ } else if (version.equals("v1_19_R1")) {
+ sk = new Skins_1_19_R1();
+ } else if (version.equals("v1_18_R1")) {
+ sk = new Skins_1_18_R1();
+ } else if (version.equals("v1_17_R1")) {
+ sk = new Skins_1_17_R1();
+ } else if (version.equals("v1_16_R3")) {
+ sk = new Skins_1_16_R3();
+ } else if (version.equals("v1_16_R2")) {
+ sk = new Skins_1_16_R2();
+ } else if (version.equals("v1_16_R1")) {
+ sk = new Skins_1_16_R1();
+ } else if (version.equals("v1_15_R1")) {
+ sk = new Skins_1_15_R1();
+ } else if (version.equals("v1_14_R1")) {
+ sk = new Skins_1_14_R1();
+ } else if (version.equals("v1_13_R2")) {
+ sk = new Skins_1_13_R2();
+ } else if (version.equals("v1_13_R1")) {
+ sk = new Skins_1_13_R1();
+ } else if (version.equals("v1_12_R1")) {
+ sk = new Skins_1_12_R1();
+ } else if (version.equals("v1_11_R1")) {
+ sk = new Skins_1_11_R1();
+ } else if (version.equals("v1_10_R1")) {
+ sk = new Skins_1_10_R1();
+ } else if (version.equals("v1_9_R2")) {
+ sk = new Skins_1_9_R2();
+ } else if (version.equals("v1_9_R1")) {
+ sk = new Skins_1_9_R1();
+ } else if (version.equals("v1_8_R3")) {
+ sk = new Skins_1_8_R3();
+ } else if (version.equals("v1_8_R2")) {
+ sk = new Skins_1_8_R2();
+ } else if (version.equals("v1_8_R1")) {
+ sk = new Skins_1_8_R1();
+ }
+ return sk;
+ }
+
+ /**
+ * Return an ItemStack with a custom texture. If Mojang changes the way they
+ * calculate Signatures this method will stop working.
+ *
+ * @param mPlayerUUID
+ * @param mDisplayName
+ * @param mTextureValue
+ * @param mTextureSignature
+ * @param money
+ * @return ItemStack with custom texture.
+ */
public static ItemStack getCustomtexture(Reward reward, String mTextureValue, String mTextureSignature) {
ItemStack skull = CoreCustomItems.getDefaultPlayerHead(1);
- if (mTextureSignature.isEmpty() || mTextureValue.isEmpty())
+ if (mTextureValue.isEmpty())
return skull;
// add custom texture to skull
SkullMeta skullMeta = (SkullMeta) skull.getItemMeta();
- GameProfile profile = new GameProfile(reward.getSkinUUID(), reward.getDisplayName());
- if (mTextureSignature.isEmpty())
- profile.getProperties().put("textures", new Property("textures", mTextureValue));
- else
- profile.getProperties().put("textures", new Property("textures", mTextureValue, mTextureSignature));
- Field profileField = null;
-
try {
- profileField = skullMeta.getClass().getDeclaredField("profile");
- } catch (NoSuchFieldException | SecurityException e) {
- e.printStackTrace();
- return skull;
- }
- profileField.setAccessible(true);
- try {
- profileField.set(skullMeta, profile);
- } catch (IllegalArgumentException | IllegalAccessException e) {
- e.printStackTrace();
- }
- skull.setItemMeta(skullMeta);
-
- // add displayname and lores to skull
- skull = Reward.setDisplayNameAndHiddenLores(skull, reward);
- return skull;
- }
-
- /**
- * Return an ItemStack with the Players head texture.
- *
- * @param name
- * @param money
- * @return
- */
- public static ItemStack getPlayerHead(UUID uuid, String name, int amount, double money) {
- ItemStack skull = CoreCustomItems.getDefaultPlayerHead(amount);
- skull.setAmount(amount);
- OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
- PlayerSettings ps = Core.getPlayerSettingsManager().getPlayerSettings(offlinePlayer);
- if (ps.getTexture() == null || ps.getSignature() == null || ps.getTexture().isEmpty()
- || ps.getSignature().isEmpty()) {
- Core.getMessages().debug("No skin found i database");
- String[] onlineSkin = new String[2];
- if (offlinePlayer.isOnline()) {
- Player player = (Player) offlinePlayer;
- Skins sk = CoreCustomItems.getSkinsClass();
- if (sk != null) {
- Core.getMessages().debug("Trying to fecth skin from Online Player Profile");
- onlineSkin = sk.getSkin(player);
- } else {
- Core.getMessages().debug("Trying to fecth skin from Minecraft Servers");
- onlineSkin = getSkinFromUUID(uuid);
- }
- }
-
- if ((onlineSkin == null || onlineSkin[0] == null || onlineSkin[0].isEmpty() || onlineSkin[1] == null
- || onlineSkin[1].isEmpty()) && Servers.isMC112OrNewer())
- return getPlayerHeadOwningPlayer(uuid, name, amount, money);
-
- if (onlineSkin != null && onlineSkin[0] != null && !onlineSkin[0].isEmpty() && onlineSkin[1] != null
- && !onlineSkin[1].isEmpty()) {
- ps.setTexture(onlineSkin[0]);
- ps.setSignature(onlineSkin[1]);
- Core.getPlayerSettingsManager().setPlayerSettings(ps);
+ // Modern API path (Paper/Spigot 1.20+): build a PlayerProfile from the encoded texture value.
+ URL skinUrl = getSkinUrlFromTextureValue(mTextureValue);
+ if (skinUrl != null) {
+ UUID profileUuid = reward.getSkinUUID() != null ? reward.getSkinUUID() : UUID.randomUUID();
+ PlayerProfile ownerProfile = Bukkit.createPlayerProfile(profileUuid);
+ PlayerTextures textures = ownerProfile.getTextures();
+ textures.setSkin(skinUrl);
+ ownerProfile.setTextures(textures);
+ skullMeta.setOwnerProfile(ownerProfile);
} else {
- Core.getMessages().debug("Empty skin");
+ throw new IllegalArgumentException("Could not decode skin URL from texture value.");
+ }
+ } catch (Throwable modernApiFailure) {
+ // Legacy fallback for old servers relying on direct GameProfile field injection.
+ GameProfile profile = new GameProfile(reward.getSkinUUID(), reward.getDisplayName());
+ if (mTextureSignature.isEmpty())
+ profile.getProperties().put("textures", new Property("textures", mTextureValue));
+ else
+ profile.getProperties().put("textures", new Property("textures", mTextureValue, mTextureSignature));
+ Field profileField = null;
+
+ try {
+ profileField = skullMeta.getClass().getDeclaredField("profile");
+ } catch (NoSuchFieldException | SecurityException e) {
+ Core.getMessages().debug("Unable to set skull profile by reflection: %s", e.getMessage());
return skull;
}
- } else {
- if (offlinePlayer.isOnline()) {
- Player player = (Player) offlinePlayer;
- Skins sk = CoreCustomItems.getSkinsClass();
- if (sk != null) {
- String[] skin = sk.getSkin(player);
- if (skin != null && skin[0] != null && !skin[0].equals(ps.getTexture())) {
- Core.getMessages().debug("%s has changed skin, updating database with new skin. (%s,%s)",
- player.getName(), ps.getTexture(), skin[0]);
- ps.setTexture(skin[0]);
- ps.setSignature(skin[1]);
- Core.getPlayerSettingsManager().setPlayerSettings(ps);
- }
- }
- } else
- Core.getMessages().debug("%s using skin from skin Cache", offlinePlayer.getName());
+ profileField.setAccessible(true);
+ try {
+ profileField.set(skullMeta, profile);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ Core.getMessages().debug("Unable to apply legacy skull texture profile: %s", e.getMessage());
+ }
}
-
- skull = new ItemStack(getCustomtexture(new Reward(offlinePlayer.getName(), money, RewardType.KILLED, uuid),
- ps.getTexture(), ps.getSignature()));
- skull.setAmount(amount);
+ skull.setItemMeta(skullMeta);
+
+ // add displayname and lores to skull
+ skull = Reward.setDisplayNameAndHiddenLores(skull, reward);
return skull;
}
- private static String[] getSkinFromUUID(UUID uuid) {
+ private static URL getSkinUrlFromTextureValue(String textureValue) {
try {
- URL url_1 = new URL(
- "https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false");
- InputStreamReader reader_1;
- reader_1 = new InputStreamReader(url_1.openStream());
-
- JsonElement json = new JsonParser().parse(reader_1);
- if (json.isJsonObject()) {
- JsonObject textureProperty = json.getAsJsonObject().get("properties").getAsJsonArray().get(0)
- .getAsJsonObject();
- String texture = textureProperty.get("value").getAsString();
- String signature = textureProperty.get("signature").getAsString();
-
- return new String[] { texture, signature };
- } else {
- Core.getMessages().debug("(1) Could not get skin data from session servers!");
+ String decoded = new String(Base64.getDecoder().decode(textureValue), StandardCharsets.UTF_8);
+ JsonElement json = new JsonParser().parse(decoded);
+ if (!json.isJsonObject())
return null;
- }
-
- } catch (IOException e) {
- Core.getMessages().debug("(2)Could not get skin data from session servers!");
+ JsonObject root = json.getAsJsonObject();
+ if (!root.has("textures"))
+ return null;
+ JsonObject textures = root.getAsJsonObject("textures");
+ if (textures == null || !textures.has("SKIN"))
+ return null;
+ JsonObject skin = textures.getAsJsonObject("SKIN");
+ if (skin == null || !skin.has("url"))
+ return null;
+ return new URL(skin.get("url").getAsString());
+ } catch (Exception e) {
return null;
}
}
-
+
+ /**
+ * Return an ItemStack with the Players head texture.
+ *
+ * @param name
+ * @param money
+ * @return
+ */
+ public static ItemStack getPlayerHead(UUID uuid, String name, int amount, double money) {
+ ItemStack skull = CoreCustomItems.getDefaultPlayerHead(amount);
+ skull.setAmount(amount);
+ OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
+ PlayerSettings ps = Core.getPlayerSettingsManager().getPlayerSettings(offlinePlayer);
+ if (ps.getTexture() == null || ps.getSignature() == null || ps.getTexture().isEmpty()
+ || ps.getSignature().isEmpty()) {
+ Core.getMessages().debug("No skin found i database");
+ String[] onlineSkin = new String[2];
+ if (offlinePlayer.isOnline()) {
+ Player player = (Player) offlinePlayer;
+ Skins sk = CoreCustomItems.getSkinsClass();
+ if (sk != null) {
+ Core.getMessages().debug("Trying to fecth skin from Online Player Profile");
+ onlineSkin = sk.getSkin(player);
+ } else {
+ Core.getMessages().debug("Trying to fecth skin from Minecraft Servers");
+ onlineSkin = getSkinFromUUID(uuid);
+ }
+ }
+
+ if ((onlineSkin == null || onlineSkin[0] == null || onlineSkin[0].isEmpty() || onlineSkin[1] == null
+ || onlineSkin[1].isEmpty()) && Servers.isMC112OrNewer())
+ return getPlayerHeadOwningPlayer(uuid, name, amount, money);
+
+ if (onlineSkin != null && onlineSkin[0] != null && !onlineSkin[0].isEmpty() && onlineSkin[1] != null
+ && !onlineSkin[1].isEmpty()) {
+ ps.setTexture(onlineSkin[0]);
+ ps.setSignature(onlineSkin[1]);
+ Core.getPlayerSettingsManager().setPlayerSettings(ps);
+ } else {
+ Core.getMessages().debug("Empty skin");
+ return skull;
+ }
+ } else {
+ if (offlinePlayer.isOnline()) {
+ Player player = (Player) offlinePlayer;
+ Skins sk = CoreCustomItems.getSkinsClass();
+ if (sk != null) {
+ String[] skin = sk.getSkin(player);
+ if (skin != null && skin[0] != null && !skin[0].equals(ps.getTexture())) {
+ Core.getMessages().debug("%s has changed skin, updating database with new skin. (%s,%s)",
+ player.getName(), ps.getTexture(), skin[0]);
+ ps.setTexture(skin[0]);
+ ps.setSignature(skin[1]);
+ Core.getPlayerSettingsManager().setPlayerSettings(ps);
+ }
+ }
+ } else
+ Core.getMessages().debug("%s using skin from skin Cache", offlinePlayer.getName());
+ }
+
+ skull = new ItemStack(getCustomtexture(new Reward(offlinePlayer.getName(), money, RewardType.KILLED, uuid),
+ ps.getTexture(), ps.getSignature()));
+ skull.setAmount(amount);
+ return skull;
+ }
+
+ private static String[] getSkinFromUUID(UUID uuid) {
+ try {
+ URL url_1 = new URL(
+ "https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false");
+ InputStreamReader reader_1;
+ reader_1 = new InputStreamReader(url_1.openStream());
+
+ JsonElement json = new JsonParser().parse(reader_1);
+ if (json.isJsonObject()) {
+ JsonObject textureProperty = json.getAsJsonObject().get("properties").getAsJsonArray().get(0)
+ .getAsJsonObject();
+ String texture = textureProperty.get("value").getAsString();
+ String signature = textureProperty.get("signature").getAsString();
+
+ return new String[] { texture, signature };
+ } else {
+ Core.getMessages().debug("(1) Could not get skin data from session servers!");
+ return null;
+ }
+
+ } catch (IOException e) {
+ Core.getMessages().debug("(2)Could not get skin data from session servers!");
+ return null;
+ }
+ }
+
private static ItemStack getPlayerHeadOwningPlayer(UUID uuid, String name, int amount, double money) {
ItemStack skull = CoreCustomItems.getDefaultPlayerHead(amount);
SkullMeta skullMeta = (SkullMeta) skull.getItemMeta();
skull.setItemMeta(skullMeta);
- skull = Reward.setDisplayNameAndHiddenLores(skull, name, money, new ArrayList(Arrays.asList(
- "Hidden(0):" + name, "Hidden(1):" + String.format(Locale.ENGLISH, "%.5f", money),
- "Hidden(2):" + RewardType.KILLED.getType(), "Hidden(4):" + uuid,
- "Hidden(5):"
- + Strings.encode(String.format(Locale.ENGLISH, "%.5f", money) + RewardType.KILLED.getType()),
- Core.getMessages().getString("core.reward.lore"))));
+ skull = Reward.setDisplayNameAndHiddenLores(skull,
+ new Reward(name, money, RewardType.KILLED, uuid));
Core.getMessages().debug("CustomItems: set the skin using OwningPlayer/Owner (%s)", name);
return skull;
}
-
- public static ItemStack getCustomHead(MobType minecraftMob, String name, int amount, double money, UUID skinUUID) {
- ItemStack skull;
- switch (minecraftMob) {
- case Skeleton:
- skull = CoreCustomItems.getDefaultSkeletonHead(amount);
- skull = Reward.setDisplayNameAndHiddenLores(skull,
- new Reward(minecraftMob.getFriendlyName(), money, RewardType.KILLED, skinUUID));
- break;
-
- case WitherSkeleton:
- skull = CoreCustomItems.getDefaultWitherSkeletonHead(amount);
- skull = Reward.setDisplayNameAndHiddenLores(skull,
- new Reward(minecraftMob.getFriendlyName(), money, RewardType.KILLED, skinUUID));
- break;
-
- case Zombie:
- skull = CoreCustomItems.getDefaultZombieHead(amount);
- skull = Reward.setDisplayNameAndHiddenLores(skull,
- new Reward(minecraftMob.getFriendlyName(), money, RewardType.KILLED, skinUUID));
- break;
-
- case PvpPlayer:
- skull = getPlayerHead(skinUUID, name, amount, money);
- break;
-
- case Creeper:
- skull = CoreCustomItems.getDefaultCreeperHead(amount);
- skull = Reward.setDisplayNameAndHiddenLores(skull,
- new Reward(minecraftMob.getFriendlyName(), money, RewardType.KILLED, skinUUID));
- break;
-
- case EnderDragon:
- skull = CoreCustomItems.getDefaultEnderDragonHead(amount);
- skull = Reward.setDisplayNameAndHiddenLores(skull,
- new Reward(minecraftMob.getFriendlyName(), money, RewardType.KILLED, skinUUID));
- break;
-
- default:
- ItemStack is = new ItemStack(
- getCustomtexture(new Reward(minecraftMob.getFriendlyName(), money, RewardType.KILLED, skinUUID),
- minecraftMob.getTextureValue(), minecraftMob.getTextureSignature()));
- is.setAmount(amount);
- return is;
- }
- return skull;
- }
-
- private static ItemStack getDefaultSkeletonHead(int amount) {
- if (Servers.isMC113OrNewer())
- return new ItemStack(Material.SKELETON_SKULL, amount);
- else
- return new ItemStack(Material.matchMaterial("SKULL_ITEM"), amount, (short) 0);
- }
-
- private static ItemStack getDefaultWitherSkeletonHead(int amount) {
- if (Servers.isMC113OrNewer())
- return new ItemStack(Material.WITHER_SKELETON_SKULL, amount);
- else
- return new ItemStack(Material.matchMaterial("SKULL_ITEM"), amount, (short) 1);
- }
-
- private static ItemStack getDefaultZombieHead(int amount) {
- if (Servers.isMC113OrNewer())
- return new ItemStack(Material.ZOMBIE_HEAD, amount);
- else
- return new ItemStack(Material.matchMaterial("SKULL_ITEM"), amount, (short) 2);
- }
-
- private static ItemStack getDefaultPlayerHead(int amount) {
- if (Servers.isMC113OrNewer())
- return new ItemStack(Material.PLAYER_HEAD, amount);
- else
- return new ItemStack(Material.matchMaterial("SKULL_ITEM"), amount, (short) 3);
- }
-
- private static ItemStack getDefaultCreeperHead(int amount) {
- if (Servers.isMC113OrNewer())
- return new ItemStack(Material.CREEPER_HEAD, amount);
- else
- return new ItemStack(Material.matchMaterial("SKULL_ITEM"), amount, (short) 4);
- }
-
- private static ItemStack getDefaultEnderDragonHead(int amount) {
- if (Servers.isMC113OrNewer())
- return new ItemStack(Material.DRAGON_HEAD, amount);
- else
- return new ItemStack(Material.matchMaterial("SKULL_ITEM"), amount, (short) 5);
- }
-
-}
+
+ public static ItemStack getCustomHead(MobType minecraftMob, String name, int amount, double money, UUID skinUUID) {
+ ItemStack skull;
+ switch (minecraftMob) {
+ case Skeleton:
+ skull = CoreCustomItems.getDefaultSkeletonHead(amount);
+ skull = Reward.setDisplayNameAndHiddenLores(skull,
+ new Reward(minecraftMob.getFriendlyName(), money, RewardType.KILLED, skinUUID));
+ break;
+
+ case WitherSkeleton:
+ skull = CoreCustomItems.getDefaultWitherSkeletonHead(amount);
+ skull = Reward.setDisplayNameAndHiddenLores(skull,
+ new Reward(minecraftMob.getFriendlyName(), money, RewardType.KILLED, skinUUID));
+ break;
+
+ case Zombie:
+ skull = CoreCustomItems.getDefaultZombieHead(amount);
+ skull = Reward.setDisplayNameAndHiddenLores(skull,
+ new Reward(minecraftMob.getFriendlyName(), money, RewardType.KILLED, skinUUID));
+ break;
+
+ case PvpPlayer:
+ skull = getPlayerHead(skinUUID, name, amount, money);
+ break;
+
+ case Creeper:
+ skull = CoreCustomItems.getDefaultCreeperHead(amount);
+ skull = Reward.setDisplayNameAndHiddenLores(skull,
+ new Reward(minecraftMob.getFriendlyName(), money, RewardType.KILLED, skinUUID));
+ break;
+
+ case EnderDragon:
+ skull = CoreCustomItems.getDefaultEnderDragonHead(amount);
+ skull = Reward.setDisplayNameAndHiddenLores(skull,
+ new Reward(minecraftMob.getFriendlyName(), money, RewardType.KILLED, skinUUID));
+ break;
+
+ default:
+ ItemStack is = new ItemStack(
+ getCustomtexture(new Reward(minecraftMob.getFriendlyName(), money, RewardType.KILLED, skinUUID),
+ minecraftMob.getTextureValue(), minecraftMob.getTextureSignature()));
+ is.setAmount(amount);
+ return is;
+ }
+ return skull;
+ }
+
+ private static ItemStack getDefaultSkeletonHead(int amount) {
+ if (Servers.isMC113OrNewer())
+ return new ItemStack(Material.SKELETON_SKULL, amount);
+ else
+ return new ItemStack(Material.matchMaterial("SKULL_ITEM"), amount, (short) 0);
+ }
+
+ private static ItemStack getDefaultWitherSkeletonHead(int amount) {
+ if (Servers.isMC113OrNewer())
+ return new ItemStack(Material.WITHER_SKELETON_SKULL, amount);
+ else
+ return new ItemStack(Material.matchMaterial("SKULL_ITEM"), amount, (short) 1);
+ }
+
+ private static ItemStack getDefaultZombieHead(int amount) {
+ if (Servers.isMC113OrNewer())
+ return new ItemStack(Material.ZOMBIE_HEAD, amount);
+ else
+ return new ItemStack(Material.matchMaterial("SKULL_ITEM"), amount, (short) 2);
+ }
+
+ private static ItemStack getDefaultPlayerHead(int amount) {
+ if (Servers.isMC113OrNewer())
+ return new ItemStack(Material.PLAYER_HEAD, amount);
+ else
+ return new ItemStack(Material.matchMaterial("SKULL_ITEM"), amount, (short) 3);
+ }
+
+ private static ItemStack getDefaultCreeperHead(int amount) {
+ if (Servers.isMC113OrNewer())
+ return new ItemStack(Material.CREEPER_HEAD, amount);
+ else
+ return new ItemStack(Material.matchMaterial("SKULL_ITEM"), amount, (short) 4);
+ }
+
+ private static ItemStack getDefaultEnderDragonHead(int amount) {
+ if (Servers.isMC113OrNewer())
+ return new ItemStack(Material.DRAGON_HEAD, amount);
+ else
+ return new ItemStack(Material.matchMaterial("SKULL_ITEM"), amount, (short) 5);
+ }
+
+}
diff --git a/src/one/lindegaard/CustomItemsLib/rewards/CoreRewardListeners.java b/src/one/lindegaard/CustomItemsLib/rewards/CoreRewardListeners.java
index e76cd75..c3b22c5 100644
--- a/src/one/lindegaard/CustomItemsLib/rewards/CoreRewardListeners.java
+++ b/src/one/lindegaard/CustomItemsLib/rewards/CoreRewardListeners.java
@@ -38,7 +38,6 @@
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
-import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.plugin.Plugin;
@@ -998,10 +997,14 @@ else if (cursorMoney > 1)
break;
case SWAP_WITH_CURSOR:
if (Reward.isReward(isCurrentSlot) && Reward.isReward(isCursor)) {
- ItemMeta imCurrent = isCurrentSlot.getItemMeta();
- ItemMeta imCursor = isCursor.getItemMeta();
- Reward reward1 = new Reward(imCurrent.getLore());
- Reward reward2 = new Reward(imCursor.getLore());
+ Reward reward1 = Reward.getReward(isCurrentSlot);
+ Reward reward2 = Reward.getReward(isCursor);
+ if (reward1 == null || reward2 == null) {
+ event.setCancelled(true);
+ Core.getMessages().debug(
+ "SWAP_WITH_CURSOR: cancelled because reward metadata could not be parsed from PDC.");
+ break;
+ }
int amount_reward1 = isCurrentSlot.getAmount();
int amount_reward2 = isCursor.getAmount();
if (reward2.isMoney() && slotType == SlotType.ARMOR) {
diff --git a/src/one/lindegaard/CustomItemsLib/rewards/CoreRewardManager.java b/src/one/lindegaard/CustomItemsLib/rewards/CoreRewardManager.java
index a76585e..5eda457 100644
--- a/src/one/lindegaard/CustomItemsLib/rewards/CoreRewardManager.java
+++ b/src/one/lindegaard/CustomItemsLib/rewards/CoreRewardManager.java
@@ -181,6 +181,24 @@ public double removeBagOfGoldFromPlayer(Player player, double amount) {
if (reward.checkHash()) {
if (reward.isMoney()) {
double saldo = Tools.round(reward.getMoney());
+ double consumedFromToken = saldo > toBeTaken ? toBeTaken : saldo;
+ TokenSpendStore.MarkResult spendResult = TokenSpendStore.MarkResult.MARKED;
+ if (Core.getTokenSpendStore() != null) {
+ spendResult = Core.getTokenSpendStore().markTokenSpent(reward.getTokenUUID(),
+ player.getUniqueId(), "inventory-spend", consumedFromToken);
+ }
+ if (spendResult == TokenSpendStore.MarkResult.DUPLICATE) {
+ Core.getMessages().debug(
+ "Rejected duplicated inventory token while spending for %s (token=%s, slot=%s).",
+ player.getName(), reward.getTokenUUID(), slot);
+ player.getInventory().clear(slot);
+ continue;
+ }
+ if (spendResult == TokenSpendStore.MarkResult.ERROR) {
+ Core.getMessages().debug(
+ "Token spend store unavailable while spending inventory token for %s. Falling back to signature-only check.",
+ player.getName());
+ }
if (saldo > toBeTaken) {
reward.setMoney(Tools.round(saldo - toBeTaken));
is = Reward.setDisplayNameAndHiddenLores(is, reward);
diff --git a/src/one/lindegaard/CustomItemsLib/rewards/PickupRewards.java b/src/one/lindegaard/CustomItemsLib/rewards/PickupRewards.java
index dbd2300..1a06d60 100644
--- a/src/one/lindegaard/CustomItemsLib/rewards/PickupRewards.java
+++ b/src/one/lindegaard/CustomItemsLib/rewards/PickupRewards.java
@@ -1,70 +1,111 @@
-package one.lindegaard.CustomItemsLib.rewards;
-
+package one.lindegaard.CustomItemsLib.rewards;
+
import one.lindegaard.CustomItemsLib.Core;
import one.lindegaard.CustomItemsLib.Tools;
-import one.lindegaard.CustomItemsLib.compatibility.BagOfGoldCompat;
import one.lindegaard.CustomItemsLib.compatibility.ProtocolLibCompat;
import one.lindegaard.CustomItemsLib.compatibility.ProtocolLibHelper;
-
-import org.bukkit.ChatColor;
-import org.bukkit.entity.Item;
-import org.bukkit.entity.Player;
-
-public class PickupRewards {
-
- private Core plugin;
-
- public PickupRewards(Core plugin) {
- this.plugin = plugin;
- }
-
+
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Item;
+import org.bukkit.entity.Player;
+
+public class PickupRewards {
+
+ private Core plugin;
+
+ public PickupRewards(Core plugin) {
+ this.plugin = plugin;
+ }
+
public void rewardPlayer(Player player, Item item, CallBack callBack) {
if (Reward.isReward(item)) {
Reward reward = Reward.getReward(item);
+ if (reward == null) {
+ callBack.setCancelled(false);
+ return;
+ }
if (reward.isBagOfGoldReward() || reward.isItemReward()) {
- callBack.setCancelled(true);
+ // BagOfGold loads after CustomItemsLib, so retry economy discovery on demand.
+ if (!Core.getEconomyManager().isActive())
+ Core.getEconomyManager().setupEconomy();
- boolean succes = false;
- if (BagOfGoldCompat.isSupported()) {
- succes = Core.getEconomyManager().depositPlayer(player, reward.getMoney());
- if (succes) {
- item.remove();
- if (Core.getCoreRewardManager().getDroppedMoney().containsKey(item.getEntityId()))
- Core.getCoreRewardManager().getDroppedMoney().remove(item.getEntityId());
- if (ProtocolLibCompat.isSupported())
- ProtocolLibHelper.pickupMoney(player, item);
+ if (!reward.checkHash()) {
+ Core.getMessages().debug("Rejected reward token for %s because signature verification failed.", player.getName());
+ callBack.setCancelled(true);
+ item.remove();
+ if (Core.getCoreRewardManager().getDroppedMoney().containsKey(item.getEntityId()))
+ Core.getCoreRewardManager().getDroppedMoney().remove(item.getEntityId());
+ return;
+ }
- if (reward.getMoney() == 0) {
- Core.getMessages().debug("%s picked up a %s" + ChatColor.RESET + " (# of rewards left=%s)",
- player.getName(), reward.isItemReward() ? "ITEM" : reward.getDisplayName(),
- Core.getCoreRewardManager().getDroppedMoney().size());
+ boolean succes = Core.getEconomyManager().depositPlayer(player, reward.getMoney());
+ if (succes) {
+ TokenSpendStore.MarkResult spendResult = TokenSpendStore.MarkResult.MARKED;
+ if (Core.getTokenSpendStore() != null) {
+ spendResult = Core.getTokenSpendStore().markTokenSpent(reward.getTokenUUID(),
+ player.getUniqueId(), "pickup", reward.getMoney());
+ }
+ if (spendResult != TokenSpendStore.MarkResult.MARKED) {
+ // Roll back if we deposited but could not mark token consumption.
+ Core.getEconomyManager().withdrawPlayer(player, reward.getMoney());
+ if (spendResult == TokenSpendStore.MarkResult.DUPLICATE) {
+ Core.getMessages().debug(
+ "Rejected duplicated reward token for %s (token=%s). Deposit rolled back.",
+ player.getName(), reward.getTokenUUID());
+ callBack.setCancelled(true);
+ item.remove();
+ if (Core.getCoreRewardManager().getDroppedMoney().containsKey(item.getEntityId()))
+ Core.getCoreRewardManager().getDroppedMoney().remove(item.getEntityId());
} else {
Core.getMessages().debug(
- "%s picked up a %s" + ChatColor.RESET + " with a value:%s (# of rewards left=%s)(PickupRewards)",
- player.getName(), reward.isItemReward() ? "ITEM" : reward.getDisplayName(),
- Tools.format(Tools.round(reward.getMoney())),
- Core.getCoreRewardManager().getDroppedMoney().size());
- if (!Core.getPlayerSettingsManager().getPlayerSettings(player).isMuted())
- Core.getMessages().playerActionBarMessageQueue(player,
- Core.getMessages().getString("core.moneypickup", "money",
- Tools.format(reward.getMoney()), "rewardname",
- ChatColor.valueOf(Core.getConfigManager().rewardTextColor)
- + (reward.getDisplayName().isEmpty()
- ? Core.getConfigManager().bagOfGoldName
- : reward.getDisplayName())));
+ "Token store unavailable for %s (token=%s). Deposit rolled back and default pickup allowed.",
+ player.getName(), reward.getTokenUUID());
+ callBack.setCancelled(false);
}
+ return;
+ }
+
+ callBack.setCancelled(true);
+ item.remove();
+ if (Core.getCoreRewardManager().getDroppedMoney().containsKey(item.getEntityId()))
+ Core.getCoreRewardManager().getDroppedMoney().remove(item.getEntityId());
+ if (ProtocolLibCompat.isSupported())
+ ProtocolLibHelper.pickupMoney(player, item);
+
+ if (reward.getMoney() == 0) {
+ Core.getMessages().debug("%s picked up a %s" + ChatColor.RESET + " (# of rewards left=%s)",
+ player.getName(), reward.isItemReward() ? "ITEM" : reward.getDisplayName(),
+ Core.getCoreRewardManager().getDroppedMoney().size());
} else {
- callBack.setCancelled(true);
+ Core.getMessages().debug(
+ "%s picked up a %s" + ChatColor.RESET + " with a value:%s (# of rewards left=%s)(PickupRewards)",
+ player.getName(), reward.isItemReward() ? "ITEM" : reward.getDisplayName(),
+ Tools.format(Tools.round(reward.getMoney())),
+ Core.getCoreRewardManager().getDroppedMoney().size());
+ if (!Core.getPlayerSettingsManager().getPlayerSettings(player).isMuted())
+ Core.getMessages().playerActionBarMessageQueue(player,
+ Core.getMessages().getString("core.moneypickup", "money",
+ Tools.format(reward.getMoney()), "rewardname",
+ ChatColor.valueOf(Core.getConfigManager().rewardTextColor)
+ + (reward.getDisplayName().isEmpty()
+ ? Core.getConfigManager().bagOfGoldName
+ : reward.getDisplayName())));
}
+ } else {
+ // If no economy provider is active, let vanilla pickup handle the item.
+ callBack.setCancelled(false);
+ Core.getMessages().debug(
+ "Could not deposit reward value to economy for %s; allowing default pickup (reward=%s, value=%s).",
+ player.getName(), reward.getDisplayName(), Tools.format(Tools.round(reward.getMoney())));
}
}
}
}
-
- public interface CallBack {
-
- void setCancelled(boolean canceled);
-
- }
-
-}
+
+ public interface CallBack {
+
+ void setCancelled(boolean canceled);
+
+ }
+
+}
diff --git a/src/one/lindegaard/CustomItemsLib/rewards/Reward.java b/src/one/lindegaard/CustomItemsLib/rewards/Reward.java
index a1b415b..9d5e6b9 100644
--- a/src/one/lindegaard/CustomItemsLib/rewards/Reward.java
+++ b/src/one/lindegaard/CustomItemsLib/rewards/Reward.java
@@ -1,14 +1,15 @@
package one.lindegaard.CustomItemsLib.rewards;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-
-import java.util.Arrays;
+import java.util.Base64;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
+import org.bukkit.NamespacedKey;
import org.bukkit.block.Block;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
@@ -17,6 +18,8 @@
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.metadata.MetadataValue;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.bukkit.persistence.PersistentDataType;
import one.lindegaard.CustomItemsLib.Core;
import one.lindegaard.CustomItemsLib.Strings;
@@ -24,21 +27,41 @@
public class Reward {
- public final static String MH_REWARD_DATA_NEW = "MH:HiddenRewardDataNew";
-
- private String displayname = ""; // Hidden(0)
- private double money = 0; // Hidden(1) - the value of the reward
- private RewardType rewardType = null; // Hidden(2)
- private UUID skinUUID; // Hidden(4)
- private String encodedHash; // Hidden(5)
- private int id; // only used when the Reward is placed as a Block and when saved
+ public static final String MH_REWARD_DATA_NEW = "MH:HiddenRewardDataNew";
+
+ private static final int TOKEN_VERSION = 2;
+ private static final String LEGACY_0 = "Hidden(0):";
+ private static final String LEGACY_1 = "Hidden(1):";
+ private static final String LEGACY_2 = "Hidden(2):";
+ private static final String LEGACY_4 = "Hidden(4):";
+ private static final String LEGACY_5 = "Hidden(5):";
+
+ private static final String PDC_VERSION = "reward_version";
+ private static final String PDC_TYPE = "reward_type";
+ private static final String PDC_VALUE = "reward_value_per_unit";
+ private static final String PDC_TOKEN = "reward_token_uuid";
+ private static final String PDC_SIG = "reward_signature";
+ private static final String PDC_KEY_ID = "reward_key_id";
+ private static final String PDC_DISPLAY = "reward_display_name";
+ private static final String PDC_SKIN_UUID = "reward_skin_uuid";
+
+ private String displayname = "";
+ private double money = 0;
+ private RewardType rewardType = null;
+ private UUID skinUUID;
+ private String encodedHash;
+ private int id;
+
+ private String tokenUuid;
+ private String keyId;
+ private String signature;
public Reward() {
this.displayname = "Skull";
this.money = 0;
this.rewardType = RewardType.BAGOFGOLD;
this.skinUUID = UUID.fromString(RewardType.BAGOFGOLD.getUUID());
- this.encodedHash = Strings.encode(makeDecodedHash());
+ rotateTokenAndSign();
this.id = 0;
}
@@ -49,14 +72,21 @@ public Reward(Reward reward) {
this.skinUUID = reward.getSkinUUID();
this.encodedHash = reward.getEncodedHash();
this.id = reward.getUniqueID();
+ this.tokenUuid = reward.getTokenUUID();
+ this.keyId = reward.getKeyId();
+ this.signature = reward.getSignature();
}
public Reward(String displayName, double money, RewardType rewardType, UUID skinUUID) {
this.displayname = displayName;
this.money = money;
- this.rewardType = rewardType;
+ this.rewardType = rewardType == null ? RewardType.BAGOFGOLD : rewardType;
this.skinUUID = skinUUID;
- this.encodedHash = Strings.encode(makeDecodedHash());
+ rotateTokenAndSign();
+ }
+
+ public Reward(List lore) {
+ setReward(lore);
}
public int getUniqueID() {
@@ -67,178 +97,237 @@ public void setUniqueID(int id) {
this.id = id;
}
- public Reward(List lore) {
- setReward(lore);
+ private static NamespacedKey pdcKey(String path) {
+ if (Core.getInstance() == null) {
+ return null;
+ }
+ return new NamespacedKey(Core.getInstance(), path);
}
- private String makeDecodedHash() {
- return String.format(Locale.ENGLISH, "%.5f", money) + rewardType.getType();
+ private String canonicalPayload() {
+ String safeDisplay = displayname == null ? "" : displayname;
+ String safeSkin = skinUUID == null ? "" : skinUUID.toString();
+ String safeType = rewardType == null ? RewardType.BAGOFGOLD.getType() : rewardType.getType();
+ String displayEncoded = Base64.getEncoder().encodeToString(safeDisplay.getBytes(StandardCharsets.UTF_8));
+ return "v=" + TOKEN_VERSION + "|type=" + safeType + "|value="
+ + String.format(Locale.ENGLISH, "%.5f", money) + "|token=" + (tokenUuid == null ? "" : tokenUuid)
+ + "|skin=" + safeSkin + "|display=" + displayEncoded + "|key=" + (keyId == null ? "" : keyId);
}
private String makeDecodedHashOld() {
- return String.format(Locale.ENGLISH, "%.5f", money) + rewardType.getUUID();
+ return String.format(Locale.ENGLISH, "%.5f", money)
+ + (rewardType == null ? RewardType.BAGOFGOLD.getUUID() : rewardType.getUUID());
+ }
+
+ private String makeDecodedHash() {
+ return String.format(Locale.ENGLISH, "%.5f", money)
+ + (rewardType == null ? RewardType.BAGOFGOLD.getType() : rewardType.getType());
+ }
+
+ private void ensureType() {
+ if (rewardType == null) {
+ rewardType = RewardType.BAGOFGOLD;
+ }
+ }
+
+ private void ensureKeyId() {
+ if (keyId != null && !keyId.isEmpty()) {
+ return;
+ }
+ if (Core.getRewardSecurity() != null && Core.getRewardSecurity().isLoaded()) {
+ keyId = Core.getRewardSecurity().getActiveKeyId();
+ } else {
+ keyId = "k1";
+ }
+ }
+
+ private void ensureTokenUuid() {
+ if (tokenUuid == null || tokenUuid.isEmpty()) {
+ tokenUuid = UUID.randomUUID().toString();
+ }
+ }
+
+ private void updateSignature() {
+ ensureType();
+ ensureTokenUuid();
+ ensureKeyId();
+ if (Core.getRewardSecurity() != null && Core.getRewardSecurity().isLoaded()) {
+ signature = Core.getRewardSecurity().sign(keyId, canonicalPayload());
+ encodedHash = signature;
+ } else {
+ signature = "";
+ encodedHash = Strings.encode(makeDecodedHash());
+ }
+ }
+
+ public void rotateTokenAndSign() {
+ tokenUuid = UUID.randomUUID().toString();
+ updateSignature();
}
public boolean checkHash() {
- if (this.encodedHash != null)
+ if (tokenUuid != null && !tokenUuid.isEmpty() && signature != null && !signature.isEmpty()) {
+ if (Core.getRewardSecurity() == null || !Core.getRewardSecurity().isLoaded()) {
+ return false;
+ }
+ return Core.getRewardSecurity().verify(keyId, canonicalPayload(), signature);
+ }
+ if (this.encodedHash != null) {
return makeDecodedHash().equals(Strings.decode(this.encodedHash))
|| makeDecodedHashOld().equals(Strings.decode(this.encodedHash));
- else
- return true;
+ }
+ return true;
}
public void updateEncodedHash() {
- this.encodedHash = Strings.encode(makeDecodedHash());
+ updateSignature();
+ }
+
+ private static RewardType parseRewardType(String rewardTypeStr) {
+ if (rewardTypeStr == null || rewardTypeStr.isEmpty()) {
+ return RewardType.BAGOFGOLD;
+ }
+ try {
+ return RewardType.valueOf(rewardTypeStr);
+ } catch (Exception ignored) {
+ }
+
+ if (RewardType.BAGOFGOLD.getUUID().equals(rewardTypeStr))
+ return RewardType.BAGOFGOLD;
+ if (RewardType.ITEM.getUUID().equals(rewardTypeStr))
+ return RewardType.ITEM;
+ if (RewardType.KILLED.getUUID().equals(rewardTypeStr))
+ return RewardType.KILLED;
+ if (RewardType.KILLER.getUUID().equals(rewardTypeStr))
+ return RewardType.KILLER;
+
+ return RewardType.BAGOFGOLD;
}
public void setReward(List lore) {
String moneyStr = "", rewardTypeStr = "";
+ tokenUuid = null;
+ keyId = null;
+ signature = null;
+ encodedHash = null;
+ if (lore == null) {
+ rewardType = RewardType.BAGOFGOLD;
+ money = 0;
+ displayname = Core.getConfigManager() != null ? Core.getConfigManager().bagOfGoldName : "BagOfGold";
+ rotateTokenAndSign();
+ return;
+ }
+
for (int n = 0; n < lore.size(); n++) {
String str = lore.get(n);
- // DisplayName
- if (str.startsWith("Hidden(0):"))
- this.displayname = str.substring(10);
-
- // Money
- else if (str.startsWith("Hidden(1):")) {
- moneyStr = str.substring(10); // Dont remove this line
+ if (str.startsWith(LEGACY_0)) {
+ this.displayname = str.substring(LEGACY_0.length());
+ } else if (str.startsWith(LEGACY_1)) {
+ moneyStr = str.substring(LEGACY_1.length());
this.money = Double.valueOf(moneyStr);
- }
-
- // RewardType
- else if (str.startsWith("Hidden(2):")) {
- rewardTypeStr = str.substring(10); // Dont remove this line
- try {
- this.rewardType = RewardType.valueOf(rewardTypeStr);
- } catch (Exception e) {
- if (RewardType.BAGOFGOLD.getUUID().equals(rewardTypeStr))
- rewardType = RewardType.BAGOFGOLD;
- else if (RewardType.ITEM.getUUID().equals(rewardTypeStr))
- rewardType = RewardType.ITEM;
- else if (RewardType.KILLED.getUUID().equals(rewardTypeStr))
- rewardType = RewardType.KILLED;
- else if (RewardType.KILLER.getUUID().equals(rewardTypeStr))
- rewardType = RewardType.KILLER;
- else
- rewardType = RewardType.BAGOFGOLD;
- }
- }
-
- // Skin UUID
- else if (str.startsWith("Hidden(4):"))
- this.skinUUID = (str.length() > 10) ? UUID.fromString(str.substring(10)) : null;
-
- // Hash
- else if (str.startsWith("Hidden(5):")) {
- this.encodedHash = str.substring(10);
+ } else if (str.startsWith(LEGACY_2)) {
+ rewardTypeStr = str.substring(LEGACY_2.length());
+ this.rewardType = parseRewardType(rewardTypeStr);
+ } else if (str.startsWith(LEGACY_4)) {
+ this.skinUUID = (str.length() > LEGACY_4.length()) ? UUID.fromString(str.substring(LEGACY_4.length()))
+ : null;
+ } else if (str.startsWith(LEGACY_5)) {
+ this.encodedHash = str.substring(LEGACY_5.length());
String compareHash = Strings.encode(moneyStr + rewardTypeStr);
if (!encodedHash.equalsIgnoreCase(compareHash)) {
Bukkit.getConsoleSender().sendMessage(Core.PREFIX + ChatColor.RED
+ "[Warning] A player has tried to change the value of a BagOfGold Item. Value set to 0!");
money = 0;
- updateEncodedHash();
+ encodedHash = Strings.encode(makeDecodedHash());
}
}
}
+ if (displayname == null || displayname.isEmpty()) {
+ displayname = Core.getConfigManager() != null ? Core.getConfigManager().bagOfGoldName : "BagOfGold";
+ }
+ ensureType();
+ rotateTokenAndSign();
}
public ArrayList getHiddenLore() {
- ArrayList lores = new ArrayList(Arrays.asList("Hidden(0):" + displayname, // displayname
- "Hidden(1):" + String.format(Locale.ENGLISH, "%.5f", money), // value
- "Hidden(2):" + rewardType.getType(), // type
- "Hidden(4):" + (skinUUID == null ? "" : skinUUID.toString()), // SkinUUID
- "Hidden(5):" + encodedHash)); // Hash
+ ArrayList lores = new ArrayList();
if (rewardType != RewardType.BAGOFGOLD)
lores.add(Core.getMessages().getString("core.reward.lore"));
return lores;
-
}
- /**
- * @return the displayname
- */
public String getDisplayName() {
return displayname;
}
- /**
- * @return the money
- */
public double getMoney() {
return money;
}
- /**
- * @return the uuid
- */
public RewardType getRewardType() {
return rewardType;
}
- /**
- * @param displayName the displayName to set
- */
+ public String getTokenUUID() {
+ return tokenUuid;
+ }
+
+ public String getKeyId() {
+ return keyId;
+ }
+
+ public String getSignature() {
+ return signature;
+ }
+
public void setDisplayname(String displayName) {
this.displayname = displayName;
+ updateSignature();
}
- /**
- * @param money the money to set
- */
public void setMoney(double money) {
this.money = money;
- updateEncodedHash();
+ updateSignature();
}
- /**
- * @param rewardType the uuid to set
- */
public void setRewardType(RewardType rewardType) {
this.rewardType = rewardType;
- updateEncodedHash();
+ updateSignature();
}
- /**
- * Get the skin UUID for the reward
- *
- * @return
- */
public UUID getSkinUUID() {
return skinUUID;
}
- /**
- * Set the skin UUID for the reward
- *
- * @param skinUUID
- */
public void setSkinUUID(UUID skinUUID) {
this.skinUUID = skinUUID;
+ updateSignature();
}
- /**
- * @return the hash
- */
public String getEncodedHash() {
return encodedHash;
}
- /**
- * @param hash the hash to set
- */
public void setHash(String hash) {
this.encodedHash = hash;
+ this.signature = hash;
}
public String toString() {
return "{Description=" + displayname + ", money=" + String.format(Locale.ENGLISH, "%.5f", money) + ", type="
- + rewardType.getType() + ", Skin=" + skinUUID + ", id=" + id + "}";
+ + rewardType.getType() + ", Skin=" + skinUUID + ", token=" + tokenUuid + ", id=" + id + "}";
}
public boolean equals(Reward reward) {
+ if (reward == null)
+ return false;
+ if (skinUUID == null && reward.getSkinUUID() != null)
+ return false;
+ if (skinUUID != null && !skinUUID.equals(reward.getSkinUUID()))
+ return false;
return displayname.equalsIgnoreCase(reward.getDisplayName()) && money == reward.money
- && rewardType == reward.getRewardType() && skinUUID.equals(reward.getSkinUUID()) && checkHash();
+ && rewardType == reward.getRewardType() && checkHash();
}
public void save(ConfigurationSection section) {
@@ -247,32 +336,38 @@ public void save(ConfigurationSection section) {
section.set("type", rewardType.getType());
section.set("skinuuid", skinUUID == null ? "" : skinUUID.toString());
section.set("hash", encodedHash == null ? "" : Strings.decode(encodedHash));
+ section.set("token_uuid", tokenUuid == null ? "" : tokenUuid);
+ section.set("token_key_id", keyId == null ? "" : keyId);
+ section.set("token_sig", signature == null ? "" : signature);
}
public void read(ConfigurationSection section) throws InvalidConfigurationException {
if (section.contains("displayname"))
displayname = section.getString("displayname");
else
- displayname = section.getString("description"); // old config name
+ displayname = section.getString("description");
money = Double.valueOf(section.getString("money").replace(",", "."));
if (section.contains("type"))
- rewardType = RewardType.valueOf(section.getString("type"));
+ rewardType = parseRewardType(section.getString("type"));
else {
- String uuid = section.getString("uuid"); // old config name
- if (RewardType.BAGOFGOLD.getUUID().equals(uuid))
- rewardType = RewardType.BAGOFGOLD;
- else if (RewardType.ITEM.getUUID().equals(uuid))
- rewardType = RewardType.ITEM;
- else if (RewardType.KILLED.getUUID().equals(uuid))
- rewardType = RewardType.KILLED;
- else if (RewardType.KILLER.getUUID().equals(uuid))
- rewardType = RewardType.KILLER;
- else
- rewardType = RewardType.BAGOFGOLD;
+ String uuid = section.getString("uuid");
+ rewardType = parseRewardType(uuid);
+ }
+
+ skinUUID = null;
+ String skin = section.getString("skinuuid", "");
+ if (skin != null && !skin.isEmpty()) {
+ skinUUID = UUID.fromString(skin);
}
- skinUUID = UUID.fromString(section.getString("skinuuid"));
+ tokenUuid = section.getString("token_uuid", "");
+ keyId = section.getString("token_key_id", "");
+ signature = section.getString("token_sig", "");
encodedHash = Strings.encode(section.getString("hash", makeDecodedHash()));
+ if (tokenUuid == null || tokenUuid.isEmpty() || keyId == null || keyId.isEmpty() || signature == null
+ || signature.isEmpty()) {
+ rotateTokenAndSign();
+ }
}
public boolean isMoney() {
@@ -296,56 +391,102 @@ public boolean isItemReward() {
}
public static boolean isReward(Item item) {
- return item.hasMetadata(MH_REWARD_DATA_NEW) || isReward(item.getItemStack());
+ return item != null && (item.hasMetadata(MH_REWARD_DATA_NEW) || isReward(item.getItemStack()));
}
public static Reward getReward(Item item) {
- if (item.hasMetadata(MH_REWARD_DATA_NEW))
+ if (item != null && item.hasMetadata(MH_REWARD_DATA_NEW)) {
for (MetadataValue mv : item.getMetadata(MH_REWARD_DATA_NEW)) {
if (mv.value() instanceof Reward)
- return (Reward) item.getMetadata(MH_REWARD_DATA_NEW).get(0).value();
+ return (Reward) mv.value();
}
- return getReward(item.getItemStack());
+ }
+ return item == null ? null : getReward(item.getItemStack());
}
- public static boolean isReward(ItemStack itemStack) {
- if (itemStack != null && itemStack.hasItemMeta() && itemStack.getItemMeta().hasLore()
- && itemStack.getItemMeta().getLore().size() > 2) {
- String lore = itemStack.getItemMeta().getLore().get(2);
- if (lore.startsWith("Hidden(2):")) {
- lore = lore.substring(10);
- return lore.equals(RewardType.BAGOFGOLD.getType()) || lore.equals(RewardType.KILLED.getType())
- || lore.equals(RewardType.KILLER.getType()) || lore.equals(RewardType.ITEM.getType())
- || lore.equals(RewardType.BAGOFGOLD.getUUID()) || lore.equals(RewardType.KILLED.getUUID())
- || lore.equals(RewardType.KILLER.getUUID()) || lore.equals(RewardType.ITEM.getUUID());
- } else
- return false;
-
- } else
+ private static boolean hasPdcReward(ItemMeta itemMeta) {
+ if (itemMeta == null)
return false;
+ NamespacedKey versionKey = pdcKey(PDC_VERSION);
+ NamespacedKey typeKey = pdcKey(PDC_TYPE);
+ NamespacedKey valueKey = pdcKey(PDC_VALUE);
+ NamespacedKey tokenKey = pdcKey(PDC_TOKEN);
+ NamespacedKey sigKey = pdcKey(PDC_SIG);
+ NamespacedKey keyIdKey = pdcKey(PDC_KEY_ID);
+ if (versionKey == null || typeKey == null || valueKey == null || tokenKey == null || sigKey == null
+ || keyIdKey == null) {
+ return false;
+ }
+ PersistentDataContainer pdc = itemMeta.getPersistentDataContainer();
+ return pdc.has(versionKey, PersistentDataType.INTEGER) && pdc.has(typeKey, PersistentDataType.STRING)
+ && pdc.has(valueKey, PersistentDataType.DOUBLE) && pdc.has(tokenKey, PersistentDataType.STRING)
+ && pdc.has(sigKey, PersistentDataType.STRING) && pdc.has(keyIdKey, PersistentDataType.STRING);
}
- public static boolean isHead(ItemStack itemStack) {
- if (itemStack != null && itemStack.hasItemMeta() && itemStack.getItemMeta().hasLore()
- && itemStack.getItemMeta().getLore().size() > 2) {
- String lore = itemStack.getItemMeta().getLore().get(2);
- if (lore.startsWith("Hidden(2):")) {
- lore = lore.substring(10);
- return lore.equals(RewardType.KILLED.getType()) || lore.equals(RewardType.KILLER.getType())
- || lore.equals(RewardType.KILLED.getUUID()) || lore.equals(RewardType.KILLER.getUUID());
- } else
- return false;
+ public static boolean isReward(ItemStack itemStack) {
+ if (itemStack == null || !itemStack.hasItemMeta())
+ return false;
+ return hasPdcReward(itemStack.getItemMeta());
+ }
- } else
+ public static boolean isLegacyReward(ItemStack itemStack) {
+ if (itemStack == null || !itemStack.hasItemMeta())
+ return false;
+ ItemMeta meta = itemStack.getItemMeta();
+ if (!meta.hasLore() || meta.getLore() == null || meta.getLore().size() < 3)
+ return false;
+ String lore = meta.getLore().get(2);
+ if (!lore.startsWith(LEGACY_2))
return false;
+ String type = lore.substring(LEGACY_2.length());
+ return type.equals(RewardType.BAGOFGOLD.getType()) || type.equals(RewardType.KILLED.getType())
+ || type.equals(RewardType.KILLER.getType()) || type.equals(RewardType.ITEM.getType())
+ || type.equals(RewardType.BAGOFGOLD.getUUID()) || type.equals(RewardType.KILLED.getUUID())
+ || type.equals(RewardType.KILLER.getUUID()) || type.equals(RewardType.ITEM.getUUID());
+ }
+
+ private static Reward fromPdc(ItemStack itemStack) {
+ if (itemStack == null || !itemStack.hasItemMeta())
+ return null;
+ ItemMeta itemMeta = itemStack.getItemMeta();
+ if (!hasPdcReward(itemMeta))
+ return null;
+
+ PersistentDataContainer pdc = itemMeta.getPersistentDataContainer();
+ Reward reward = new Reward();
+ reward.displayname = pdc.getOrDefault(pdcKey(PDC_DISPLAY), PersistentDataType.STRING,
+ (itemMeta.hasDisplayName() ? ChatColor.stripColor(itemMeta.getDisplayName()) : "BagOfGold"));
+ reward.money = pdc.getOrDefault(pdcKey(PDC_VALUE), PersistentDataType.DOUBLE, 0D);
+ reward.rewardType = parseRewardType(pdc.getOrDefault(pdcKey(PDC_TYPE), PersistentDataType.STRING,
+ RewardType.BAGOFGOLD.getType()));
+ String skin = pdc.getOrDefault(pdcKey(PDC_SKIN_UUID), PersistentDataType.STRING, "");
+ reward.skinUUID = skin.isEmpty() ? null : UUID.fromString(skin);
+ reward.tokenUuid = pdc.getOrDefault(pdcKey(PDC_TOKEN), PersistentDataType.STRING, "");
+ reward.keyId = pdc.getOrDefault(pdcKey(PDC_KEY_ID), PersistentDataType.STRING, "");
+ reward.signature = pdc.getOrDefault(pdcKey(PDC_SIG), PersistentDataType.STRING, "");
+ reward.encodedHash = reward.signature;
+ return reward;
}
public static Reward getReward(ItemStack itemStack) {
+ return fromPdc(itemStack);
+ }
+
+ public static Reward getLegacyReward(ItemStack itemStack) {
+ if (!isLegacyReward(itemStack))
+ return null;
return new Reward(itemStack.getItemMeta().getLore());
}
+ public static ItemStack migrateLegacyReward(ItemStack itemStack) {
+ Reward legacy = getLegacyReward(itemStack);
+ if (legacy == null)
+ return itemStack;
+ return setDisplayNameAndHiddenLores(itemStack, legacy);
+ }
+
public static boolean isReward(Block block) {
- return block.hasMetadata(MH_REWARD_DATA_NEW);
+ return block != null && block.hasMetadata(MH_REWARD_DATA_NEW);
}
public static Reward getReward(Block block) {
@@ -353,24 +494,39 @@ public static Reward getReward(Block block) {
}
public static boolean isReward(Entity entity) {
- return entity.hasMetadata(MH_REWARD_DATA_NEW);
+ return entity != null && entity.hasMetadata(MH_REWARD_DATA_NEW);
}
public static Reward getReward(Entity entity) {
return (Reward) entity.getMetadata(MH_REWARD_DATA_NEW).get(0).value();
}
+ private static void writeRewardPdc(ItemMeta itemMeta, Reward reward) {
+ PersistentDataContainer pdc = itemMeta.getPersistentDataContainer();
+ pdc.set(pdcKey(PDC_VERSION), PersistentDataType.INTEGER, TOKEN_VERSION);
+ pdc.set(pdcKey(PDC_TYPE), PersistentDataType.STRING,
+ reward.getRewardType() == null ? RewardType.BAGOFGOLD.getType() : reward.getRewardType().getType());
+ pdc.set(pdcKey(PDC_VALUE), PersistentDataType.DOUBLE, reward.getMoney());
+ pdc.set(pdcKey(PDC_TOKEN), PersistentDataType.STRING, reward.getTokenUUID());
+ pdc.set(pdcKey(PDC_SIG), PersistentDataType.STRING, reward.getSignature());
+ pdc.set(pdcKey(PDC_KEY_ID), PersistentDataType.STRING, reward.getKeyId());
+ pdc.set(pdcKey(PDC_DISPLAY), PersistentDataType.STRING,
+ reward.getDisplayName() == null ? "" : reward.getDisplayName());
+ pdc.set(pdcKey(PDC_SKIN_UUID), PersistentDataType.STRING,
+ reward.getSkinUUID() == null ? "" : reward.getSkinUUID().toString());
+ }
+
/**
- * setDisplayNameAndHiddenLores: add the Display name and the (hidden) Lores.
- * The lores identifies the reward and contain secret information.
- *
- * @param skull - The base itemStack without the information.
- * @param reward - The reward information is added to the ItemStack
- * @return the updated ItemStack.
+ * setDisplayNameAndHiddenLores: add the Display name and secure metadata.
*/
public static ItemStack setDisplayNameAndHiddenLores(ItemStack skull, Reward reward) {
ItemMeta skullMeta = skull.getItemMeta();
- skullMeta.setLore(reward.getHiddenLore());
+ if (skullMeta == null) {
+ return skull;
+ }
+
+ reward.rotateTokenAndSign();
+ skullMeta.setLore(null);
if (reward.getRewardType() == RewardType.BAGOFGOLD) {
skullMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&',
@@ -379,9 +535,8 @@ public static ItemStack setDisplayNameAndHiddenLores(ItemStack skull, Reward rew
} else if (reward.getRewardType() == RewardType.ITEM)
if (reward.getMoney() == 0)
- skullMeta.setDisplayName(
- ChatColor.translateAlternateColorCodes('&', Core.getConfigManager().itemDisplayNameFormatNoValue
- .replace("{name}", reward.getDisplayName())));
+ skullMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&',
+ Core.getConfigManager().itemDisplayNameFormatNoValue.replace("{name}", reward.getDisplayName())));
else
skullMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&',
Core.getConfigManager().itemDisplayNameFormat.replace("{name}", reward.getDisplayName())
@@ -406,33 +561,67 @@ else if (reward.getRewardType() == RewardType.KILLER)
skullMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&',
Core.getConfigManager().killerHeadDisplayNameFormat.replace("{name}", reward.getDisplayName())
.replace("{value}", Tools.format(reward.getMoney()))));
+
+ writeRewardPdc(skullMeta, reward);
skull.setItemMeta(skullMeta);
return skull;
}
public static ItemStack setDisplayNameAndHiddenLores(ItemStack skull, String name, double value,
List lores) {
- ItemMeta skullMeta = skull.getItemMeta();
- skullMeta.setLore(lores);
- skullMeta.setDisplayName(
- ChatColor.translateAlternateColorCodes('&', Core.getConfigManager().bagOfGoldDisplayNameFormat
- .replace("{name}", name).replace("{value}", Tools.format(value))));
- skull.setItemMeta(skullMeta);
- return skull;
+ RewardType rewardType = RewardType.BAGOFGOLD;
+ UUID skinUuid = null;
+ List visibleLore = new ArrayList();
+ if (lores != null) {
+ for (String line : lores) {
+ if (line == null)
+ continue;
+ if (line.startsWith(LEGACY_2)) {
+ rewardType = parseRewardType(line.substring(LEGACY_2.length()));
+ } else if (line.startsWith(LEGACY_4)) {
+ String raw = line.substring(LEGACY_4.length());
+ if (!raw.isEmpty()) {
+ skinUuid = UUID.fromString(raw);
+ }
+ } else if (!line.trim().startsWith("Hidden(")) {
+ visibleLore.add(line);
+ }
+ }
+ }
+
+ Reward reward = new Reward(name, value, rewardType, skinUuid);
+ ItemStack updated = setDisplayNameAndHiddenLores(skull, reward);
+ if (!visibleLore.isEmpty()) {
+ ItemMeta skullMeta = updated.getItemMeta();
+ if (skullMeta != null) {
+ skullMeta.setLore(visibleLore);
+ updated.setItemMeta(skullMeta);
+ }
+ }
+ return updated;
}
public static boolean isFakeReward(Item item) {
- ItemStack itemStack = item.getItemStack();
+ ItemStack itemStack = item == null ? null : item.getItemStack();
return isFakeReward(itemStack);
}
public static boolean isFakeReward(ItemStack itemStack) {
if (itemStack != null && itemStack.hasItemMeta() && itemStack.getItemMeta().hasDisplayName()
&& itemStack.getItemMeta().getDisplayName().contains(Core.getConfigManager().bagOfGoldName)) {
- if (!itemStack.getItemMeta().hasLore()) {
- return true;
- }
+ return !isReward(itemStack);
}
return false;
}
+
+ public static boolean isHead(ItemStack itemStack) {
+ if (!isReward(itemStack)) {
+ return false;
+ }
+ Reward reward = getReward(itemStack);
+ if (reward == null) {
+ return false;
+ }
+ return reward.isKilledHeadReward() || reward.isKillerHeadReward();
+ }
}
diff --git a/src/one/lindegaard/CustomItemsLib/rewards/RewardSecurity.java b/src/one/lindegaard/CustomItemsLib/rewards/RewardSecurity.java
new file mode 100644
index 0000000..d9cf3f1
--- /dev/null
+++ b/src/one/lindegaard/CustomItemsLib/rewards/RewardSecurity.java
@@ -0,0 +1,139 @@
+package one.lindegaard.CustomItemsLib.rewards;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import one.lindegaard.CustomItemsLib.Core;
+
+public class RewardSecurity {
+
+ private static final int SECRET_BYTES = 32;
+ private static final String HMAC_ALGORITHM = "HmacSHA256";
+
+ private final Core plugin;
+ private final File file;
+
+ private final Map keys = new HashMap<>();
+ private String activeKeyId = "k1";
+ private boolean loaded = false;
+
+ public RewardSecurity(Core plugin) {
+ this.plugin = plugin;
+ this.file = new File(plugin.getDataFolder(), "security.yml");
+ }
+
+ public synchronized void initialize() throws IOException {
+ if (!file.exists()) {
+ createDefaultConfig();
+ }
+
+ YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
+ activeKeyId = config.getString("hmac.active-key-id", "k1");
+
+ keys.clear();
+ ConfigurationSection sec = config.getConfigurationSection("hmac.keys");
+ if (sec != null) {
+ Set ids = sec.getKeys(false);
+ for (String id : ids) {
+ String encoded = sec.getString(id, "").trim();
+ if (!encoded.isEmpty()) {
+ keys.put(id, Base64.getDecoder().decode(encoded));
+ }
+ }
+ }
+
+ if (keys.isEmpty()) {
+ createDefaultConfig();
+ config = YamlConfiguration.loadConfiguration(file);
+ activeKeyId = config.getString("hmac.active-key-id", "k1");
+ sec = config.getConfigurationSection("hmac.keys");
+ if (sec != null) {
+ for (String id : sec.getKeys(false)) {
+ String encoded = sec.getString(id, "").trim();
+ if (!encoded.isEmpty()) {
+ keys.put(id, Base64.getDecoder().decode(encoded));
+ }
+ }
+ }
+ }
+
+ if (!keys.containsKey(activeKeyId)) {
+ byte[] randomSecret = randomSecret();
+ keys.put(activeKeyId, randomSecret);
+ config.set("hmac.keys." + activeKeyId, Base64.getEncoder().encodeToString(randomSecret));
+ config.save(file);
+ }
+
+ loaded = true;
+ }
+
+ private void createDefaultConfig() throws IOException {
+ YamlConfiguration config = new YamlConfiguration();
+ config.set("security-format-version", 1);
+ config.set("hmac.active-key-id", "k1");
+ config.set("hmac.accept-legacy-keys", true);
+ config.set("hmac.keys.k1", Base64.getEncoder().encodeToString(randomSecret()));
+ config.save(file);
+ }
+
+ private byte[] randomSecret() {
+ SecureRandom secureRandom = new SecureRandom();
+ byte[] bytes = new byte[SECRET_BYTES];
+ secureRandom.nextBytes(bytes);
+ return bytes;
+ }
+
+ public synchronized boolean isLoaded() {
+ return loaded;
+ }
+
+ public synchronized String getActiveKeyId() {
+ return activeKeyId;
+ }
+
+ public synchronized String sign(String keyId, String payload) {
+ if (!loaded || payload == null || keyId == null) {
+ return "";
+ }
+ byte[] key = keys.get(keyId);
+ if (key == null) {
+ return "";
+ }
+
+ try {
+ Mac mac = Mac.getInstance(HMAC_ALGORITHM);
+ mac.init(new SecretKeySpec(key, HMAC_ALGORITHM));
+ byte[] signed = mac.doFinal(payload.getBytes(java.nio.charset.StandardCharsets.UTF_8));
+ return Base64.getEncoder().encodeToString(signed);
+ } catch (GeneralSecurityException ex) {
+ return "";
+ }
+ }
+
+ public synchronized boolean verify(String keyId, String payload, String signature) {
+ if (!loaded || keyId == null || payload == null || signature == null || signature.isEmpty()) {
+ return false;
+ }
+
+ String expected = sign(keyId, payload);
+ if (expected.isEmpty()) {
+ return false;
+ }
+
+ return MessageDigest.isEqual(expected.getBytes(java.nio.charset.StandardCharsets.UTF_8),
+ signature.getBytes(java.nio.charset.StandardCharsets.UTF_8));
+ }
+}
diff --git a/src/one/lindegaard/CustomItemsLib/rewards/TokenSpendStore.java b/src/one/lindegaard/CustomItemsLib/rewards/TokenSpendStore.java
new file mode 100644
index 0000000..055f340
--- /dev/null
+++ b/src/one/lindegaard/CustomItemsLib/rewards/TokenSpendStore.java
@@ -0,0 +1,144 @@
+package one.lindegaard.CustomItemsLib.rewards;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.UUID;
+
+import com.mysql.cj.jdbc.MysqlDataSource;
+
+import one.lindegaard.CustomItemsLib.Core;
+
+public class TokenSpendStore {
+
+ public enum MarkResult {
+ MARKED,
+ DUPLICATE,
+ ERROR
+ }
+
+ private final Core plugin;
+
+ public TokenSpendStore(Core plugin) {
+ this.plugin = plugin;
+ }
+
+ public void initialize() throws Exception {
+ try (Connection connection = openConnection(); Statement statement = connection.createStatement()) {
+ statement.executeUpdate("CREATE TABLE IF NOT EXISTS mh_spent_tokens ("
+ + " token_uuid VARCHAR(64) NOT NULL PRIMARY KEY,"
+ + " player_uuid VARCHAR(36),"
+ + " source VARCHAR(64),"
+ + " amount DOUBLE,"
+ + " spent_at BIGINT NOT NULL"
+ + ")");
+ }
+ }
+
+ public MarkResult markTokenSpent(String tokenUuid, UUID playerUuid, String source, double amount) {
+ if (tokenUuid == null || tokenUuid.isEmpty()) {
+ return MarkResult.ERROR;
+ }
+
+ String sql;
+ boolean mysql = isMySQL();
+ if (mysql) {
+ sql = "INSERT IGNORE INTO mh_spent_tokens(token_uuid, player_uuid, source, amount, spent_at) VALUES(?,?,?,?,?)";
+ } else {
+ sql = "INSERT OR IGNORE INTO mh_spent_tokens(token_uuid, player_uuid, source, amount, spent_at) VALUES(?,?,?,?,?)";
+ }
+
+ try (Connection connection = openConnection(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setString(1, tokenUuid);
+ statement.setString(2, playerUuid == null ? null : playerUuid.toString());
+ statement.setString(3, source == null ? "unknown" : source);
+ statement.setDouble(4, amount);
+ statement.setLong(5, System.currentTimeMillis());
+
+ int updated = statement.executeUpdate();
+ return updated > 0 ? MarkResult.MARKED : MarkResult.DUPLICATE;
+ } catch (SQLException ex) {
+ if (mysql && isDuplicateKey(ex)) {
+ return MarkResult.DUPLICATE;
+ }
+ Core.getMessages().debug("TokenSpendStore markTokenSpent failed for %s: %s", tokenUuid, ex.getMessage());
+ return MarkResult.ERROR;
+ } catch (Exception ex) {
+ Core.getMessages().debug("TokenSpendStore markTokenSpent failed for %s: %s", tokenUuid, ex.getMessage());
+ return MarkResult.ERROR;
+ }
+ }
+
+ public boolean isTokenSpent(String tokenUuid) {
+ if (tokenUuid == null || tokenUuid.isEmpty()) {
+ return false;
+ }
+
+ try (Connection connection = openConnection();
+ PreparedStatement statement = connection
+ .prepareStatement("SELECT 1 FROM mh_spent_tokens WHERE token_uuid=? LIMIT 1")) {
+ statement.setString(1, tokenUuid);
+ try (ResultSet rs = statement.executeQuery()) {
+ return rs.next();
+ }
+ } catch (Exception ex) {
+ Core.getMessages().debug("TokenSpendStore isTokenSpent failed for %s: %s", tokenUuid, ex.getMessage());
+ return false;
+ }
+ }
+
+ public boolean revokeToken(String tokenUuid, String source) {
+ return markTokenSpent(tokenUuid, null, source == null ? "manual-revoke" : source, 0D) == MarkResult.MARKED;
+ }
+
+ public int countTokens() {
+ try (Connection connection = openConnection();
+ Statement statement = connection.createStatement();
+ ResultSet rs = statement.executeQuery("SELECT COUNT(*) FROM mh_spent_tokens")) {
+ if (rs.next()) {
+ return rs.getInt(1);
+ }
+ return 0;
+ } catch (Exception ex) {
+ Core.getMessages().debug("TokenSpendStore countTokens failed: %s", ex.getMessage());
+ return 0;
+ }
+ }
+
+ private boolean isMySQL() {
+ return Core.getConfigManager().databaseType != null
+ && Core.getConfigManager().databaseType.equalsIgnoreCase("mysql");
+ }
+
+ private boolean isDuplicateKey(SQLException ex) {
+ return ex.getErrorCode() == 1062 || (ex.getSQLState() != null && ex.getSQLState().startsWith("23"));
+ }
+
+ private Connection openConnection() throws Exception {
+ if (isMySQL()) {
+ Class.forName("com.mysql.cj.jdbc.Driver");
+ MysqlDataSource dataSource = new MysqlDataSource();
+ dataSource.setUser(Core.getConfigManager().databaseUsername);
+ dataSource.setPassword(Core.getConfigManager().databasePassword);
+ if (Core.getConfigManager().databaseHost.contains(":")) {
+ dataSource.setServerName(Core.getConfigManager().databaseHost.split(":")[0]);
+ dataSource.setPort(Integer.parseInt(Core.getConfigManager().databaseHost.split(":")[1]));
+ } else {
+ dataSource.setServerName(Core.getConfigManager().databaseHost);
+ }
+ dataSource.setDatabaseName(Core.getConfigManager().databaseName);
+ Connection connection = dataSource.getConnection();
+ connection.setAutoCommit(true);
+ return connection;
+ }
+
+ Connection connection = DriverManager
+ .getConnection("jdbc:sqlite:" + plugin.getDataFolder().getPath() + "/" + Core.getConfigManager().databaseName
+ + ".db");
+ connection.setAutoCommit(true);
+ return connection;
+ }
+}
diff --git a/src/one/lindegaard/CustomItemsLib/server/Servers.java b/src/one/lindegaard/CustomItemsLib/server/Servers.java
index 3a4aa1c..34383bb 100644
--- a/src/one/lindegaard/CustomItemsLib/server/Servers.java
+++ b/src/one/lindegaard/CustomItemsLib/server/Servers.java
@@ -4,180 +4,183 @@
public class Servers {
+ private static final VersionInfo SERVER_VERSION = detectServerVersion();
+
+ private static final class VersionInfo {
+ private final int major;
+ private final int minor;
+ private final int patch;
+ private final boolean valid;
+
+ private VersionInfo(int major, int minor, int patch, boolean valid) {
+ this.major = major;
+ this.minor = minor;
+ this.patch = patch;
+ this.valid = valid;
+ }
+ }
+
+ private static VersionInfo detectServerVersion() {
+ try {
+ String bukkitVersion = Bukkit.getBukkitVersion();
+ String coreVersion = bukkitVersion.split("-", 2)[0];
+ String[] numbers = coreVersion.split("\\.");
+ if (numbers.length < 2) {
+ return new VersionInfo(0, 0, 0, false);
+ }
+
+ int major = Integer.parseInt(numbers[0]);
+ int minor = Integer.parseInt(numbers[1]);
+ int patch = numbers.length >= 3 ? Integer.parseInt(numbers[2]) : 0;
+ return new VersionInfo(major, minor, patch, true);
+ } catch (Exception ignored) {
+ return new VersionInfo(0, 0, 0, false);
+ }
+ }
+
+ private static boolean isVersion(int major, int minor) {
+ return SERVER_VERSION.valid && SERVER_VERSION.major == major && SERVER_VERSION.minor == minor;
+ }
+
+ private static boolean isVersion(int major, int minor, int patch) {
+ return SERVER_VERSION.valid && SERVER_VERSION.major == major && SERVER_VERSION.minor == minor
+ && SERVER_VERSION.patch == patch;
+ }
+
+ private static boolean isAtLeast(int major, int minor) {
+ return isAtLeast(major, minor, 0);
+ }
+
+ private static boolean isAtLeast(int major, int minor, int patch) {
+ // Fail-open for unknown version formats to preserve legacy behavior.
+ if (!SERVER_VERSION.valid)
+ return true;
+
+ if (SERVER_VERSION.major != major)
+ return SERVER_VERSION.major > major;
+ if (SERVER_VERSION.minor != minor)
+ return SERVER_VERSION.minor > minor;
+ return SERVER_VERSION.patch >= patch;
+ }
+
// *******************************************************************
// Version detection
// *******************************************************************
public static boolean isMC121() {
- return Bukkit.getBukkitVersion().contains("1.21");
+ return isVersion(1, 21);
}
public static boolean isMC120() {
- return Bukkit.getBukkitVersion().contains("1.20");
+ return isVersion(1, 20);
}
public static boolean isMC119() {
- return Bukkit.getBukkitVersion().contains("1.19");
+ return isVersion(1, 19);
}
public static boolean isMC118() {
- return Bukkit.getBukkitVersion().contains("1.18");
+ return isVersion(1, 18);
}
public static boolean isMC117() {
- return Bukkit.getBukkitVersion().contains("1.17");
+ return isVersion(1, 17);
}
public static boolean isMC1162() {
- return Bukkit.getBukkitVersion().contains("1.16.2");
+ return isVersion(1, 16, 2);
}
public static boolean isMC116() {
- return Bukkit.getBukkitVersion().contains("1.16");
+ return isVersion(1, 16);
}
public static boolean isMC115() {
- return Bukkit.getBukkitVersion().contains("1.15");
+ return isVersion(1, 15);
}
public static boolean isMC114() {
- return Bukkit.getBukkitVersion().contains("1.14");
+ return isVersion(1, 14);
}
public static boolean isMC113() {
- return Bukkit.getBukkitVersion().contains("1.13");
+ return isVersion(1, 13);
}
public static boolean isMC112() {
- return Bukkit.getBukkitVersion().contains("1.12");
+ return isVersion(1, 12);
}
public static boolean isMC111() {
- return Bukkit.getBukkitVersion().contains("1.11");
+ return isVersion(1, 11);
}
public static boolean isMC110() {
- return Bukkit.getBukkitVersion().contains("1.10");
+ return isVersion(1, 10);
}
public static boolean isMC19() {
- return Bukkit.getBukkitVersion().matches("1\\.9[^0-9].*");
+ return isVersion(1, 9);
}
public static boolean isMC18() {
- return Bukkit.getBukkitVersion().matches("1\\.8[^0-9].*");
+ return isVersion(1, 8);
}
public static boolean isMC121OrNewer() {
- if (isMC121())
- return true;
- else if (isMC120() || isMC119() || isMC118() || isMC117() || isMC1162() || isMC116() || isMC115() || isMC114() || isMC113() || isMC112() || isMC111() || isMC110() || isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 21);
}
public static boolean isMC120OrNewer() {
- if (isMC120())
- return true;
- else if (isMC119() || isMC118() || isMC117() || isMC1162() || isMC116() || isMC115() || isMC114() || isMC113() || isMC112() || isMC111() || isMC110() || isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 20);
}
public static boolean isMC119OrNewer() {
- if (isMC119())
- return true;
- else if (isMC118() || isMC117() || isMC1162() || isMC116() || isMC115() || isMC114() || isMC113() || isMC112() || isMC111() || isMC110() || isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 19);
}
public static boolean isMC118OrNewer() {
- if (isMC118())
- return true;
- else if (isMC117() || isMC1162() || isMC116() || isMC115() || isMC114() || isMC113() || isMC112() || isMC111() || isMC110() || isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 18);
}
public static boolean isMC117OrNewer() {
- if (isMC117())
- return true;
- else if (isMC1162() || isMC116() || isMC115() || isMC114() || isMC113() || isMC112() || isMC111() || isMC110() || isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 17);
}
public static boolean isMC1162OrNewer() {
- if (isMC1162())
- return true;
- else if (isMC116() || isMC115() || isMC114() || isMC113() || isMC112() || isMC111() || isMC110() || isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 16, 2);
}
public static boolean isMC116OrNewer() {
- if (isMC116())
- return true;
- else if (isMC115() || isMC114() || isMC113() || isMC112() || isMC111() || isMC110() || isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 16);
}
public static boolean isMC115OrNewer() {
- if (isMC115())
- return true;
- else if (isMC114() || isMC113() || isMC112() || isMC111() || isMC110() || isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 15);
}
public static boolean isMC114OrNewer() {
- if (isMC114())
- return true;
- else if (isMC113() || isMC112() || isMC111() || isMC110() || isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 14);
}
public static boolean isMC113OrNewer() {
- if (isMC113())
- return true;
- else if (isMC112() || isMC111() || isMC110() || isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 13);
}
public static boolean isMC112OrNewer() {
- if (isMC112())
- return true;
- else if (isMC111() || isMC110() || isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 12);
}
public static boolean isMC111OrNewer() {
- if (isMC111())
- return true;
- else if (isMC110() || isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 11);
}
public static boolean isMC110OrNewer() {
- if (isMC110())
- return true;
- else if (isMC19() || isMC18())
- return false;
- return true;
+ return isAtLeast(1, 10);
}
public static boolean isMC19OrNewer() {
- if (isMC19())
- return true;
- else if (isMC18())
- return false;
- return true;
+ return isAtLeast(1, 9);
}
// *******************************************************************
@@ -187,22 +190,58 @@ public static boolean isGlowstoneServer() {
return Bukkit.getServer().getName().equalsIgnoreCase("Glowstone");
}
- public static boolean isPaperServer() {
- return Bukkit.getServer().getName().equalsIgnoreCase("Paper")
- && Bukkit.getServer().getVersion().toLowerCase().contains("paper");
- }
+ private static String serverNameLower() {
+ return Bukkit.getServer().getName().toLowerCase();
+ }
- public static boolean isPurpurServer() {
- return Bukkit.getServer().getName().equalsIgnoreCase("Purpur")
- && Bukkit.getServer().getVersion().toLowerCase().contains("purpur");
- }
+ private static String serverVersionLower() {
+ return Bukkit.getServer().getVersion().toLowerCase();
+ }
+
+ private static boolean classExists(String className) {
+ try {
+ Class.forName(className, false, Bukkit.getServer().getClass().getClassLoader());
+ return true;
+ } catch (Throwable ignored) {
+ return false;
+ }
+ }
+
+ public static boolean isPaperServer() {
+ String name = serverNameLower();
+ String version = serverVersionLower();
+
+ if (name.contains("paper") || version.contains("paper"))
+ return true;
+
+ // Support both legacy and modern Paper package names.
+ return classExists("com.destroystokyo.paper.PaperConfig")
+ || classExists("io.papermc.paper.configuration.GlobalConfiguration");
+ }
+
+ public static boolean isPurpurServer() {
+ String name = serverNameLower();
+ String version = serverVersionLower();
+
+ if (name.contains("purpur") || version.contains("purpur"))
+ return true;
+
+ return classExists("org.purpurmc.purpur.PurpurConfig");
+ }
public static boolean isSpigotServer() {
- return Bukkit.getServer().getName().equalsIgnoreCase("CraftBukkit")
- && Bukkit.getServer().getVersion().toLowerCase().contains("spigot");
+ if (isPaperServer() || isPurpurServer())
+ return false;
+
+ String name = serverNameLower();
+ String version = serverVersionLower();
+ return name.contains("spigot") || (name.contains("craftbukkit") && version.contains("spigot"));
}
public static boolean isCraftBukkitServer() {
+ if (isPaperServer() || isPurpurServer() || isSpigotServer())
+ return false;
+
return Bukkit.getServer().getName().equalsIgnoreCase("CraftBukkit")
&& Bukkit.getServer().getVersion().toLowerCase().contains("bukkit");
}
diff --git a/src/one/lindegaard/CustomItemsLib/storage/DataStoreManager.java b/src/one/lindegaard/CustomItemsLib/storage/DataStoreManager.java
index be3b2be..22351b8 100644
--- a/src/one/lindegaard/CustomItemsLib/storage/DataStoreManager.java
+++ b/src/one/lindegaard/CustomItemsLib/storage/DataStoreManager.java
@@ -1,6 +1,7 @@
package one.lindegaard.CustomItemsLib.storage;
import java.util.LinkedHashSet;
+import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@@ -92,14 +93,45 @@ public OfflinePlayer getPlayerByName(String name) {
* @throws UserNotFoundException
*/
public int getPlayerId(OfflinePlayer offlinePlayer) throws UserNotFoundException {
+ if (offlinePlayer == null)
+ return 0;
+
+ try {
+ int playerId = mStore.getPlayerId(offlinePlayer);
+ if (playerId > 0)
+ return playerId;
+ } catch (DataStoreException e) {
+ if (Core.getConfigManager().debug)
+ e.printStackTrace();
+ }
+
try {
- return mStore.getPlayerId(offlinePlayer);
+ PlayerSettings playerSettings;
+ try {
+ playerSettings = mStore.loadPlayerSettings(offlinePlayer);
+ } catch (UserNotFoundException e) {
+ String worldgroup = offlinePlayer.isOnline()
+ ? Core.getWorldGroupManager().getCurrentWorldGroup(offlinePlayer)
+ : Core.getWorldGroupManager().getDefaultWorldgroup();
+ playerSettings = new PlayerSettings(offlinePlayer, worldgroup, Core.getConfigManager().learningMode, false,
+ null, null, System.currentTimeMillis(), System.currentTimeMillis());
+ }
+
+ Set playerDataSet = new LinkedHashSet();
+ playerDataSet.add(playerSettings);
+ mStore.savePlayerSettings(playerDataSet, false);
+
+ int recoveredPlayerId = mStore.getPlayerId(offlinePlayer);
+ if (recoveredPlayerId > 0)
+ return recoveredPlayerId;
} catch (DataStoreException e) {
if (Core.getConfigManager().debug)
e.printStackTrace();
}
+
throw new UserNotFoundException(
- Core.PREFIX + " User " + offlinePlayer.getName() + " is not present in Core database");
+ Core.PREFIX + " User " + offlinePlayer.getName() + " (" + offlinePlayer.getUniqueId()
+ + ") is not present in Core database");
}
public OfflinePlayer getPlayerByPlayerId(int playerId) throws UserNotFoundException {