Applied-Energistics-2-tiler.../src/main/java/appeng/recipes/RecipeHandler.java

663 lines
15 KiB
Java
Raw Normal View History

2014-11-14 12:02:52 +01:00
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Applied Energistics 2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.recipes;
import java.io.BufferedReader;
2014-03-05 20:34:56 +01:00
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;
2014-03-05 20:34:56 +01:00
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import net.minecraft.item.ItemStack;
import cpw.mods.fml.common.LoaderState;
import cpw.mods.fml.common.registry.GameRegistry;
import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier;
import com.google.common.collect.HashMultimap;
2014-02-20 00:33:36 +01:00
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;
2014-03-05 20:34:56 +01:00
import appeng.core.AEConfig;
import appeng.core.AELog;
import appeng.core.AppEng;
2014-03-05 20:34:56 +01:00
import appeng.core.features.AEFeature;
import appeng.items.materials.ItemMultiMaterial;
2014-03-13 05:50:26 +01:00
import appeng.items.misc.ItemCrystalSeed;
import appeng.items.parts.ItemMultiPart;
import appeng.recipes.handlers.IWebsiteSerializer;
import appeng.recipes.handlers.OreRegistration;
2014-07-26 04:52:21 +02:00
public class RecipeHandler implements IRecipeHandler
{
final public List<String> tokens = new LinkedList<String>();
final RecipeData data;
public RecipeHandler() {
data = new RecipeData();
}
RecipeHandler(RecipeHandler parent) {
data = parent.data;
}
2014-02-20 00:33:36 +01:00
private void addCrafting(ICraftHandler ch)
{
data.Handlers.add( ch );
}
2014-09-21 02:29:41 +02:00
public List<IWebsiteSerializer> findRecipe(ItemStack output)
2014-03-05 20:34:56 +01:00
{
2014-09-21 02:29:41 +02:00
List<IWebsiteSerializer> out = new LinkedList<IWebsiteSerializer>();
2014-03-06 06:52:25 +01:00
for (ICraftHandler ch : data.Handlers)
2014-03-05 20:34:56 +01:00
{
try
{
2014-09-21 02:29:41 +02:00
if ( ch instanceof IWebsiteSerializer && ((IWebsiteSerializer) ch).canCraft( output ) )
2014-03-05 20:34:56 +01:00
{
2014-09-21 02:29:41 +02:00
out.add( (IWebsiteSerializer) ch );
2014-03-05 20:34:56 +01:00
}
}
2014-03-06 06:52:25 +01:00
catch (Throwable t)
2014-03-05 20:34:56 +01:00
{
2014-03-06 06:52:25 +01:00
AELog.error( t );
2014-03-05 20:34:56 +01:00
}
}
2014-03-06 06:52:25 +01:00
2014-03-05 20:34:56 +01:00
return out;
}
2014-03-06 06:52:25 +01:00
@Override
public void injectRecipes()
{
2014-07-26 04:52:21 +02:00
if ( cpw.mods.fml.common.Loader.instance().hasReachedState( LoaderState.POSTINITIALIZATION ) )
throw new RuntimeException( "Recipes must now be loaded in Init." );
HashMap<Class, Integer> processed = new HashMap<Class, Integer>();
try
{
2014-02-20 00:33:36 +01:00
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)
{
2014-09-21 01:43:55 +02:00
AELog.warning( "Unable to register a recipe: " + e.getMessage() );
if ( data.exceptions )
AELog.error( e );
if ( data.crash )
throw e;
}
catch (MissingIngredientError e)
{
2014-11-04 12:32:33 +01:00
if ( data.errorOnMissing )
{
2014-09-21 01:43:55 +02:00
AELog.warning( "Unable to register 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<Class, Integer> e : processed.entrySet())
{
AELog.info( "Recipes Loading: " + e.getKey().getSimpleName() + ": " + e.getValue() + " loaded." );
}
2014-03-06 06:52:25 +01:00
if ( AEConfig.instance.isFeatureEnabled( AEFeature.WebsiteRecipes ) )
2014-03-05 20:34:56 +01:00
{
2014-03-06 06:52:25 +01:00
try
{
ZipOutputStream out = new ZipOutputStream( new FileOutputStream( "recipes.zip" ) );
2014-09-21 02:29:41 +02:00
HashMultimap<String, IWebsiteSerializer> combined = HashMultimap.create();
2014-07-26 04:52:21 +02:00
2014-03-06 06:52:25 +01:00
for (String s : data.knownItem)
2014-03-05 20:34:56 +01:00
{
2014-07-26 04:52:21 +02:00
try
{
2014-03-05 20:34:56 +01:00
Ingredient i = new Ingredient( this, s, 1 );
2014-07-26 04:52:21 +02:00
2014-03-06 06:52:25 +01:00
for (ItemStack is : i.getItemStackSet())
2014-03-05 20:34:56 +01:00
{
2014-07-15 19:02:12 +02:00
String realName = getName( is );
2014-09-21 02:29:41 +02:00
List<IWebsiteSerializer> recipes = findRecipe( is );
2014-03-05 20:34:56 +01:00
if ( !recipes.isEmpty() )
2014-07-26 04:52:21 +02:00
combined.putAll( realName, recipes );
2014-03-06 06:52:25 +01:00
}
2014-07-26 04:52:21 +02:00
}
catch (RecipeError ignored)
2014-07-26 04:52:21 +02:00
{
}
catch (MissedIngredientSet ignored)
2014-07-26 04:52:21 +02:00
{
}
catch (RegistrationError ignored)
2014-07-26 04:52:21 +02:00
{
}
catch (MissingIngredientError ignored)
2014-07-26 04:52:21 +02:00
{
2014-03-05 20:34:56 +01:00
}
2014-07-15 19:02:12 +02:00
}
2014-07-26 04:52:21 +02:00
for (String realName : combined.keySet())
2014-07-15 19:02:12 +02:00
{
int offset = 0;
2014-07-26 04:52:21 +02:00
2014-09-21 02:29:41 +02:00
for (IWebsiteSerializer ws : combined.get( realName ))
2014-03-05 20:34:56 +01:00
{
2014-07-15 19:02:12 +02:00
String rew = ws.getPattern( this );
if ( rew != null && rew.length() > 0 )
{
out.putNextEntry( new ZipEntry( realName + '_' + offset + ".txt" ) );
2014-07-15 19:02:12 +02:00
offset++;
out.write( rew.getBytes() );
}
2014-07-26 04:52:21 +02:00
}
2014-03-05 20:34:56 +01:00
}
2014-03-06 06:52:25 +01:00
2014-03-05 20:34:56 +01:00
out.close();
2014-03-06 06:52:25 +01:00
}
catch (FileNotFoundException e1)
{
AELog.error( e1 );
}
catch (IOException e1)
{
AELog.error( e1 );
2014-03-05 20:34:56 +01:00
}
2014-03-06 06:52:25 +01:00
2014-03-05 20:34:56 +01:00
}
}
2014-03-06 06:52:25 +01:00
public String getName(IIngredient i)
2014-03-05 20:34:56 +01:00
{
try
{
for (ItemStack is : i.getItemStackSet())
{
try
{
return getName( is );
}
catch (RecipeError ignored)
{
}
}
2014-03-05 20:34:56 +01:00
}
2014-03-06 06:52:25 +01:00
catch (Throwable t)
2014-03-05 20:34:56 +01:00
{
t.printStackTrace();
2014-03-05 20:34:56 +01:00
// :P
}
2014-03-06 06:52:25 +01:00
return i.getNameSpace() + ':' + i.getItemName();
2014-03-05 20:34:56 +01:00
}
2014-03-06 06:52:25 +01:00
public String getName(ItemStack is) throws RecipeError
2014-03-06 06:52:25 +01:00
{
UniqueIdentifier id = GameRegistry.findUniqueIdentifierFor( is.getItem() );
String realName = id.modId + ':' + id.name;
2014-03-06 06:52:25 +01:00
if ( !id.modId.equals( AppEng.MOD_ID ) && !id.modId.equals( "minecraft" ) )
throw new RecipeError( "Not applicable for website" );
2014-03-13 05:50:26 +01:00
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:
}
}
2014-07-12 21:27:20 +02:00
else if ( is.getItem() == AEApi.instance().blocks().blockCraftingStorage1k.item() )
{
switch (is.getItemDamage())
{
case 1:
realName += "4k";
break;
case 2:
realName += "16k";
break;
case 3:
realName += "64k";
break;
default:
}
}
else if ( is.getItem() == AEApi.instance().blocks().blockCraftingUnit.item() )
{
switch (is.getItemDamage())
{
case 1:
2014-07-15 19:02:12 +02:00
realName = realName.replace( "Unit", "Accelerator" );
2014-07-12 21:27:20 +02:00
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 ItemMultiMaterial )
2014-07-12 21:27:20 +02:00
{
realName = realName.replace( "ItemMultiMaterial", "ItemMaterial" );
realName += '.' + ((ItemMultiMaterial) is.getItem()).getTypeByStack( is ).name();
2014-07-12 21:27:20 +02:00
}
else if ( is.getItem() instanceof ItemMultiPart )
2014-07-12 21:27:20 +02:00
{
realName = realName.replace( "ItemMultiPart", "ItemPart" );
realName += '.' + ((ItemMultiPart) is.getItem()).getTypeByStack( is ).name();
2014-07-12 21:27:20 +02:00
}
2014-03-05 20:34:56 +01:00
else if ( is.getItemDamage() > 0 )
realName += "." + is.getItemDamage();
2014-03-06 06:52:25 +01:00
2014-03-05 20:34:56 +01:00
return realName;
2014-03-06 06:52:25 +01:00
}
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;
}
}
}
if ( token.length() > 0 )
tokens.add( token );
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
{
2014-02-20 00:33:36 +01:00
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<String> pre = tokens.subList( 0, split - 1 );
List<String> post = tokens.subList( split, tokens.size() );
2014-02-20 00:33:36 +01:00
List<List<IIngredient>> 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<String> pre = tokens.subList( 0, split - 1 );
List<String> post = tokens.subList( split, tokens.size() );
2014-02-20 00:33:36 +01:00
List<List<IIngredient>> inputs = parseLines( pre );
if ( inputs.size() == 1 && inputs.get( 0 ).size() > 0 && post.size() == 1 )
{
2014-02-20 00:33:36 +01:00
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<String> pre = tokens.subList( 0, split - 1 );
List<String> post = tokens.subList( split, tokens.size() );
2014-02-20 00:33:36 +01:00
List<List<IIngredient>> inputs = parseLines( pre );
List<List<IIngredient>> 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" )) )
{
2014-11-04 12:32:33 +01:00
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();
}
2014-02-20 00:33:36 +01:00
private List<List<IIngredient>> parseLines(List<String> subList) throws RecipeError
{
2014-02-20 00:33:36 +01:00
List<List<IIngredient>> out = new LinkedList<List<IIngredient>>();
List<IIngredient> cList = new LinkedList<IIngredient>();
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 );
2014-02-20 00:33:36 +01:00
cList = new LinkedList<IIngredient>();
}
else
{
if ( isNumber( v ) )
{
if ( hasQty )
throw new RecipeError( "Qty found with no item." );
hasQty = true;
qty = Integer.parseInt( v );
}
else
{
if ( hasQty )
{
2014-09-21 01:43:30 +02:00
cList.add( findIngredient( v, qty ) );
hasQty = false;
}
else
2014-09-21 01:43:30 +02:00
cList.add( findIngredient( v, 1 ) );
}
}
}
if ( !cList.isEmpty() )
out.add( cList );
return out;
}
2014-09-21 01:43:30 +02:00
private IIngredient findIngredient(String v, int qty) throws RecipeError
{
GroupIngredient gi = data.groups.get( v );
2014-03-06 06:52:25 +01:00
if ( gi != null )
return gi.copy( qty );
2014-03-06 06:52:25 +01:00
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;
}
2014-03-05 20:34:56 +01:00
}