Files
dmg 65e3af7c78
CI / validate (push) Successful in 7m15s
Release / release (push) Successful in 12m13s
feat(rcon): add vanilla-safe custom gear builder
2026-09-17 23:11:01 -04:00

111 lines
8.7 KiB
Java

import java.nio.file.*;
import java.util.*;
import java.util.stream.Stream;
import com.google.gson.*;
import com.mojang.brigadier.StringReader;
import net.minecraft.SharedConstants;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.item.ItemParser;
import net.minecraft.core.*;
import net.minecraft.core.registries.*;
import net.minecraft.resources.*;
import net.minecraft.server.*;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.ServerPacksSource;
import net.minecraft.server.packs.resources.MultiPackResourceManager;
import net.minecraft.server.permissions.LevelBasedPermissionSet;
import net.minecraft.tags.TagLoader;
import net.minecraft.util.Util;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.level.*;
import org.bukkit.craftbukkit.CraftRegistry;
/** Offline verification only: loads cached vanilla registries, never starts a server or sends commands. */
public class VerifyGear {
public static void main(String[] args) throws Exception {
SharedConstants.tryDetectVersion();
if (!SharedConstants.getCurrentVersion().name().equals("26.2")) throw new AssertionError("Expected Minecraft 26.2");
Bootstrap.bootStrap();
var flags = FeatureFlags.VANILLA_SET;
var packs = ServerPacksSource.createVanillaTrustedRepository();
MinecraftServer.configurePackRepository(packs, new WorldDataConfiguration(new DataPackConfig(
FeatureFlags.REGISTRY.toNames(flags).stream().map(Identifier::getPath).toList(), List.of()), flags), true, false);
try (var resources = new MultiPackResourceManager(PackType.SERVER_DATA, packs.openAllSelected())) {
LayeredRegistryAccess<RegistryLayer> layers = RegistryLayer.createRegistryAccess();
List<Registry.PendingTags<?>> tags = TagLoader.loadTagsForExistingRegistries(resources, layers.getLayer(RegistryLayer.STATIC));
List<HolderLookup.RegistryLookup<?>> lookups = TagLoader.buildUpdatedLookups(layers.getAccessForLoading(RegistryLayer.WORLDGEN), tags);
RegistryAccess.Frozen worldgen = RegistryDataLoader.load(resources, lookups, RegistryDataLoader.WORLDGEN_REGISTRIES, Util.backgroundExecutor()).join();
layers = layers.replaceFrom(RegistryLayer.WORLDGEN, worldgen);
RegistryAccess.Frozen dimensions = RegistryDataLoader.load(resources, Stream.concat(lookups.stream(), worldgen.listRegistries()).toList(), RegistryDataLoader.DIMENSION_REGISTRIES, Util.backgroundExecutor()).join();
layers = layers.replaceFrom(RegistryLayer.DIMENSIONS, dimensions);
Class.forName(org.bukkit.Registry.class.getName());
var datapack = ReloadableServerResources.loadResources(resources, layers, tags, flags,
Commands.CommandSelection.DEDICATED, LevelBasedPermissionSet.ALL_PERMISSIONS, Util.backgroundExecutor(), Runnable::run).join();
datapack.updateComponentsAndStaticRegistryTags();
var registries = layers.compositeAccess().freeze();
CraftRegistry.setMinecraftRegistry(registries);
var items = BuiltInRegistries.ITEM.keySet().stream().filter(id -> id.getNamespace().equals("minecraft") &&
(id.getPath().matches("(wooden|stone|copper|iron|golden|diamond|netherite)_(sword|axe|pickaxe|shovel|hoe|spear)") ||
id.getPath().matches("(leather|copper|chainmail|iron|golden|diamond|netherite)_(helmet|chestplate|leggings|boots)") ||
Set.of("bow", "crossbow", "trident", "mace", "fishing_rod", "shears", "shield", "turtle_helmet").contains(id.getPath())))
.sorted(Comparator.comparing(Identifier::toString)).toList();
var enchantments = registries.lookupOrThrow(Registries.ENCHANTMENT);
var catalog = new JsonObject();
catalog.addProperty("version", "26.2"); catalog.addProperty("purpurBuild", 2618);
var itemList = new JsonArray(); items.forEach(id -> itemList.add(id.toString())); catalog.add("items", itemList);
var enchantmentList = new JsonArray();
enchantments.listElements().sorted(Comparator.comparing(h -> h.key().identifier().toString())).forEach(holder -> {
var value = holder.value(); var entry = new JsonObject();
entry.addProperty("id", holder.key().identifier().toString()); entry.addProperty("maxLevel", value.getMaxLevel());
var supported = new JsonArray(); items.stream().filter(id -> value.isSupportedItem(new net.minecraft.world.item.ItemStack(BuiltInRegistries.ITEM.getValue(id)))).forEach(id -> supported.add(id.toString()));
entry.add("items", supported);
var conflicts = new JsonArray(); enchantments.listElements().filter(other -> !Enchantment.areCompatible(holder, other)).map(h -> h.key().identifier().toString()).sorted().forEach(conflicts::add);
entry.add("conflicts", conflicts); enchantmentList.add(entry);
});
catalog.add("enchantments", enchantmentList);
Files.writeString(Path.of(args[0]), new GsonBuilder().setPrettyPrinting().create().toJson(catalog) + "\n");
if (args.length > 1) {
var parser = new ItemParser(registries);
var wrapper = new org.bukkit.craftbukkit.command.VanillaCommandWrapper(datapack.getCommands().getDispatcher().getRoot().getChild("give"));
var toDispatcher = wrapper.getClass().getDeclaredMethod("toDispatcher", String[].class, String.class);
toDispatcher.setAccessible(true);
var cases = JsonParser.parseString(Files.readString(Path.of(args[1]))).getAsJsonArray();
for (var element : cases) {
var test = element.getAsJsonObject(); var command = test.get("command").getAsString();
boolean valid = false;
try {
// Bukkit maps minecraft:give to this vanilla wrapper; use its real argument
// reconstruction rather than inventing the command dispatched to Brigadier.
if (!command.startsWith("minecraft:give ")) throw new AssertionError("Expected literal minecraft:give");
var arguments = command.substring("minecraft:give ".length()).split(" ");
var dispatched = (String) toDispatcher.invoke(wrapper, arguments, wrapper.getName());
var reader = new StringReader(dispatched);
var parsed = datapack.getCommands().getDispatcher().parse(reader, Commands.createCompilationContext(LevelBasedPermissionSet.ALL_PERMISSIONS));
Commands.validateParseResults(parsed);
if (parsed.getContext().getCommand() == null || parsed.getReader().canRead()) throw new AssertionError("Incomplete give parse");
// Decode components into an actual ItemStack, not just a syntactic SNBT parse.
var itemReader = new StringReader(command.substring(command.indexOf(' ', command.indexOf(' ') + 1) + 1));
var item = parser.parse(itemReader).createItemStack(1);
if (item.getCount() != 1) throw new AssertionError("Count mismatch");
if (test.has("name") && !item.getHoverName().getString().equals(test.get("name").getAsString())) throw new AssertionError("Plain text name mismatch");
if (test.has("lore")) {
var lore = item.get(net.minecraft.core.component.DataComponents.LORE);
var actual = new JsonArray(); if (lore != null) lore.lines().forEach(line -> actual.add(line.getString()));
if (!actual.equals(test.get("lore"))) throw new AssertionError("Lore mismatch: " + actual);
}
if (test.has("enchantments")) {
var actual = new JsonObject(); item.getEnchantments().entrySet().forEach(e -> actual.addProperty(e.getKey().unwrapKey().orElseThrow().identifier().toString(), e.getIntValue()));
if (!actual.equals(test.get("enchantments"))) throw new AssertionError("Enchantment mismatch: " + actual);
}
valid = true;
} catch (com.mojang.brigadier.exceptions.CommandSyntaxException expected) { /* invalid fixture */ }
if (valid != test.get("valid").getAsBoolean()) throw new AssertionError("Unexpected grammar result: " + command);
}
System.out.println("Verified " + cases.size() + " give grammar/component cases against Minecraft 26.2 / Purpur 2618");
}
}
System.exit(0);
}
}