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,255 @@
package cn.infstar.magicgem.item;
import cn.infstar.magicgem.gem.GemDefinition;
import cn.infstar.magicgem.gem.GemRegistry;
import cn.infstar.magicgem.migration.LegacyItemMigrator;
import cn.infstar.magicgem.util.PotionTypes;
import cn.infstar.magicgem.util.MaterialResolver;
import de.tr7zw.changeme.nbtapi.NBT;
import org.bukkit.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import com.destroystokyo.paper.profile.ProfileProperty;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.java.JavaPlugin;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
public final class ItemCodec {
private final GemRegistry registry;
private final NamespacedKey gemNameKey;
private final NamespacedKey dataVersionKey;
private final NamespacedKey embeddedKey;
private final NamespacedKey skillsKey;
private final LegacyItemMigrator legacyMigrator;
public ItemCodec(JavaPlugin plugin, GemRegistry registry) {
this.registry = registry;
this.gemNameKey = new NamespacedKey(plugin, "gem_name");
this.dataVersionKey = new NamespacedKey(plugin, "data_version");
this.embeddedKey = new NamespacedKey(plugin, "embedded_gems");
this.skillsKey = new NamespacedKey(plugin, "skills");
this.legacyMigrator = new LegacyItemMigrator(registry);
}
public ItemStack createGem(GemDefinition gem, int amount) {
ItemStack item = createItem(new ItemAppearance(gem.material().name(), amount, gem.display(), gem.tips(),
gem.texture(), gem.color(), gem.effects(), gem.customModelData(), gem.glow()));
ItemMeta meta = item.getItemMeta();
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;
}
public ItemStack createItem(ItemAppearance appearance) {
String materialName = appearance.material() == null || appearance.material().isBlank()
? appearance.texture() == null || appearance.texture().isBlank() ? "STONE" : "PLAYER_HEAD"
: appearance.material();
Material material = MaterialResolver.resolve(materialName);
if (material == null) throw new IllegalArgumentException("未知材料: " + materialName);
int amount = Math.max(1, Math.min(appearance.amount(), material.getMaxStackSize()));
ItemStack item = new ItemStack(material, amount);
ItemMeta meta = item.getItemMeta();
if (appearance.display() != null && !appearance.display().isBlank())
meta.setDisplayName(color(appearance.display()));
if (appearance.lore() != null && !appearance.lore().isEmpty())
meta.setLore(appearance.lore().stream().map(this::color).toList());
if (appearance.customModelData() != null) meta.setCustomModelData(appearance.customModelData());
if (meta instanceof SkullMeta skull && appearance.texture() != null && !appearance.texture().isBlank()) {
var profile = Bukkit.createProfile(UUID.randomUUID());
profile.setProperty(new ProfileProperty("textures", appearance.texture()));
skull.setPlayerProfile(profile);
}
if (meta instanceof PotionMeta potion) {
if (appearance.color() != null) potion.setColor(Color.fromRGB(appearance.color() & 0xFFFFFF));
addPotionEffects(potion, appearance.effects());
}
if (appearance.glow()) {
meta.addEnchant(Enchantment.UNBREAKING, 1, true);
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
}
item.setItemMeta(meta);
return item;
}
private void addPotionEffects(PotionMeta potion, String effects) {
if (effects == null || effects.isBlank()) return;
for (String encoded : effects.split(";")) {
String[] parts = encoded.trim().split(":");
if (parts.length == 0) continue;
PotionEffectType type = PotionTypes.resolve(parts[0]);
if (type == null) continue;
int duration = parts.length > 1 ? parseInt(parts[1], 1) * 20 : 20;
int level = parts.length > 2 ? parseInt(parts[2], 1) : 1;
potion.addCustomEffect(new PotionEffect(type, duration, Math.max(0, level - 1)), true);
}
}
public static Integer parseColor(Object value) {
if (value instanceof Number number) return number.intValue();
if (value == null || value.toString().isBlank()) return null;
return switch (value.toString().toUpperCase()) {
case "WHITE" -> 0xFFFFFF; case "SILVER" -> 0xC0C0C0; case "GRAY" -> 0x808080;
case "BLACK" -> 0x000000; case "RED" -> 0xFF0000; case "MAROON" -> 0x800000;
case "YELLOW" -> 0xFFFF00; case "OLIVE" -> 0x808000; case "LIME" -> 0x00FF00;
case "GREEN" -> 0x008000; case "AQUA" -> 0x00FFFF; case "TEAL" -> 0x008080;
case "BLUE" -> 0x0000FF; case "NAVY" -> 0x000080; case "FUCHSIA" -> 0xFF00FF;
case "PURPLE" -> 0x800080; case "ORANGE" -> 0xFFA500; case "GOLD" -> 0xFFD700;
default -> {
try { yield Integer.decode(value.toString()); } catch (NumberFormatException ignored) { yield null; }
}
};
}
public record ItemAppearance(String material, int amount, String display, List<String> lore,
String texture, Integer color, String effects,
Integer customModelData, boolean glow) { }
private int parseInt(String value, int fallback) {
try { return Integer.parseInt(value); } catch (NumberFormatException ignored) { return fallback; }
}
public Optional<GemDefinition> readGem(ItemStack item, boolean allowLore) {
if (item == null || item.getType().isAir() || !item.hasItemMeta()) return Optional.empty();
ItemMeta meta = item.getItemMeta();
String name = meta.getPersistentDataContainer().get(gemNameKey, PersistentDataType.STRING);
Optional<GemDefinition> direct = registry.find(name);
if (direct.isPresent()) return direct;
LegacyItemMigrator.LegacyData legacy = legacyMigrator.inspect(item);
if (legacy.identity() != null) {
Optional<GemDefinition> migrated = registry.find(legacy.identity());
if (migrated.isPresent()) {
meta.getPersistentDataContainer().set(gemNameKey, PersistentDataType.STRING, migrated.get().name());
meta.getPersistentDataContainer().set(dataVersionKey, PersistentDataType.INTEGER, 1);
item.setItemMeta(meta);
return migrated;
}
}
if (!allowLore || !meta.hasLore()) return Optional.empty();
List<String> lore = meta.getLore();
if (lore == null) return Optional.empty();
for (GemDefinition gem : registry.all()) {
if (gem.loreIdentity() == null) continue;
String expected = strip(gem.loreIdentity());
if (lore.stream().map(this::strip).anyMatch(line -> line.contains(expected))) return Optional.of(gem);
}
return Optional.empty();
}
public Map<String, Integer> embedded(ItemStack item) {
if (item == null || !item.hasItemMeta()) return Map.of();
PersistentDataContainer data = item.getItemMeta().getPersistentDataContainer();
String encoded = data.get(embeddedKey, PersistentDataType.STRING);
Map<String, Integer> result = new LinkedHashMap<>();
if (encoded == null || encoded.isBlank()) {
LegacyItemMigrator.LegacyData legacy = legacyMigrator.inspect(item);
if (!legacy.embedded().isEmpty()) {
writeEmbedded(item, legacy.embedded());
return new LinkedHashMap<>(legacy.embedded());
}
return result;
}
for (String line : encoded.split("\\n")) {
int separator = line.lastIndexOf('=');
if (separator < 0) continue;
try {
result.put(URLDecoder.decode(line.substring(0, separator), StandardCharsets.UTF_8),
Integer.parseInt(line.substring(separator + 1)));
} catch (IllegalArgumentException ignored) {
}
}
return result;
}
public ItemStack setEmbedded(ItemStack item, Map<String, Integer> gems) {
ItemStack result = item.clone();
writeEmbedded(result, gems);
return result;
}
private void writeEmbedded(ItemStack item, Map<String, Integer> gems) {
ItemMeta meta = item.getItemMeta();
StringBuilder encoded = new StringBuilder();
gems.forEach((name, count) -> encoded.append(URLEncoder.encode(name, StandardCharsets.UTF_8))
.append('=').append(count).append('\n'));
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<>();
PersistentDataContainer data = item.getItemMeta().getPersistentDataContainer();
String value = data.get(skillsKey, PersistentDataType.STRING);
if (value == null || value.isBlank()) {
List<String> legacy = new ArrayList<>(legacyMigrator.inspect(item).skills());
if (!legacy.isEmpty()) writeSkills(item, legacy);
return legacy;
}
return new ArrayList<>(List.of(value.split("\\n")));
}
public ItemStack setSkills(ItemStack item, List<String> skills) {
ItemStack result = item.clone();
writeSkills(result, skills);
return result;
}
private void writeSkills(ItemStack item, List<String> skills) {
ItemMeta meta = item.getItemMeta();
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) {
return ChatColor.translateAlternateColorCodes('&', value == null ? "" : value);
}
public String strip(String value) {
return ChatColor.stripColor(color(value));
}
}