Rename internal CMagicGem implementation

This commit is contained in:
Coldsmiles
2026-10-05 01:46:32 +08:00
parent 953c15e380
commit ff9b6ce4f1
68 changed files with 298 additions and 306 deletions
@@ -0,0 +1,27 @@
package cn.infstar.magicgem.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.magicgem.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,65 @@
package cn.infstar.magicgem.dsl;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.Set;
class DirectiveParserTest {
@Test
void keepsNestedRewardTogether() {
Directive directive = new DirectiveParser().parse("Conditional{condition=v>1;reward=Enchant{name=sharpness;level=1}} $ignorable");
assertEquals("Conditional", directive.type());
assertEquals("Enchant{name=sharpness;level=1}", directive.argument("", "reward"));
assertTrue(directive.ignorable());
}
@Test
void parsesLegacyRewardPhases() {
DirectiveParser parser = new DirectiveParser();
assertEquals(Directive.Phase.SUCCESS, parser.parse("Money{amount=1} $onSuccess").phase());
assertEquals(Directive.Phase.FAIL, parser.parse("Chat{m=失败} $onFail").phase());
assertEquals(Directive.Phase.REMOVE, parser.parse("MaxHealth{amount=-1} $onRemove").phase());
}
@Test
void removesBalancedArgumentQuotes() {
Directive directive = new DirectiveParser().parse(
"Conditional{condition=\"v>=2&&v<=8\";reward='Chat{m=ok}'}");
assertEquals("v>=2&&v<=8", directive.argument("", "condition"));
assertEquals("Chat{m=ok}", directive.argument("", "reward"));
}
@Test
void retainsLegacyDelimitedMessageSegments() {
DirectiveParser parser = new DirectiveParser();
Directive directive = parser.parse("Title{messages=主标题;副标题;第二页;第二页副标题;interval=20;stay=60}");
assertEquals(List.of("主标题", "副标题", "第二页", "第二页副标题"),
parser.delimitedArgument(directive, Set.of("messages", "message", "m"),
Set.of("interval", "fadein", "fadeout", "stay")));
}
@Test
void retainsSwitchCycleAfterNamedFirstEntry() {
Directive directive = new DirectiveParser().parse("Switch{s=精准;时运}");
assertEquals("精准", directive.argument("", "s"));
assertEquals(List.of("时运"), directive.positional());
}
@Test
void splitsLegacyMultiLineLoreGroups() {
DirectiveParser parser = new DirectiveParser();
Directive directive = parser.parse("LoreMultiReplace{old=第一行;第二行;new=新一行;新二行;limit=v<5}");
assertEquals(List.of("第一行", "第二行"),
parser.delimitedArgument(directive, Set.of("old"), Set.of("lore", "new", "limit")));
assertEquals(List.of("新一行", "新二行"),
parser.delimitedArgument(directive, Set.of("new"), Set.of("limit")));
}
}
@@ -0,0 +1,35 @@
package cn.infstar.magicgem.expression;
import org.junit.jupiter.api.Test;
import java.util.Map;
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 ExpressionTest {
private final Expression expression = new Expression();
@Test
void evaluatesArithmeticAndVariables() {
assertEquals(7, expression.evaluate("v*2+1", Map.of("v", 3.0)));
assertEquals(9, expression.evaluate("(v+1)^2", Map.of("v", 2.0)));
}
@Test
void evaluatesConditions() {
assertTrue(expression.test("v>=2&&v<=8", Map.of("v", 5.0)));
assertFalse(expression.test("v%3==0", Map.of("v", 5.0)));
assertFalse(expression.test("false&&v>0", Map.of("v", 5.0)));
assertTrue(expression.test("true||v<0", Map.of("v", 5.0)));
}
@Test
void evaluatesLegacyFunctionsAndTernary() {
assertEquals(8, expression.evaluate("true?$POW(2,3):0", Map.of()));
assertEquals(2, expression.evaluate("false?1:$FLOOR(2.9)", Map.of()));
double random = expression.evaluate("$RANDOM()", Map.of());
assertTrue(random >= 0 && random < 1);
}
}
@@ -0,0 +1,29 @@
package cn.infstar.magicgem.gem;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class GemTypeTest {
@Test
void acceptsLegacyAndEnumSpellings() {
assertEquals(GemType.ITEM_GEM, GemType.parse("ItemGem"));
assertEquals(GemType.PLAYER_GEM, GemType.parse("PlayerGem"));
assertEquals(GemType.RANDOM_GEM, GemType.parse("RandomGem"));
assertEquals(GemType.PLAYER_GEM, GemType.parse("PLAYER_GEM"));
assertEquals(GemType.RANDOM_GEM, GemType.parse("random-gem"));
assertEquals(GemType.ITEM_GEM, GemType.parse("item gem"));
}
@Test
void defaultsBlankValuesToItemGems() {
assertEquals(GemType.ITEM_GEM, GemType.parse(null));
assertEquals(GemType.ITEM_GEM, GemType.parse(" "));
}
@Test
void rejectsUnknownValues() {
assertThrows(IllegalArgumentException.class, () -> GemType.parse("not-a-gem"));
}
}
@@ -0,0 +1,57 @@
package cn.infstar.magicgem.gem;
import org.bukkit.Material;
import org.junit.jupiter.api.Test;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RandomGemValidatorTest {
@Test
void acceptsNestedRandomGemsWithOneTerminalType() {
Map<String, GemDefinition> gems = gems(
gem("item", GemType.ITEM_GEM),
random("nested", new GemDefinition.WeightedGem("item", 1)),
random("root", new GemDefinition.WeightedGem("nested", 1)));
assertTrue(RandomGemValidator.validate(gems).isEmpty());
}
@Test
void rejectsCyclesUnknownReferencesAndMixedTypes() {
Map<String, GemDefinition> gems = gems(
gem("item", GemType.ITEM_GEM), gem("player", GemType.PLAYER_GEM),
random("mixed", new GemDefinition.WeightedGem("item", 1), new GemDefinition.WeightedGem("player", 1)),
random("unknown", new GemDefinition.WeightedGem("missing", 1)),
random("cycle-a", new GemDefinition.WeightedGem("cycle-b", 1)),
random("cycle-b", new GemDefinition.WeightedGem("cycle-a", 1)));
Map<String, String> errors = RandomGemValidator.validate(gems);
assertEquals(Set.of("mixed", "unknown", "cycle-a", "cycle-b"), errors.keySet());
}
private Map<String, GemDefinition> gems(GemDefinition... definitions) {
Map<String, GemDefinition> result = new LinkedHashMap<>();
for (GemDefinition definition : definitions) result.put(definition.name(), definition);
return result;
}
private GemDefinition random(String name, GemDefinition.WeightedGem... targets) {
return gem(name, GemType.RANDOM_GEM, List.of(targets));
}
private GemDefinition gem(String name, GemType type) {
return gem(name, type, List.of());
}
private GemDefinition gem(String name, GemType type, List<GemDefinition.WeightedGem> targets) {
return new GemDefinition(name, name, type, Material.STONE, name, List.of(), null, null, null,
null, false, Set.of(), List.of(), 0, "100", "0", "0", null, false,
false, 0, null, false, targets, List.of(), List.of(), null, null, null, null, null, null);
}
}
@@ -0,0 +1,86 @@
package cn.infstar.magicgem.migration;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
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 importsOriginalMagicGemDirectoryIntoReforgedTarget() throws Exception {
Path plugins = Files.createDirectory(temporaryDirectory.resolve("plugins"));
Path source = Files.createDirectories(plugins.resolve("MagicGem/gems"));
Path target = plugins.resolve("MagicGem-Reforged");
Files.writeString(source.resolve("Custom.yml"), "Custom:\n Name: Imported\n");
Files.writeString(source.getParent().resolve("config.yml"), "language: en_US\nbackpack: true\n");
LegacyDirectoryImporter.ImportResult result = importer(plugins, target).importIfPresent();
assertTrue(Files.isRegularFile(target.resolve("gems/Custom.yml")));
assertTrue(Files.isRegularFile(target.resolve("config.yml")));
assertEquals("Custom:\n Name: Imported\n", Files.readString(target.resolve("gems/Custom.yml")));
assertEquals("language: en_US\nbackpack: true\n", Files.readString(target.resolve("config.yml")));
assertTrue(Files.isDirectory(source.getParent()));
assertTrue(result.copiedFiles() >= 2);
assertEquals(0, result.conflictFiles());
assertFalse(result.alreadyMigrated());
}
@Test
void preservesTargetFilesAndBacksUpConflicts() throws Exception {
Path plugins = Files.createDirectory(temporaryDirectory.resolve("plugins"));
Path source = Files.createDirectories(plugins.resolve("MagicGem/gems"));
Path target = Files.createDirectories(plugins.resolve("MagicGem-Reforged/gems"));
Files.writeString(source.resolve("Shared.yml"), "Shared:\n Name: Original\n");
Files.writeString(target.resolve("Shared.yml"), "Shared:\n Name: Reforged\n");
LegacyDirectoryImporter.ImportResult result = importer(plugins, target.getParent()).importIfPresent();
assertEquals("Shared:\n Name: Reforged\n", Files.readString(target.resolve("Shared.yml")));
assertEquals("Shared:\n Name: Original\n",
Files.readString(target.getParent().resolve("migration-backup/MagicGem/gems/Shared.yml")));
assertEquals(1, result.conflictFiles());
}
@Test
void markerMakesMigrationIdempotent() throws Exception {
Path plugins = Files.createDirectory(temporaryDirectory.resolve("plugins"));
Path source = Files.createDirectories(plugins.resolve("MagicGem"));
Path target = plugins.resolve("MagicGem-Reforged");
Files.writeString(source.resolve("config.yml"), "language: zh_CN\n");
LegacyDirectoryImporter importer = importer(plugins, target);
importer.importIfPresent();
LegacyDirectoryImporter.ImportResult repeated = importer.importIfPresent();
assertTrue(repeated.alreadyMigrated());
assertEquals(0, repeated.copiedFiles());
assertEquals(0, repeated.conflictFiles());
}
@Test
void missingOriginalDirectoryDoesNothing() throws Exception {
Path plugins = Files.createDirectory(temporaryDirectory.resolve("plugins"));
Path target = plugins.resolve("MagicGem-Reforged");
LegacyDirectoryImporter.ImportResult result = importer(plugins, target).importIfPresent();
assertEquals(0, result.copiedFiles());
assertEquals(0, result.conflictFiles());
assertFalse(result.alreadyMigrated());
assertFalse(Files.exists(target));
}
private LegacyDirectoryImporter importer(Path plugins, Path target) {
return new LegacyDirectoryImporter(plugins, target, Logger.getLogger("migration-test"));
}
}
@@ -0,0 +1,23 @@
package cn.infstar.magicgem.reward;
import org.junit.jupiter.api.Test;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.assertEquals;
class RewardContextTest {
@Test
void evaluatesEachDynamicRewardValueOnce() {
RewardContext context = new RewardContext(null, null, null, false);
Object reward = new Object();
AtomicInteger evaluations = new AtomicInteger();
assertEquals(7.5, context.evaluatedNumber(reward, () -> {
evaluations.incrementAndGet();
return 7.5;
}));
assertEquals(7.5, context.evaluatedNumber(reward, () -> 99));
assertEquals(1, evaluations.get());
}
}
@@ -0,0 +1,97 @@
package cn.infstar.magicgem.skill;
import cn.infstar.magicgem.dsl.Directive;
import cn.infstar.magicgem.dsl.DirectiveParser;
import cn.infstar.magicgem.reward.RewardEngine;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class BundledConfigurationTest {
private final DirectiveParser parser = new DirectiveParser();
@Test
void pluginDescriptorUsesReforgedEntryPointAndKeepsCompatibility() {
YamlConfiguration descriptor = YamlConfiguration.loadConfiguration(new File("src/main/resources/plugin.yml"));
assertTrue(descriptor.getString("name").equalsIgnoreCase("MagicGem-Reforged"));
assertTrue(descriptor.getString("main").equals("cn.infstar.magicgem.MagicGem"));
assertNotNull(descriptor.getConfigurationSection("commands.mgem"));
assertTrue(descriptor.getStringList("commands.mgem.aliases").isEmpty());
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
void everyBundledGemRewardAndReferenceLoads() throws Exception {
Path directory = Path.of("src/main/resources/gems");
Set<String> names = new LinkedHashSet<>();
try (var files = Files.list(directory)) {
for (Path file : files.filter(path -> path.toString().endsWith(".yml")).toList()) {
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file.toFile());
for (String key : yaml.getKeys(false)) {
ConfigurationSection section = yaml.getConfigurationSection(key);
if (section == null) continue;
names.add(section.getString("Name", key).toLowerCase(Locale.ROOT));
for (String source : section.getStringList("Rewards")) {
Directive reward = parser.parse(source);
assertTrue(RewardEngine.supports(reward.type()), file + "/" + key + ": " + source);
}
for (String source : section.getStringList("Conditions")) assertFalse(parser.parse(source).type().isBlank());
}
}
}
try (var files = Files.list(directory)) {
for (Path file : files.filter(path -> path.toString().endsWith(".yml")).toList()) {
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file.toFile());
for (String key : yaml.getKeys(false)) {
ConfigurationSection random = yaml.getConfigurationSection(key + ".Gems");
if (random == null) continue;
for (String target : random.getKeys(false))
assertTrue(names.contains(target.toLowerCase(Locale.ROOT)), file + "/" + key + " -> " + target);
}
}
}
}
@Test
void everyBundledSkillActionParses() throws Exception {
SkillRegistry registry = new SkillRegistry(null, parser);
try (var files = Files.list(Path.of("src/main/resources/skills"))) {
for (Path path : files.filter(file -> file.toString().endsWith(".yml")).toList()) {
File file = path.toFile();
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
for (String key : yaml.getKeys(false)) {
if (key.equalsIgnoreCase("depend")) continue;
ConfigurationSection section = yaml.getConfigurationSection(key);
assertNotNull(section, file + "/" + key);
for (String source : section.getStringList("Skills")) {
SkillAction action = registry.parseAction(source);
assertFalse(action.mechanic().type().isBlank(), file + "/" + key + ": " + source);
if (action.mechanic().type().equalsIgnoreCase("Reward")
|| action.mechanic().type().equalsIgnoreCase("RewardSwitch")) {
String nested = action.mechanic().positional().isEmpty()
? action.mechanic().argument("", "reward", "r")
: action.mechanic().positional().getFirst();
assertTrue(RewardEngine.supports(parser.parse(nested).type()), file + "/" + key + ": " + source);
}
}
}
}
}
}
}
@@ -0,0 +1,35 @@
package cn.infstar.magicgem.skill;
import cn.infstar.magicgem.dsl.DirectiveParser;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class SkillRegistryTest {
private final SkillRegistry registry = new SkillRegistry(null, new DirectiveParser());
@Test
void preservesMythicMechanicAndTargeterWithSpaces() {
String line = "message{m=\"Player <mob.name> used a skill\"} @PlayersInRadius{r=20;targetself=true} ~onUse";
SkillAction action = registry.parseAction(line);
assertEquals("message", action.mechanic().type());
assertEquals("Player <mob.name> used a skill", action.mechanic().argument("", "m").replace("\"", ""));
assertEquals("message{m=\"Player <mob.name> used a skill\"} @PlayersInRadius{r=20;targetself=true}",
action.mechanicLine());
assertEquals(TriggerType.USE, action.trigger());
assertEquals("playersinradius", action.target());
}
@Test
void separatesTriggerFiltersAndWorldLimits() {
SkillAction action = registry.parseAction(
"PotionBuff{type=SPEED;level=$LORE:Speed:1$} ~onHit:ARROW;TRIDENT @Self $world:3");
assertEquals("PotionBuff{type=SPEED;level=$LORE:Speed:1$} @Self", action.mechanicLine());
assertEquals(TriggerType.HIT, action.trigger());
assertEquals(java.util.List.of("ARROW", "TRIDENT"), action.triggerFilters());
assertEquals(java.util.Map.of("world", 3), action.worldLimits());
}
}