Document MagicGem migration workflow
This commit is contained in:
@@ -32,7 +32,7 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class CMagicGem extends JavaPlugin {
|
||||
public class CMagicGem extends JavaPlugin {
|
||||
private volatile PluginSettings settings;
|
||||
private GemRegistry gemRegistry;
|
||||
private SkillRegistry skillRegistry;
|
||||
@@ -83,7 +83,7 @@ public final class CMagicGem extends JavaPlugin {
|
||||
|
||||
CMagicGemCommand command = new CMagicGemCommand(gemRegistry, gemService, itemCodec, gemBrowser,
|
||||
guiManager, skillEngine, messages, languages, this::reloadScope);
|
||||
PluginCommand pluginCommand = Objects.requireNonNull(getCommand("cmgem"));
|
||||
PluginCommand pluginCommand = Objects.requireNonNull(getCommand("mgem"));
|
||||
pluginCommand.setExecutor(command);
|
||||
pluginCommand.setTabCompleter(command);
|
||||
getServer().getPluginManager().registerEvents(new GemUseListener(gemService, itemCodec, messages, () -> settings), this);
|
||||
@@ -94,7 +94,7 @@ public final class CMagicGem extends JavaPlugin {
|
||||
+ ", PlayerPoints=" + points.status()
|
||||
+ ", PlaceholderAPI=" + placeholders.status()
|
||||
+ ", MythicMobs=" + mythic.status());
|
||||
getLogger().info("CMagicGem 已启用,命令 /cmgem,加载 " + gemRegistry.all().size() + " 颗宝石。");
|
||||
getLogger().info("MagicGem 已启用,命令 /mgem,加载 " + gemRegistry.all().size() + " 颗宝石。");
|
||||
}
|
||||
|
||||
public void reloadAll() {
|
||||
@@ -109,6 +109,7 @@ public final class CMagicGem extends JavaPlugin {
|
||||
reloadConfig();
|
||||
if (languages != null) languages.reload();
|
||||
settings = PluginSettings.load(getConfig());
|
||||
onSettingsReloaded(settings);
|
||||
if (economy != null) economy.refresh();
|
||||
if (points != null) points.refresh();
|
||||
if (placeholders != null) placeholders.refresh();
|
||||
@@ -123,6 +124,8 @@ public final class CMagicGem extends JavaPlugin {
|
||||
if (isEnabled()) getServer().getPluginManager().callEvent(new CMagicReloadEvent());
|
||||
}
|
||||
|
||||
protected void onSettingsReloaded(PluginSettings settings) { }
|
||||
|
||||
private void saveBundledResource(String path) {
|
||||
File file = new File(getDataFolder(), path);
|
||||
if (!file.exists()) saveResource(path, false);
|
||||
|
||||
@@ -88,19 +88,19 @@ public final class CMagicGemCommand implements CommandExecutor, TabCompleter {
|
||||
}
|
||||
|
||||
private boolean help(CommandSender sender) {
|
||||
sender.sendMessage(itemCodec.color("&6CMagicGem &7- &f/cmgem"));
|
||||
sender.sendMessage(itemCodec.color("&6MagicGem &7- &f/mgem"));
|
||||
List<String> help = languages.section("command.help");
|
||||
if (help.isEmpty()) help = List.of("&e/cmgem give <玩家> <宝石> [数量]",
|
||||
"&e/cmgem apply <玩家> <宝石> [槽位]", "&e/cmgem remove <玩家> <宝石> [槽位]",
|
||||
"&e/cmgem list | material | lore | enchant | nbt | reload",
|
||||
"&e/cmgem embed [界面] | signal <玩家> <信号>");
|
||||
if (help.isEmpty()) help = List.of("&e/mgem give <玩家> <宝石> [数量]",
|
||||
"&e/mgem apply <玩家> <宝石> [槽位]", "&e/mgem remove <玩家> <宝石> [槽位]",
|
||||
"&e/mgem list | material | lore | enchant | nbt | reload",
|
||||
"&e/mgem embed [界面] | signal <玩家> <信号>");
|
||||
help.forEach(line -> sender.sendMessage(itemCodec.color(line)));
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean signal(CommandSender sender, String[] args) {
|
||||
requireAdmin(sender);
|
||||
if (args.length < 3) throw new IllegalArgumentException("用法: /cmgem signal <玩家> <信号>");
|
||||
if (args.length < 3) throw new IllegalArgumentException("用法: /mgem signal <玩家> <信号>");
|
||||
String signal = String.join(" ", Arrays.copyOfRange(args, 2, args.length));
|
||||
if (args[1].equalsIgnoreCase("@all")) {
|
||||
Bukkit.getOnlinePlayers().forEach(player -> skillEngine.signal(player, signal));
|
||||
@@ -129,7 +129,7 @@ public final class CMagicGemCommand implements CommandExecutor, TabCompleter {
|
||||
private boolean addBlock(CommandSender sender, String[] args) {
|
||||
requireAdmin(sender);
|
||||
if (!(sender instanceof Player player)) throw new IllegalArgumentException(languages.text("command.player_only", "&c该命令只能由玩家执行"));
|
||||
if (args.length < 2) throw new IllegalArgumentException("用法: /cmgem addblock <界面>");
|
||||
if (args.length < 2) throw new IllegalArgumentException("用法: /mgem addblock <界面>");
|
||||
var block = player.getLocation().clone().subtract(0, 1, 0).getBlock();
|
||||
String name = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
|
||||
if (!guiManager.addBlock(name, block.getLocation())) throw new IllegalArgumentException("未知界面或保存失败: " + name);
|
||||
@@ -150,12 +150,12 @@ public final class CMagicGemCommand implements CommandExecutor, TabCompleter {
|
||||
requireAdmin(sender);
|
||||
String scope = args.length < 2 ? "all" : args[1].toLowerCase(Locale.ROOT);
|
||||
if (!Set.of("all", "gem", "skill").contains(scope))
|
||||
throw new IllegalArgumentException("用法: /cmgem reload [gem|skill]");
|
||||
throw new IllegalArgumentException("用法: /mgem reload [gem|skill]");
|
||||
reload.accept(scope);
|
||||
String fallback = switch (scope) {
|
||||
case "gem" -> "&a宝石配置已重载,共加载 {amount} 颗宝石。";
|
||||
case "skill" -> "&a技能配置已重载。";
|
||||
default -> "&aCMagicGem 已安全重载,共加载 {amount} 颗宝石。";
|
||||
default -> "&aMagicGem 已安全重载,共加载 {amount} 颗宝石。";
|
||||
};
|
||||
sender.sendMessage(itemCodec.color(languages.text("cmagic.command.reload_" + scope, fallback,
|
||||
"amount", registry.all().size())));
|
||||
@@ -164,7 +164,7 @@ public final class CMagicGemCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
private boolean give(CommandSender sender, String[] args) {
|
||||
requireAdmin(sender);
|
||||
if (args.length < 3) throw new IllegalArgumentException("用法: /cmgem give <玩家> <宝石> [数量]");
|
||||
if (args.length < 3) throw new IllegalArgumentException("用法: /mgem give <玩家> <宝石> [数量]");
|
||||
Player player = requirePlayer(args[1]);
|
||||
GemDefinition gem = requireGem(joinGemName(args, 2, args.length > 3 && integer(args[args.length - 1]) ? args.length - 1 : args.length));
|
||||
int amount = args.length > 3 && integer(args[args.length - 1]) ? Integer.parseInt(args[args.length - 1]) : 1;
|
||||
@@ -176,7 +176,7 @@ public final class CMagicGemCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
private boolean apply(CommandSender sender, String[] args) {
|
||||
requireAdmin(sender);
|
||||
if (args.length < 3) throw new IllegalArgumentException("用法: /cmgem apply <玩家> <宝石> [槽位]");
|
||||
if (args.length < 3) throw new IllegalArgumentException("用法: /mgem apply <玩家> <宝石> [槽位]");
|
||||
Player player = requirePlayer(args[1]);
|
||||
String possibleSlot = args[args.length - 1];
|
||||
EquipmentSlot slot = args.length > 3 ? parseSlot(possibleSlot) : EquipmentSlot.HAND;
|
||||
@@ -192,7 +192,7 @@ public final class CMagicGemCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
private boolean remove(CommandSender sender, String[] args) {
|
||||
requireAdmin(sender);
|
||||
if (args.length < 3) throw new IllegalArgumentException("用法: /cmgem remove <玩家> <宝石> [槽位]");
|
||||
if (args.length < 3) throw new IllegalArgumentException("用法: /mgem remove <玩家> <宝石> [槽位]");
|
||||
Player player = requirePlayer(args[1]);
|
||||
EquipmentSlot slot = args.length > 3 ? parseSlot(args[args.length - 1]) : EquipmentSlot.HAND;
|
||||
int nameEnd = args.length > 3 && slot != null ? args.length - 1 : args.length;
|
||||
@@ -295,8 +295,7 @@ public final class CMagicGemCommand implements CommandExecutor, TabCompleter {
|
||||
}
|
||||
|
||||
private boolean hasPermission(CommandSender sender, String permission) {
|
||||
return sender.hasPermission("cmagicgem.command." + permission)
|
||||
|| sender.hasPermission("magicgem.command." + permission);
|
||||
return PermissionPolicy.allows(permission, sender::hasPermission);
|
||||
}
|
||||
|
||||
private Player requirePlayer(String name) {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.infstar.cmagicgem.command;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/** Keeps the legacy MagicGem admin node's "all permissions" behavior. */
|
||||
final class PermissionPolicy {
|
||||
private PermissionPolicy() { }
|
||||
|
||||
static boolean allows(String permission, Predicate<String> hasPermission) {
|
||||
String requested = permission.toLowerCase(Locale.ROOT);
|
||||
boolean admin = hasPermission.test("magicgem.command.admin")
|
||||
|| hasPermission.test("cmagicgem.command.admin");
|
||||
if (admin) return true;
|
||||
return hasPermission.test("magicgem.command." + requested)
|
||||
|| hasPermission.test("cmagicgem.command." + requested);
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ public final class LanguageManager {
|
||||
}
|
||||
|
||||
private String commandName(String value) {
|
||||
return value.replace("/mgem", "/cmgem").replace("/magicgem", "/cmagicgem");
|
||||
return value;
|
||||
}
|
||||
|
||||
private String safeName(String configured) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.infstar.cmagicgem.gem.GemRegistry;
|
||||
import cn.infstar.cmagicgem.migration.LegacyItemMigrator;
|
||||
import cn.infstar.cmagicgem.util.PotionTypes;
|
||||
import cn.infstar.cmagicgem.util.MaterialResolver;
|
||||
import de.tr7zw.changeme.nbtapi.NBT;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
@@ -40,6 +41,9 @@ public final class ItemCodec {
|
||||
private final NamespacedKey dataVersionKey;
|
||||
private final NamespacedKey embeddedKey;
|
||||
private final NamespacedKey skillsKey;
|
||||
private final NamespacedKey cMagicGemNameKey;
|
||||
private final NamespacedKey cMagicGemEmbeddedKey;
|
||||
private final NamespacedKey cMagicGemSkillsKey;
|
||||
private final LegacyItemMigrator legacyMigrator;
|
||||
|
||||
public ItemCodec(JavaPlugin plugin, GemRegistry registry) {
|
||||
@@ -48,6 +52,9 @@ public final class ItemCodec {
|
||||
this.dataVersionKey = new NamespacedKey(plugin, "data_version");
|
||||
this.embeddedKey = new NamespacedKey(plugin, "embedded_gems");
|
||||
this.skillsKey = new NamespacedKey(plugin, "skills");
|
||||
this.cMagicGemNameKey = new NamespacedKey("cmagicgem", "gem_name");
|
||||
this.cMagicGemEmbeddedKey = new NamespacedKey("cmagicgem", "embedded_gems");
|
||||
this.cMagicGemSkillsKey = new NamespacedKey("cmagicgem", "skills");
|
||||
this.legacyMigrator = new LegacyItemMigrator(registry);
|
||||
}
|
||||
|
||||
@@ -58,6 +65,7 @@ public final class ItemCodec {
|
||||
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 item;
|
||||
}
|
||||
|
||||
@@ -133,6 +141,7 @@ public final class ItemCodec {
|
||||
if (item == null || item.getType().isAir() || !item.hasItemMeta()) return Optional.empty();
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
String name = meta.getPersistentDataContainer().get(gemNameKey, PersistentDataType.STRING);
|
||||
if (name == null) name = meta.getPersistentDataContainer().get(cMagicGemNameKey, PersistentDataType.STRING);
|
||||
Optional<GemDefinition> direct = registry.find(name);
|
||||
if (direct.isPresent()) return direct;
|
||||
LegacyItemMigrator.LegacyData legacy = legacyMigrator.inspect(item);
|
||||
@@ -158,7 +167,9 @@ public final class ItemCodec {
|
||||
|
||||
public Map<String, Integer> embedded(ItemStack item) {
|
||||
if (item == null || !item.hasItemMeta()) return Map.of();
|
||||
String encoded = item.getItemMeta().getPersistentDataContainer().get(embeddedKey, PersistentDataType.STRING);
|
||||
PersistentDataContainer data = item.getItemMeta().getPersistentDataContainer();
|
||||
String encoded = data.get(embeddedKey, PersistentDataType.STRING);
|
||||
if (encoded == null) encoded = data.get(cMagicGemEmbeddedKey, PersistentDataType.STRING);
|
||||
Map<String, Integer> result = new LinkedHashMap<>();
|
||||
if (encoded == null || encoded.isBlank()) {
|
||||
LegacyItemMigrator.LegacyData legacy = legacyMigrator.inspect(item);
|
||||
@@ -194,11 +205,26 @@ public final class ItemCodec {
|
||||
meta.getPersistentDataContainer().set(embeddedKey, PersistentDataType.STRING, encoded.toString());
|
||||
meta.getPersistentDataContainer().set(dataVersionKey, PersistentDataType.INTEGER, 1);
|
||||
item.setItemMeta(meta);
|
||||
NBT.modify(item, nbt -> {
|
||||
String legacyKey = LegacyItemMigrator.EMBEDDED_GEMS_KEY;
|
||||
if (gems.isEmpty()) {
|
||||
nbt.removeKey(legacyKey);
|
||||
return;
|
||||
}
|
||||
var legacy = nbt.getOrCreateCompound(legacyKey);
|
||||
for (String name : legacy.getKeys()) legacy.removeKey(name);
|
||||
gems.forEach((name, count) -> {
|
||||
if (count != null && count > 0) legacy.setInteger(name, count);
|
||||
});
|
||||
if (legacy.getKeys().isEmpty()) nbt.removeKey(legacyKey);
|
||||
});
|
||||
}
|
||||
|
||||
public List<String> skills(ItemStack item) {
|
||||
if (item == null || !item.hasItemMeta()) return new ArrayList<>();
|
||||
String value = item.getItemMeta().getPersistentDataContainer().get(skillsKey, PersistentDataType.STRING);
|
||||
PersistentDataContainer data = item.getItemMeta().getPersistentDataContainer();
|
||||
String value = data.get(skillsKey, PersistentDataType.STRING);
|
||||
if (value == null) value = data.get(cMagicGemSkillsKey, PersistentDataType.STRING);
|
||||
if (value == null || value.isBlank()) {
|
||||
List<String> legacy = new ArrayList<>(legacyMigrator.inspect(item).skills());
|
||||
if (!legacy.isEmpty()) writeSkills(item, legacy);
|
||||
@@ -218,6 +244,14 @@ public final class ItemCodec {
|
||||
PersistentDataContainer data = meta.getPersistentDataContainer();
|
||||
data.set(skillsKey, PersistentDataType.STRING, String.join("\n", skills));
|
||||
item.setItemMeta(meta);
|
||||
NBT.modify(item, nbt -> {
|
||||
String legacyKey = LegacyItemMigrator.SKILLS_KEY;
|
||||
nbt.removeKey(legacyKey);
|
||||
if (!skills.isEmpty()) {
|
||||
var legacy = nbt.getStringList(legacyKey);
|
||||
skills.forEach(legacy::add);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String color(String value) {
|
||||
|
||||
@@ -1,82 +1,248 @@
|
||||
package cn.infstar.cmagicgem.migration;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/** Imports files left by the previous CMagicGem reimplementation without touching the source directory. */
|
||||
public final class LegacyDirectoryImporter {
|
||||
private static final String LEGACY_PLUGIN_DIRECTORY = "CMagicGem";
|
||||
private static final List<String> DATA_DIRECTORIES = List.of("gems", "skills", "gui", "languages");
|
||||
private final JavaPlugin plugin;
|
||||
private static final String MARKER = ".migration-cmagicgem-v1.complete";
|
||||
|
||||
private final Path pluginsDirectory;
|
||||
private final Path targetDirectory;
|
||||
private final Runnable saveDefaultConfig;
|
||||
private final Logger logger;
|
||||
|
||||
public LegacyDirectoryImporter(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this(plugin.getDataFolder().toPath().toAbsolutePath().normalize().getParent(),
|
||||
plugin.getDataFolder().toPath().toAbsolutePath().normalize(),
|
||||
plugin::saveDefaultConfig, plugin.getLogger());
|
||||
}
|
||||
|
||||
LegacyDirectoryImporter(Path pluginsDirectory, Path targetDirectory,
|
||||
Runnable saveDefaultConfig, Logger logger) {
|
||||
this.pluginsDirectory = Objects.requireNonNull(pluginsDirectory).toAbsolutePath().normalize();
|
||||
this.targetDirectory = Objects.requireNonNull(targetDirectory).toAbsolutePath().normalize();
|
||||
this.saveDefaultConfig = Objects.requireNonNull(saveDefaultConfig);
|
||||
this.logger = Objects.requireNonNull(logger);
|
||||
}
|
||||
|
||||
public ImportResult importIfPresent() {
|
||||
Path pluginsDirectory = plugin.getDataFolder().toPath().toAbsolutePath().getParent();
|
||||
if (pluginsDirectory == null) return new ImportResult(0, false);
|
||||
Path legacy = pluginsDirectory.resolve("MagicGem");
|
||||
Path target = plugin.getDataFolder().toPath();
|
||||
if (!Files.isDirectory(legacy) || legacy.equals(target)) return new ImportResult(0, false);
|
||||
int copied = 0;
|
||||
if (targetDirectory.equals(pluginsDirectory.resolve(LEGACY_PLUGIN_DIRECTORY).normalize())) {
|
||||
return ImportResult.none();
|
||||
}
|
||||
|
||||
Path sourceDirectory = pluginsDirectory.resolve(LEGACY_PLUGIN_DIRECTORY).normalize();
|
||||
if (!Files.isDirectory(sourceDirectory)) return ImportResult.none();
|
||||
Path marker = targetDirectory.resolve(MARKER);
|
||||
if (Files.exists(marker)) return new ImportResult(0, 0, 0, false, true);
|
||||
|
||||
int copiedFiles = 0;
|
||||
int mergedFiles = 0;
|
||||
int conflictFiles = 0;
|
||||
boolean configImported = false;
|
||||
List<Conflict> conflicts = new ArrayList<>();
|
||||
try {
|
||||
Files.createDirectories(target);
|
||||
for (String directory : DATA_DIRECTORIES) copied += copyDirectory(legacy.resolve(directory), target.resolve(directory));
|
||||
boolean configImported = importConfig(legacy.resolve("config.yml"), target.resolve("config.yml"));
|
||||
plugin.getLogger().info("发现旧 MagicGem 数据目录,安全导入 " + copied + " 个文件。原目录保持不变。");
|
||||
return new ImportResult(copied, configImported);
|
||||
} catch (IOException exception) {
|
||||
plugin.getLogger().log(Level.SEVERE, "导入旧 MagicGem 数据失败,未删除任何旧文件", exception);
|
||||
return new ImportResult(copied, false);
|
||||
Files.createDirectories(targetDirectory);
|
||||
for (String directory : DATA_DIRECTORIES) {
|
||||
ImportCounts counts = importDirectory(sourceDirectory.resolve(directory), targetDirectory.resolve(directory),
|
||||
Path.of(directory), conflicts);
|
||||
copiedFiles += counts.copiedFiles;
|
||||
mergedFiles += counts.mergedFiles;
|
||||
conflictFiles += counts.conflictFiles;
|
||||
}
|
||||
|
||||
Path sourceConfig = sourceDirectory.resolve("config.yml");
|
||||
Path targetConfig = targetDirectory.resolve("config.yml");
|
||||
if (Files.isRegularFile(sourceConfig)) {
|
||||
boolean targetConfigExisted = Files.isRegularFile(targetConfig);
|
||||
if (!targetConfigExisted) saveDefaultConfig.run();
|
||||
MergeResult result = mergeYaml(sourceConfig, targetConfig, Path.of("config.yml"),
|
||||
!targetConfigExisted, conflicts);
|
||||
configImported = result.changed;
|
||||
if (result.changed) mergedFiles++;
|
||||
if (result.conflicted) conflictFiles++;
|
||||
}
|
||||
|
||||
writeConflictBackups(sourceDirectory, conflicts);
|
||||
Files.writeString(marker, "Imported from plugins/CMagicGem. Source directory remains unchanged.\n");
|
||||
if (!conflicts.isEmpty()) {
|
||||
for (Conflict conflict : conflicts) {
|
||||
logger.warning("CMagicGem migration kept the MagicGem value for "
|
||||
+ conflict.sourceRelative() + " (" + conflict.key() + ")"
|
||||
+ "; the original source file was saved under migration-backup/CMagicGem.");
|
||||
}
|
||||
}
|
||||
logger.info("Imported plugins/CMagicGem into plugins/MagicGem: " + copiedFiles + " files copied, "
|
||||
+ mergedFiles + " files merged, " + conflictFiles + " files with conflicts. "
|
||||
+ "The source directory was left untouched.");
|
||||
return new ImportResult(copiedFiles, mergedFiles, conflictFiles, configImported, false);
|
||||
} catch (IOException | InvalidConfigurationException exception) {
|
||||
logger.log(Level.SEVERE, "Could not finish importing plugins/CMagicGem. No source files were deleted; "
|
||||
+ "the migration will be retried on the next startup.", exception);
|
||||
return new ImportResult(copiedFiles, mergedFiles, conflictFiles, configImported, false);
|
||||
}
|
||||
}
|
||||
|
||||
private int copyDirectory(Path source, Path target) throws IOException {
|
||||
if (!Files.isDirectory(source)) return 0;
|
||||
private ImportCounts importDirectory(Path source, Path target, Path sourcePrefix, List<Conflict> conflicts)
|
||||
throws IOException, InvalidConfigurationException {
|
||||
if (!Files.isDirectory(source)) return new ImportCounts(0, 0, 0);
|
||||
int copied = 0;
|
||||
int merged = 0;
|
||||
int conflicted = 0;
|
||||
Path normalizedTarget = target.toAbsolutePath().normalize();
|
||||
try (var paths = Files.walk(source)) {
|
||||
for (Path path : paths.toList()) {
|
||||
Path relative = source.relativize(path);
|
||||
Path destination = target.resolve(relative).normalize();
|
||||
if (!destination.startsWith(target.normalize())) continue;
|
||||
if (Files.isDirectory(path)) Files.createDirectories(destination);
|
||||
else if (!Files.exists(destination)) {
|
||||
Path destination = normalizedTarget.resolve(relative).normalize();
|
||||
if (!destination.startsWith(normalizedTarget)) continue;
|
||||
if (Files.isDirectory(path)) {
|
||||
Files.createDirectories(destination);
|
||||
continue;
|
||||
}
|
||||
if (!Files.isRegularFile(path)) continue;
|
||||
if (!Files.exists(destination)) {
|
||||
Files.createDirectories(destination.getParent());
|
||||
Files.copy(path, destination, StandardCopyOption.COPY_ATTRIBUTES);
|
||||
copied++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isYaml(path)) {
|
||||
MergeResult result = mergeYaml(path, destination,
|
||||
sourcePrefix.resolve(source.relativize(path)), false, conflicts);
|
||||
if (result.changed) merged++;
|
||||
if (result.conflicted) conflicted++;
|
||||
} else if (Files.mismatch(path, destination) != -1) {
|
||||
conflicts.add(new Conflict(sourcePrefix.resolve(source.relativize(path)), "<binary content>"));
|
||||
conflicted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return copied;
|
||||
return new ImportCounts(copied, merged, conflicted);
|
||||
}
|
||||
|
||||
private boolean importConfig(Path source, Path destination) throws IOException {
|
||||
if (!Files.isRegularFile(source)) return false;
|
||||
YamlConfiguration oldConfig = YamlConfiguration.loadConfiguration(source.toFile());
|
||||
YamlConfiguration newConfig = YamlConfiguration.loadConfiguration(destination.toFile());
|
||||
boolean changed = false;
|
||||
for (String key : List.of("lang", "workbench", "backpack", "gem-lore", "gem-craft", "skill-engine",
|
||||
"skill-engine-lore-color", "potion-global-limit")) {
|
||||
if (oldConfig.contains(key) && !newConfig.contains(key)) {
|
||||
newConfig.set(key, oldConfig.get(key));
|
||||
changed = true;
|
||||
private MergeResult mergeYaml(Path source, Path destination, Path sourceRelative,
|
||||
boolean preferSource, List<Conflict> conflicts)
|
||||
throws IOException, InvalidConfigurationException {
|
||||
if (!Files.exists(destination)) {
|
||||
Files.createDirectories(destination.getParent());
|
||||
Files.copy(source, destination, StandardCopyOption.COPY_ATTRIBUTES);
|
||||
return new MergeResult(true, false);
|
||||
}
|
||||
|
||||
YamlConfiguration sourceYaml = new YamlConfiguration();
|
||||
sourceYaml.load(source.toFile());
|
||||
YamlConfiguration destinationYaml = new YamlConfiguration();
|
||||
destinationYaml.load(destination.toFile());
|
||||
List<String> fileConflicts = new ArrayList<>();
|
||||
boolean changed = mergeSections(sourceYaml, destinationYaml, "", preferSource, fileConflicts);
|
||||
if (source.getFileName().toString().equalsIgnoreCase("config.yml")) {
|
||||
String legacyLanguage = sourceYaml.getString("language", sourceYaml.getString("lang"));
|
||||
if (legacyLanguage != null && (preferSource || !destinationYaml.contains("language"))) {
|
||||
if (!Objects.equals(destinationYaml.getString("language"), legacyLanguage)) {
|
||||
destinationYaml.set("language", legacyLanguage);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldConfig.contains("lang") && !newConfig.contains("language")) {
|
||||
newConfig.set("language", oldConfig.getString("lang", "zh_CN"));
|
||||
changed = true;
|
||||
if (changed) destinationYaml.save(destination.toFile());
|
||||
for (String conflict : fileConflicts) conflicts.add(new Conflict(sourceRelative, conflict));
|
||||
return new MergeResult(changed, !fileConflicts.isEmpty());
|
||||
}
|
||||
|
||||
private boolean mergeSections(ConfigurationSection source, ConfigurationSection destination,
|
||||
String prefix, boolean preferSource, List<String> conflicts) {
|
||||
boolean changed = false;
|
||||
for (String key : source.getKeys(false)) {
|
||||
String path = prefix.isEmpty() ? key : prefix + "." + key;
|
||||
ConfigurationSection sourceSection = source.getConfigurationSection(key);
|
||||
ConfigurationSection destinationSection = destination.getConfigurationSection(key);
|
||||
if (sourceSection != null && destinationSection == null) {
|
||||
if (!destination.contains(key)) {
|
||||
destination.createSection(key);
|
||||
changed = true;
|
||||
} else {
|
||||
if (preferSource) {
|
||||
destination.set(key, null);
|
||||
destination.createSection(key);
|
||||
changed = true;
|
||||
} else {
|
||||
conflicts.add(path);
|
||||
}
|
||||
}
|
||||
destinationSection = destination.getConfigurationSection(key);
|
||||
if (destinationSection != null) {
|
||||
changed |= mergeSections(sourceSection, destinationSection, path, preferSource, conflicts);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (sourceSection == null && destinationSection != null) {
|
||||
if (preferSource) {
|
||||
destination.set(key, source.get(key));
|
||||
changed = true;
|
||||
} else {
|
||||
conflicts.add(path);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (sourceSection != null) {
|
||||
changed |= mergeSections(sourceSection, destinationSection, path, preferSource, conflicts);
|
||||
continue;
|
||||
}
|
||||
|
||||
Object sourceValue = source.get(key);
|
||||
if (!destination.contains(key)) {
|
||||
destination.set(key, sourceValue);
|
||||
changed = true;
|
||||
} else if (!Objects.equals(sourceValue, destination.get(key))) {
|
||||
if (preferSource) {
|
||||
destination.set(key, sourceValue);
|
||||
changed = true;
|
||||
} else {
|
||||
conflicts.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed) newConfig.save(destination.toFile());
|
||||
return changed;
|
||||
}
|
||||
|
||||
public record ImportResult(int copiedFiles, boolean configImported) {
|
||||
private void writeConflictBackups(Path sourceDirectory, List<Conflict> conflicts) throws IOException {
|
||||
for (Path relative : conflicts.stream().map(Conflict::sourceRelative).distinct().toList()) {
|
||||
Path sourceFile = sourceDirectory.resolve(relative).normalize();
|
||||
if (!sourceFile.startsWith(sourceDirectory) || !Files.isRegularFile(sourceFile)) continue;
|
||||
Path backup = targetDirectory.resolve("migration-backup").resolve(LEGACY_PLUGIN_DIRECTORY).resolve(relative);
|
||||
Files.createDirectories(backup.getParent());
|
||||
Files.copy(sourceFile, backup, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isYaml(Path path) {
|
||||
String name = path.getFileName().toString().toLowerCase(java.util.Locale.ROOT);
|
||||
return name.endsWith(".yml") || name.endsWith(".yaml");
|
||||
}
|
||||
|
||||
public record ImportResult(int copiedFiles, int mergedFiles, int conflictFiles,
|
||||
boolean configImported, boolean alreadyMigrated) {
|
||||
private static ImportResult none() {
|
||||
return new ImportResult(0, 0, 0, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private record ImportCounts(int copiedFiles, int mergedFiles, int conflictFiles) { }
|
||||
private record MergeResult(boolean changed, boolean conflicted) { }
|
||||
private record Conflict(Path sourceRelative, String key) { }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package pku.yim.magicgem;
|
||||
|
||||
import cn.infstar.cmagicgem.CMagicGem;
|
||||
import cn.infstar.cmagicgem.config.PluginSettings;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
/** Legacy entry point retained for plugins that resolve MagicGem by class name. */
|
||||
public final class MagicGem extends CMagicGem {
|
||||
public static MagicGem instance;
|
||||
public static boolean placeholderapi;
|
||||
public static boolean MMCorMMA;
|
||||
public static boolean isArtifact;
|
||||
public static boolean workbench;
|
||||
public static boolean backpack;
|
||||
public static boolean gemLore;
|
||||
public static boolean gemCraft;
|
||||
public static boolean skillEngine;
|
||||
|
||||
public static MagicGem getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
super.onEnable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
try {
|
||||
super.onDisable();
|
||||
} finally {
|
||||
if (instance == this) instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSettingsReloaded(PluginSettings settings) {
|
||||
placeholderapi = getServer().getPluginManager().getPlugin("PlaceholderAPI") != null;
|
||||
MMCorMMA = getServer().getPluginManager().getPlugin("MythicMobs") != null;
|
||||
isArtifact = getServer().getPluginManager().getPlugin("MythicArtifacts") != null
|
||||
|| getServer().getPluginManager().getPlugin("MythicCrucible") != null;
|
||||
workbench = settings.workbench();
|
||||
backpack = settings.backpack();
|
||||
gemLore = settings.gemLore();
|
||||
gemCraft = settings.gemCraft();
|
||||
skillEngine = settings.skillEngine();
|
||||
}
|
||||
|
||||
public void info(String message) {
|
||||
getLogger().info(message);
|
||||
}
|
||||
|
||||
public void disablePlugin() {
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
}
|
||||
|
||||
public boolean isPrimaryThread() {
|
||||
return Bukkit.isPrimaryThread();
|
||||
}
|
||||
|
||||
/** 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.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package pku.yim.magicgem.api;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/** Slot names retained for plugins compiled against MagicGem's public API. */
|
||||
public enum PlayerSlotType {
|
||||
HELMET(5, "HEAD"),
|
||||
CHESTPLATE(6, "CHEST"),
|
||||
LEGGINGS(7, "LEGS"),
|
||||
BOOTS(8, "FEET"),
|
||||
OFFHAND(45, "OFFHAND"),
|
||||
MAINHAND(-1, "HAND");
|
||||
|
||||
private final int slot;
|
||||
private final String mythicSlot;
|
||||
|
||||
PlayerSlotType(int slot, String mythicSlot) {
|
||||
this.slot = slot;
|
||||
this.mythicSlot = mythicSlot;
|
||||
}
|
||||
|
||||
public static PlayerSlotType getByName(String name) {
|
||||
if (name == null) return null;
|
||||
return switch (name.trim().toLowerCase(Locale.ROOT).replace('-', '_').replace(' ', '_')) {
|
||||
case "helmet", "head" -> HELMET;
|
||||
case "chest", "chestplate" -> CHESTPLATE;
|
||||
case "legs", "leggings" -> LEGGINGS;
|
||||
case "boots", "feet" -> BOOTS;
|
||||
case "offhand" -> OFFHAND;
|
||||
case "mainhand", "hand" -> MAINHAND;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public static PlayerSlotType getById(int id) {
|
||||
for (PlayerSlotType type : values()) if (type.slot == id) return type;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isAir(ItemStack item) {
|
||||
return item == null || item.getType().isAir();
|
||||
}
|
||||
|
||||
public static PlayerSlotType matchType(ItemStack item) {
|
||||
if (isAir(item)) return null;
|
||||
return matchType(item.getType().name());
|
||||
}
|
||||
|
||||
public static PlayerSlotType matchType(String material) {
|
||||
if (material == null) return null;
|
||||
String name = material.toUpperCase(Locale.ROOT);
|
||||
if (name.endsWith("_HELMET") || name.endsWith("_HEAD")) return HELMET;
|
||||
if (name.endsWith("_CHESTPLATE")) return CHESTPLATE;
|
||||
if (name.endsWith("_LEGGINGS")) return LEGGINGS;
|
||||
if (name.endsWith("_BOOTS")) return BOOTS;
|
||||
if (name.equals("ELYTRA")) return CHESTPLATE;
|
||||
if (name.equals("SHIELD")) return OFFHAND;
|
||||
return null;
|
||||
}
|
||||
|
||||
public String toMMSlot() {
|
||||
return mythicSlot;
|
||||
}
|
||||
|
||||
public ItemStack pickInvItem(PlayerInventory inventory) {
|
||||
return switch (this) {
|
||||
case HELMET -> inventory.getHelmet();
|
||||
case CHESTPLATE -> inventory.getChestplate();
|
||||
case LEGGINGS -> inventory.getLeggings();
|
||||
case BOOTS -> inventory.getBoots();
|
||||
case OFFHAND -> inventory.getItemInOffHand();
|
||||
case MAINHAND -> inventory.getItemInMainHand();
|
||||
};
|
||||
}
|
||||
|
||||
public void setInvItem(PlayerInventory inventory, ItemStack item) {
|
||||
switch (this) {
|
||||
case HELMET -> inventory.setHelmet(item);
|
||||
case CHESTPLATE -> inventory.setChestplate(item);
|
||||
case LEGGINGS -> inventory.setLeggings(item);
|
||||
case BOOTS -> inventory.setBoots(item);
|
||||
case OFFHAND -> inventory.setItemInOffHand(item);
|
||||
case MAINHAND -> inventory.setItemInMainHand(item);
|
||||
}
|
||||
}
|
||||
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package pku.yim.magicgem.api.wrappers;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class CursorWrapper implements ISlotWrapper {
|
||||
private final Player player;
|
||||
|
||||
public CursorWrapper(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override public void set(ItemStack item) { player.setItemOnCursor(item); }
|
||||
@Override public ItemStack get() { return player.getItemOnCursor(); }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package pku.yim.magicgem.api.wrappers;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class DummyGemWrapper implements ISlotWrapper {
|
||||
private ItemStack item;
|
||||
|
||||
public DummyGemWrapper(ItemStack item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
@Override public void set(ItemStack item) { this.item = item; }
|
||||
@Override public ItemStack get() { return item; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package pku.yim.magicgem.api.wrappers;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public interface ISlotWrapper {
|
||||
void set(ItemStack item);
|
||||
ItemStack get();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package pku.yim.magicgem.api.wrappers;
|
||||
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class InventoryClickWrapper implements ISlotWrapper {
|
||||
private final InventoryClickEvent event;
|
||||
|
||||
public InventoryClickWrapper(InventoryClickEvent event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
@Override public void set(ItemStack item) { event.setCurrentItem(item); }
|
||||
@Override public ItemStack get() { return event.getCurrentItem(); }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package pku.yim.magicgem.api.wrappers;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import pku.yim.magicgem.api.PlayerSlotType;
|
||||
|
||||
public class PlayerInventoryWrapper implements ISlotWrapper {
|
||||
private final PlayerInventory inventory;
|
||||
private final PlayerSlotType slot;
|
||||
|
||||
public PlayerInventoryWrapper(PlayerInventory inventory, PlayerSlotType slot) {
|
||||
this.inventory = inventory;
|
||||
this.slot = slot;
|
||||
}
|
||||
|
||||
@Override public void set(ItemStack item) { slot.setInvItem(inventory, item); }
|
||||
@Override public ItemStack get() { return slot.pickInvItem(inventory); }
|
||||
public PlayerSlotType getSlot() { return slot; }
|
||||
}
|
||||
@@ -276,7 +276,7 @@ cmagic:
|
||||
gem_not_found: "&cUnknown gem: {gem}"
|
||||
not_embedded: "&cThat gem is not embedded in this item"
|
||||
command:
|
||||
reload: "&aCMagicGem reloaded safely; {amount} gems loaded."
|
||||
reload: "&aMagicGem reloaded safely; {amount} gems loaded."
|
||||
give: "&aGave {player} {amount} {gem}"
|
||||
list: "&6Loaded gems (&e{amount}&6):"
|
||||
signal_all: "&aSent the skill signal to every online player."
|
||||
|
||||
@@ -276,7 +276,7 @@ cmagic:
|
||||
gem_not_found: "&c未知宝石: {gem}"
|
||||
not_embedded: "&c装备未镶嵌该宝石"
|
||||
command:
|
||||
reload: "&aCMagicGem 已安全重载,共加载 {amount} 颗宝石。"
|
||||
reload: "&aMagicGem 已安全重载,共加载 {amount} 颗宝石。"
|
||||
give: "&a已给予 {player} {amount} 个 {gem}"
|
||||
list: "&6已加载宝石 (&e{amount}&6):"
|
||||
signal_all: "&a已向所有在线玩家发送技能信号。"
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
name: CMagicGem
|
||||
name: MagicGem
|
||||
version: '${version}'
|
||||
description: Paper 1.21.x compatible MagicGem reimplementation
|
||||
main: cn.infstar.cmagicgem.CMagicGem
|
||||
main: pku.yim.magicgem.MagicGem
|
||||
api-version: '1.21'
|
||||
authors: [ Coldsmile_7 ]
|
||||
softdepend: [PlaceholderAPI, Vault, PlayerPoints, MythicMobs, MMOItems, MythicArtifacts, MythicCrucible, Residence, PvPManager, EcoEnchants, GoldenEnchants]
|
||||
commands:
|
||||
cmgem:
|
||||
description: CMagicGem main command
|
||||
usage: /cmgem help
|
||||
aliases: [cmg]
|
||||
mgem:
|
||||
description: MagicGem main command
|
||||
usage: /mgem help
|
||||
aliases: [cmgem, cmg]
|
||||
permissions:
|
||||
cmagicgem.command.admin:
|
||||
default: op
|
||||
@@ -17,3 +17,9 @@ permissions:
|
||||
default: op
|
||||
cmagicgem.command.embed:
|
||||
default: op
|
||||
magicgem.command.admin:
|
||||
default: op
|
||||
magicgem.command.view:
|
||||
default: op
|
||||
magicgem.command.embed:
|
||||
default: op
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.infstar.cmagicgem.api;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import pku.yim.magicgem.api.PlayerSlotType;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
class PlayerSlotTypeCompatibilityTest {
|
||||
@Test
|
||||
void legacyIdsAndMythicSlotNamesArePreserved() {
|
||||
assertEquals(PlayerSlotType.MAINHAND, PlayerSlotType.getById(-1));
|
||||
assertEquals(PlayerSlotType.HELMET, PlayerSlotType.getById(5));
|
||||
assertEquals(PlayerSlotType.OFFHAND, PlayerSlotType.getById(45));
|
||||
assertEquals("HAND", PlayerSlotType.MAINHAND.toMMSlot());
|
||||
assertEquals(PlayerSlotType.CHESTPLATE, PlayerSlotType.getByName("CHEST"));
|
||||
assertNull(PlayerSlotType.getById(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void materialMatchingUsesTheArmorSlot() {
|
||||
assertEquals(PlayerSlotType.HELMET, PlayerSlotType.matchType("DIAMOND_HELMET"));
|
||||
assertEquals(PlayerSlotType.CHESTPLATE, PlayerSlotType.matchType("ELYTRA"));
|
||||
assertEquals(PlayerSlotType.OFFHAND, PlayerSlotType.matchType("SHIELD"));
|
||||
assertNull(PlayerSlotType.matchType("MAINHAND"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.infstar.cmagicgem.command;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PermissionPolicyTest {
|
||||
@Test
|
||||
void legacyAdminPermissionIncludesViewAndEmbed() {
|
||||
Set<String> permissions = Set.of("magicgem.command.admin");
|
||||
|
||||
assertTrue(PermissionPolicy.allows("view", permissions::contains));
|
||||
assertTrue(PermissionPolicy.allows("embed", permissions::contains));
|
||||
assertTrue(PermissionPolicy.allows("admin", permissions::contains));
|
||||
}
|
||||
|
||||
@Test
|
||||
void specificPermissionsDoNotGrantOtherCommands() {
|
||||
Set<String> permissions = Set.of("magicgem.command.view");
|
||||
|
||||
assertTrue(PermissionPolicy.allows("view", permissions::contains));
|
||||
assertFalse(PermissionPolicy.allows("embed", permissions::contains));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package cn.infstar.cmagicgem.migration;
|
||||
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class LegacyDirectoryImporterTest {
|
||||
@TempDir
|
||||
Path temporaryDirectory;
|
||||
|
||||
@Test
|
||||
void mergesOldDirectoryWithoutOverwritingTargetAndKeepsConflictBackup() throws Exception {
|
||||
Path plugins = Files.createDirectory(temporaryDirectory.resolve("plugins"));
|
||||
Path source = Files.createDirectories(plugins.resolve("CMagicGem/gems"));
|
||||
Path target = Files.createDirectories(plugins.resolve("MagicGem/gems"));
|
||||
Path oldGem = source.resolve("Shared.yml");
|
||||
Path currentGem = target.resolve("Shared.yml");
|
||||
Files.writeString(oldGem, "Shared:\n Name: Old Name\n Material: DIAMOND\n OldOnly: retained\n");
|
||||
Files.writeString(currentGem, "Shared:\n Name: Current Name\n Material: DIAMOND\n CurrentOnly: retained\n");
|
||||
Files.writeString(source.resolve("Custom.yml"), "Custom:\n Name: Imported\n");
|
||||
Files.writeString(source.getParent().resolve("config.yml"), "language: en_US\nbackpack: true\nold-option: kept\n");
|
||||
Files.writeString(target.getParent().resolve("config.yml"), "language: zh_CN\nbackpack: false\n");
|
||||
|
||||
LegacyDirectoryImporter importer = importer(plugins, plugins.resolve("MagicGem"), () -> { });
|
||||
LegacyDirectoryImporter.ImportResult result = importer.importIfPresent();
|
||||
|
||||
YamlConfiguration mergedGem = load(currentGem);
|
||||
YamlConfiguration mergedConfig = load(target.getParent().resolve("config.yml"));
|
||||
assertEquals("Current Name", mergedGem.getString("Shared.Name"));
|
||||
assertEquals("retained", mergedGem.getString("Shared.OldOnly"));
|
||||
assertEquals("retained", mergedGem.getString("Shared.CurrentOnly"));
|
||||
assertTrue(Files.isRegularFile(target.resolve("Custom.yml")));
|
||||
assertEquals("zh_CN", mergedConfig.getString("language"));
|
||||
assertEquals(false, mergedConfig.getBoolean("backpack"));
|
||||
assertEquals("kept", mergedConfig.getString("old-option"));
|
||||
assertTrue(Files.isRegularFile(plugins.resolve("MagicGem/migration-backup/CMagicGem/gems/Shared.yml")));
|
||||
assertEquals("Old Name", load(oldGem).getString("Shared.Name"));
|
||||
assertTrue(result.copiedFiles() >= 1);
|
||||
assertTrue(result.mergedFiles() >= 2);
|
||||
assertTrue(result.conflictFiles() >= 1);
|
||||
|
||||
LegacyDirectoryImporter.ImportResult repeated = importer.importIfPresent();
|
||||
assertTrue(repeated.alreadyMigrated());
|
||||
assertEquals(0, repeated.copiedFiles());
|
||||
}
|
||||
|
||||
@Test
|
||||
void oldConfigurationOverridesBundledDefaultsWhenTargetHasNoConfig() throws Exception {
|
||||
Path plugins = Files.createDirectory(temporaryDirectory.resolve("plugins"));
|
||||
Path source = Files.createDirectories(plugins.resolve("CMagicGem"));
|
||||
Path target = plugins.resolve("MagicGem");
|
||||
Files.writeString(source.resolve("config.yml"), "lang: en_US\nbackpack: true\n");
|
||||
Runnable createDefaults = () -> {
|
||||
try {
|
||||
Files.createDirectories(target);
|
||||
Files.writeString(target.resolve("config.yml"), "language: zh_CN\nbackpack: false\nworkbench: true\n");
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
};
|
||||
|
||||
LegacyDirectoryImporter.ImportResult result = importer(plugins, target, createDefaults).importIfPresent();
|
||||
|
||||
YamlConfiguration config = load(target.resolve("config.yml"));
|
||||
assertEquals("en_US", config.getString("language"));
|
||||
assertEquals(true, config.getBoolean("backpack"));
|
||||
assertEquals(true, config.getBoolean("workbench"));
|
||||
assertTrue(result.configImported());
|
||||
}
|
||||
|
||||
@Test
|
||||
void originalMagicGemDirectoryIsTreatedAsTheActiveTarget() throws Exception {
|
||||
Path plugins = Files.createDirectory(temporaryDirectory.resolve("plugins"));
|
||||
Path target = Files.createDirectory(plugins.resolve("MagicGem"));
|
||||
|
||||
LegacyDirectoryImporter.ImportResult result = importer(plugins, target, () -> { }).importIfPresent();
|
||||
|
||||
assertFalse(result.alreadyMigrated());
|
||||
assertFalse(Files.exists(target.resolve(".migration-cmagicgem-v1.complete")));
|
||||
}
|
||||
|
||||
private LegacyDirectoryImporter importer(Path plugins, Path target, Runnable saveDefaults) {
|
||||
return new LegacyDirectoryImporter(plugins, target, saveDefaults, Logger.getLogger("migration-test"));
|
||||
}
|
||||
|
||||
private YamlConfiguration load(Path path) {
|
||||
return YamlConfiguration.loadConfiguration(path.toFile());
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,19 @@ class BundledConfigurationTest {
|
||||
private final DirectiveParser parser = new DirectiveParser();
|
||||
|
||||
@Test
|
||||
void pluginDescriptorOnlyRegistersNewCommandNames() {
|
||||
void pluginDescriptorKeepsLegacyCommandAndPermissionCompatibility() {
|
||||
YamlConfiguration descriptor = YamlConfiguration.loadConfiguration(new File("src/main/resources/plugin.yml"));
|
||||
|
||||
assertNotNull(descriptor.getConfigurationSection("commands.cmgem"));
|
||||
assertTrue(descriptor.getStringList("commands.cmgem.aliases").contains("cmg"));
|
||||
assertNull(descriptor.getConfigurationSection("commands.mgem"));
|
||||
assertTrue(descriptor.getString("name").equalsIgnoreCase("MagicGem"));
|
||||
assertTrue(descriptor.getString("main").equals("pku.yim.magicgem.MagicGem"));
|
||||
assertNotNull(descriptor.getConfigurationSection("commands.mgem"));
|
||||
assertTrue(descriptor.getStringList("commands.mgem.aliases").contains("cmg"));
|
||||
assertTrue(descriptor.getStringList("commands.mgem.aliases").contains("cmgem"));
|
||||
assertNull(descriptor.getConfigurationSection("commands.cmgem"));
|
||||
assertNull(descriptor.getConfigurationSection("commands.magicgem"));
|
||||
assertNotNull(descriptor.getConfigurationSection("permissions.magicgem.command.admin"));
|
||||
assertNotNull(descriptor.getConfigurationSection("permissions.magicgem.command.view"));
|
||||
assertNotNull(descriptor.getConfigurationSection("permissions.magicgem.command.embed"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user