From 778ba65a656ae0572925f60ac764c3de9d9be710 Mon Sep 17 00:00:00 2001 From: Coldsmiles Date: Tue, 20 Oct 2026 02:10:54 +0800 Subject: [PATCH] Fix migrated gem voucher redemption --- .../cn/infstar/magicgem/MagicGemPlugin.java | 16 +++--- .../cn/infstar/magicgem/gem/GemRegistry.java | 8 ++- .../cn/infstar/magicgem/gem/GemService.java | 7 ++- .../magicgem/integration/EconomyBridge.java | 40 +++++++++++++-- .../magicgem/integration/MythicBridge.java | 5 +- .../integration/PlayerPointsBridge.java | 50 +++++++++++++++---- .../cn/infstar/magicgem/item/ItemCodec.java | 28 +++++++++++ .../cn/infstar/magicgem/item/ItemMatcher.java | 13 +++++ .../magicgem/listener/GemUseListener.java | 4 +- .../migration/LegacyDirectoryImporter.java | 20 ++++---- .../migration/LegacyItemMigrator.java | 24 ++++++--- .../infstar/magicgem/reward/RewardEngine.java | 6 ++- src/main/java/pku/yim/magicgem/MagicGem.java | 2 +- src/main/resources/gems/FunctionGem.yml | 1 - .../magicgem/item/ItemMatcherTest.java | 33 ++++++++++++ .../skill/BundledConfigurationTest.java | 11 +++- 16 files changed, 217 insertions(+), 51 deletions(-) create mode 100644 src/test/java/cn/infstar/magicgem/item/ItemMatcherTest.java diff --git a/src/main/java/cn/infstar/magicgem/MagicGemPlugin.java b/src/main/java/cn/infstar/magicgem/MagicGemPlugin.java index d4187d3..c05c6e2 100644 --- a/src/main/java/cn/infstar/magicgem/MagicGemPlugin.java +++ b/src/main/java/cn/infstar/magicgem/MagicGemPlugin.java @@ -96,10 +96,10 @@ public class MagicGemPlugin extends JavaPlugin { } private void logIntegrationStatus() { - logOptionalPlugin("PlaceholderAPI", "api=" + (placeholders.available() ? "available" : "unavailable")); - logOptionalPlugin("Vault", "economy=" + (economy.available() ? "available" : "unavailable")); - logOptionalPlugin("PlayerPoints", "api=" + (points.available() ? "available" : "unavailable")); - logOptionalPlugin("MythicMobs", "adapter=" + mythic.status()); + logOptionalPlugin("PlaceholderAPI", "接口=" + (placeholders.available() ? "可用" : "不可用")); + logOptionalPlugin("Vault", "经济接口=" + (economy.available() ? "可用" : "不可用")); + logOptionalPlugin("PlayerPoints", "接口=" + (points.available() ? "可用" : "不可用")); + logOptionalPlugin("MythicMobs", "适配器=" + mythic.status()); logOptionalPlugin("MMOItems", null); logOptionalPlugin("MythicArtifacts", null); logOptionalPlugin("MythicCrucible", null); @@ -112,16 +112,16 @@ public class MagicGemPlugin extends JavaPlugin { private void logOptionalPlugin(String name, String adapterStatus) { Plugin dependency = getServer().getPluginManager().getPlugin(name); if (dependency == null) { - getLogger().info("Optional integration " + name + ": absent"); + getLogger().info("可选插件 " + name + ":未安装"); return; } String version = dependency.getDescription().getVersion(); if (!dependency.isEnabled()) { - getLogger().warning("Optional integration " + name + ": present but disabled (version " + version + ")"); + getLogger().warning("可选插件 " + name + ":已安装但未启用(版本 " + version + ")"); return; } - String suffix = adapterStatus == null ? "" : ", " + adapterStatus; - getLogger().info("Optional integration " + name + ": enabled (version " + version + suffix + ")"); + String suffix = adapterStatus == null ? "" : "," + adapterStatus; + getLogger().info("可选插件 " + name + ":已启用(版本 " + version + suffix + ")"); } public void reloadAll() { diff --git a/src/main/java/cn/infstar/magicgem/gem/GemRegistry.java b/src/main/java/cn/infstar/magicgem/gem/GemRegistry.java index 7f482a5..ed02d23 100644 --- a/src/main/java/cn/infstar/magicgem/gem/GemRegistry.java +++ b/src/main/java/cn/infstar/magicgem/gem/GemRegistry.java @@ -152,7 +152,13 @@ public final class GemRegistry { public Optional find(String name) { if (name == null) return Optional.empty(); - return Optional.ofNullable(gems.get(name.toLowerCase(Locale.ROOT))); + String normalized = name.trim().toLowerCase(Locale.ROOT); + GemDefinition direct = gems.get(normalized); + if (direct != null) return Optional.of(direct); + // Legacy MagicGem items may store the YAML section id instead of Name. + return gems.values().stream() + .filter(gem -> gem.id().equalsIgnoreCase(name.trim())) + .findFirst(); } public Collection all() { diff --git a/src/main/java/cn/infstar/magicgem/gem/GemService.java b/src/main/java/cn/infstar/magicgem/gem/GemService.java index 085371f..b39de81 100644 --- a/src/main/java/cn/infstar/magicgem/gem/GemService.java +++ b/src/main/java/cn/infstar/magicgem/gem/GemService.java @@ -147,8 +147,11 @@ public final class GemService { return new GemUseResult(true, successful, target, defaultMessage(successful ? gem.successTip() : gem.failTip(), languages.text(successful ? "gem.tip.success" : "gem.tip.fail", successful ? "&a宝石使用成功" : "&c宝石使用失败"))); - } catch (RewardException exception) { - return GemUseResult.rejected(target, exception.getMessage()); + } catch (RewardException | RuntimeException exception) { + String message = exception.getMessage() == null || exception.getMessage().isBlank() + ? "兑换奖励执行失败,请查看服务器日志" : exception.getMessage(); + Bukkit.getLogger().warning("MagicGem-Reforged 兑换奖励执行失败:" + message); + return GemUseResult.rejected(target, message); } } diff --git a/src/main/java/cn/infstar/magicgem/integration/EconomyBridge.java b/src/main/java/cn/infstar/magicgem/integration/EconomyBridge.java index 1e4189a..ef20c87 100644 --- a/src/main/java/cn/infstar/magicgem/integration/EconomyBridge.java +++ b/src/main/java/cn/infstar/magicgem/integration/EconomyBridge.java @@ -5,6 +5,7 @@ import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; public final class EconomyBridge { private final JavaPlugin plugin; @@ -20,6 +21,9 @@ public final class EconomyBridge { public void refresh() { provider = null; + getBalance = null; + deposit = null; + withdraw = null; try { Class economy = Class.forName("net.milkbowl.vault.economy.Economy"); RegisteredServiceProvider registration = plugin.getServer().getServicesManager().getRegistration(economy); @@ -28,7 +32,8 @@ public final class EconomyBridge { getBalance = economy.getMethod("getBalance", OfflinePlayer.class); deposit = economy.getMethod("depositPlayer", OfflinePlayer.class, double.class); withdraw = economy.getMethod("withdrawPlayer", OfflinePlayer.class, double.class); - } catch (ReflectiveOperationException ignored) { + } catch (ReflectiveOperationException | LinkageError exception) { + plugin.getLogger().warning("Vault 经济接口加载失败:" + detail(exception)); } } @@ -41,20 +46,47 @@ public final class EconomyBridge { } public double balance(OfflinePlayer player) { + if (!available()) refresh(); if (!available()) return 0; try { return ((Number) getBalance.invoke(provider, player)).doubleValue(); } - catch (ReflectiveOperationException exception) { return 0; } + catch (ReflectiveOperationException | RuntimeException exception) { + plugin.getLogger().warning("读取玩家金币失败:" + detail(exception)); + return 0; + } } public boolean change(OfflinePlayer player, double amount) { + if (!Double.isFinite(amount) || Math.abs(amount) < 1.0E-9) return true; + if (!available()) refresh(); if (!available()) return false; if (amount < 0 && balance(player) + amount < 0) return false; try { Object response = (amount >= 0 ? deposit : withdraw).invoke(provider, player, Math.abs(amount)); Method successful = response.getClass().getMethod("transactionSuccess"); - return (boolean) successful.invoke(response); - } catch (ReflectiveOperationException exception) { + boolean result = (boolean) successful.invoke(response); + if (!result) { + plugin.getLogger().warning("Vault 经济插件拒绝交易:玩家=" + player.getName() + + ",金额=" + amount + ",原因=" + responseError(response)); + } + return result; + } catch (ReflectiveOperationException | RuntimeException exception) { + plugin.getLogger().warning("修改玩家金币失败:" + detail(exception)); return false; } } + + private String detail(Throwable exception) { + Throwable cause = exception instanceof InvocationTargetException target && target.getCause() != null + ? target.getCause() : exception; + return cause.getMessage() == null ? cause.getClass().getSimpleName() : cause.getMessage(); + } + + private String responseError(Object response) { + try { + Object message = response.getClass().getField("errorMessage").get(response); + return message == null || message.toString().isBlank() ? "未提供原因" : message.toString(); + } catch (ReflectiveOperationException ignored) { + return "未提供原因"; + } + } } diff --git a/src/main/java/cn/infstar/magicgem/integration/MythicBridge.java b/src/main/java/cn/infstar/magicgem/integration/MythicBridge.java index 105ba99..272b710 100644 --- a/src/main/java/cn/infstar/magicgem/integration/MythicBridge.java +++ b/src/main/java/cn/infstar/magicgem/integration/MythicBridge.java @@ -71,8 +71,9 @@ public final class MythicBridge { } public String status() { - if (!available()) return "unavailable"; - return "items=" + itemManager.getClass().getName() + ", skillApi=" + skillApiAvailable(); + if (!available()) return "不可用"; + return "物品接口=" + itemManager.getClass().getSimpleName() + ",技能接口=" + + (skillApiAvailable() ? "可用" : "不可用"); } public Optional item(String name) { diff --git a/src/main/java/cn/infstar/magicgem/integration/PlayerPointsBridge.java b/src/main/java/cn/infstar/magicgem/integration/PlayerPointsBridge.java index 87134ff..c36f50f 100644 --- a/src/main/java/cn/infstar/magicgem/integration/PlayerPointsBridge.java +++ b/src/main/java/cn/infstar/magicgem/integration/PlayerPointsBridge.java @@ -5,6 +5,7 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; import java.util.UUID; public final class PlayerPointsBridge { @@ -21,16 +22,23 @@ public final class PlayerPointsBridge { public void refresh() { api = null; + look = null; + give = null; + take = null; Plugin dependency = plugin.getServer().getPluginManager().getPlugin("PlayerPoints"); if (dependency == null || !dependency.isEnabled()) return; try { - api = dependency.getClass().getMethod("getAPI").invoke(dependency); - look = api.getClass().getMethod("look", UUID.class); - give = api.getClass().getMethod("give", UUID.class, int.class); - take = api.getClass().getMethod("take", UUID.class, int.class); - } catch (ReflectiveOperationException exception) { + Method getApi = findMethod(dependency.getClass(), "getAPI"); + if (getApi == null) throw new NoSuchMethodException("PlayerPoints.getAPI()"); + api = getApi.invoke(dependency); + look = findMethod(api.getClass(), "look", UUID.class); + give = findMethod(api.getClass(), "give", UUID.class, int.class); + take = findMethod(api.getClass(), "take", UUID.class, int.class); + if (api == null || look == null || give == null || take == null) + throw new NoSuchMethodException("PlayerPoints API 缺少 look/give/take 方法"); + } catch (ReflectiveOperationException | RuntimeException exception) { api = null; - plugin.getLogger().warning("PlayerPoints 存在但接口不可用: " + exception.getMessage()); + plugin.getLogger().warning("PlayerPoints 接口加载失败:" + detail(exception)); } } @@ -43,23 +51,47 @@ public final class PlayerPointsBridge { } public boolean change(Player player, int amount) { + if (amount == 0) return true; + if (!available()) refresh(); if (!available()) return false; try { int balance = ((Number) look.invoke(api, player.getUniqueId())).intValue(); if (amount < 0 && balance + amount < 0) return false; Object result = (amount >= 0 ? give : take).invoke(api, player.getUniqueId(), Math.abs(amount)); - return !(result instanceof Boolean value) || value; - } catch (ReflectiveOperationException exception) { + if (result instanceof Boolean value && !value) { + plugin.getLogger().warning("PlayerPoints 拒绝交易:玩家=" + player.getName() + + ",金额=" + amount + ",当前点券=" + balance); + return false; + } + return true; + } catch (ReflectiveOperationException | RuntimeException exception) { + plugin.getLogger().warning("修改玩家点券失败:" + detail(exception)); return false; } } public int balance(Player player) { + if (!available()) refresh(); if (!available()) return 0; try { return ((Number) look.invoke(api, player.getUniqueId())).intValue(); - } catch (ReflectiveOperationException exception) { + } catch (ReflectiveOperationException | RuntimeException exception) { + plugin.getLogger().warning("读取玩家点券失败:" + detail(exception)); return 0; } } + + private Method findMethod(Class type, String name, Class... parameters) { + for (Class current = type; current != null; current = current.getSuperclass()) { + try { return current.getMethod(name, parameters); } + catch (NoSuchMethodException ignored) { } + } + return null; + } + + private String detail(Throwable exception) { + Throwable cause = exception instanceof InvocationTargetException target && target.getCause() != null + ? target.getCause() : exception; + return cause.getMessage() == null ? cause.getClass().getSimpleName() : cause.getMessage(); + } } diff --git a/src/main/java/cn/infstar/magicgem/item/ItemCodec.java b/src/main/java/cn/infstar/magicgem/item/ItemCodec.java index 0332568..db01075 100644 --- a/src/main/java/cn/infstar/magicgem/item/ItemCodec.java +++ b/src/main/java/cn/infstar/magicgem/item/ItemCodec.java @@ -42,8 +42,10 @@ public final class ItemCodec { private final NamespacedKey embeddedKey; private final NamespacedKey skillsKey; private final LegacyItemMigrator legacyMigrator; + private final JavaPlugin plugin; public ItemCodec(JavaPlugin plugin, GemRegistry registry) { + this.plugin = plugin; this.registry = registry; this.gemNameKey = new NamespacedKey(plugin, "gem_name"); this.dataVersionKey = new NamespacedKey(plugin, "data_version"); @@ -147,6 +149,8 @@ public final class ItemCodec { return migrated; } } + Optional fallback = displayFallback(item, meta); + if (fallback.isPresent()) return fallback; if (!allowLore || !meta.hasLore()) return Optional.empty(); List lore = meta.getLore(); if (lore == null) return Optional.empty(); @@ -158,6 +162,30 @@ public final class ItemCodec { return Optional.empty(); } + /** + * Old MagicGem items can lose their custom NBT when copied through a server + * version converter. The display name and material are still stable for the + * bundled vouchers, so use them as a last-resort migration identity. + */ + private Optional displayFallback(ItemStack item, ItemMeta meta) { + String display = meta.getDisplayName(); + if (display == null || display.isBlank()) return Optional.empty(); + String normalizedDisplay = strip(display); + for (GemDefinition gem : registry.all()) { + if (gem.display() == null || gem.display().isBlank() || gem.material() != item.getType()) continue; + if (!strip(gem.display()).equals(normalizedDisplay)) continue; + plugin.getLogger().info("检测到缺少旧版标识的宝石物品,已按显示名称迁移:" + gem.name()); + meta.getPersistentDataContainer().set(gemNameKey, PersistentDataType.STRING, gem.name()); + meta.getPersistentDataContainer().set(dataVersionKey, PersistentDataType.INTEGER, 1); + item.setItemMeta(meta); + NBT.modify(item, nbt -> { + nbt.setString(LegacyItemMigrator.GEM_NAME_KEY, gem.name()); + }); + return Optional.of(gem); + } + return Optional.empty(); + } + public Map embedded(ItemStack item) { if (item == null || !item.hasItemMeta()) return Map.of(); PersistentDataContainer data = item.getItemMeta().getPersistentDataContainer(); diff --git a/src/main/java/cn/infstar/magicgem/item/ItemMatcher.java b/src/main/java/cn/infstar/magicgem/item/ItemMatcher.java index b85e75a..51034ad 100644 --- a/src/main/java/cn/infstar/magicgem/item/ItemMatcher.java +++ b/src/main/java/cn/infstar/magicgem/item/ItemMatcher.java @@ -49,6 +49,19 @@ public final class ItemMatcher { return parts.length == 1 || nbt.getString(parts[0]).equals(parts[1]); }); } + // The legacy configuration uses a leading underscore for suffix matches, + // for example _AXE means every material whose name ends with _AXE. + return matchesMaterial(material, requirement); + } + + boolean matchesMaterial(Material material, String rawRequirement) { + if (material == null || rawRequirement == null) return false; + String requirement = rawRequirement.toUpperCase(Locale.ROOT); + if (requirement.startsWith("_") && requirement.length() > 1) + return material.name().endsWith(requirement); + // The original checker treated AXE as a name suffix without requiring + // a separator, so PICKAXE also matched the AXE requirement. + if (requirement.equals("AXE") && material.name().endsWith("AXE")) return true; if (material.name().equals(requirement) || material.name().endsWith('_' + requirement)) return true; return switch (requirement) { case "ARMOR" -> material.name().endsWith("_HELMET") || material.name().endsWith("_CHESTPLATE") diff --git a/src/main/java/cn/infstar/magicgem/listener/GemUseListener.java b/src/main/java/cn/infstar/magicgem/listener/GemUseListener.java index 89a15c0..5c65a5a 100644 --- a/src/main/java/cn/infstar/magicgem/listener/GemUseListener.java +++ b/src/main/java/cn/infstar/magicgem/listener/GemUseListener.java @@ -78,7 +78,9 @@ public final class GemUseListener implements Listener { ItemStack target = targetSlot == null ? null : get(event.getPlayer().getInventory(), targetSlot); GemUseResult result = gemService.use(event.getPlayer(), gem, target); if (result.consumed() && used != null) { - used.setAmount(used.getAmount() - 1); + EquipmentSlot hand = event.getHand() == null ? EquipmentSlot.HAND : event.getHand(); + ItemStack held = get(event.getPlayer().getInventory(), hand); + if (held != null && !held.getType().isAir()) held.setAmount(Math.max(0, held.getAmount() - 1)); if (result.target() != null && targetSlot != null) set(event.getPlayer().getInventory(), targetSlot, result.target()); } messages.send(event.getPlayer(), result.message()); diff --git a/src/main/java/cn/infstar/magicgem/migration/LegacyDirectoryImporter.java b/src/main/java/cn/infstar/magicgem/migration/LegacyDirectoryImporter.java index 7b6ace1..13caa48 100644 --- a/src/main/java/cn/infstar/magicgem/migration/LegacyDirectoryImporter.java +++ b/src/main/java/cn/infstar/magicgem/migration/LegacyDirectoryImporter.java @@ -37,18 +37,18 @@ public final class LegacyDirectoryImporter { public ImportResult importIfPresent() { Path sourceDirectory = pluginsDirectory.resolve(LEGACY_PLUGIN_DIRECTORY).normalize(); if (targetDirectory.equals(sourceDirectory)) { - logger.info("MagicGem migration skipped because the legacy source and target directories are identical: " + logger.info("MagicGem 迁移已跳过:旧版源目录与当前插件目录相同:" + sourceDirectory); return ImportResult.none(); } if (!Files.isDirectory(sourceDirectory)) { - logger.info("MagicGem migration source not found at " + sourceDirectory + "; no legacy import is needed."); + logger.info("未找到 MagicGem 旧版数据目录:" + sourceDirectory + ",无需迁移。"); return ImportResult.none(); } Path marker = targetDirectory.resolve(MARKER); if (Files.exists(marker)) { - logger.info("MagicGem migration already completed; skipping legacy import (marker: " + marker + ")."); + logger.info("MagicGem 迁移已完成,跳过重复导入(标记文件:" + marker + ")。"); return new ImportResult(0, 0, true); } @@ -56,7 +56,7 @@ public final class LegacyDirectoryImporter { int conflictFiles = 0; List conflicts = new ArrayList<>(); try { - logger.info("Starting MagicGem migration: source=" + sourceDirectory + ", target=" + targetDirectory); + logger.info("开始迁移 MagicGem 数据:源目录=" + sourceDirectory + ",目标目录=" + targetDirectory); Files.createDirectories(targetDirectory); for (String directory : DATA_DIRECTORIES) { ImportCounts counts = importDirectory(sourceDirectory.resolve(directory), @@ -76,16 +76,14 @@ public final class LegacyDirectoryImporter { writeConflictBackups(sourceDirectory, conflicts); Files.writeString(marker, "Imported from plugins/MagicGem. Source directory remains unchanged.\n"); for (Conflict conflict : conflicts) { - logger.warning("MagicGem Reforged kept the target file for " + conflict.relativePath() - + "; the original file was saved under migration-backup/MagicGem."); + logger.warning("MagicGem-Reforged 保留了目标文件 " + conflict.relativePath() + + ";旧版文件已备份到 migration-backup/MagicGem。"); } - logger.info("Imported original plugins/MagicGem into " + targetDirectory.getFileName() - + ": " + copiedFiles + " files copied, " + conflictFiles + " conflicts. " - + "The original directory was left untouched."); + logger.info("已将旧版 plugins/MagicGem 导入 " + targetDirectory.getFileName() + + ":复制 " + copiedFiles + " 个文件,发现 " + conflictFiles + " 个冲突;旧版目录未被修改。"); return new ImportResult(copiedFiles, conflictFiles, false); } catch (IOException exception) { - logger.log(Level.SEVERE, "Could not finish importing original plugins/MagicGem. " - + "The original directory was not deleted; migration will be retried on the next startup.", exception); + logger.log(Level.SEVERE, "导入旧版 plugins/MagicGem 未完成;旧版目录未被删除,插件将在下次启动时重试迁移。", exception); return new ImportResult(copiedFiles, conflictFiles, false); } } diff --git a/src/main/java/cn/infstar/magicgem/migration/LegacyItemMigrator.java b/src/main/java/cn/infstar/magicgem/migration/LegacyItemMigrator.java index 8d2556d..bf17ba5 100644 --- a/src/main/java/cn/infstar/magicgem/migration/LegacyItemMigrator.java +++ b/src/main/java/cn/infstar/magicgem/migration/LegacyItemMigrator.java @@ -39,28 +39,36 @@ public final class LegacyItemMigrator { } private void readExact(ReadableNBT nbt, Collector collector) { - if (nbt.hasTag(GEM_NAME_KEY) && nbt.getType(GEM_NAME_KEY) == NBTType.NBTTagString) { - registry.find(nbt.getString(GEM_NAME_KEY)).ifPresent(gem -> collector.identity = gem.name()); + String identityKey = findKey(nbt, GEM_NAME_KEY); + if (identityKey != null && nbt.getType(identityKey) == NBTType.NBTTagString) { + registry.find(nbt.getString(identityKey)).ifPresent(gem -> collector.identity = gem.name()); } - if (nbt.hasTag(EMBEDDED_GEMS_KEY) && nbt.getType(EMBEDDED_GEMS_KEY) == NBTType.NBTTagCompound) { - ReadableNBT embedded = nbt.getCompound(EMBEDDED_GEMS_KEY); + String embeddedKey = findKey(nbt, EMBEDDED_GEMS_KEY); + if (embeddedKey != null && nbt.getType(embeddedKey) == NBTType.NBTTagCompound) { + ReadableNBT embedded = nbt.getCompound(embeddedKey); if (embedded != null) for (String name : embedded.getKeys()) { NBTType type = embedded.getType(name); if (!numeric(type)) continue; registry.find(name).ifPresent(gem -> collector.embedded.put(gem.name(), number(embedded, name, type))); } } - if (nbt.hasTag(SKILLS_KEY) && nbt.getType(SKILLS_KEY) == NBTType.NBTTagList) { - var skills = nbt.getStringList(SKILLS_KEY); + String skillsKey = findKey(nbt, SKILLS_KEY); + if (skillsKey != null && nbt.getType(skillsKey) == NBTType.NBTTagList) { + var skills = nbt.getStringList(skillsKey); for (int index = 0; index < skills.size(); index++) collector.skills.add(skills.get(index)); } } + private String findKey(ReadableNBT nbt, String expected) { + if (nbt == null) return null; + for (String key : nbt.getKeys()) if (key.equalsIgnoreCase(expected)) return key; + return null; + } + private void scan(ReadableNBT nbt, Collector collector, int depth) { if (nbt == null || depth > 12) return; for (String key : nbt.getKeys()) { - if (key.equalsIgnoreCase(GEM_NAME_KEY) || key.equalsIgnoreCase(EMBEDDED_GEMS_KEY) - || key.equalsIgnoreCase(SKILLS_KEY)) continue; + if (key.equalsIgnoreCase(EMBEDDED_GEMS_KEY) || key.equalsIgnoreCase(SKILLS_KEY)) continue; NBTType type = nbt.getType(key); try { if (type == NBTType.NBTTagString) { diff --git a/src/main/java/cn/infstar/magicgem/reward/RewardEngine.java b/src/main/java/cn/infstar/magicgem/reward/RewardEngine.java index 0e76d8e..416bcc9 100644 --- a/src/main/java/cn/infstar/magicgem/reward/RewardEngine.java +++ b/src/main/java/cn/infstar/magicgem/reward/RewardEngine.java @@ -153,6 +153,10 @@ public final class RewardEngine { private void validatePlayerRewards(RewardContext context, List directives) throws RewardException { Player player = context.player(); + // Vault/PlayerPoints may register their services after this plugin enables. + // Refresh before validation so a late service registration cannot reject a valid exchange. + if (!economy.available()) economy.refresh(); + if (!points.available()) points.refresh(); double moneyBalance = economy.available() ? economy.balance(player) : 0; int pointBalance = points.available() ? points.balance(player) : 0; int levels = player.getLevel(); @@ -1090,13 +1094,11 @@ public final class RewardEngine { private void money(RewardContext context, Directive directive) throws RewardException { double amount = amount(context, directive); - if (!economy.available()) throw new RewardException("Vault 经济服务不可用"); if (!economy.change(context.player(), amount)) throw new RewardException("金币不足或经济操作失败"); } private void point(RewardContext context, Directive directive) throws RewardException { int amount = (int) Math.round(amount(context, directive)); - if (!points.available()) throw new RewardException("PlayerPoints 不可用"); if (!points.change(context.player(), amount)) throw new RewardException("点券不足或操作失败"); } diff --git a/src/main/java/pku/yim/magicgem/MagicGem.java b/src/main/java/pku/yim/magicgem/MagicGem.java index 3e8b155..e04f68c 100644 --- a/src/main/java/pku/yim/magicgem/MagicGem.java +++ b/src/main/java/pku/yim/magicgem/MagicGem.java @@ -63,6 +63,6 @@ public class MagicGem extends MagicGemPlugin { /** Network license activation belonged to the retired vendor service. */ public void license(CommandSender sender) { - sender.sendMessage("MagicGem license service is not configured in this build."); + sender.sendMessage("MagicGem-Reforged 未配置授权服务;当前版本无需授权。"); } } diff --git a/src/main/resources/gems/FunctionGem.yml b/src/main/resources/gems/FunctionGem.yml index 59ab3ca..7843955 100644 --- a/src/main/resources/gems/FunctionGem.yml +++ b/src/main/resources/gems/FunctionGem.yml @@ -67,7 +67,6 @@ Version: 1.05 Name: 精准时运切换宝石 Require: - AXE - - PICKAXE - SHOVEL Display: "&8&l&k||&a&l精准/时运切换宝石&8&l&k||" Tips: diff --git a/src/test/java/cn/infstar/magicgem/item/ItemMatcherTest.java b/src/test/java/cn/infstar/magicgem/item/ItemMatcherTest.java new file mode 100644 index 0000000..34fa278 --- /dev/null +++ b/src/test/java/cn/infstar/magicgem/item/ItemMatcherTest.java @@ -0,0 +1,33 @@ +package cn.infstar.magicgem.item; + +import org.bukkit.Material; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ItemMatcherTest { + private final ItemMatcher matcher = new ItemMatcher(null); + + @Test + void supportsLegacySuffixRequirements() { + assertTrue(matcher.matchesMaterial(Material.DIAMOND_AXE, "_AXE")); + assertTrue(matcher.matchesMaterial(Material.NETHERITE_AXE, "_AXE")); + assertFalse(matcher.matchesMaterial(Material.DIAMOND_SWORD, "_AXE")); + } + + @Test + void preservesOriginalAxeAliasForPickaxes() { + assertTrue(matcher.matchesMaterial(Material.DIAMOND_AXE, "AXE")); + assertTrue(matcher.matchesMaterial(Material.DIAMOND_PICKAXE, "AXE")); + assertFalse(matcher.matchesMaterial(Material.DIAMOND_SHOVEL, "AXE")); + } + + @Test + void supportsExplicitMaterialRequirementsUsedByBundledGems() { + assertTrue(matcher.matchesMaterial(Material.SHIELD, "SHIELD")); + assertTrue(matcher.matchesMaterial(Material.FLINT_AND_STEEL, "FLINT_AND_STEEL")); + assertTrue(matcher.matchesMaterial(Material.DIAMOND_PICKAXE, "PICKAXE")); + assertFalse(matcher.matchesMaterial(Material.DIAMOND_PICKAXE, "TRIDENT")); + } +} diff --git a/src/test/java/cn/infstar/magicgem/skill/BundledConfigurationTest.java b/src/test/java/cn/infstar/magicgem/skill/BundledConfigurationTest.java index 509249f..37cac28 100644 --- a/src/test/java/cn/infstar/magicgem/skill/BundledConfigurationTest.java +++ b/src/test/java/cn/infstar/magicgem/skill/BundledConfigurationTest.java @@ -131,6 +131,15 @@ class BundledConfigurationTest { void precisionFortuneSwitchGemAcceptsPickaxes() { YamlConfiguration yaml = YamlConfiguration.loadConfiguration( new File("src/main/resources/gems/FunctionGem.yml")); - assertTrue(yaml.getStringList("内置-精准时运切换宝石.Require").contains("PICKAXE")); + assertTrue(yaml.getStringList("内置-精准时运切换宝石.Require").contains("AXE")); + assertTrue(yaml.getStringList("内置-精准时运切换宝石.Require").contains("SHOVEL")); + } + + @Test + void efficiencyGemsAcceptPickaxes() { + YamlConfiguration yaml = YamlConfiguration.loadConfiguration( + new File("src/main/resources/gems/EnchantGem.yml")); + assertTrue(yaml.getStringList("内置-效率宝石.Require").contains("AXE")); + assertTrue(yaml.getStringList("内置-真效率宝石.Require").contains("AXE")); } }