package net.t7seven7t.craftfx.item;
import net.t7seven7t.craftfx.CraftFX;
import net.t7seven7t.craftfx.recipe.RecipeLoader;
import net.t7seven7t.craftfx.trigger.TriggerLoader;
import net.t7seven7t.craftfx.util.MessageUtil;
import net.t7seven7t.util.MaterialDataUtil;
import net.t7seven7t.util.PotionEffectUtil;
import org.bukkit.Color;
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.BookMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.material.MaterialData;
import org.bukkit.potion.PotionEffect;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.AUTHOR;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.DURABILITY;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.ENCHANTMENTS;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.LEATHER_ARMOR;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.LORE;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.MATERIAL;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.NAME;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.NBT;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.OWNER;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.PAGES;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.POTION_EFFECTS;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.RECIPE;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.RECIPES_SECTION;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.TITLE;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.TRIGGER;
import static net.t7seven7t.craftfx.item.ItemLoader.ConfigPath.TRIGGERS_SECTION;
/**
*
*/
public class ItemLoader {
private final RecipeLoader recipeLoader = new RecipeLoader();
private final TriggerLoader triggerLoader = new TriggerLoader();
private final CraftFX fx = CraftFX.instance();
public void loadItems() {
List<ConfigurationSection> roots = getRootConfigurationSections();
List<ItemDefinition> items = new ArrayList<>();
roots.forEach(c -> items.addAll(loadItems(c)));
items.forEach(i -> fx.getItemRegistry().register(i));
int recipes = 0;
for (ItemDefinition i : items) {
try {
postLoad(i);
} catch (Exception e) {
logException(i.getName(), e);
continue;
}
recipes += i.getRecipes().size();
}
CraftFX.log().info("%s items loaded. %s recipes added.", items.size(), recipes);
}
private void logException(String name, Throwable t) {
CraftFX.log().severe("Item '%s' encountered the problem: %s", name, t.getMessage(), t);
}
/**
* Gets a list of ConfigurationSections for all the places where CraftFX may expect to load
* items from
*
* @return List of ConfigurationSections
*/
private List<ConfigurationSection> getRootConfigurationSections() {
final List<ConfigurationSection> ret = new ArrayList<>();
if (CraftFX.plugin().getConfig().contains("items")) {
ret.add(CraftFX.plugin().getConfig().getConfigurationSection("items"));
}
File itemsFolder = new File(CraftFX.plugin().getDataFolder(), "items");
if (!itemsFolder.exists()) {
itemsFolder.mkdir();
return ret;
}
final Pattern p = Pattern.compile("\\.yml$");
for (File file : itemsFolder.listFiles()) {
if (p.matcher(file.getName()).find()) {
try {
ret.add(YamlConfiguration.loadConfiguration(file));
} catch (Exception e) {
CraftFX.log().severe("Caught exception trying to load item file '%s'",
file.getName(), e);
}
}
}
return ret;
}
/**
* Loads a list of ItemDefinitions from a configuration. Recipes and triggers for items have not
* yet been created
*
* @param config config section to load from
* @return a list of ItemDefinitions
*/
private List<ItemDefinition> loadItems(ConfigurationSection config) {
List<ItemDefinition> items = new ArrayList<>();
config.getKeys(false).forEach(k -> {
try {
ConfigurationSection section = config.getConfigurationSection(k);
ItemDefinition item = new ItemDefinition(loadItem(section), section);
if (fx.getItemRegistry().contains(item)) {
throw new Exception("An item is already registered with the name " + k
+ ". (Color codes don't make a unique item. Consider changing the " +
"'name' tag if you want two items with the same display name)");
}
fx.getItemRegistry().register(item);
items.add(item);
} catch (Exception e) {
logException(k, e);
}
});
return items;
}
/**
* Creates an ItemStack from a configuration. More information regarding the format of the
* config can be found in the readme and wiki documentation.
*
* @param config config section to load from
* @return an ItemStack
* @throws Exception any exception thrown while attempting to load the item
*/
public ItemStack loadItem(ConfigurationSection config) throws Exception {
if (!config.contains(MATERIAL)) {
throw new Exception("Config does not contain material path '" + MATERIAL + "'");
}
MaterialData data = MaterialDataUtil.getMaterialData(config.getString(MATERIAL));
if (data == null) {
throw new Exception("Material '" + config.getString(MATERIAL) + "' is invalid.");
}
ItemStack item = data.toItemStack(1);
if (!fx.getNmsInterface().isValidItem(item)) {
throw new Exception("Material '" + item.getType() + "' is no longer a valid " +
"material in Minecraft 1.8+ and will not show in inventories.");
}
if (config.contains(DURABILITY)) {
item.setDurability((short) config.getInt(DURABILITY));
}
ItemMeta meta = item.getItemMeta();
// Set custom name of item
String name = MessageUtil.color(config.getString(NAME, config.getName()));
meta.setDisplayName(name);
// Add lore
addLore(meta, config);
// Add enchantments
addEnchantments(meta, config);
// Add other meta types if they exist:
// LeatherArmorMeta
if (meta instanceof LeatherArmorMeta) {
addLeatherArmorMeta(meta, config);
}
// BookMeta
if (meta instanceof BookMeta) {
addBookMeta(meta, config);
}
// SkullMeta
if (meta instanceof SkullMeta) {
addSkullMeta(meta, config);
}
// PotionMeta
if (meta instanceof PotionMeta) {
addPotionMeta(meta, config);
}
// Update item meta
item.setItemMeta(meta);
if (config.contains(NBT)) {
String nbt = config.getString(NBT);
if (!nbt.matches("\\{.+\\}")) {
nbt = "{" + nbt + "}";
}
item = fx.getNmsInterface().applyNBT(item, nbt);
}
return item;
}
/**
* Adds potion effects to PotionMeta
*
* @param meta ItemMeta to change
* @param config Config to read from
* @throws Exception if an error occurs
*/
private void addPotionMeta(ItemMeta meta, ConfigurationSection config) throws Exception {
if (config.contains(POTION_EFFECTS)) {
List<PotionEffect> potionEffectList = PotionEffectUtil
.getPotionEffects(config.getStringList(POTION_EFFECTS));
// Add each effect to the meta
potionEffectList.forEach(e -> ((PotionMeta) meta).addCustomEffect(e, true));
}
}
/**
* Adds ownership data to SkullMeta
*
* @param meta ItemMeta to change
* @param config Config to read from
*/
private void addSkullMeta(ItemMeta meta, ConfigurationSection config) {
if (config.contains(OWNER)) {
((SkullMeta) meta).setOwner(config.getString(OWNER));
}
}
/**
* Adds pages, title, and author to BookMeta
*
* @param meta ItemMeta to change
* @param config Config to read from
*/
private void addBookMeta(ItemMeta meta, ConfigurationSection config) {
BookMeta bMeta = (BookMeta) meta;
if (config.contains(PAGES)) {
bMeta.setPages(config.getStringList(PAGES));
}
if (config.contains(AUTHOR)) {
bMeta.setAuthor(config.getString(AUTHOR));
}
if (config.contains(TITLE)) {
bMeta.setTitle(config.getString(TITLE));
}
}
/**
* Adds lore to ItemMeta
*
* @param meta meta ItemMeta to change
* @param config Config to read from
*/
private void addLore(ItemMeta meta, ConfigurationSection config) {
List<String> lore;
if (config.isList(LORE)) {
lore = config.getStringList(LORE);
} else if (config.contains(LORE)) {
lore = Arrays.asList(config.getString(LORE).split("\\|"));
} else {
return;
}
// Replace color codes: as well as set lore
meta.setLore(lore.stream().map(l -> MessageUtil.format(l)).collect(Collectors.toList()));
}
/**
* Adds color to LeatherArmorMeta
*
* @param meta ItemMeta to change
* @param config Config to read from
* @throws Exception if there was an error
*/
private void addLeatherArmorMeta(ItemMeta meta, ConfigurationSection config) throws Exception {
if (!config.contains(LEATHER_ARMOR)) {
return;
}
String color = config.getString(LEATHER_ARMOR);
String[] rgb = color.split(",");
if (rgb.length < 3) {
throw new Exception("Color tag '" + color + "' is invalid");
}
((LeatherArmorMeta) meta).setColor(
Color.fromRGB(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]),
Integer.parseInt(rgb[2])));
}
/**
* Adds enchantments to an ItemMeta from config
*
* @param meta ItemMeta to change
* @param config Config to read from
* @throws Exception If an error occurs
*/
private void addEnchantments(ItemMeta meta, ConfigurationSection config) throws Exception {
if (!config.contains(ENCHANTMENTS)) {
return;
}
List<String> values = config.getStringList(ENCHANTMENTS);
for (String value : values) {
// Split into name and result
String[] split = value.split(":");
String enchantString = split[0].toUpperCase().replaceAll("\\s+", "_")
.replaceAll("\\W", "");
Enchantment enchantment = Enchantment.getByName(enchantString);
if (enchantment == null) {
throw new Exception("Enchantment '" + enchantString + "' is invalid.");
}
meta.addEnchant(enchantment, Integer.parseInt(split[1]), true);
}
}
/**
* Signalises that all item definitions have been created and can be used in recipes, triggers,
* etc
*/
private void postLoad(ItemDefinition item) throws Exception {
loadRecipes(item);
loadTriggers(item);
fx.getItemRegistry().addRecipes(item);
}
/**
* Load recipes for this item from the config but doesn't yet register them to the server
*/
private void loadRecipes(ItemDefinition item) throws Exception {
if (item.config.contains(RECIPE)) {
item.recipeList.add(
recipeLoader.load(item, item.config.getConfigurationSection(RECIPE)));
}
if (item.config.contains(RECIPES_SECTION)) {
for (String key : item.config.getConfigurationSection(RECIPES_SECTION).getKeys(false)) {
item.recipeList.add(recipeLoader.load(item,
item.config.getConfigurationSection(RECIPES_SECTION + "." + key)));
}
}
}
/**
* Load triggers for this item from the config
*/
private void loadTriggers(ItemDefinition item) throws Exception {
if (item.config.contains(TRIGGER)) {
triggerLoader.loadTriggers(item, item.config.getConfigurationSection(TRIGGER));
}
if (item.config.contains(TRIGGERS_SECTION)) {
for (String key : item.config.getConfigurationSection(TRIGGERS_SECTION)
.getKeys(false)) {
triggerLoader.loadTriggers(item,
item.config.getConfigurationSection(TRIGGERS_SECTION + "." + key));
}
}
}
public static final class ConfigPath {
public static final String NAME = "name";
public static final String MATERIAL = "id";
public static final String DURABILITY = "durability";
public static final String LORE = "lore"; // Lore meta
public static final String LEATHER_ARMOR = "color"; // Colorable: Leather Armor
public static final String ENCHANTMENTS = "enchants"; // Enchantment meta
public static final String PAGES = "pages"; // Book meta
public static final String AUTHOR = "author"; //Book meta
public static final String TITLE = "title"; // Book meta
public static final String POTION_EFFECTS = "potion-effects"; // Potion meta
public static final String OWNER = "owner"; // Skull meta
public static final String RECIPE = "recipe";
public static final String RECIPES_SECTION = "recipes";
public static final String TRIGGER = "trigger";
public static final String TRIGGERS_SECTION = "triggers";
public static final String NBT = "nbt";
}
}