147 lines
6.5 KiB
Java
147 lines
6.5 KiB
Java
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<String, Double> variables) {
|
|
return evaluate(source, player, item, variables, false);
|
|
}
|
|
|
|
public double evaluateRoman(String source, Player player, ItemStack item, Map<String, Double> variables) {
|
|
return evaluate(source, player, item, variables, true);
|
|
}
|
|
|
|
private double evaluate(String source, Player player, ItemStack item, Map<String, Double> 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<String, Double> 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<String, Double> 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;
|
|
}
|
|
}
|