package appeng.recipes; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import net.minecraft.item.ItemStack; import appeng.api.AEApi; import appeng.api.exceptions.MissingIngredientError; import appeng.api.exceptions.RecipeError; import appeng.api.exceptions.RegistrationError; import appeng.api.features.IRecipeHandlerRegistry; import appeng.api.recipes.ICraftHandler; import appeng.api.recipes.IIngredient; import appeng.api.recipes.IRecipeHandler; import appeng.api.recipes.IRecipeLoader; import appeng.core.AEConfig; import appeng.core.AELog; import appeng.core.AppEng; import appeng.core.features.AEFeature; import appeng.items.materials.ItemMaterial; import appeng.items.misc.ItemCrystalSeed; import appeng.items.parts.ItemPart; import appeng.recipes.handlers.IWebsiteSeralizer; import appeng.recipes.handlers.OreRegistration; import cpw.mods.fml.common.registry.GameRegistry; import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier; public class RecipeHandler implements IRecipeHandler { final public List tokens = new LinkedList(); final RecipeData data; public RecipeHandler() { data = new RecipeData(); } RecipeHandler(RecipeHandler parent) { data = parent.data; } private void addCrafting(ICraftHandler ch) { data.Handlers.add( ch ); } public List findRecipe(ItemStack output) { List out = new LinkedList(); for (ICraftHandler ch : data.Handlers) { try { if ( ch instanceof IWebsiteSeralizer && ((IWebsiteSeralizer) ch).canCraft( output ) ) { out.add( (IWebsiteSeralizer) ch ); } } catch (Throwable t) { AELog.error( t ); } } return out; } @Override public void registerHandlers() { HashMap processed = new HashMap(); try { for (ICraftHandler ch : data.Handlers) { try { ch.register(); Class clz = ch.getClass(); Integer i = processed.get( clz ); if ( i == null ) processed.put( clz, 1 ); else processed.put( clz, i + 1 ); } catch (RegistrationError e) { AELog.warning( "Unable to regsiter a recipe: " + e.getMessage() ); if ( data.exceptions ) AELog.error( e ); if ( data.crash ) throw e; } catch (MissingIngredientError e) { if ( data.erroronmissing ) { AELog.warning( "Unable to regsiter a recipe:" + e.getMessage() ); if ( data.exceptions ) AELog.error( e ); if ( data.crash ) throw e; } } } } catch (Throwable e) { if ( data.exceptions ) AELog.error( e ); if ( data.crash ) throw new RuntimeException( e ); } for (Entry e : processed.entrySet()) { AELog.info( "Recipes Loading: " + e.getKey().getSimpleName() + ": " + e.getValue() + " loaded." ); } if ( AEConfig.instance.isFeatureEnabled( AEFeature.WebsiteRecipes ) ) { try { ZipOutputStream out = new ZipOutputStream( new FileOutputStream( "recipes.zip" ) ); for (String s : data.knownItem) { try { Ingredient i = new Ingredient( this, s, 1 ); for (ItemStack is : i.getItemStackSet()) { List recipes = findRecipe( is ); if ( !recipes.isEmpty() ) { int offset = 0; String realName = getName( is ); for (IWebsiteSeralizer ws : recipes) { out.putNextEntry( new ZipEntry( realName + "_" + offset + ".txt" ) ); offset++; out.write( ws.getPattern( this ).getBytes() ); } } } } catch (Throwable t) { // :P } } out.close(); } catch (FileNotFoundException e1) { AELog.error( e1 ); } catch (IOException e1) { AELog.error( e1 ); } } } public String getName(IIngredient i) { try { for (ItemStack is : i.getItemStackSet()) { try { return getName( is ); } catch (RecipeError notappicable) { } } } catch (Throwable t) { t.printStackTrace(); // :P } return i.getNameSpace() + ":" + i.getItemName(); } public String getName(ItemStack is) throws RecipeError { UniqueIdentifier id = GameRegistry.findUniqueIdentifierFor( is.getItem() ); String realName = id.modId + ":" + id.name; if ( !id.modId.equals( AppEng.modid ) && !id.modId.equals( "minecraft" ) ) throw new RecipeError( "Not applicable for website" ); if ( is.getItem() == AEApi.instance().items().itemCrystalSeed.item() ) { int dmg = is.getItemDamage(); if ( dmg < ItemCrystalSeed.Nether ) realName += ".Certus"; else if ( dmg < ItemCrystalSeed.Fluix ) realName += ".Nether"; else if ( dmg < ItemCrystalSeed.END ) realName += ".Fluix"; } else if ( is.getItem() == AEApi.instance().blocks().blockSkyStone.item() ) { switch (is.getItemDamage()) { case 1: realName += ".Block"; break; case 2: realName += ".Brick"; break; case 3: realName += ".SmallBrick"; break; default: } } else if ( is.getItem() == AEApi.instance().blocks().blockSkyChest.item() ) { switch (is.getItemDamage()) { case 1: realName += ".Block"; break; default: } } else if ( is.getItem() instanceof ItemMaterial ) realName += "." + ((ItemMaterial) is.getItem()).getTypeByStack( is ).name(); else if ( is.getItem() instanceof ItemPart ) realName += "." + ((ItemPart) is.getItem()).getTypeByStack( is ).name(); else if ( is.getItemDamage() > 0 ) realName += "." + is.getItemDamage(); return realName; } public String alias(String in) { String out = data.aliases.get( in ); if ( out != null ) return out; return in; } @Override public void parseRecipes(IRecipeLoader loader, String path) { try { BufferedReader reader = null; try { reader = loader.getFile( path ); } catch (Exception err) { AELog.warning( "Error Loading Recipe File:" + path ); if ( data.exceptions ) AELog.error( err ); return; } boolean inQuote = false; boolean inComment = false; String token = ""; int line = 0; int val = -1; while ((val = reader.read()) != -1) { char c = (char) val; if ( c == '\n' ) line++; if ( inComment ) { if ( c == '\n' || c == '\r' ) inComment = false; } else if ( inQuote ) { switch (c) { case '"': inQuote = !inQuote; break; default: token = token + c; } } else { switch (c) { case '"': inQuote = !inQuote; break; case ',': if ( token.length() > 0 ) { tokens.add( token ); tokens.add( "," ); } token = ""; break; case '=': processTokens( loader, path, line ); if ( token.length() > 0 ) tokens.add( token ); token = ""; break; case '#': inComment = true; // then add a token if you can... case '\n': case '\t': case '\r': case ' ': if ( token.length() > 0 ) tokens.add( token ); token = ""; break; default: token = token + c; } } } reader.close(); processTokens( loader, path, line ); } catch (Throwable e) { AELog.error( e ); if ( data.crash ) throw new RuntimeException( e ); } } private void processTokens(IRecipeLoader loader, String file, int line) throws RecipeError { try { IRecipeHandlerRegistry cr = AEApi.instance().registries().recipes(); if ( tokens.isEmpty() ) return; int split = tokens.indexOf( "->" ); if ( split != -1 ) { String operation = tokens.remove( 0 ).toLowerCase(); if ( operation.equals( "alias" ) ) { if ( tokens.size() == 3 && tokens.indexOf( "->" ) == 1 ) data.aliases.put( tokens.get( 0 ), tokens.get( 2 ) ); else throw new RecipeError( "Alias must have exactly 1 input and 1 output." ); } else if ( operation.equals( "group" ) ) { List pre = tokens.subList( 0, split - 1 ); List post = tokens.subList( split, tokens.size() ); List> inputs = parseLines( pre ); if ( inputs.size() == 1 && inputs.get( 0 ).size() > 0 && post.size() == 1 ) { data.groups.put( post.get( 0 ), new GroupIngredient( post.get( 0 ), inputs.get( 0 ) ) ); } else throw new RecipeError( "Group must have exactly 1 output, and 1 or more inputs." ); } else if ( operation.equals( "ore" ) ) { List pre = tokens.subList( 0, split - 1 ); List post = tokens.subList( split, tokens.size() ); List> inputs = parseLines( pre ); if ( inputs.size() == 1 && inputs.get( 0 ).size() > 0 && post.size() == 1 ) { ICraftHandler ch = new OreRegistration( inputs.get( 0 ), post.get( 0 ) ); addCrafting( ch ); } else throw new RecipeError( "Group must have exactly 1 output, and 1 or more inputs in a single row." ); } else { List pre = tokens.subList( 0, split - 1 ); List post = tokens.subList( split, tokens.size() ); List> inputs = parseLines( pre ); List> outputs = parseLines( post ); ICraftHandler ch = cr.getCraftHandlerFor( operation ); if ( ch != null ) { ch.setup( inputs, outputs ); addCrafting( ch ); } else throw new RecipeError( "Invalid crafting type: " + operation ); } } else { String operation = tokens.remove( 0 ).toLowerCase(); if ( operation.equals( "exceptions" ) && (tokens.get( 0 ).equals( "true" ) || tokens.get( 0 ).equals( "false" )) ) { if ( tokens.size() == 1 ) { data.exceptions = tokens.get( 0 ).equals( "true" ); } else throw new RecipeError( "exceptions must be true or false explicitly." ); } else if ( operation.equals( "crash" ) && (tokens.get( 0 ).equals( "true" ) || tokens.get( 0 ).equals( "false" )) ) { if ( tokens.size() == 1 ) { data.crash = tokens.get( 0 ).equals( "true" ); } else throw new RecipeError( "crash must be true or false explicitly." ); } else if ( operation.equals( "erroronmissing" ) ) { if ( tokens.size() == 1 && (tokens.get( 0 ).equals( "true" ) || tokens.get( 0 ).equals( "false" )) ) { data.erroronmissing = tokens.get( 0 ).equals( "true" ); } else throw new RecipeError( "erroronmissing must be true or false explicitly." ); } else if ( operation.equals( "import" ) ) { if ( tokens.size() == 1 ) (new RecipeHandler( this )).parseRecipes( loader, tokens.get( 0 ) ); else throw new RecipeError( "Import must have exactly 1 input." ); } else throw new RecipeError( operation + ": " + tokens.toString() + "; recipe without an output." ); } } catch (RecipeError e) { AELog.warning( "Recipe Error '" + e.getMessage() + "' near line:" + line + " in " + file + " with: " + tokens.toString() ); if ( data.exceptions ) AELog.error( e ); if ( data.crash ) throw e; } tokens.clear(); } private List> parseLines(List subList) throws RecipeError { List> out = new LinkedList>(); List cList = new LinkedList(); boolean hasQty = false; int qty = 1; for (String v : subList) { if ( v.equals( "," ) ) { if ( hasQty ) throw new RecipeError( "Qty found with no item." ); if ( !cList.isEmpty() ) out.add( cList ); cList = new LinkedList(); } else { if ( isNumber( v ) ) { if ( hasQty ) throw new RecipeError( "Qty found with no item." ); hasQty = true; qty = Integer.parseInt( v ); } else { if ( hasQty ) { cList.add( findIngrident( v, qty ) ); hasQty = false; } else cList.add( findIngrident( v, 1 ) ); } } } if ( !cList.isEmpty() ) out.add( cList ); return out; } private IIngredient findIngrident(String v, int qty) throws RecipeError { GroupIngredient gi = data.groups.get( v ); if ( gi != null ) return gi.copy( qty ); data.knownItem.add( v ); try { return new Ingredient( this, v, qty ); } catch (MissedIngredientSet grp) { return new IngredientSet( grp.rrs ); } } private boolean isNumber(String v) { if ( v.length() <= 0 ) return false; int l = v.length(); for (int x = 0; x < l; x++) { if ( !Character.isDigit( v.charAt( x ) ) ) return false; } return true; } }