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 cpw.mods.fml.common.registry.GameRegistry; import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier; 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.features.AEFeature; import appeng.items.materials.ItemMaterial; import appeng.items.parts.ItemPart; import appeng.recipes.handlers.IWebsiteSeralizer; import appeng.recipes.handlers.OreRegistration; 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 && 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 { return getName( i.getItemStack() ); } catch (Throwable t ) { // :P } return i.getNameSpace()+":"+i.getItemName(); } public String getName(ItemStack is) { UniqueIdentifier id = GameRegistry.findUniqueIdentifierFor(is.getItem()); String realName = id.modId+":"+id.name; if ( is.getItem() instanceof ItemMaterial ) realName += ":"+( (ItemMaterial)is.getItem() ).getType(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 ); return new Ingredient( this, v, qty ); } 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; } }