Rename internal CMagicGem implementation
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user