Fix migrated gem voucher redemption
This commit is contained in:
@@ -96,10 +96,10 @@ public class MagicGemPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void logIntegrationStatus() {
|
private void logIntegrationStatus() {
|
||||||
logOptionalPlugin("PlaceholderAPI", "api=" + (placeholders.available() ? "available" : "unavailable"));
|
logOptionalPlugin("PlaceholderAPI", "接口=" + (placeholders.available() ? "可用" : "不可用"));
|
||||||
logOptionalPlugin("Vault", "economy=" + (economy.available() ? "available" : "unavailable"));
|
logOptionalPlugin("Vault", "经济接口=" + (economy.available() ? "可用" : "不可用"));
|
||||||
logOptionalPlugin("PlayerPoints", "api=" + (points.available() ? "available" : "unavailable"));
|
logOptionalPlugin("PlayerPoints", "接口=" + (points.available() ? "可用" : "不可用"));
|
||||||
logOptionalPlugin("MythicMobs", "adapter=" + mythic.status());
|
logOptionalPlugin("MythicMobs", "适配器=" + mythic.status());
|
||||||
logOptionalPlugin("MMOItems", null);
|
logOptionalPlugin("MMOItems", null);
|
||||||
logOptionalPlugin("MythicArtifacts", null);
|
logOptionalPlugin("MythicArtifacts", null);
|
||||||
logOptionalPlugin("MythicCrucible", null);
|
logOptionalPlugin("MythicCrucible", null);
|
||||||
@@ -112,16 +112,16 @@ public class MagicGemPlugin extends JavaPlugin {
|
|||||||
private void logOptionalPlugin(String name, String adapterStatus) {
|
private void logOptionalPlugin(String name, String adapterStatus) {
|
||||||
Plugin dependency = getServer().getPluginManager().getPlugin(name);
|
Plugin dependency = getServer().getPluginManager().getPlugin(name);
|
||||||
if (dependency == null) {
|
if (dependency == null) {
|
||||||
getLogger().info("Optional integration " + name + ": absent");
|
getLogger().info("可选插件 " + name + ":未安装");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String version = dependency.getDescription().getVersion();
|
String version = dependency.getDescription().getVersion();
|
||||||
if (!dependency.isEnabled()) {
|
if (!dependency.isEnabled()) {
|
||||||
getLogger().warning("Optional integration " + name + ": present but disabled (version " + version + ")");
|
getLogger().warning("可选插件 " + name + ":已安装但未启用(版本 " + version + ")");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String suffix = adapterStatus == null ? "" : ", " + adapterStatus;
|
String suffix = adapterStatus == null ? "" : "," + adapterStatus;
|
||||||
getLogger().info("Optional integration " + name + ": enabled (version " + version + suffix + ")");
|
getLogger().info("可选插件 " + name + ":已启用(版本 " + version + suffix + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reloadAll() {
|
public void reloadAll() {
|
||||||
|
|||||||
@@ -152,7 +152,13 @@ public final class GemRegistry {
|
|||||||
|
|
||||||
public Optional<GemDefinition> find(String name) {
|
public Optional<GemDefinition> find(String name) {
|
||||||
if (name == null) return Optional.empty();
|
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<GemDefinition> all() {
|
public Collection<GemDefinition> all() {
|
||||||
|
|||||||
@@ -147,8 +147,11 @@ public final class GemService {
|
|||||||
return new GemUseResult(true, successful, target, defaultMessage(successful ? gem.successTip() : gem.failTip(),
|
return new GemUseResult(true, successful, target, defaultMessage(successful ? gem.successTip() : gem.failTip(),
|
||||||
languages.text(successful ? "gem.tip.success" : "gem.tip.fail",
|
languages.text(successful ? "gem.tip.success" : "gem.tip.fail",
|
||||||
successful ? "&a宝石使用成功" : "&c宝石使用失败")));
|
successful ? "&a宝石使用成功" : "&c宝石使用失败")));
|
||||||
} catch (RewardException exception) {
|
} catch (RewardException | RuntimeException exception) {
|
||||||
return GemUseResult.rejected(target, exception.getMessage());
|
String message = exception.getMessage() == null || exception.getMessage().isBlank()
|
||||||
|
? "兑换奖励执行失败,请查看服务器日志" : exception.getMessage();
|
||||||
|
Bukkit.getLogger().warning("MagicGem-Reforged 兑换奖励执行失败:" + message);
|
||||||
|
return GemUseResult.rejected(target, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.bukkit.plugin.RegisteredServiceProvider;
|
|||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
public final class EconomyBridge {
|
public final class EconomyBridge {
|
||||||
private final JavaPlugin plugin;
|
private final JavaPlugin plugin;
|
||||||
@@ -20,6 +21,9 @@ public final class EconomyBridge {
|
|||||||
|
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
provider = null;
|
provider = null;
|
||||||
|
getBalance = null;
|
||||||
|
deposit = null;
|
||||||
|
withdraw = null;
|
||||||
try {
|
try {
|
||||||
Class<?> economy = Class.forName("net.milkbowl.vault.economy.Economy");
|
Class<?> economy = Class.forName("net.milkbowl.vault.economy.Economy");
|
||||||
RegisteredServiceProvider<?> registration = plugin.getServer().getServicesManager().getRegistration(economy);
|
RegisteredServiceProvider<?> registration = plugin.getServer().getServicesManager().getRegistration(economy);
|
||||||
@@ -28,7 +32,8 @@ public final class EconomyBridge {
|
|||||||
getBalance = economy.getMethod("getBalance", OfflinePlayer.class);
|
getBalance = economy.getMethod("getBalance", OfflinePlayer.class);
|
||||||
deposit = economy.getMethod("depositPlayer", OfflinePlayer.class, double.class);
|
deposit = economy.getMethod("depositPlayer", OfflinePlayer.class, double.class);
|
||||||
withdraw = economy.getMethod("withdrawPlayer", 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) {
|
public double balance(OfflinePlayer player) {
|
||||||
|
if (!available()) refresh();
|
||||||
if (!available()) return 0;
|
if (!available()) return 0;
|
||||||
try { return ((Number) getBalance.invoke(provider, player)).doubleValue(); }
|
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) {
|
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 (!available()) return false;
|
||||||
if (amount < 0 && balance(player) + amount < 0) return false;
|
if (amount < 0 && balance(player) + amount < 0) return false;
|
||||||
try {
|
try {
|
||||||
Object response = (amount >= 0 ? deposit : withdraw).invoke(provider, player, Math.abs(amount));
|
Object response = (amount >= 0 ? deposit : withdraw).invoke(provider, player, Math.abs(amount));
|
||||||
Method successful = response.getClass().getMethod("transactionSuccess");
|
Method successful = response.getClass().getMethod("transactionSuccess");
|
||||||
return (boolean) successful.invoke(response);
|
boolean result = (boolean) successful.invoke(response);
|
||||||
} catch (ReflectiveOperationException exception) {
|
if (!result) {
|
||||||
|
plugin.getLogger().warning("Vault 经济插件拒绝交易:玩家=" + player.getName()
|
||||||
|
+ ",金额=" + amount + ",原因=" + responseError(response));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (ReflectiveOperationException | RuntimeException exception) {
|
||||||
|
plugin.getLogger().warning("修改玩家金币失败:" + detail(exception));
|
||||||
return false;
|
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 "未提供原因";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,9 @@ public final class MythicBridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String status() {
|
public String status() {
|
||||||
if (!available()) return "unavailable";
|
if (!available()) return "不可用";
|
||||||
return "items=" + itemManager.getClass().getName() + ", skillApi=" + skillApiAvailable();
|
return "物品接口=" + itemManager.getClass().getSimpleName() + ",技能接口="
|
||||||
|
+ (skillApiAvailable() ? "可用" : "不可用");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ItemStack> item(String name) {
|
public Optional<ItemStack> item(String name) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.bukkit.plugin.Plugin;
|
|||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public final class PlayerPointsBridge {
|
public final class PlayerPointsBridge {
|
||||||
@@ -21,16 +22,23 @@ public final class PlayerPointsBridge {
|
|||||||
|
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
api = null;
|
api = null;
|
||||||
|
look = null;
|
||||||
|
give = null;
|
||||||
|
take = null;
|
||||||
Plugin dependency = plugin.getServer().getPluginManager().getPlugin("PlayerPoints");
|
Plugin dependency = plugin.getServer().getPluginManager().getPlugin("PlayerPoints");
|
||||||
if (dependency == null || !dependency.isEnabled()) return;
|
if (dependency == null || !dependency.isEnabled()) return;
|
||||||
try {
|
try {
|
||||||
api = dependency.getClass().getMethod("getAPI").invoke(dependency);
|
Method getApi = findMethod(dependency.getClass(), "getAPI");
|
||||||
look = api.getClass().getMethod("look", UUID.class);
|
if (getApi == null) throw new NoSuchMethodException("PlayerPoints.getAPI()");
|
||||||
give = api.getClass().getMethod("give", UUID.class, int.class);
|
api = getApi.invoke(dependency);
|
||||||
take = api.getClass().getMethod("take", UUID.class, int.class);
|
look = findMethod(api.getClass(), "look", UUID.class);
|
||||||
} catch (ReflectiveOperationException exception) {
|
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;
|
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) {
|
public boolean change(Player player, int amount) {
|
||||||
|
if (amount == 0) return true;
|
||||||
|
if (!available()) refresh();
|
||||||
if (!available()) return false;
|
if (!available()) return false;
|
||||||
try {
|
try {
|
||||||
int balance = ((Number) look.invoke(api, player.getUniqueId())).intValue();
|
int balance = ((Number) look.invoke(api, player.getUniqueId())).intValue();
|
||||||
if (amount < 0 && balance + amount < 0) return false;
|
if (amount < 0 && balance + amount < 0) return false;
|
||||||
Object result = (amount >= 0 ? give : take).invoke(api, player.getUniqueId(), Math.abs(amount));
|
Object result = (amount >= 0 ? give : take).invoke(api, player.getUniqueId(), Math.abs(amount));
|
||||||
return !(result instanceof Boolean value) || value;
|
if (result instanceof Boolean value && !value) {
|
||||||
} catch (ReflectiveOperationException exception) {
|
plugin.getLogger().warning("PlayerPoints 拒绝交易:玩家=" + player.getName()
|
||||||
|
+ ",金额=" + amount + ",当前点券=" + balance);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (ReflectiveOperationException | RuntimeException exception) {
|
||||||
|
plugin.getLogger().warning("修改玩家点券失败:" + detail(exception));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int balance(Player player) {
|
public int balance(Player player) {
|
||||||
|
if (!available()) refresh();
|
||||||
if (!available()) return 0;
|
if (!available()) return 0;
|
||||||
try {
|
try {
|
||||||
return ((Number) look.invoke(api, player.getUniqueId())).intValue();
|
return ((Number) look.invoke(api, player.getUniqueId())).intValue();
|
||||||
} catch (ReflectiveOperationException exception) {
|
} catch (ReflectiveOperationException | RuntimeException exception) {
|
||||||
|
plugin.getLogger().warning("读取玩家点券失败:" + detail(exception));
|
||||||
return 0;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,10 @@ public final class ItemCodec {
|
|||||||
private final NamespacedKey embeddedKey;
|
private final NamespacedKey embeddedKey;
|
||||||
private final NamespacedKey skillsKey;
|
private final NamespacedKey skillsKey;
|
||||||
private final LegacyItemMigrator legacyMigrator;
|
private final LegacyItemMigrator legacyMigrator;
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
|
||||||
public ItemCodec(JavaPlugin plugin, GemRegistry registry) {
|
public ItemCodec(JavaPlugin plugin, GemRegistry registry) {
|
||||||
|
this.plugin = plugin;
|
||||||
this.registry = registry;
|
this.registry = registry;
|
||||||
this.gemNameKey = new NamespacedKey(plugin, "gem_name");
|
this.gemNameKey = new NamespacedKey(plugin, "gem_name");
|
||||||
this.dataVersionKey = new NamespacedKey(plugin, "data_version");
|
this.dataVersionKey = new NamespacedKey(plugin, "data_version");
|
||||||
@@ -147,6 +149,8 @@ public final class ItemCodec {
|
|||||||
return migrated;
|
return migrated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Optional<GemDefinition> fallback = displayFallback(item, meta);
|
||||||
|
if (fallback.isPresent()) return fallback;
|
||||||
if (!allowLore || !meta.hasLore()) return Optional.empty();
|
if (!allowLore || !meta.hasLore()) return Optional.empty();
|
||||||
List<String> lore = meta.getLore();
|
List<String> lore = meta.getLore();
|
||||||
if (lore == null) return Optional.empty();
|
if (lore == null) return Optional.empty();
|
||||||
@@ -158,6 +162,30 @@ public final class ItemCodec {
|
|||||||
return Optional.empty();
|
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<GemDefinition> 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<String, Integer> embedded(ItemStack item) {
|
public Map<String, Integer> embedded(ItemStack item) {
|
||||||
if (item == null || !item.hasItemMeta()) return Map.of();
|
if (item == null || !item.hasItemMeta()) return Map.of();
|
||||||
PersistentDataContainer data = item.getItemMeta().getPersistentDataContainer();
|
PersistentDataContainer data = item.getItemMeta().getPersistentDataContainer();
|
||||||
|
|||||||
@@ -49,6 +49,19 @@ public final class ItemMatcher {
|
|||||||
return parts.length == 1 || nbt.getString(parts[0]).equals(parts[1]);
|
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;
|
if (material.name().equals(requirement) || material.name().endsWith('_' + requirement)) return true;
|
||||||
return switch (requirement) {
|
return switch (requirement) {
|
||||||
case "ARMOR" -> material.name().endsWith("_HELMET") || material.name().endsWith("_CHESTPLATE")
|
case "ARMOR" -> material.name().endsWith("_HELMET") || material.name().endsWith("_CHESTPLATE")
|
||||||
|
|||||||
@@ -78,7 +78,9 @@ public final class GemUseListener implements Listener {
|
|||||||
ItemStack target = targetSlot == null ? null : get(event.getPlayer().getInventory(), targetSlot);
|
ItemStack target = targetSlot == null ? null : get(event.getPlayer().getInventory(), targetSlot);
|
||||||
GemUseResult result = gemService.use(event.getPlayer(), gem, target);
|
GemUseResult result = gemService.use(event.getPlayer(), gem, target);
|
||||||
if (result.consumed() && used != null) {
|
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());
|
if (result.target() != null && targetSlot != null) set(event.getPlayer().getInventory(), targetSlot, result.target());
|
||||||
}
|
}
|
||||||
messages.send(event.getPlayer(), result.message());
|
messages.send(event.getPlayer(), result.message());
|
||||||
|
|||||||
@@ -37,18 +37,18 @@ public final class LegacyDirectoryImporter {
|
|||||||
public ImportResult importIfPresent() {
|
public ImportResult importIfPresent() {
|
||||||
Path sourceDirectory = pluginsDirectory.resolve(LEGACY_PLUGIN_DIRECTORY).normalize();
|
Path sourceDirectory = pluginsDirectory.resolve(LEGACY_PLUGIN_DIRECTORY).normalize();
|
||||||
if (targetDirectory.equals(sourceDirectory)) {
|
if (targetDirectory.equals(sourceDirectory)) {
|
||||||
logger.info("MagicGem migration skipped because the legacy source and target directories are identical: "
|
logger.info("MagicGem 迁移已跳过:旧版源目录与当前插件目录相同:"
|
||||||
+ sourceDirectory);
|
+ sourceDirectory);
|
||||||
return ImportResult.none();
|
return ImportResult.none();
|
||||||
}
|
}
|
||||||
if (!Files.isDirectory(sourceDirectory)) {
|
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();
|
return ImportResult.none();
|
||||||
}
|
}
|
||||||
|
|
||||||
Path marker = targetDirectory.resolve(MARKER);
|
Path marker = targetDirectory.resolve(MARKER);
|
||||||
if (Files.exists(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);
|
return new ImportResult(0, 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ public final class LegacyDirectoryImporter {
|
|||||||
int conflictFiles = 0;
|
int conflictFiles = 0;
|
||||||
List<Conflict> conflicts = new ArrayList<>();
|
List<Conflict> conflicts = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
logger.info("Starting MagicGem migration: source=" + sourceDirectory + ", target=" + targetDirectory);
|
logger.info("开始迁移 MagicGem 数据:源目录=" + sourceDirectory + ",目标目录=" + targetDirectory);
|
||||||
Files.createDirectories(targetDirectory);
|
Files.createDirectories(targetDirectory);
|
||||||
for (String directory : DATA_DIRECTORIES) {
|
for (String directory : DATA_DIRECTORIES) {
|
||||||
ImportCounts counts = importDirectory(sourceDirectory.resolve(directory),
|
ImportCounts counts = importDirectory(sourceDirectory.resolve(directory),
|
||||||
@@ -76,16 +76,14 @@ public final class LegacyDirectoryImporter {
|
|||||||
writeConflictBackups(sourceDirectory, conflicts);
|
writeConflictBackups(sourceDirectory, conflicts);
|
||||||
Files.writeString(marker, "Imported from plugins/MagicGem. Source directory remains unchanged.\n");
|
Files.writeString(marker, "Imported from plugins/MagicGem. Source directory remains unchanged.\n");
|
||||||
for (Conflict conflict : conflicts) {
|
for (Conflict conflict : conflicts) {
|
||||||
logger.warning("MagicGem Reforged kept the target file for " + conflict.relativePath()
|
logger.warning("MagicGem-Reforged 保留了目标文件 " + conflict.relativePath()
|
||||||
+ "; the original file was saved under migration-backup/MagicGem.");
|
+ ";旧版文件已备份到 migration-backup/MagicGem。");
|
||||||
}
|
}
|
||||||
logger.info("Imported original plugins/MagicGem into " + targetDirectory.getFileName()
|
logger.info("已将旧版 plugins/MagicGem 导入 " + targetDirectory.getFileName()
|
||||||
+ ": " + copiedFiles + " files copied, " + conflictFiles + " conflicts. "
|
+ ":复制 " + copiedFiles + " 个文件,发现 " + conflictFiles + " 个冲突;旧版目录未被修改。");
|
||||||
+ "The original directory was left untouched.");
|
|
||||||
return new ImportResult(copiedFiles, conflictFiles, false);
|
return new ImportResult(copiedFiles, conflictFiles, false);
|
||||||
} catch (IOException exception) {
|
} catch (IOException exception) {
|
||||||
logger.log(Level.SEVERE, "Could not finish importing original plugins/MagicGem. "
|
logger.log(Level.SEVERE, "导入旧版 plugins/MagicGem 未完成;旧版目录未被删除,插件将在下次启动时重试迁移。", exception);
|
||||||
+ "The original directory was not deleted; migration will be retried on the next startup.", exception);
|
|
||||||
return new ImportResult(copiedFiles, conflictFiles, false);
|
return new ImportResult(copiedFiles, conflictFiles, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,28 +39,36 @@ public final class LegacyItemMigrator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void readExact(ReadableNBT nbt, Collector collector) {
|
private void readExact(ReadableNBT nbt, Collector collector) {
|
||||||
if (nbt.hasTag(GEM_NAME_KEY) && nbt.getType(GEM_NAME_KEY) == NBTType.NBTTagString) {
|
String identityKey = findKey(nbt, GEM_NAME_KEY);
|
||||||
registry.find(nbt.getString(GEM_NAME_KEY)).ifPresent(gem -> collector.identity = gem.name());
|
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) {
|
String embeddedKey = findKey(nbt, EMBEDDED_GEMS_KEY);
|
||||||
ReadableNBT embedded = nbt.getCompound(EMBEDDED_GEMS_KEY);
|
if (embeddedKey != null && nbt.getType(embeddedKey) == NBTType.NBTTagCompound) {
|
||||||
|
ReadableNBT embedded = nbt.getCompound(embeddedKey);
|
||||||
if (embedded != null) for (String name : embedded.getKeys()) {
|
if (embedded != null) for (String name : embedded.getKeys()) {
|
||||||
NBTType type = embedded.getType(name);
|
NBTType type = embedded.getType(name);
|
||||||
if (!numeric(type)) continue;
|
if (!numeric(type)) continue;
|
||||||
registry.find(name).ifPresent(gem -> collector.embedded.put(gem.name(), number(embedded, name, type)));
|
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) {
|
String skillsKey = findKey(nbt, SKILLS_KEY);
|
||||||
var skills = nbt.getStringList(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));
|
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) {
|
private void scan(ReadableNBT nbt, Collector collector, int depth) {
|
||||||
if (nbt == null || depth > 12) return;
|
if (nbt == null || depth > 12) return;
|
||||||
for (String key : nbt.getKeys()) {
|
for (String key : nbt.getKeys()) {
|
||||||
if (key.equalsIgnoreCase(GEM_NAME_KEY) || key.equalsIgnoreCase(EMBEDDED_GEMS_KEY)
|
if (key.equalsIgnoreCase(EMBEDDED_GEMS_KEY) || key.equalsIgnoreCase(SKILLS_KEY)) continue;
|
||||||
|| key.equalsIgnoreCase(SKILLS_KEY)) continue;
|
|
||||||
NBTType type = nbt.getType(key);
|
NBTType type = nbt.getType(key);
|
||||||
try {
|
try {
|
||||||
if (type == NBTType.NBTTagString) {
|
if (type == NBTType.NBTTagString) {
|
||||||
|
|||||||
@@ -153,6 +153,10 @@ public final class RewardEngine {
|
|||||||
|
|
||||||
private void validatePlayerRewards(RewardContext context, List<Directive> directives) throws RewardException {
|
private void validatePlayerRewards(RewardContext context, List<Directive> directives) throws RewardException {
|
||||||
Player player = context.player();
|
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;
|
double moneyBalance = economy.available() ? economy.balance(player) : 0;
|
||||||
int pointBalance = points.available() ? points.balance(player) : 0;
|
int pointBalance = points.available() ? points.balance(player) : 0;
|
||||||
int levels = player.getLevel();
|
int levels = player.getLevel();
|
||||||
@@ -1090,13 +1094,11 @@ public final class RewardEngine {
|
|||||||
|
|
||||||
private void money(RewardContext context, Directive directive) throws RewardException {
|
private void money(RewardContext context, Directive directive) throws RewardException {
|
||||||
double amount = amount(context, directive);
|
double amount = amount(context, directive);
|
||||||
if (!economy.available()) throw new RewardException("Vault 经济服务不可用");
|
|
||||||
if (!economy.change(context.player(), amount)) throw new RewardException("金币不足或经济操作失败");
|
if (!economy.change(context.player(), amount)) throw new RewardException("金币不足或经济操作失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void point(RewardContext context, Directive directive) throws RewardException {
|
private void point(RewardContext context, Directive directive) throws RewardException {
|
||||||
int amount = (int) Math.round(amount(context, directive));
|
int amount = (int) Math.round(amount(context, directive));
|
||||||
if (!points.available()) throw new RewardException("PlayerPoints 不可用");
|
|
||||||
if (!points.change(context.player(), amount)) throw new RewardException("点券不足或操作失败");
|
if (!points.change(context.player(), amount)) throw new RewardException("点券不足或操作失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,6 @@ public class MagicGem extends MagicGemPlugin {
|
|||||||
|
|
||||||
/** Network license activation belonged to the retired vendor service. */
|
/** Network license activation belonged to the retired vendor service. */
|
||||||
public void license(CommandSender sender) {
|
public void license(CommandSender sender) {
|
||||||
sender.sendMessage("MagicGem license service is not configured in this build.");
|
sender.sendMessage("MagicGem-Reforged 未配置授权服务;当前版本无需授权。");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ Version: 1.05
|
|||||||
Name: 精准时运切换宝石
|
Name: 精准时运切换宝石
|
||||||
Require:
|
Require:
|
||||||
- AXE
|
- AXE
|
||||||
- PICKAXE
|
|
||||||
- SHOVEL
|
- SHOVEL
|
||||||
Display: "&8&l&k||&a&l精准/时运切换宝石&8&l&k||"
|
Display: "&8&l&k||&a&l精准/时运切换宝石&8&l&k||"
|
||||||
Tips:
|
Tips:
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -131,6 +131,15 @@ class BundledConfigurationTest {
|
|||||||
void precisionFortuneSwitchGemAcceptsPickaxes() {
|
void precisionFortuneSwitchGemAcceptsPickaxes() {
|
||||||
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(
|
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(
|
||||||
new File("src/main/resources/gems/FunctionGem.yml"));
|
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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user