635 lines
22 KiB
Java
635 lines
22 KiB
Java
/*
|
|
* This file is part of Applied Energistics 2.
|
|
* Copyright (c) 2013 - 2015, 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;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.util.*;
|
|
import java.util.Map.Entry;
|
|
import java.util.zip.ZipEntry;
|
|
import java.util.zip.ZipOutputStream;
|
|
import javax.annotation.Nonnull;
|
|
|
|
import appeng.api.AEApi;
|
|
import appeng.api.definitions.IBlocks;
|
|
import appeng.api.definitions.IDefinitions;
|
|
import appeng.api.definitions.IItems;
|
|
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.ItemMultiMaterial;
|
|
import appeng.items.misc.ItemCrystalSeed;
|
|
import appeng.items.parts.ItemMultiPart;
|
|
import appeng.recipes.handlers.IWebsiteSerializer;
|
|
import appeng.recipes.handlers.OreRegistration;
|
|
import com.google.common.base.Optional;
|
|
import com.google.common.base.Preconditions;
|
|
import com.google.common.collect.HashMultimap;
|
|
import cpw.mods.fml.common.LoaderState;
|
|
import cpw.mods.fml.common.registry.GameRegistry;
|
|
import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier;
|
|
import net.minecraft.item.Item;
|
|
import net.minecraft.item.ItemStack;
|
|
|
|
/**
|
|
* @author AlgorithmX2
|
|
* @author thatsIch
|
|
* @version rv3 - 10.08.2015
|
|
* @since rv0
|
|
*/
|
|
public class RecipeHandler implements IRecipeHandler {
|
|
private final RecipeData data;
|
|
private final List<String> tokens = new LinkedList<String>();
|
|
|
|
public RecipeHandler() {
|
|
this.data = new RecipeData();
|
|
}
|
|
|
|
/**
|
|
* Called recursively from parent
|
|
*
|
|
* @param parent owner of this handler
|
|
*/
|
|
private RecipeHandler(final RecipeHandler parent) {
|
|
Preconditions.checkNotNull(parent);
|
|
this.data = parent.data;
|
|
}
|
|
|
|
private void addCrafting(final ICraftHandler ch) {
|
|
this.data.handlers.add(ch);
|
|
}
|
|
|
|
public String getName(@Nonnull final IIngredient i) {
|
|
try {
|
|
for (final ItemStack is : i.getItemStackSet()) {
|
|
return this.getName(is);
|
|
}
|
|
} catch (final RecipeError ignored) {
|
|
} catch (final Throwable t) {
|
|
t.printStackTrace();
|
|
// :P
|
|
}
|
|
|
|
return i.getNameSpace() + ':' + i.getItemName();
|
|
}
|
|
|
|
private String getName(final ItemStack is) throws RecipeError {
|
|
Preconditions.checkNotNull(is);
|
|
|
|
final UniqueIdentifier id = GameRegistry.findUniqueIdentifierFor(is.getItem());
|
|
String realName = id.modId + ':' + id.name;
|
|
|
|
if (!id.modId.equals(AppEng.MOD_ID) && !id.modId.equals("minecraft")) {
|
|
throw new RecipeError("Not applicable for website");
|
|
}
|
|
|
|
final IDefinitions definitions = AEApi.instance().definitions();
|
|
final IItems items = definitions.items();
|
|
final IBlocks blocks = definitions.blocks();
|
|
|
|
final Optional<Item> maybeCrystalSeedItem = items.crystalSeed().maybeItem();
|
|
final Optional<Item> maybeSkyStoneItem = blocks.skyStone().maybeItem();
|
|
final Optional<Item> maybeCStorageItem = blocks.craftingStorage1k().maybeItem();
|
|
final Optional<Item> maybeCUnitItem = blocks.craftingUnit().maybeItem();
|
|
final Optional<Item> maybeSkyChestItem = blocks.skyChest().maybeItem();
|
|
|
|
if (maybeCrystalSeedItem.isPresent()
|
|
&& is.getItem() == maybeCrystalSeedItem.get()) {
|
|
final int dmg = is.getItemDamage();
|
|
if (dmg < ItemCrystalSeed.NETHER) {
|
|
realName += ".Certus";
|
|
} else if (dmg < ItemCrystalSeed.FLUIX) {
|
|
realName += ".Nether";
|
|
} else if (dmg < ItemCrystalSeed.FINAL_STAGE) {
|
|
realName += ".Fluix";
|
|
}
|
|
} else if (maybeSkyStoneItem.isPresent() && is.getItem() == maybeSkyStoneItem.get()) {
|
|
switch (is.getItemDamage()) {
|
|
case 1:
|
|
realName += ".Block";
|
|
break;
|
|
case 2:
|
|
realName += ".Brick";
|
|
break;
|
|
case 3:
|
|
realName += ".SmallBrick";
|
|
break;
|
|
default:
|
|
}
|
|
} else if (maybeCStorageItem.isPresent() && is.getItem() == maybeCStorageItem.get()) {
|
|
switch (is.getItemDamage()) {
|
|
case 1:
|
|
realName += "4k";
|
|
break;
|
|
case 2:
|
|
realName += "16k";
|
|
break;
|
|
case 3:
|
|
realName += "64k";
|
|
break;
|
|
default:
|
|
}
|
|
} else if (maybeCUnitItem.isPresent() && is.getItem() == maybeCUnitItem.get()) {
|
|
switch (is.getItemDamage()) {
|
|
case 1:
|
|
realName = realName.replace("Unit", "Accelerator");
|
|
break;
|
|
default:
|
|
}
|
|
} else if (maybeSkyChestItem.isPresent() && is.getItem() == maybeSkyChestItem.get()) {
|
|
switch (is.getItemDamage()) {
|
|
case 1:
|
|
realName += ".Block";
|
|
break;
|
|
default:
|
|
}
|
|
} else if (is.getItem() instanceof ItemMultiMaterial) {
|
|
realName = realName.replace("ItemMultiMaterial", "ItemMaterial");
|
|
realName
|
|
+= '.' + ((ItemMultiMaterial) is.getItem()).getTypeByStack(is).name();
|
|
} else if (is.getItem() instanceof ItemMultiPart) {
|
|
realName = realName.replace("ItemMultiPart", "ItemPart");
|
|
realName += '.' + ((ItemMultiPart) is.getItem()).getTypeByStack(is).name();
|
|
} else if (is.getItemDamage() > 0) {
|
|
realName += "." + is.getItemDamage();
|
|
}
|
|
|
|
return realName;
|
|
}
|
|
|
|
String alias(final String in) {
|
|
Preconditions.checkNotNull(in);
|
|
|
|
final String out = this.data.aliases.get(in);
|
|
|
|
if (out != null) {
|
|
return out;
|
|
}
|
|
|
|
return in;
|
|
}
|
|
|
|
@Override
|
|
public void parseRecipes(final IRecipeLoader loader, final String path) {
|
|
Preconditions.checkNotNull(loader);
|
|
Preconditions.checkNotNull(path);
|
|
|
|
try {
|
|
BufferedReader reader = null;
|
|
try {
|
|
reader = loader.getFile(path);
|
|
} catch (final Exception err) {
|
|
AELog.warn("Error Loading Recipe File:" + path);
|
|
if (this.data.exceptions) {
|
|
AELog.debug(err);
|
|
}
|
|
return;
|
|
}
|
|
|
|
boolean inQuote = false;
|
|
boolean inComment = false;
|
|
|
|
String token = "";
|
|
int line = 0;
|
|
|
|
int val = -1;
|
|
while ((val = reader.read()) != -1) {
|
|
final 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 += c;
|
|
}
|
|
} else {
|
|
switch (c) {
|
|
case '"':
|
|
inQuote = !inQuote;
|
|
break;
|
|
case ',':
|
|
|
|
if (token.length() > 0) {
|
|
this.tokens.add(token);
|
|
this.tokens.add(",");
|
|
}
|
|
token = "";
|
|
break;
|
|
|
|
case '=':
|
|
|
|
this.processTokens(loader, path, line);
|
|
|
|
if (token.length() > 0) {
|
|
this.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) {
|
|
this.tokens.add(token);
|
|
}
|
|
token = "";
|
|
|
|
break;
|
|
default:
|
|
token += c;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (token.length() > 0) {
|
|
this.tokens.add(token);
|
|
}
|
|
|
|
reader.close();
|
|
this.processTokens(loader, path, line);
|
|
} catch (final Throwable e) {
|
|
AELog.debug(e);
|
|
if (this.data.crash) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void injectRecipes() {
|
|
if (cpw.mods.fml.common.Loader.instance().hasReachedState(
|
|
LoaderState.POSTINITIALIZATION
|
|
)) {
|
|
throw new IllegalStateException("Recipes must now be loaded in Init.");
|
|
}
|
|
|
|
final Map<Class, Integer> processed = new HashMap<Class, Integer>();
|
|
try {
|
|
for (final ICraftHandler ch : this.data.handlers) {
|
|
try {
|
|
ch.register();
|
|
|
|
final Class clz = ch.getClass();
|
|
final Integer i = processed.get(clz);
|
|
if (i == null) {
|
|
processed.put(clz, 1);
|
|
} else {
|
|
processed.put(clz, i + 1);
|
|
}
|
|
} catch (final RegistrationError e) {
|
|
AELog.warn("Unable to register a recipe: " + e.getMessage());
|
|
if (this.data.exceptions) {
|
|
AELog.debug(e);
|
|
}
|
|
if (this.data.crash) {
|
|
throw e;
|
|
}
|
|
} catch (final MissingIngredientError e) {
|
|
if (this.data.errorOnMissing) {
|
|
AELog.warn("Unable to register a recipe:" + e.getMessage());
|
|
if (this.data.exceptions) {
|
|
AELog.debug(e);
|
|
}
|
|
if (this.data.crash) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (final Throwable e) {
|
|
if (this.data.exceptions) {
|
|
AELog.debug(e);
|
|
}
|
|
if (this.data.crash) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
}
|
|
|
|
for (final Entry<Class, Integer> e : processed.entrySet()) {
|
|
AELog.info(
|
|
"Recipes Loading: " + e.getKey().getSimpleName() + ": " + e.getValue()
|
|
+ " loaded."
|
|
);
|
|
}
|
|
|
|
if (AEConfig.instance.isFeatureEnabled(AEFeature.WebsiteRecipes)) {
|
|
try {
|
|
final ZipOutputStream out
|
|
= new ZipOutputStream(new FileOutputStream("recipes.zip"));
|
|
|
|
final HashMultimap<String, IWebsiteSerializer> combined
|
|
= HashMultimap.create();
|
|
|
|
for (final String s : this.data.knownItem) {
|
|
try {
|
|
final IIngredient i = new Ingredient(this, s, 1);
|
|
|
|
for (final ItemStack is : i.getItemStackSet()) {
|
|
final String realName = this.getName(is);
|
|
final List<IWebsiteSerializer> recipes = this.findRecipe(is);
|
|
if (!recipes.isEmpty()) {
|
|
combined.putAll(realName, recipes);
|
|
}
|
|
}
|
|
} catch (final RecipeError ignored) {
|
|
} catch (final MissedIngredientSet ignored) {
|
|
} catch (final RegistrationError ignored) {
|
|
} catch (final MissingIngredientError ignored) {}
|
|
}
|
|
|
|
for (final String realName : combined.keySet()) {
|
|
int offset = 0;
|
|
|
|
for (final IWebsiteSerializer ws : combined.get(realName)) {
|
|
final String rew = ws.getPattern(this);
|
|
if (rew != null && rew.length() > 0) {
|
|
out.putNextEntry(
|
|
new ZipEntry(realName + '_' + offset + ".txt")
|
|
);
|
|
offset++;
|
|
out.write(rew.getBytes());
|
|
}
|
|
}
|
|
}
|
|
|
|
out.close();
|
|
} catch (final FileNotFoundException e1) {
|
|
AELog.debug(e1);
|
|
} catch (final IOException e1) {
|
|
AELog.debug(e1);
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<IWebsiteSerializer> findRecipe(final ItemStack output) {
|
|
final List<IWebsiteSerializer> out = new LinkedList<IWebsiteSerializer>();
|
|
|
|
for (final ICraftHandler ch : this.data.handlers) {
|
|
try {
|
|
if (ch instanceof IWebsiteSerializer
|
|
&& ((IWebsiteSerializer) ch).canCraft(output)) {
|
|
out.add((IWebsiteSerializer) ch);
|
|
}
|
|
} catch (final Throwable t) {
|
|
AELog.debug(t);
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
RecipeData getData() {
|
|
return this.data;
|
|
}
|
|
|
|
private void
|
|
processTokens(final IRecipeLoader loader, final String file, final int line)
|
|
throws RecipeError {
|
|
try {
|
|
final IRecipeHandlerRegistry cr = AEApi.instance().registries().recipes();
|
|
|
|
if (this.tokens.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
final int split = this.tokens.indexOf("->");
|
|
if (split != -1) {
|
|
final String operation
|
|
= this.tokens.remove(0).toLowerCase(Locale.ENGLISH);
|
|
|
|
if (operation.equals("alias")) {
|
|
if (this.tokens.size() == 3 && this.tokens.indexOf("->") == 1) {
|
|
this.data.aliases.put(this.tokens.get(0), this.tokens.get(2));
|
|
} else {
|
|
throw new RecipeError(
|
|
"Alias must have exactly 1 input and 1 output."
|
|
);
|
|
}
|
|
} else if (operation.equals("group")) {
|
|
final List<String> pre = this.tokens.subList(0, split - 1);
|
|
final List<String> post
|
|
= this.tokens.subList(split, this.tokens.size());
|
|
|
|
final List<List<IIngredient>> inputs = this.parseLines(pre);
|
|
|
|
if (inputs.size() == 1 && inputs.get(0).size() > 0
|
|
&& post.size() == 1) {
|
|
this.data.groups.put(
|
|
post.get(0),
|
|
new GroupIngredient(post.get(0), inputs.get(0), 1)
|
|
);
|
|
} else {
|
|
throw new RecipeError(
|
|
"Group must have exactly 1 output, and 1 or more inputs."
|
|
);
|
|
}
|
|
} else if (operation.equals("ore")) {
|
|
final List<String> pre = this.tokens.subList(0, split - 1);
|
|
final List<String> post
|
|
= this.tokens.subList(split, this.tokens.size());
|
|
|
|
final List<List<IIngredient>> inputs = this.parseLines(pre);
|
|
|
|
if (inputs.size() == 1 && inputs.get(0).size() > 0
|
|
&& post.size() == 1) {
|
|
final ICraftHandler ch
|
|
= new OreRegistration(inputs.get(0), post.get(0));
|
|
this.addCrafting(ch);
|
|
} else {
|
|
throw new RecipeError(
|
|
"Group must have exactly 1 output, and 1 or more inputs in a single row."
|
|
);
|
|
}
|
|
} else {
|
|
final List<String> pre = this.tokens.subList(0, split - 1);
|
|
final List<String> post
|
|
= this.tokens.subList(split, this.tokens.size());
|
|
|
|
final List<List<IIngredient>> inputs = this.parseLines(pre);
|
|
final List<List<IIngredient>> outputs = this.parseLines(post);
|
|
|
|
final ICraftHandler ch = cr.getCraftHandlerFor(operation);
|
|
|
|
if (ch != null) {
|
|
ch.setup(inputs, outputs);
|
|
this.addCrafting(ch);
|
|
} else {
|
|
throw new RecipeError("Invalid crafting type: " + operation);
|
|
}
|
|
}
|
|
} else {
|
|
final String operation = this.tokens.remove(0).toLowerCase();
|
|
|
|
if (operation.equals("exceptions")
|
|
&& (this.tokens.get(0).equals("true")
|
|
|| this.tokens.get(0).equals("false"))) {
|
|
if (this.tokens.size() == 1) {
|
|
this.data.exceptions = this.tokens.get(0).equals("true");
|
|
} else {
|
|
throw new RecipeError(
|
|
"exceptions must be true or false explicitly."
|
|
);
|
|
}
|
|
}
|
|
else if( operation.equals( "crash" ) && ( this.tokens.get( 0 ).equals( "true" ) || this.tokens.get( 0 ).equals( "false" ) ) )
|
|
{
|
|
if (this.tokens.size() == 1) {
|
|
this.data.crash = this.tokens.get(0).equals("true");
|
|
} else {
|
|
throw new RecipeError("crash must be true or false explicitly.");
|
|
}
|
|
} else if (operation.equals("erroronmissing")) {
|
|
if (this.tokens.size() == 1
|
|
&& (this.tokens.get(0).equals("true")
|
|
|| this.tokens.get(0).equals("false"))) {
|
|
this.data.errorOnMissing = this.tokens.get(0).equals("true");
|
|
} else {
|
|
throw new RecipeError(
|
|
"erroronmissing must be true or false explicitly."
|
|
);
|
|
}
|
|
} else if (operation.equals("import")) {
|
|
if (this.tokens.size() == 1) {
|
|
(new RecipeHandler(this))
|
|
.parseRecipes(loader, this.tokens.get(0));
|
|
} else {
|
|
throw new RecipeError("Import must have exactly 1 input.");
|
|
}
|
|
} else {
|
|
throw new RecipeError(
|
|
operation + ": " + this.tokens.toString()
|
|
+ "; recipe without an output."
|
|
);
|
|
}
|
|
}
|
|
} catch (final RecipeError e) {
|
|
AELog.warn(
|
|
"Recipe Error '" + e.getMessage() + "' near line:" + line + " in " + file
|
|
+ " with: " + this.tokens.toString()
|
|
);
|
|
if (this.data.exceptions) {
|
|
AELog.debug(e);
|
|
}
|
|
if (this.data.crash) {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
this.tokens.clear();
|
|
}
|
|
|
|
private List<List<IIngredient>> parseLines(final Iterable<String> subList)
|
|
throws RecipeError {
|
|
final List<List<IIngredient>> out = new LinkedList<List<IIngredient>>();
|
|
List<IIngredient> cList = new LinkedList<IIngredient>();
|
|
|
|
boolean hasQty = false;
|
|
int qty = 1;
|
|
|
|
for (final 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<IIngredient>();
|
|
} else {
|
|
if (this.isNumber(v)) {
|
|
if (hasQty) {
|
|
throw new RecipeError("Qty found with no item.");
|
|
}
|
|
hasQty = true;
|
|
qty = Integer.parseInt(v);
|
|
} else {
|
|
if (hasQty) {
|
|
cList.add(this.findIngredient(v, qty));
|
|
hasQty = false;
|
|
} else {
|
|
cList.add(this.findIngredient(v, 1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cList.isEmpty()) {
|
|
out.add(cList);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
private IIngredient findIngredient(final String v, final int qty) throws RecipeError {
|
|
final GroupIngredient gi = this.data.groups.get(v);
|
|
|
|
if (gi != null) {
|
|
return gi.copy(qty);
|
|
}
|
|
|
|
try {
|
|
return new Ingredient(this, v, qty);
|
|
} catch (final MissedIngredientSet grp) {
|
|
return new IngredientSet(grp.getResolverResultSet(), qty);
|
|
}
|
|
}
|
|
|
|
private boolean isNumber(final CharSequence v) {
|
|
if (v.length() <= 0) {
|
|
return false;
|
|
}
|
|
|
|
final int l = v.length();
|
|
for (int x = 0; x < l; x++) {
|
|
if (!Character.isDigit(v.charAt(x))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|