package tc.oc.pgm.crafting; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; import javax.annotation.Nullable; import com.google.common.collect.ImmutableSet; import org.bukkit.inventory.FurnaceRecipe; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.ShapedRecipe; import org.bukkit.inventory.ShapelessRecipe; import org.jdom2.Attribute; import org.jdom2.Document; import org.jdom2.Element; import tc.oc.pgm.kits.ItemParser; import tc.oc.pgm.map.MapModule; import tc.oc.pgm.map.MapModuleContext; import tc.oc.pgm.map.MapModuleFactory; import tc.oc.pgm.match.Match; import tc.oc.pgm.match.MatchModuleFactory; import tc.oc.pgm.module.ModuleDescription; import tc.oc.pgm.module.ModuleLoadException; import tc.oc.pgm.utils.MaterialPattern; import tc.oc.pgm.utils.XMLUtils; import tc.oc.pgm.xml.InvalidXMLException; @ModuleDescription(name = "Crafting") public class CraftingModule implements MapModule, MatchModuleFactory<CraftingMatchModule> { private final Set<Recipe> customRecipes; private final Set<MaterialPattern> disabledRecipes; public CraftingModule(Set<Recipe> customRecipes, Set<MaterialPattern> disabledRecipes) { this.customRecipes = ImmutableSet.copyOf(customRecipes); this.disabledRecipes = ImmutableSet.copyOf(disabledRecipes); } @Override public CraftingMatchModule createMatchModule(Match match) throws ModuleLoadException { return new CraftingMatchModule(match, customRecipes, disabledRecipes); } public static class Factory extends MapModuleFactory<CraftingModule> { @Override public @Nullable CraftingModule parse(MapModuleContext context, Logger logger, Document doc) throws InvalidXMLException { Set<Recipe> customRecipes = new HashSet<>(); Set<MaterialPattern> disabledRecipes = new HashSet<>(); for(Element elCrafting : doc.getRootElement().getChildren("crafting")) { for(Element elDisable : elCrafting.getChildren("disable")) { disabledRecipes.add(XMLUtils.parseMaterialPattern(elDisable)); } for(Element elRecipe : XMLUtils.getChildren(elCrafting, "shapeless", "shaped", "smelt")) { Recipe recipe; switch(elRecipe.getName()) { case "shapeless": recipe = parseShapelessRecipe(context, elRecipe); break; case "shaped": recipe = parseShapedRecipe(context, elRecipe); break; case "smelt": recipe = parseSmeltingRecipe(context, elRecipe); break; default: throw new IllegalStateException(); } customRecipes.add(recipe); if(XMLUtils.parseBoolean(elRecipe.getAttribute("override"), false)) { // Disable specific material + data disabledRecipes.add(new MaterialPattern(recipe.getResult().getData())); } else if(XMLUtils.parseBoolean(elRecipe.getAttribute("override-all"), false)) { // Disable all of this material disabledRecipes.add(new MaterialPattern(recipe.getResult().getType())); } } } return customRecipes.isEmpty() && disabledRecipes.isEmpty() ? null : new CraftingModule(customRecipes, disabledRecipes); } private ItemStack parseRecipeResult(MapModuleContext context, Element elRecipe) throws InvalidXMLException { return context.needModule(ItemParser.class) .parseItem(XMLUtils.getRequiredUniqueChild(elRecipe, "result"), false) .immutableCopy(); } public Recipe parseShapelessRecipe(MapModuleContext context, Element elRecipe) throws InvalidXMLException { ShapelessRecipe recipe = new ShapelessRecipe(parseRecipeResult(context, elRecipe)); for(Element elIngredient : XMLUtils.getChildren(elRecipe, "ingredient", "i")) { MaterialPattern item = XMLUtils.parseMaterialPattern(elIngredient); int count = XMLUtils.parseNumber(elIngredient.getAttribute("amount"), Integer.class, 1); if(item.dataMatters()) { recipe.addIngredient(count, item.getMaterialData()); } else { recipe.addIngredient(count, item.getMaterial()); } } if(recipe.getIngredientList().isEmpty()) { throw new InvalidXMLException("Crafting recipe must have at least one ingredient", elRecipe); } return recipe; } public Recipe parseShapedRecipe(MapModuleContext context, Element elRecipe) throws InvalidXMLException { ShapedRecipe recipe = new ShapedRecipe(parseRecipeResult(context, elRecipe)); Element elShape = XMLUtils.getRequiredUniqueChild(elRecipe, "shape"); List<String> rows = new ArrayList<>(3); for(Element elRow : elShape.getChildren("row")) { String row = elRow.getTextNormalize(); if(rows.size() >= 3) { throw new InvalidXMLException("Shape must have no more than 3 rows (" + row + ")", elShape); } if(rows.isEmpty()) { if(row.length() > 3) { throw new InvalidXMLException("Shape must have no more than 3 columns (" + row + ")", elShape); } } else if(row.length() != rows.get(0).length()) { throw new InvalidXMLException("All rows must be the same width", elShape); } rows.add(row); } if(rows.isEmpty()) { throw new InvalidXMLException("Shape must have at least one row", elShape); } recipe.shape(rows.toArray(new String[rows.size()])); Set<Character> keys = recipe.getIngredientMap().keySet(); // All shape symbols are present and mapped to null at this point for(Element elIngredient : elRecipe.getChildren("ingredient")) { MaterialPattern item = XMLUtils.parseMaterialPattern(elIngredient); Attribute attrSymbol = XMLUtils.getRequiredAttribute(elIngredient, "symbol"); String symbol = attrSymbol.getValue(); if(symbol.length() != 1) { throw new InvalidXMLException("Ingredient key must be a single character from the recipe shape", attrSymbol); } char key = symbol.charAt(0); if(!keys.contains(key)) { throw new InvalidXMLException("Ingredient key '" + key + "' does not appear in the recipe shape", attrSymbol); } if(item.dataMatters()) { recipe.setIngredient(key, item.getMaterialData()); } else { recipe.setIngredient(key, item.getMaterial()); } } if(recipe.getIngredientMap().isEmpty()) { throw new InvalidXMLException("Crafting recipe must have at least one ingredient", elRecipe); } return recipe; } public Recipe parseSmeltingRecipe(MapModuleContext context, Element elRecipe) throws InvalidXMLException { MaterialPattern ingredient = XMLUtils.parseMaterialPattern(XMLUtils.getRequiredUniqueChild(elRecipe, "ingredient", "i")); ItemStack result = parseRecipeResult(context, elRecipe); if(ingredient.dataMatters()) { return new FurnaceRecipe(result, ingredient.getMaterialData()); } else { return new FurnaceRecipe(result, ingredient.getMaterial()); } } } }