package net.t7seven7t.craftfx.item;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MapMaker;
import net.md_5.bungee.api.ChatColor;
import net.t7seven7t.craftfx.CraftFX;
import net.t7seven7t.craftfx.recipe.FXRecipe;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Recipe;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
*
*/
public class ItemRegistry {
/**
* List of all item definitions
*/
private final Set<ItemDefinition> itemDefinitionSet = Collections
.newSetFromMap(new MapMaker().makeMap());
private final Set<FXRecipe> recipeSet = new HashSet<>();
/**
* Registers an item definition into this ItemRegistry. Recipes will be registered with the
* server
*
* @param item ItemDefinition to register
*/
public void register(ItemDefinition item) {
getDefinition(item.getName()).ifPresent(i -> {
CraftFX.log().warning("Item with name '%s' was re-registered.", item.getName());
itemDefinitionSet.remove(i);
});
itemDefinitionSet.add(item);
}
public void addRecipes(ItemDefinition item) {
Iterator<Recipe> it = Bukkit.recipeIterator();
while (it.hasNext()) {
final Recipe r = it.next();
if (item.isSimilar(r.getResult())) {
it.remove();
recipeSet.remove(r);
}
}
item.getRecipes().forEach(Bukkit::addRecipe);
recipeSet.addAll(item.getRecipes());
}
/**
* Checks whether an item definition has been registered.
*
* @param item ItemDefinition to check
* @return true if the definition has already been registered
*/
public boolean contains(ItemDefinition item) {
return itemDefinitionSet.contains(item);
}
/**
* Gets a list containing all the ItemDefinitions that are registered
*
* @return list of all ItemDefinitions
*/
public List<ItemDefinition> getItemDefinitions() {
return ImmutableList.copyOf(itemDefinitionSet);
}
public Collection<FXRecipe> getRecipes() {
return ImmutableSet.copyOf(recipeSet);
}
/**
* Gets a list of recipes used to craft a specific ItemStack
*
* @param item the ItemStack
* @return A list of recipes
*/
public List<FXRecipe> getRecipes(ItemStack item) {
final Optional<ItemDefinition> opt = getDefinition(item);
return opt.map(ItemDefinition::getRecipes).orElse(ImmutableList.of());
}
/**
* Gets the ItemStack from the given collection which matches the specified ItemStack by {@link
* ItemStack#isSimilar(ItemStack)}
*
* @param item the ItemStack to match
* @param collection collection to search
* @return An Optional containing ItemStack if the collection contains a matching ItemStack,
* otherwise Optional.empty()
*/
public Optional<ItemStack> getMatching(ItemStack item, Collection<ItemStack> collection) {
for (ItemStack ingredient : collection) {
if (isSimilar(ingredient, item)) {
return Optional.ofNullable(ingredient);
}
}
return Optional.empty();
}
/**
* Searches for a registered item firstly by the unique key that it was registered with, and
* then by its display name. Searches are not case sensitive and items will match if they start
* with the specified String.
*
* @param name name the item was registered as
* @return An Optional containing ItemDefinition if it exists, otherwise Optional.empty()
*/
public Optional<ItemDefinition> matchDefinition(String name) {
final Optional<ItemDefinition> exact = getDefinition(name);
if (exact.isPresent()) return exact;
final String lower = ChatColor.stripColor(name).toLowerCase();
final String lowerStripped = lower.replaceAll("\\s+", "_");
for (ItemDefinition def : itemDefinitionSet) {
if (def.getName().startsWith(lowerStripped) || def.getName().startsWith(lower)) {
return Optional.of(def);
}
}
for (ItemDefinition def : itemDefinitionSet) {
if (ChatColor.stripColor(def.getDisplayName()).startsWith(lower)) {
return Optional.of(def);
}
}
return Optional.empty();
}
/**
* Searches for an ItemDefinition matching the specified ItemStack
*
* @param item the ItemStack to match
* @return An Optional containing ItemDefinition if found, otherwise Optional.empty()
*/
public Optional<ItemDefinition> getDefinition(ItemStack item) {
for (ItemDefinition def : itemDefinitionSet) {
if (def.isSimilar(item)) {
return Optional.of(def);
}
}
return Optional.empty();
}
/**
* Searches for a registered item firstly by the unique key that it was registered with, and
* then by its display name. Searches are case sensitive.
*
* @param name name the item was registered as
* @return An Optional containing ItemDefinition if it exists, otherwise Optional.empty()
*/
public Optional<ItemDefinition> getDefinition(String name) {
final String lower = ChatColor.stripColor(name).toLowerCase();
final String lowerStripped = lower.replaceAll("\\s+", "_");
for (ItemDefinition def : itemDefinitionSet) {
if (lowerStripped.equals(def.getName()) || lower.equals(def.getName())) {
return Optional.of(def);
}
}
for (ItemDefinition def : itemDefinitionSet) {
if (lower.equalsIgnoreCase(ChatColor.stripColor(def.getDisplayName()))) {
return Optional.of(def);
}
}
return Optional.empty();
}
/**
* Compares two ItemStacks by their type and item meta ignoring amounts and also damage values
* for tools
*/
public boolean isSimilar(ItemStack item1, ItemStack item2) {
if ((item1 == null || item1.getType().equals(Material.AIR))
&& (item2 == null || item2.getType().equals(Material.AIR))) {
return true;
}
if (item1 == null || item2 == null) {
return false;
}
if (item1 == item2) {
return true;
}
try {
final String id1 = CraftFX.instance().getNmsInterface().getCraftFXId(item1);
final String id2 = CraftFX.instance().getNmsInterface().getCraftFXId(item2);
if (id1 != null || id2 != null) {
return id1 != null && id1.equals(id2);
}
} catch (UnsupportedOperationException e) {
// ignore & use display name check instead
}
return item1.getType().equals(item2.getType())
&& item1.hasItemMeta() == item2.hasItemMeta()
// Check durability for tools:
&& (item1.getType().getMaxDurability() <= 0 || item1.getDurability() == item2
.getDurability())
// Check item meta:
&& (!item1.hasItemMeta() || Bukkit.getItemFactory()
.equals(item1.getItemMeta(), item2.getItemMeta()));
}
/**
* Checks whether an Item has the same unique id as a definition. If NBT ids are unsupported
* this method defaults to using the comparison described by {@link #isSimilar(ItemStack,
* ItemStack)}
*
* @param def item definition
* @param item item
* @return true if the item's NBT id matches the item definition's name (ie. they match)
*/
public boolean matchesDefinition(ItemDefinition def, ItemStack item) {
try {
if (item == null || item.getTypeId() == 0) return false;
String id = CraftFX.instance().getNmsInterface().getCraftFXId(item);
return id != null && id.equals(def.getName());
} catch (Exception e) {
return isSimilar(def.getItem(), item);
}
}
}