package cn.infstar.magicgem.expression; import cn.infstar.magicgem.integration.PlaceholderBridge; import cn.infstar.magicgem.util.EnchantmentResolver; import de.tr7zw.changeme.nbtapi.NBT; import org.bukkit.ChatColor; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public final class DynamicExpression { private static final Pattern ITEM_VARIABLE = Pattern.compile("\\$(LORE|WORD|NBT|ENCHANT):([^?$]+?)(?:\\?([^$]*))?\\$", Pattern.CASE_INSENSITIVE); private static final Pattern NUMBER = Pattern.compile("[-+]?\\d+(?:\\.\\d+)?"); private final Expression expression; private final PlaceholderBridge placeholders; public DynamicExpression(Expression expression, PlaceholderBridge placeholders) { this.expression = expression; this.placeholders = placeholders; } public double evaluate(String source, Player player, ItemStack item, Map variables) { return evaluate(source, player, item, variables, false); } public double evaluateRoman(String source, Player player, ItemStack item, Map variables) { return evaluate(source, player, item, variables, true); } private double evaluate(String source, Player player, ItemStack item, Map variables, boolean roman) { String resolved = placeholders.resolve(player, source == null ? "0" : source); Matcher matcher = ITEM_VARIABLE.matcher(resolved); StringBuffer buffer = new StringBuffer(); while (matcher.find()) { double fallback = parseNumber(matcher.group(3), 0); double value = resolveItem(matcher.group(1), matcher.group(2), item, fallback, roman); matcher.appendReplacement(buffer, Matcher.quoteReplacement(Double.toString(value))); } matcher.appendTail(buffer); Map normalized = new HashMap<>(); variables.forEach((key, value) -> { normalized.put(key, value); normalized.put(key.toLowerCase(Locale.ROOT), value); }); return expression.evaluate(buffer.toString(), normalized); } public boolean test(String source, Player player, ItemStack item, Map variables) { return Math.abs(evaluate(source, player, item, variables)) > 1.0E-9; } private double resolveItem(String type, String key, ItemStack item, double fallback, boolean roman) { if (item == null || item.getType().isAir()) return fallback; return switch (type.toUpperCase(Locale.ROOT)) { case "LORE" -> lore(item, key, fallback, roman); case "WORD" -> word(item, key, fallback); case "NBT" -> nbt(item, key, fallback); case "ENCHANT" -> enchant(item, key, fallback); default -> fallback; }; } private double word(ItemStack item, String expected, double value) { if (!item.hasItemMeta()) return 0; String cleanExpected = strip(expected); if (item.getItemMeta().hasDisplayName() && strip(item.getItemMeta().getDisplayName()).contains(cleanExpected)) return value; if (item.getItemMeta().hasLore() && item.getItemMeta().getLore() != null) { for (String line : item.getItemMeta().getLore()) if (strip(line).contains(cleanExpected)) return value; } return 0; } private double lore(ItemStack item, String marker, double fallback, boolean roman) { if (!item.hasItemMeta() || !item.getItemMeta().hasLore() || item.getItemMeta().getLore() == null) return fallback; String cleanMarker = strip(marker); for (String line : item.getItemMeta().getLore()) { String clean = strip(line); int index = clean.indexOf(cleanMarker); if (index < 0) continue; Matcher number = NUMBER.matcher(clean.substring(index + cleanMarker.length())); if (number.find()) return parseNumber(number.group(), fallback); if (roman) { Matcher numeral = Pattern.compile("(?i)\\b[IVXLCDM]+\\b") .matcher(clean.substring(index + cleanMarker.length())); if (numeral.find()) return parseRoman(numeral.group(), fallback); } } return fallback; } private double nbt(ItemStack item, String key, double fallback) { try { return NBT.get(item, nbt -> { if (!nbt.hasTag(key)) return fallback; return switch (nbt.getType(key)) { case NBTTagByte -> nbt.getByte(key).doubleValue(); case NBTTagShort -> nbt.getShort(key).doubleValue(); case NBTTagInt -> nbt.getInteger(key).doubleValue(); case NBTTagLong -> nbt.getLong(key).doubleValue(); case NBTTagFloat -> nbt.getFloat(key).doubleValue(); case NBTTagDouble -> nbt.getDouble(key); default -> parseNumber(nbt.getString(key), fallback); }; }); } catch (RuntimeException exception) { return fallback; } } private double enchant(ItemStack item, String key, double fallback) { Enchantment enchantment = EnchantmentResolver.resolve(key); return enchantment == null ? fallback : item.getEnchantmentLevel(enchantment); } private String strip(String value) { return ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', value == null ? "" : value)); } private double parseNumber(String value, double fallback) { try { return value == null || value.isBlank() ? fallback : Double.parseDouble(value); } catch (NumberFormatException ignored) { return fallback; } } private double parseRoman(String value, double fallback) { if (value == null || value.isBlank()) return fallback; int total = 0; int previous = 0; String normalized = value.toUpperCase(Locale.ROOT); for (int index = normalized.length() - 1; index >= 0; index--) { int current = switch (normalized.charAt(index)) { case 'I' -> 1; case 'V' -> 5; case 'X' -> 10; case 'L' -> 50; case 'C' -> 100; case 'D' -> 500; case 'M' -> 1000; default -> 0; }; if (current == 0) return fallback; total += current < previous ? -current : current; previous = current; } return total; } }