256 lines
12 KiB
Java
256 lines
12 KiB
Java
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));
|
|
}
|
|
}
|