Last active
February 17, 2020 21:51
-
-
Save Phoenix616/119a227c1dbbd678940872a38cbb2e4a to your computer and use it in GitHub Desktop.
Utility class to match items with all their properties (no NBT support though, only Bukkit API settings)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# item-matcher: The definition of a matcher (all values can be left out and count as a match-all that way | |
# inverted: Whether or not this match is inverted (match only items that don't match the values) | |
# material: List of materials to match | |
# name: List of strings in name to match (case-insensitive), use the prefix r= to use regex | |
# lore: List of strings in lore to match (case-insensitive), use the prefix r= to use regex | |
# durability: Item durability value, can use comparators, <x, >x, =x, !=x or just equal a single number. An empty string matches all durabilities. Also supports chaining of comparators with a comma. E.g. >5,<20 for between 5 and 20 | |
# unbreakable: Match items with the unbreakable tag | |
# enchantments: List of enchantments to match, can match both all or only certain levels. Can take the same comparators as the durability for the level. | |
# serialized: Serialize the item to YAML and filter it with regex. This is only for advanced users and is less efficient, leave empty to disable. | |
parameter-example: | |
inverted: false | |
material: [] | |
name: [] | |
lore: [] | |
durability: '' | |
unbreakable: true | |
enchantments: [] | |
serialized: '' | |
specific-example: | |
material: | |
- diamond_sword | |
name: | |
- Phoenix616's Sword | |
unbreakable: true | |
enchantments: | |
- damage_all:>9000 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Nietzsche Public License v0.6-m1 | |
* | |
* Copyright 2020 Max Lee aka Phoenix616 ([email protected]) | |
* | |
* Copyright, like God, is dead. Let its corpse serve only to guard against its | |
* resurrection. You may do anything with this work that copyright law would | |
* normally restrict so long as you retain the above notice(s), this license, and | |
* the following misquote and disclaimer of warranty with all redistributed | |
* copies, modified or verbatim. You may also replace this license with the Open | |
* Works License version 0.9.4 (available at the http://owl.apotheon.org website) | |
* or with the MIT License (available at https://moep.tv/licenses/mit.txt). | |
* | |
* Copyright is dead. Copyright remains dead, and we have killed it. How | |
* shall we comfort ourselves, the murderers of all murderers? What was | |
* holiest and mightiest of all that the world of censorship has yet owned has | |
* bled to death under our knives: who will wipe this blood off us? What | |
* water is there for us to clean ourselves? What festivals of atonement, | |
* what sacred games shall we have to invent? Is not the greatness of this | |
* deed too great for us? Must we ourselves not become authors simply to | |
* appear worthy of it? | |
* - apologies to Friedrich Wilhelm Nietzsche | |
* | |
* No warranty is implied by distribution under the terms of this license. | |
*/ | |
import org.bukkit.Material; | |
import org.bukkit.configuration.ConfigurationSection; | |
import org.bukkit.configuration.file.YamlConfiguration; | |
import org.bukkit.enchantments.Enchantment; | |
import org.bukkit.inventory.ItemStack; | |
import org.bukkit.inventory.meta.EnchantmentStorageMeta; | |
import org.bukkit.inventory.meta.ItemMeta; | |
import org.bukkit.plugin.java.JavaPlugin; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.EnumSet; | |
import java.util.HashMap; | |
import java.util.LinkedHashSet; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.logging.Level; | |
import java.util.regex.Pattern; | |
import java.util.regex.PatternSyntaxException; | |
public class ItemMatcher { | |
private final String name; | |
private final double worth; | |
private final List<MatchDefinition> specificDefinitions = new ArrayList<>(); | |
public ItemMatcher(JavaPlugin plugin, String name, ConfigurationSection config) throws IllegalArgumentException { | |
this.name = name; | |
this.worth = config.getDouble("worth"); | |
for (String specificKey : config.getKeys(false)) { | |
if (config.isConfigurationSection(specificKey)) { | |
specificDefinitions.add(new MatchDefinition(plugin, specificKey, config.getConfigurationSection(specificKey))); | |
} | |
} | |
} | |
/** | |
* Get the name of this matcher | |
* @return The name of the matcher as passed in the constructor | |
*/ | |
public String getName() { | |
return name; | |
} | |
/** | |
* How much this item group is worth | |
* @return The worth of the item group | |
*/ | |
public double getWorth() { | |
return worth; | |
} | |
/** | |
* Get the matching definition | |
* @param item The item to match against | |
* @return The matching definition or null if none match. The default one will have a "null" name! | |
*/ | |
public MatchDefinition getMatching(ItemStack item) { | |
for (MatchDefinition definition : specificDefinitions) { | |
if (definition.matches(item)) { | |
return definition; | |
} | |
} | |
return null; | |
} | |
/** | |
* Check if this matcher matches an item, | |
* calls {@link #getMatching(ItemStack)} internally so use that if you need the actual matched definition later on | |
* @param item The item to match against | |
* @return Whether or not this matcher matches it | |
*/ | |
public boolean matches(ItemStack item) { | |
return getMatching(item) != null; | |
} | |
public static class MatchDefinition { | |
private final String name; | |
private final boolean inverted; | |
private final Set<Material> materials = EnumSet.noneOf(Material.class); | |
private final Set<Pattern> names = new LinkedHashSet<>(); | |
private final Set<Pattern> lores = new LinkedHashSet<>(); | |
private final List<NumberComparator> durability = new ArrayList<>(); | |
private final Boolean unbreakable; | |
private final Map<Enchantment, List<NumberComparator>> enchantments = new HashMap<>(); | |
private final Pattern serializedRegex; | |
public MatchDefinition(JavaPlugin plugin, String name, ConfigurationSection config) { | |
this.name = name; | |
inverted = config.getBoolean("inverted"); | |
for (String matStr : config.getStringList("material")) { | |
Material mat = Material.getMaterial(matStr.toUpperCase()); | |
if (mat != null) { | |
materials.add(mat); | |
} else { | |
plugin.getLogger().log(Level.WARNING, matStr + " is not a valid material name!"); | |
} | |
} | |
for (String nameStr : config.getStringList("name")) { | |
try { | |
if (nameStr.startsWith("r=")) { | |
names.add(Pattern.compile("(?i)" + nameStr.substring(2))); | |
} else { | |
names.add(Pattern.compile("(?i)" + Pattern.quote(nameStr))); | |
} | |
} catch (PatternSyntaxException e) { | |
plugin.getLogger().log(Level.WARNING, nameStr + " is not a valid name pattern!"); | |
} | |
} | |
for (String lore : config.getStringList("lore")) { | |
try { | |
if (lore.startsWith("r=")) { | |
lores.add(Pattern.compile("(?i)" + lore.substring(2))); | |
} else { | |
lores.add(Pattern.compile("(?i)" + Pattern.quote(lore))); | |
} | |
} catch (PatternSyntaxException e) { | |
plugin.getLogger().log(Level.WARNING, lore + " is not a valid lore pattern!"); | |
} | |
} | |
if (config.contains("durability")) { | |
for (String def : config.getString("durability").split(",")) { | |
try { | |
durability.add(new NumberComparator(def)); | |
} catch (IllegalArgumentException e) { | |
plugin.getLogger().log(Level.WARNING, def + " is not a valid durability comparator string!"); | |
} | |
} | |
} | |
unbreakable = config.isBoolean("unbreakable") ? config.getBoolean("unbreakable") : null; | |
for (String enchStr : config.getStringList("enchantments")) { | |
Enchantment ench; | |
String levelStr = ""; | |
if (enchStr.contains(":")) { | |
String[] enchStrParts = enchStr.split(":"); | |
if (enchStrParts.length > 1) { | |
enchStr = enchStrParts[0]; | |
levelStr = enchStrParts[1]; | |
} | |
} | |
ench = Enchantment.getByName(enchStr.toUpperCase()); | |
if (ench != null) { | |
List<NumberComparator> comparators = new ArrayList<>(); | |
for (String def : levelStr.split(",")) { | |
try { | |
comparators.add(new NumberComparator(def)); | |
} catch (IllegalArgumentException e) { | |
plugin.getLogger().log(Level.WARNING, def + " is not a valid level comparator string!"); | |
} | |
} | |
enchantments.put(ench, comparators); | |
} else { | |
plugin.getLogger().log(Level.WARNING, enchStr + " is not a valid Enchantment name!"); | |
} | |
} | |
String serializedStr = config.getString("serialized"); | |
Pattern serializedPattern = null; | |
if (serializedStr != null && !serializedStr.isEmpty()) { | |
try { | |
serializedPattern = Pattern.compile(serializedStr); | |
} catch (PatternSyntaxException e) { | |
plugin.getLogger().log(Level.WARNING, serializedStr + " is not a valid serialized regex string! " + e.getMessage()); | |
} | |
} | |
serializedRegex = serializedPattern; | |
} | |
public boolean matches(ItemStack item) { | |
if (item == null) { | |
return false; | |
} | |
boolean matches = !inverted; | |
boolean doesNotMatch = inverted; | |
if (!materials.contains(item.getType())) { | |
return doesNotMatch; | |
} | |
if (!comparatorsMatch(durability, item.getDurability())) { | |
return doesNotMatch; | |
} | |
if (item.hasItemMeta()) { | |
ItemMeta meta = item.getItemMeta(); | |
if (unbreakable != null && unbreakable != meta.isUnbreakable()) { | |
return doesNotMatch; | |
} | |
if (!enchantmentsMatch(item.getEnchantments())) { | |
return doesNotMatch; | |
} | |
if (meta instanceof EnchantmentStorageMeta) { | |
if (!enchantmentsMatch(((EnchantmentStorageMeta) meta).getStoredEnchants())) { | |
return doesNotMatch; | |
} | |
} | |
if (meta.hasDisplayName()) { | |
if (!patternsMatch(names, meta.getDisplayName())) { | |
return doesNotMatch; | |
} | |
} | |
if (meta.hasLore()) { | |
if (!patternsMatch(names, String.join("\n", meta.getLore()))) { | |
return doesNotMatch; | |
} | |
} | |
} | |
if (serializedRegex != null) { | |
YamlConfiguration yaml = new YamlConfiguration(); | |
yaml.set("item", item); | |
String yamlStr = yaml.saveToString(); | |
if (!serializedRegex.matcher(yamlStr).find()) { | |
return doesNotMatch; | |
} | |
} | |
return matches; | |
} | |
private boolean comparatorsMatch(List<NumberComparator> comparators, int i) { | |
for (NumberComparator comparator : comparators) { | |
if (!comparator.matches(i)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
private boolean patternsMatch(Collection<Pattern> patterns, String toMatch) { | |
for (Pattern pattern : patterns) { | |
if (pattern.matcher(toMatch).find()) { | |
return true; | |
} | |
} | |
return false; | |
} | |
private boolean enchantmentsMatch(Map<Enchantment, Integer> enchantments) { | |
if (this.enchantments.isEmpty()) { | |
return true; | |
} | |
for (Map.Entry<Enchantment, Integer> ench : enchantments.entrySet()) { | |
if (this.enchantments.containsKey(ench.getKey()) | |
&& comparatorsMatch(this.enchantments.get(ench.getKey()), ench.getValue())) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* The name of the definition | |
* @return The name of the definition | |
*/ | |
public String getName() { | |
return name; | |
} | |
public static class NumberComparator { | |
private final Type type; | |
private final int number; | |
public NumberComparator(String def) { | |
if (def == null || def.isEmpty() || "*".equals(def)) { | |
type = Type.ANY; | |
number = -1; | |
return; | |
} | |
for (Type t : Type.values()) { | |
if (def.startsWith(t.symbol())) { | |
type = t; | |
try { | |
number = Integer.parseInt(def.substring(t.symbol().length())); | |
} catch (NumberFormatException ignored) { | |
throw new IllegalArgumentException(def + " is not a valid NumberComparator definition!"); | |
} | |
return; | |
} | |
} | |
type = Type.EQUALS; | |
try { | |
number = Integer.parseInt(def); | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException(def + " is not a valid NumberComparator definition!"); | |
} | |
} | |
public boolean matches(int i) { | |
switch (type) { | |
case ANY: | |
return true; | |
case EQUALS: | |
return i == number; | |
case NOT_EQUALS: | |
return i != number; | |
case LESS_THAN: | |
return i < number; | |
case GREATHER_THAN: | |
return i > number; | |
} | |
return false; | |
} | |
public enum Type { | |
ANY("*"), | |
GREATHER_THAN(">"), | |
LESS_THAN("<"), | |
EQUALS("="), | |
NOT_EQUALS("!="); | |
private final String symbol; | |
Type(String symbol) { | |
this.symbol = symbol; | |
} | |
public String symbol() { | |
return symbol; | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment